Compare commits

...
Author SHA1 Message Date
nmzik 1d5f3c8f66 gpu emulation: remove unnecessary Vulkan synchronization 2026-08-08 01:21:03 +02:00
nmzik ec78afb966 Revert "gpu: queue AGC suspend points"
This reverts commit 90bf3c9236.
2026-08-07 23:58:09 +02:00
nmzik 3a7656336e fix regression properly (fix pthread init abi) 2026-08-07 21:39:16 +02:00
nmzik 90bf3c9236 gpu: queue AGC suspend points 2026-08-07 21:02:31 +02:00
nmzik d4f8338937 renderer: lower masked stencil xor exactly 2026-08-07 19:13:21 +02:00
nmzik 5125cd09c7 growable command buffer pool 2026-08-07 18:26:42 +02:00
nmzik 67cfec9063 shader: remove redundant runtime descriptor provenance walks (perf) 2026-08-07 18:08:40 +02:00
nmzik 81e52df7bf precompute scissor rules (optimization) 2026-08-07 17:54:00 +02:00
nmzik 4b26d03dba fix pthread init abi 2026-08-07 17:51:59 +02:00
nmzik 216c580d13 perf: limit high-resolution sleep busy-waiting 2026-08-07 17:44:55 +02:00
nmzik 7907a50817 minor optimizations 2026-08-07 09:26:37 +02:00
nmzik 769edf470b allow partial releases 2026-08-07 05:32:17 +02:00
nmzik 68fffdfb66 fix macos build 2026-08-07 05:32:12 +02:00
nmzik 26781e6fea fix(windows): add experimental guest red-zone protection.
Port the static instruction patcher from shadPS4.
2026-08-07 05:19:14 +02:00
nmzik d5c95bc3ee kernel: coalesce fragmented Windows placeholders before reuse 2026-08-07 03:13:01 +02:00
nmzik ace549ab21 reduce complexity of buffer overlap detection 2026-08-06 23:46:47 +02:00
nmzik 0cbae39e41 remove double locking. small perf boost 2026-08-06 23:23:28 +02:00
nmzikandPouare514 e9f5da27f8 gpu: implement polygon offset
Co-authored-by: Pouare514 <julienaulycee@gmail.com>
2026-08-06 22:02:19 +02:00
nmzik 3052dac82e gpu: correct HS, PS, GS and compute resource register decoding 2026-08-06 21:21:47 +02:00
nmzik 2bb0483473 shader: add missing attributes 2026-08-06 20:32:14 +02:00
nmzik 389fd1e5be refactor gpu: consolidate texture tile layout calculations 2026-08-06 20:03:42 +02:00
nmzik a76f3ac2fe gpu_defs: add missing layouts 2026-08-06 18:18:07 +02:00
nmzik 2fc4855f29 fix renderDoc 2026-08-06 16:55:53 +02:00
nmzik 869329ee68 renderer: minor optimizations 2026-08-05 19:08:18 +02:00
nmzik b2963aa6e0 graphics: make DispatchDirect asynchronous 2026-08-05 14:49:45 +02:00
nmzik 9859b3e1d6 defer image refresh to final acquisition 2026-08-05 12:52:36 +02:00
nmzik cd19fa283f Implement IME dialog overlay 2026-08-05 05:14:16 +02:00
nmzik b6af7c73ec graphics: support Standard256B storage textures 2026-08-05 05:09:28 +02:00
nmzik ac64d202af shader: implement BUFFER_ATOMIC_FMAX 2026-08-05 02:59:28 +02:00
nmzik df44ad3fac shader: implement BUFFER_ATOMIC_FMIN, unhandled IR opcode now fails 2026-08-05 02:59:28 +02:00
nmzik 0a2f4814ac refactor(shader): centralize opcode metadata and lookup paths 2026-08-05 02:59:28 +02:00
nmzik e6b4707a5d renderer: support R8 uint render targets 2026-08-05 02:59:28 +02:00
nmzik 9a3468e3a0 shader: implement V_MIN_U32 2026-08-05 02:59:28 +02:00
nmzik 73da43a7be shader: implement V_CNDMASK_B32 2026-08-05 02:59:28 +02:00
nmzik aa88d94755 shader: implement V_SUB_NC_U32 2026-08-05 02:59:27 +02:00
nmzik 51562cd8d2 tests: use valid image descriptors in shader CFG cases 2026-08-05 02:59:27 +02:00
nmzik 822ca3c10b shader: implement S_CMP_EQ_U64 2026-08-05 02:59:27 +02:00
nmzik 2b6fd699e7 shader: implement V_CMP_EQ_I64 2026-08-05 02:59:27 +02:00
nmzikandStefanos Costa b9aa0dc3b3 fix KernelLseek lock leak
Co-authored-by: Stefanos Costa <stefanos.costa1999@gmail.com>
2026-08-05 02:25:05 +02:00
nmzikandStefanos Costa f380d69b9d fix AudioOut pacing
Co-authored-by: Stefanos Costa <stefanos.costa1999@gmail.com>
2026-08-05 02:17:25 +02:00
DeathWhispers f06d7e6084 launcher: disable Vulkan validation by default
Keep validation opt-in while validation-layer errors remain fatal.

Refs #161
2026-08-04 22:09:40 +02:00
104 changed files with 7986 additions and 3101 deletions

No files matched your search

+64 -25
View File
@@ -105,12 +105,12 @@ jobs:
- name: Build - name: Build
shell: cmd shell: cmd
run: | run: |
cmake --build _Build/windows --target launcher audio_out2_port_tests virtual_memory_allocation_tests --parallel cmake --build _Build/windows --target launcher audio_out2_port_tests ime_dialog_tests virtual_memory_allocation_tests --parallel
- name: Test - name: Test
shell: cmd shell: cmd
run: | run: |
ctest --test-dir _Build/windows --output-on-failure -R "^(audio_out2_port|virtual_memory_allocation)$" ctest --test-dir _Build/windows --output-on-failure -R "^(audio_out2_port|ime_dialog|virtual_memory_allocation)$"
- name: Install - name: Install
shell: cmd shell: cmd
@@ -186,14 +186,14 @@ jobs:
shell: bash shell: bash
run: | run: |
cmake --build _Build/macos \ cmake --build _Build/macos \
--target launcher audio_out2_port_tests virtual_memory_allocation_tests \ --target launcher audio_out2_port_tests ime_dialog_tests virtual_memory_allocation_tests \
--parallel --parallel
- name: Test - name: Test
shell: bash shell: bash
run: | run: |
ctest --test-dir _Build/macos --output-on-failure \ ctest --test-dir _Build/macos --output-on-failure \
-R '^(audio_out2_port|virtual_memory_allocation)$' -R '^(audio_out2_port|ime_dialog|virtual_memory_allocation)$'
- name: Install - name: Install
shell: bash shell: bash
@@ -329,14 +329,14 @@ jobs:
run: | run: |
cmake --build _Build/linux \ cmake --build _Build/linux \
--target launcher page_manager_tests memory_tracker_tests \ --target launcher page_manager_tests memory_tracker_tests \
audio_out2_port_tests virtual_memory_allocation_tests \ audio_out2_port_tests ime_dialog_tests virtual_memory_allocation_tests \
--parallel --parallel
- name: Test - name: Test
shell: bash shell: bash
run: | run: |
ctest --test-dir _Build/linux --output-on-failure \ ctest --test-dir _Build/linux --output-on-failure \
-R '^(audio_out2_port|page_manager|memory_tracker|virtual_memory_allocation)$' -R '^(audio_out2_port|ime_dialog|page_manager|memory_tracker|virtual_memory_allocation)$'
- name: Install - name: Install
shell: bash shell: bash
@@ -373,7 +373,13 @@ jobs:
release: release:
name: Release KytyPS5 name: Release KytyPS5
if: github.event_name == 'push' && github.repository == 'KytyPS5/KytyPS5' if: >-
always() &&
github.event_name == 'push' &&
github.repository == 'KytyPS5/KytyPS5' &&
(needs.windows.result == 'success' ||
needs.macos.result == 'success' ||
needs.linux.result == 'success')
needs: [metadata, windows, macos, linux] needs: [metadata, windows, macos, linux]
runs-on: ubuntu-latest runs-on: ubuntu-latest
permissions: permissions:
@@ -382,34 +388,62 @@ jobs:
RELEASE_NAME: ${{ needs.metadata.outputs.release_tag }} RELEASE_NAME: ${{ needs.metadata.outputs.release_tag }}
steps: steps:
- name: Download builds - name: Download Windows build
if: needs.windows.result == 'success'
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
with: with:
path: artifacts name: KytyPS5-Windows-x64
path: artifacts/KytyPS5-Windows-x64
- name: Package builds - name: Download macOS build
if: needs.macos.result == 'success'
uses: actions/download-artifact@v4
with:
name: KytyPS5-macOS-x86_64
path: artifacts/KytyPS5-macOS-x86_64
- name: Download Linux build
if: needs.linux.result == 'success'
uses: actions/download-artifact@v4
with:
name: KytyPS5-Linux-x86_64
path: artifacts/KytyPS5-Linux-x86_64
- name: Package Windows build
if: needs.windows.result == 'success'
shell: bash shell: bash
run: | run: |
windows_dir="artifacts/KytyPS5-Windows-x64" windows_dir="artifacts/KytyPS5-Windows-x64"
macos_dir="artifacts/KytyPS5-macOS-x86_64"
linux_dir="artifacts/KytyPS5-Linux-x86_64"
test -d "$windows_dir" test -d "$windows_dir"
test -d "$macos_dir"
test -d "$linux_dir"
chmod a+x \
"$macos_dir/launcher" \
"$macos_dir/kyty_emulator" \
"$macos_dir/libMoltenVK.dylib" \
"$linux_dir/launcher" \
"$linux_dir/kyty_emulator"
( (
cd "$windows_dir" cd "$windows_dir"
zip -r "$GITHUB_WORKSPACE/$RELEASE_NAME-Windows-x64.zip" . zip -r "$GITHUB_WORKSPACE/$RELEASE_NAME-Windows-x64.zip" .
) )
- name: Package macOS build
if: needs.macos.result == 'success'
shell: bash
run: |
macos_dir="artifacts/KytyPS5-macOS-x86_64"
test -d "$macos_dir"
chmod a+x \
"$macos_dir/launcher" \
"$macos_dir/kyty_emulator" \
"$macos_dir/libMoltenVK.dylib"
( (
cd "$macos_dir" cd "$macos_dir"
zip -r "$GITHUB_WORKSPACE/$RELEASE_NAME-macOS-x86_64.zip" . zip -r "$GITHUB_WORKSPACE/$RELEASE_NAME-macOS-x86_64.zip" .
) )
- name: Package Linux build
if: needs.linux.result == 'success'
shell: bash
run: |
linux_dir="artifacts/KytyPS5-Linux-x86_64"
test -d "$linux_dir"
chmod a+x \
"$linux_dir/launcher" \
"$linux_dir/kyty_emulator"
tar -C "$linux_dir" -czf \ tar -C "$linux_dir" -czf \
"$GITHUB_WORKSPACE/$RELEASE_NAME-Linux-x86_64.tar.gz" . "$GITHUB_WORKSPACE/$RELEASE_NAME-Linux-x86_64.tar.gz" .
@@ -418,11 +452,16 @@ jobs:
env: env:
GH_TOKEN: ${{ github.token }} GH_TOKEN: ${{ github.token }}
run: | run: |
assets=( assets=()
"$RELEASE_NAME-Windows-x64.zip" for artifact in \
"$RELEASE_NAME-macOS-x86_64.zip" "$RELEASE_NAME-Windows-x64.zip" \
"$RELEASE_NAME-Linux-x86_64.tar.gz" "$RELEASE_NAME-macOS-x86_64.zip" \
) "$RELEASE_NAME-Linux-x86_64.tar.gz"; do
if [[ -f "$artifact" ]]; then
assets+=("$artifact")
fi
done
test "${#assets[@]}" -gt 0
if gh release view "$RELEASE_NAME" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then if gh release view "$RELEASE_NAME" --repo "$GITHUB_REPOSITORY" > /dev/null 2>&1; then
gh release upload "$RELEASE_NAME" "${assets[@]}" \ gh release upload "$RELEASE_NAME" "${assets[@]}" \
--clobber \ --clobber \
+3
View File
@@ -39,3 +39,6 @@
[submodule "3rdparty/nlohmann_json"] [submodule "3rdparty/nlohmann_json"]
path = 3rdparty/nlohmann_json path = 3rdparty/nlohmann_json
url = https://github.com/nlohmann/json.git url = https://github.com/nlohmann/json.git
[submodule "3rdparty/imgui"]
path = 3rdparty/imgui
url = https://github.com/ocornut/imgui.git
+45
View File
@@ -2,6 +2,32 @@ set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
add_subdirectory(cpuinfo) add_subdirectory(cpuinfo)
# The Windows guest red-zone patcher needs a decoder/encoder and a small
# trampoline assembler. Pin the same revisions used by shadPS4's implementation.
include(FetchContent)
set(XBYAK_BUILD_TESTS OFF CACHE BOOL "" FORCE)
FetchContent_Declare(
xbyak
GIT_REPOSITORY https://github.com/herumi/xbyak.git
GIT_TAG 44a72f369268f7d552650891b296693e91db86bb
GIT_SHALLOW FALSE
)
set(ZYDIS_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
set(ZYDIS_BUILD_TOOLS OFF CACHE BOOL "" FORCE)
set(ZYDIS_BUILD_TESTS OFF CACHE BOOL "" FORCE)
set(ZYDIS_BUILD_DOXYGEN OFF CACHE BOOL "" FORCE)
FetchContent_Declare(
zydis
GIT_REPOSITORY https://github.com/zyantific/zydis.git
GIT_TAG 120e0e705f8e3b507dc49377ac2879979f0d545c
GIT_SUBMODULES_RECURSE TRUE
GIT_SHALLOW FALSE
)
FetchContent_MakeAvailable(xbyak zydis)
set(SDL_SHARED OFF CACHE BOOL "" FORCE) set(SDL_SHARED OFF CACHE BOOL "" FORCE)
add_subdirectory(SDL2) add_subdirectory(SDL2)
@@ -11,6 +37,25 @@ target_include_directories(Vulkan-Headers INTERFACE
"${CMAKE_CURRENT_SOURCE_DIR}/Vulkan-Headers/include" "${CMAKE_CURRENT_SOURCE_DIR}/Vulkan-Headers/include"
) )
add_library(imgui STATIC
imgui/imgui.cpp
imgui/imgui_draw.cpp
imgui/imgui_tables.cpp
imgui/imgui_widgets.cpp
imgui/backends/imgui_impl_vulkan.cpp
)
target_include_directories(imgui PUBLIC
"${CMAKE_CURRENT_SOURCE_DIR}/imgui"
"${CMAKE_CURRENT_SOURCE_DIR}/imgui/backends"
)
target_compile_definitions(imgui PRIVATE IMGUI_IMPL_VULKAN_NO_PROTOTYPES)
target_link_libraries(imgui PUBLIC Vulkan::Headers)
if(MSVC)
target_compile_options(imgui PRIVATE /W0)
else()
target_compile_options(imgui PRIVATE -w)
endif()
set(SPIRV_SKIP_TESTS ON CACHE BOOL "" FORCE) set(SPIRV_SKIP_TESTS ON CACHE BOOL "" FORCE)
set(SPIRV_SKIP_EXECUTABLES ON CACHE BOOL "" FORCE) set(SPIRV_SKIP_EXECUTABLES ON CACHE BOOL "" FORCE)
set(SKIP_SPIRV_TOOLS_INSTALL ON CACHE BOOL "" FORCE) set(SKIP_SPIRV_TOOLS_INSTALL ON CACHE BOOL "" FORCE)
Vendored Submodule
+1
Submodule 3rdparty/imgui added at f1cc2ae15e.
+11 -10
View File
@@ -292,7 +292,7 @@ if (KYTY_CLANG_CL)
set_source_files_properties(${kyty_emulator_src} PROPERTIES COMPILE_FLAGS "-Wno-pragma-pack -Wno-deprecated-declarations -D_TIMESPEC_DEFINED") set_source_files_properties(${kyty_emulator_src} PROPERTIES COMPILE_FLAGS "-Wno-pragma-pack -Wno-deprecated-declarations -D_TIMESPEC_DEFINED")
endif() 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 imgui xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9 kyty_git_version Zydis::Zydis xbyak::xbyak)
# Linux system libraries required by the static FFmpeg archive. # Linux system libraries required by the static FFmpeg archive.
if(LINUX) if(LINUX)
@@ -385,6 +385,12 @@ add_executable(bit_array_tests EXCLUDE_FROM_ALL
) )
target_include_directories(bit_array_tests PRIVATE ${inc_headers}) target_include_directories(bit_array_tests PRIVATE ${inc_headers})
add_executable(ime_dialog_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/ImeDialogTests.cpp"
"${KYTY_SOURCE_DIR}/libs/imeDialog.cpp"
)
target_include_directories(ime_dialog_tests PRIVATE ${inc_headers})
add_executable(memory_tracker_tests EXCLUDE_FROM_ALL add_executable(memory_tracker_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/MemoryTrackerTests.cpp" "${KYTY_TESTS_DIR}/MemoryTrackerTests.cpp"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/pageManager.cpp" "${KYTY_SOURCE_DIR}/graphics/host_gpu/pageManager.cpp"
@@ -427,13 +433,6 @@ add_executable(resource_tracking_tests EXCLUDE_FROM_ALL
target_link_libraries(resource_tracking_tests fmt::fmt) target_link_libraries(resource_tracking_tests fmt::fmt)
target_include_directories(resource_tracking_tests PRIVATE ${inc_headers}) 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"
)
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 add_executable(audio_out2_port_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/AudioOut2PortTests.cpp" "${KYTY_TESTS_DIR}/AudioOut2PortTests.cpp"
"${KYTY_SOURCE_DIR}/libs/libAudio2.cpp" "${KYTY_SOURCE_DIR}/libs/libAudio2.cpp"
@@ -499,6 +498,7 @@ if(NOT KYTY_CLANG_CL)
endif() endif()
if(BUILD_TESTING) if(BUILD_TESTING)
add_test(NAME ime_dialog COMMAND $<TARGET_FILE:ime_dialog_tests>)
add_test(NAME shader_cfg COMMAND $<TARGET_FILE:shader_cfg_tests>) add_test(NAME shader_cfg COMMAND $<TARGET_FILE:shader_cfg_tests>)
add_test(NAME scalar_provenance COMMAND $<TARGET_FILE:scalar_provenance_tests>) add_test(NAME scalar_provenance COMMAND $<TARGET_FILE:scalar_provenance_tests>)
add_test(NAME image_page_table COMMAND $<TARGET_FILE:image_page_table_tests>) add_test(NAME image_page_table COMMAND $<TARGET_FILE:image_page_table_tests>)
@@ -508,13 +508,14 @@ if(BUILD_TESTING)
add_test(NAME shader_vertex_metadata COMMAND $<TARGET_FILE:shader_vertex_metadata_tests>) add_test(NAME shader_vertex_metadata COMMAND $<TARGET_FILE:shader_vertex_metadata_tests>)
add_test(NAME shader_stage_runtime COMMAND $<TARGET_FILE:shader_stage_runtime_tests>) add_test(NAME shader_stage_runtime COMMAND $<TARGET_FILE:shader_stage_runtime_tests>)
add_test(NAME resource_tracking COMMAND $<TARGET_FILE:resource_tracking_tests>) add_test(NAME resource_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 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 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 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 shader_recompiler_compute COMMAND $<TARGET_FILE:shader_recompiler_compute_tests>)
add_test(NAME virtual_memory_allocation add_test(NAME virtual_memory_allocation
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests>) COMMAND $<TARGET_FILE:virtual_memory_allocation_tests>)
add_test(NAME guest_red_zone_patcher
COMMAND $<TARGET_FILE:virtual_memory_allocation_tests> --red-zone-patcher-only)
add_test(NAME command_scheduler_timeline add_test(NAME command_scheduler_timeline
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --scheduler-only) COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --scheduler-only)
add_test(NAME stream_buffer_ring add_test(NAME stream_buffer_ring
@@ -548,6 +549,7 @@ if(BUILD_TESTING)
endif() endif()
add_custom_target(kyty_tests DEPENDS add_custom_target(kyty_tests DEPENDS
ime_dialog_tests
shader_cfg_tests shader_cfg_tests
scalar_provenance_tests scalar_provenance_tests
image_page_table_tests image_page_table_tests
@@ -557,7 +559,6 @@ if(BUILD_TESTING)
shader_vertex_metadata_tests shader_vertex_metadata_tests
shader_stage_runtime_tests shader_stage_runtime_tests
resource_tracking_tests resource_tracking_tests
resource_mutex_tests
event_queue_lifetime_tests event_queue_lifetime_tests
sync_on_address_tests sync_on_address_tests
shader_recompiler_compute_tests shader_recompiler_compute_tests
+6
View File
@@ -101,6 +101,12 @@ bool ReadbackLinearImagesEnabled() {
return g_config->readback_linear_images; return g_config->readback_linear_images;
} }
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
bool RedZoneProtectionEnabled() {
return g_config->red_zone_protection_enabled;
}
#endif
const Keymap& GetKeymap() { const Keymap& GetKeymap() {
return g_config->keymap; return g_config->keymap;
} }
+7 -1
View File
@@ -45,7 +45,10 @@ struct ConfigOptions {
bool spirv_debug_printf_enabled = false; bool spirv_debug_printf_enabled = false;
bool renderdoc_enabled = false; bool renderdoc_enabled = false;
bool readback_linear_images = false; bool readback_linear_images = false;
Keymap keymap; #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
bool red_zone_protection_enabled = false;
#endif
Keymap keymap;
}; };
void Load(const ConfigOptions& cfg); void Load(const ConfigOptions& cfg);
@@ -76,6 +79,9 @@ bool SpirvDebugPrintfEnabled();
bool RenderDocEnabled(); bool RenderDocEnabled();
bool ReadbackLinearImagesEnabled(); bool ReadbackLinearImagesEnabled();
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
bool RedZoneProtectionEnabled();
#endif
const Keymap& GetKeymap(); const Keymap& GetKeymap();
+6 -2
View File
@@ -28,7 +28,8 @@
#ifdef KYTY_WIN_CS #ifdef KYTY_WIN_CS
#include <windows.h> // IWYU pragma: keep #include <windows.h> // IWYU pragma: keep
// IWYU pragma: no_include <winbase.h> // IWYU pragma: no_include <winbase.h>
constexpr DWORD KYTY_CS_SPIN_COUNT = 4000; constexpr DWORD KYTY_CS_SPIN_COUNT = 4000;
constexpr uint64_t KYTY_SLEEP_SPIN_LIMIT_100NS = 500; // 50 us
#ifndef CREATE_WAITABLE_TIMER_HIGH_RESOLUTION #ifndef CREATE_WAITABLE_TIMER_HIGH_RESOLUTION
#define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x00000002 #define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x00000002
@@ -39,7 +40,10 @@ static void SleepHighResolution100ns(uint64_t units_100ns) {
return; return;
} }
if (units_100ns <= 10000) { // Keep spinning only where a kernel transition is
// likely to cost more than the requested delay; ordinary millisecond sleeps use the
// per-thread high-resolution waitable timer below.
if (units_100ns <= KYTY_SLEEP_SPIN_LIMIT_100NS) {
LARGE_INTEGER frequency {}; LARGE_INTEGER frequency {};
LARGE_INTEGER start {}; LARGE_INTEGER start {};
if (QueryPerformanceFrequency(&frequency) != 0 && QueryPerformanceCounter(&start) != 0 && if (QueryPerformanceFrequency(&frequency) != 0 && QueryPerformanceCounter(&start) != 0 &&
@@ -18,7 +18,9 @@
#include <algorithm> #include <algorithm>
#include <array> #include <array>
#include <atomic> #include <atomic>
#include <bit>
#include <cstdio> #include <cstdio>
#include <cstring>
#include <vector> #include <vector>
#define KYTY_HW_CTX_PARSER_ARGS \ #define KYTY_HW_CTX_PARSER_ARGS \
@@ -895,23 +897,44 @@ KYTY_HW_CTX_PARSER(HwCtxSetModeControl) {
return 1; return 1;
} }
static void HwCtxIgnorePolyOffsetRegister(uint32_t cmd_offset, uint32_t value) { static void HwCtxSetPolyOffsetRegister(CommandProcessor& cp, uint32_t cmd_offset, uint32_t value) {
static std::atomic<uint32_t> log_count = 0; HW::PolyOffset offset = cp.GetCtx().GetPolyOffset();
auto count = log_count.fetch_add(1); switch (cmd_offset) {
if (count < 8) { case Pm4::PA_SU_POLY_OFFSET_DB_FMT_CNTL: {
LOGF_COLOR(Log::Color::Red, const auto neg_bits =
"\t temporary: ignoring polygon offset context register 0x%03" PRIx32 KYTY_PM4_GET(value, PA_SU_POLY_OFFSET_DB_FMT_CNTL, NEG_NUM_DB_BITS);
" = 0x%08" PRIx32 " (depth bias not implemented)\n", offset.neg_num_db_bits = std::bit_cast<int8_t>(static_cast<uint8_t>(neg_bits));
cmd_offset, value); offset.db_is_float_fmt =
KYTY_PM4_GET(value, PA_SU_POLY_OFFSET_DB_FMT_CNTL, DB_IS_FLOAT_FMT) != 0;
break;
}
case Pm4::PA_SU_POLY_OFFSET_CLAMP: offset.clamp = std::bit_cast<float>(value); break;
case Pm4::PA_SU_POLY_OFFSET_FRONT_SCALE:
offset.front_scale = std::bit_cast<float>(value);
break;
case Pm4::PA_SU_POLY_OFFSET_FRONT_OFFSET:
offset.front_offset = std::bit_cast<float>(value);
break;
case Pm4::PA_SU_POLY_OFFSET_BACK_SCALE:
offset.back_scale = std::bit_cast<float>(value);
break;
case Pm4::PA_SU_POLY_OFFSET_BACK_OFFSET:
offset.back_offset = std::bit_cast<float>(value);
break;
default:
EXIT("unknown polygon offset context register 0x%03" PRIx32 " = 0x%08" PRIx32 "\n",
cmd_offset, value);
} }
cp.GetCtx().SetPolyOffset(offset);
} }
KYTY_HW_CTX_PARSER(HwCtxSetPolyOffsetRegisters) { KYTY_HW_CTX_PARSER(HwCtxSetPolyOffsetRegisters) {
auto num_values = KYTY_PM4_LEN(cmd_id) - 2u; auto num_values = KYTY_PM4_LEN(cmd_id) - 2u;
for (uint32_t i = 0; i < num_values; i++) { for (uint32_t i = 0; i < num_values; i++) {
HwCtxIgnorePolyOffsetRegister(cmd_offset + i, buffer[i]); HwCtxSetPolyOffsetRegister(cp, cmd_offset + i, buffer[i]);
} }
return num_values; return num_values;
@@ -1312,26 +1335,36 @@ static void HwShSetCsRegister(CommandProcessor& cp, uint32_t cmd_offset, uint32_
(value >> Pm4::COMPUTE_PGM_RSRC1_VGPRS_SHIFT) & Pm4::COMPUTE_PGM_RSRC1_VGPRS_MASK; (value >> Pm4::COMPUTE_PGM_RSRC1_VGPRS_SHIFT) & Pm4::COMPUTE_PGM_RSRC1_VGPRS_MASK;
cs_regs.sgprs = cs_regs.sgprs =
(value >> Pm4::COMPUTE_PGM_RSRC1_SGPRS_SHIFT) & Pm4::COMPUTE_PGM_RSRC1_SGPRS_MASK; (value >> Pm4::COMPUTE_PGM_RSRC1_SGPRS_SHIFT) & Pm4::COMPUTE_PGM_RSRC1_SGPRS_MASK;
cs_regs.bulky = cs_regs.float_mode = (value >> Pm4::COMPUTE_PGM_RSRC1_FLOAT_MODE_SHIFT) &
(value >> Pm4::COMPUTE_PGM_RSRC1_BULKY_SHIFT) & Pm4::COMPUTE_PGM_RSRC1_BULKY_MASK; Pm4::COMPUTE_PGM_RSRC1_FLOAT_MODE_MASK;
cs_regs.wave_size = (((value >> Pm4::COMPUTE_PGM_RSRC1_W32_EN_SHIFT) & cs_regs.dx10_clamp = ((value >> Pm4::COMPUTE_PGM_RSRC1_DX10_CLAMP_SHIFT) &
Pm4::COMPUTE_PGM_RSRC1_W32_EN_MASK) != 0u) Pm4::COMPUTE_PGM_RSRC1_DX10_CLAMP_MASK) != 0u;
? 32u cs_regs.ieee_mode = ((value >> Pm4::COMPUTE_PGM_RSRC1_IEEE_MODE_SHIFT) &
: 64u; Pm4::COMPUTE_PGM_RSRC1_IEEE_MODE_MASK) != 0u;
cs_regs.fp16_overflow = ((value >> Pm4::COMPUTE_PGM_RSRC1_FP16_OVFL_SHIFT) &
Pm4::COMPUTE_PGM_RSRC1_FP16_OVFL_MASK) != 0u;
cs_regs.bulky = ((value >> Pm4::COMPUTE_PGM_RSRC1_BULKY_SHIFT) &
Pm4::COMPUTE_PGM_RSRC1_BULKY_MASK) != 0u;
cs_regs.threadgroup_configuration = ((value >> Pm4::COMPUTE_PGM_RSRC1_WGP_MODE_SHIFT) &
Pm4::COMPUTE_PGM_RSRC1_WGP_MODE_MASK) != 0u;
cs_regs.wave_size = (((value >> Pm4::COMPUTE_PGM_RSRC1_W32_EN_SHIFT) &
Pm4::COMPUTE_PGM_RSRC1_W32_EN_MASK) != 0u)
? 32u
: 64u;
break; break;
case Pm4::COMPUTE_PGM_RSRC2: case Pm4::COMPUTE_PGM_RSRC2:
cs_regs.scratch_en = (value >> Pm4::COMPUTE_PGM_RSRC2_SCRATCH_EN_SHIFT) & cs_regs.scratch_en = ((value >> Pm4::COMPUTE_PGM_RSRC2_SCRATCH_EN_SHIFT) &
Pm4::COMPUTE_PGM_RSRC2_SCRATCH_EN_MASK; Pm4::COMPUTE_PGM_RSRC2_SCRATCH_EN_MASK) != 0u;
cs_regs.user_sgpr = (value >> Pm4::COMPUTE_PGM_RSRC2_USER_SGPR_SHIFT) & cs_regs.user_sgpr = (value >> Pm4::COMPUTE_PGM_RSRC2_USER_SGPR_SHIFT) &
Pm4::COMPUTE_PGM_RSRC2_USER_SGPR_MASK; Pm4::COMPUTE_PGM_RSRC2_USER_SGPR_MASK;
cs_regs.tgid_x_en = (value >> Pm4::COMPUTE_PGM_RSRC2_TGID_X_EN_SHIFT) & cs_regs.tgid_x_en = ((value >> Pm4::COMPUTE_PGM_RSRC2_TGID_X_EN_SHIFT) &
Pm4::COMPUTE_PGM_RSRC2_TGID_X_EN_MASK; Pm4::COMPUTE_PGM_RSRC2_TGID_X_EN_MASK) != 0u;
cs_regs.tgid_y_en = (value >> Pm4::COMPUTE_PGM_RSRC2_TGID_Y_EN_SHIFT) & cs_regs.tgid_y_en = ((value >> Pm4::COMPUTE_PGM_RSRC2_TGID_Y_EN_SHIFT) &
Pm4::COMPUTE_PGM_RSRC2_TGID_Y_EN_MASK; Pm4::COMPUTE_PGM_RSRC2_TGID_Y_EN_MASK) != 0u;
cs_regs.tgid_z_en = (value >> Pm4::COMPUTE_PGM_RSRC2_TGID_Z_EN_SHIFT) & cs_regs.tgid_z_en = ((value >> Pm4::COMPUTE_PGM_RSRC2_TGID_Z_EN_SHIFT) &
Pm4::COMPUTE_PGM_RSRC2_TGID_Z_EN_MASK; Pm4::COMPUTE_PGM_RSRC2_TGID_Z_EN_MASK) != 0u;
cs_regs.tg_size_en = (value >> Pm4::COMPUTE_PGM_RSRC2_TG_SIZE_EN_SHIFT) & cs_regs.tg_size_en = ((value >> Pm4::COMPUTE_PGM_RSRC2_TG_SIZE_EN_SHIFT) &
Pm4::COMPUTE_PGM_RSRC2_TG_SIZE_EN_MASK; Pm4::COMPUTE_PGM_RSRC2_TG_SIZE_EN_MASK) != 0u;
cs_regs.tidig_comp_cnt = (value >> Pm4::COMPUTE_PGM_RSRC2_TIDIG_COMP_CNT_SHIFT) & cs_regs.tidig_comp_cnt = (value >> Pm4::COMPUTE_PGM_RSRC2_TIDIG_COMP_CNT_SHIFT) &
Pm4::COMPUTE_PGM_RSRC2_TIDIG_COMP_CNT_MASK; Pm4::COMPUTE_PGM_RSRC2_TIDIG_COMP_CNT_MASK;
cs_regs.lds_size = (value >> Pm4::COMPUTE_PGM_RSRC2_LDS_SIZE_SHIFT) & cs_regs.lds_size = (value >> Pm4::COMPUTE_PGM_RSRC2_LDS_SIZE_SHIFT) &
@@ -2102,7 +2135,7 @@ KYTY_CP_OP_PARSER(CpOpClearState) {
EXIT_NOT_IMPLEMENTED(cmd_id != 0xc0001200); EXIT_NOT_IMPLEMENTED(cmd_id != 0xc0001200);
EXIT_NOT_IMPLEMENTED((buffer[0] & ~0xfu) != 0); EXIT_NOT_IMPLEMENTED((buffer[0] & ~0xfu) != 0);
cp.Reset(); cp.ApplyContextStateOperation(ContextStateOperation::Clear);
return 1; return 1;
} }
@@ -2649,14 +2682,21 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) {
const bool gl2_writeback = ((gcr_cntl & GcrGl2Writeback) != 0); const bool gl2_writeback = ((gcr_cntl & GcrGl2Writeback) != 0);
auto trigger_interrupt = [&]() { auto trigger_interrupt = [&]() {
bool queued = false;
switch (interrupt_selector) { switch (interrupt_selector) {
case 0x00: case 0x00:
case 0x03: break; case 0x03: break;
case 0x01: case 0x01:
case 0x02: case 0x02:
case 0x04: cp.TriggerEopEventAtEndOfPipe(interrupt_context_id); break; case 0x04:
cp.TriggerEopEventAtEndOfPipe(interrupt_context_id);
queued = true;
break;
default: EXIT("unknown release_mem interrupt selector\n"); default: EXIT("unknown release_mem interrupt selector\n");
} }
if (queued) {
cp.BufferFlush();
}
}; };
if (data_sel == 0 || interrupt_selector == 4) { if (data_sel == 0 || interrupt_selector == 4) {
@@ -2664,10 +2704,6 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) {
cp.EmitGlobalBarrier(); cp.EmitGlobalBarrier();
} }
if (gl2_writeback) {
cp.SynchronizeGpu();
}
trigger_interrupt(); trigger_interrupt();
return 7; return 7;
@@ -2678,10 +2714,6 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) {
cp.EmitGlobalBarrier(); cp.EmitGlobalBarrier();
} }
if (gl2_writeback) {
cp.SynchronizeGpu();
}
trigger_interrupt(); trigger_interrupt();
return 7; return 7;
@@ -2695,17 +2727,6 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) {
cache_policy = 0; cache_policy = 0;
if (data_sel == 1) { if (data_sel == 1) {
static std::atomic<uint32_t> log_count {0};
const auto dst_addr = reinterpret_cast<uint64_t>(dst_gpu_addr);
if ((dst_addr & 0xffffffffff000000ull) == 0x0000001021000000ull || log_count.load() < 64) {
if (log_count.fetch_add(1) < 256) {
LOGF(
"\t runtime release_mem32: dst=0x%016" PRIx64 ", value=0x%08" PRIx32
", action=0x%02" PRIx32 ", gcr=0x%04" PRIx32 ", cache_action=0x%08" PRIx32 "\n",
dst_addr, static_cast<uint32_t>(value), eop_event_type, gcr_cntl, cache_action);
}
}
eop_event_type = 0x2f; eop_event_type = 0x2f;
event_index = 6; event_index = 6;
auto event_source = 2u; auto event_source = 2u;
@@ -2720,7 +2741,6 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) {
if (data_sel == 5) { if (data_sel == 5) {
if (gl2_writeback) { if (gl2_writeback) {
cp.SynchronizeGpu();
cache_action = 0; cache_action = 0;
} }
@@ -2731,7 +2751,9 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) {
cp.WriteAtEndOfPipe32(cache_policy, event_write_dest, eop_event_type, cache_action, cp.WriteAtEndOfPipe32(cache_policy, event_write_dest, eop_event_type, cache_action,
event_index, event_source, dst_gpu_addr, static_cast<uint32_t>(value), event_index, event_source, dst_gpu_addr, static_cast<uint32_t>(value),
interrupt_selector, interrupt_context_id); interrupt_selector, interrupt_context_id);
cp.BufferFlush(); if (interrupt_selector == 0x01) {
cp.BufferFlush();
}
return 7; return 7;
} }
@@ -3695,22 +3717,22 @@ void GraphicsInitJmpTablesCxIndirect() {
}; };
g_hw_ctx_indirect_func[Pm4::PA_SU_POLY_OFFSET_DB_FMT_CNTL] = [](KYTY_HW_CTX_INDIRECT_ARGS) { g_hw_ctx_indirect_func[Pm4::PA_SU_POLY_OFFSET_DB_FMT_CNTL] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnorePolyOffsetRegister(cmd_offset, value); HwCtxSetPolyOffsetRegister(cp, cmd_offset, value);
}; };
g_hw_ctx_indirect_func[Pm4::PA_SU_POLY_OFFSET_CLAMP] = [](KYTY_HW_CTX_INDIRECT_ARGS) { g_hw_ctx_indirect_func[Pm4::PA_SU_POLY_OFFSET_CLAMP] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnorePolyOffsetRegister(cmd_offset, value); HwCtxSetPolyOffsetRegister(cp, cmd_offset, value);
}; };
g_hw_ctx_indirect_func[Pm4::PA_SU_POLY_OFFSET_FRONT_SCALE] = [](KYTY_HW_CTX_INDIRECT_ARGS) { g_hw_ctx_indirect_func[Pm4::PA_SU_POLY_OFFSET_FRONT_SCALE] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnorePolyOffsetRegister(cmd_offset, value); HwCtxSetPolyOffsetRegister(cp, cmd_offset, value);
}; };
g_hw_ctx_indirect_func[Pm4::PA_SU_POLY_OFFSET_FRONT_OFFSET] = [](KYTY_HW_CTX_INDIRECT_ARGS) { g_hw_ctx_indirect_func[Pm4::PA_SU_POLY_OFFSET_FRONT_OFFSET] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnorePolyOffsetRegister(cmd_offset, value); HwCtxSetPolyOffsetRegister(cp, cmd_offset, value);
}; };
g_hw_ctx_indirect_func[Pm4::PA_SU_POLY_OFFSET_BACK_SCALE] = [](KYTY_HW_CTX_INDIRECT_ARGS) { g_hw_ctx_indirect_func[Pm4::PA_SU_POLY_OFFSET_BACK_SCALE] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnorePolyOffsetRegister(cmd_offset, value); HwCtxSetPolyOffsetRegister(cp, cmd_offset, value);
}; };
g_hw_ctx_indirect_func[Pm4::PA_SU_POLY_OFFSET_BACK_OFFSET] = [](KYTY_HW_CTX_INDIRECT_ARGS) { g_hw_ctx_indirect_func[Pm4::PA_SU_POLY_OFFSET_BACK_OFFSET] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnorePolyOffsetRegister(cmd_offset, value); HwCtxSetPolyOffsetRegister(cp, cmd_offset, value);
}; };
g_hw_ctx_indirect_func[Pm4::DB_Z_INFO] = [](KYTY_HW_CTX_INDIRECT_ARGS) { g_hw_ctx_indirect_func[Pm4::DB_Z_INFO] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
@@ -4233,29 +4255,28 @@ void GraphicsInitJmpTablesShIndirect() {
}; };
g_hw_sh_indirect_func[Pm4::SPI_SHADER_PGM_RSRC1_HS] = [](KYTY_HW_SH_INDIRECT_ARGS) { g_hw_sh_indirect_func[Pm4::SPI_SHADER_PGM_RSRC1_HS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HW::VsShaderResource1 r1; HW::HsShaderResource1 r1;
r1.vgprs = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_VS, VGPRS); r1.vgprs = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_HS, VGPRS);
r1.sgprs = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_VS, SGPRS); r1.priority = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_HS, PRIORITY);
r1.priority = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_VS, PRIORITY); r1.float_mode = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_HS, FLOAT_MODE);
r1.float_mode = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_VS, FLOAT_MODE); r1.dx10_clamp = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_HS, DX10_CLAMP) != 0;
r1.dx10_clamp = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_VS, DX10_CLAMP) != 0; r1.debug_mode = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_HS, DEBUG_MODE) != 0;
r1.ieee_mode = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_VS, IEEE_MODE) != 0; r1.ieee_mode = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_HS, IEEE_MODE) != 0;
r1.vgpr_component_count = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_VS, VGPR_COMP_CNT);
r1.cu_group_enable = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_VS, CU_GROUP_ENABLE) != 0;
r1.require_forward_progress = r1.require_forward_progress =
KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_VS, FWD_PROGRESS) != 0; KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_HS, FWD_PROGRESS) != 0;
r1.fp16_overflow = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_VS, FP16_OVFL) != 0; r1.threadgroup_configuration = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_HS, WGP_MODE) != 0;
r1.ls_vgpr_component_count = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_HS, LS_VGPR_COMP_CNT);
r1.fp16_overflow = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_HS, FP16_OVFL) != 0;
cp.GetShCtx().SetHsShaderResource1(r1); cp.GetShCtx().SetHsShaderResource1(r1);
}; };
g_hw_sh_indirect_func[Pm4::SPI_SHADER_PGM_RSRC2_HS] = [](KYTY_HW_SH_INDIRECT_ARGS) { g_hw_sh_indirect_func[Pm4::SPI_SHADER_PGM_RSRC2_HS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HW::VsShaderResource2 r2; HW::HsShaderResource2 r2;
r2.scratch_en = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC2_VS, SCRATCH_EN) != 0; r2.scratch_en = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC2_HS, SCRATCH_EN) != 0;
r2.user_sgpr = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC2_VS, USER_SGPR) + r2.user_sgpr = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC2_HS, USER_SGPR) +
(KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC2_VS, USER_SGPR_MSB) << 5u); (KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC2_HS, USER_SGPR_MSB) << 5u);
r2.offchip_lds = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC2_VS, OC_LDS_EN) != 0; r2.lds_size = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC2_HS, LDS_SIZE);
r2.streamout_enabled = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC2_VS, SO_EN) != 0; r2.shared_vgprs = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC2_HS, SHARED_VGPR_CNT);
r2.shared_vgprs = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC2_VS, SHARED_VGPR_CNT);
cp.GetShCtx().SetHsShaderResource2(r2); cp.GetShCtx().SetHsShaderResource2(r2);
}; };
@@ -4343,7 +4364,7 @@ void GraphicsInitJmpTablesShIndirect() {
r1.cu_group_enable = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_GS, CU_GROUP_ENABLE) != 0; r1.cu_group_enable = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_GS, CU_GROUP_ENABLE) != 0;
r1.require_forward_progress = r1.require_forward_progress =
KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_GS, FWD_PROGRESS) != 0; KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_GS, FWD_PROGRESS) != 0;
r1.lds_configuration = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_GS, WGP_MODE) != 0; r1.threadgroup_configuration = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_GS, WGP_MODE) != 0;
r1.gs_vgpr_component_count = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_GS, GS_VGPR_COMP_CNT); r1.gs_vgpr_component_count = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_GS, GS_VGPR_COMP_CNT);
r1.fp16_overflow = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_GS, FP16_OVFL) != 0; r1.fp16_overflow = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_GS, FP16_OVFL) != 0;
cp.GetShCtx().SetGsShaderResource1(r1); cp.GetShCtx().SetGsShaderResource1(r1);
@@ -4403,7 +4424,7 @@ void GraphicsInitJmpTablesShIndirect() {
r2.wave_cnt_en = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC2_PS, WAVE_CNT_EN); r2.wave_cnt_en = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC2_PS, WAVE_CNT_EN);
r2.extra_lds_size = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC2_PS, EXTRA_LDS_SIZE); r2.extra_lds_size = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC2_PS, EXTRA_LDS_SIZE);
r2.raster_ordered_shading = r2.raster_ordered_shading =
KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC2_PS, LOAD_INTRAWAVE_COLLISION); KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC2_PS, RASTER_ORDERED_SHADING);
r2.shared_vgprs = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC2_PS, SHARED_VGPR_CNT); r2.shared_vgprs = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC2_PS, SHARED_VGPR_CNT);
cp.GetShCtx().SetPsShaderResource2(r2); cp.GetShCtx().SetPsShaderResource2(r2);
}; };
+28 -18
View File
@@ -60,24 +60,34 @@ enum class GsOutputPrimitiveType : uint32_t {
}; };
enum class ChannelLayout : uint32_t { enum class ChannelLayout : uint32_t {
kInvalid = 0, kInvalid = 0,
k8 = 1, k8 = 1,
k16 = 2, k16 = 2,
k8_8 = 3, k8_8 = 3,
k32 = 4, k32 = 4,
k16_16 = 5, k16_16 = 5,
k11_11_10 = 6, k11_11_10 = 6,
k10_10_10_2 = 9, k10_11_11 = 7,
k8_8_8_8 = 10, k2_10_10_10 = 8,
k32_32 = 11, k10_10_10_2 = 9,
k16_16_16_16 = 12, k8_8_8_8 = 10,
k32_32_32_32 = 14, k32_32 = 11,
k5_6_5 = 16, k16_16_16_16 = 12,
k5_5_5_1 = 17, k32_32_32 = 13,
k4_4_4_4 = 19, k32_32_32_32 = 14,
kBc1 = 35, k5_6_5 = 16,
kBc2 = 36, k5_5_5_1 = 17,
kBc3 = 37, k1_5_5_5 = 18,
k4_4_4_4 = 19,
k10_10_10_2Float = 31,
k9_9_9_5 = 34,
kBc1 = 35,
kBc2 = 36,
kBc3 = 37,
kBc4 = 38,
kBc5 = 39,
kBc6 = 40,
kBc7 = 41,
}; };
enum class ChannelType : uint32_t { enum class ChannelType : uint32_t {
+21 -30
View File
@@ -124,7 +124,6 @@ private:
uint64_t flip_request_id = 0; uint64_t flip_request_id = 0;
}; };
void WaitLocked();
void Enqueue(Submission submission); void Enqueue(Submission submission);
void WaitForIdle(); void WaitForIdle();
void ProcessCommands(); void ProcessCommands();
@@ -276,10 +275,8 @@ void GpuState::SubmitFlipPreparation(uint64_t request_id) {
void GpuState::Done() { void GpuState::Done() {
GpuMutexLock lock(m_submission_mutex); GpuMutexLock lock(m_submission_mutex);
if (IsGpuThread()) { if (!IsGpuThread()) {
m_gfx_cp->BufferWait(); WaitForIdle();
} else {
WaitLocked();
} }
m_graphics_done = true; m_graphics_done = true;
m_done_num++; m_done_num++;
@@ -289,11 +286,6 @@ int GpuState::GetFrameNum() {
return m_done_num; return m_done_num;
} }
void GpuState::WaitLocked() {
WaitForIdle();
SendCommandSync([this] { m_gfx_cp->BufferWait(); });
}
CommandProcessor& GpuState::GetProcessor(uint32_t queue_id) { CommandProcessor& GpuState::GetProcessor(uint32_t queue_id) {
EXIT_IF(queue_id >= QueueCount); EXIT_IF(queue_id >= QueueCount);
if (queue_id == 0) { if (queue_id == 0) {
@@ -307,13 +299,11 @@ CommandProcessor& GpuState::GetProcessor(uint32_t queue_id) {
} }
void CommandProcessor::Reset() { void CommandProcessor::Reset() {
BufferWait();
m_sh_ctx.Reset(); m_sh_ctx.Reset();
m_ucfg.Reset(); m_ucfg.Reset();
m_ctx.Reset(); m_ctx.Reset();
m_saved_ctx.Reset(); m_saved_ctx.Reset();
m_context_state_pushed = false; m_context_state_pushed = false;
m_index_type_and_size = 0; m_index_type_and_size = 0;
m_index_buffer_size = 0; m_index_buffer_size = 0;
m_user_data_marker = HW::UserSgprType::Unknown; m_user_data_marker = HW::UserSgprType::Unknown;
@@ -385,7 +375,6 @@ void CommandProcessor::WaitDeDiff(uint32_t diff) {
} }
void CommandProcessor::IncrementDe() { void CommandProcessor::IncrementDe() {
BufferWait();
m_de_count++; m_de_count++;
} }
@@ -1176,9 +1165,9 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun
void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_group_y, void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_group_y,
uint32_t thread_group_z, uint32_t mode) { uint32_t thread_group_z, uint32_t mode) {
uint32_t frame_num = 0; uint32_t frame_num = 0;
uint32_t local_x = 1; // uint32_t local_x = 1;
uint32_t local_y = 1; // uint32_t local_y = 1;
uint32_t local_z = 1; // uint32_t local_z = 1;
{ {
CheckBuffer(); CheckBuffer();
@@ -1202,9 +1191,9 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
} }
const auto& cs = m_sh_ctx.GetCs().cs_regs; const auto& cs = m_sh_ctx.GetCs().cs_regs;
local_x = std::max(cs.num_thread_x, 1u); // local_x = std::max(cs.num_thread_x, 1u);
local_y = std::max(cs.num_thread_y, 1u); // local_y = std::max(cs.num_thread_y, 1u);
local_z = std::max(cs.num_thread_z, 1u); // local_z = std::max(cs.num_thread_z, 1u);
if (cs.wave_size == 64u) { if (cs.wave_size == 64u) {
static std::atomic_bool logged_wave64_shader {false}; static std::atomic_bool logged_wave64_shader {false};
if (!logged_wave64_shader.exchange(true, std::memory_order_relaxed)) { if (!logged_wave64_shader.exchange(true, std::memory_order_relaxed)) {
@@ -1220,27 +1209,27 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
thread_group_y, thread_group_z, mode); thread_group_y, thread_group_z, mode);
} }
constexpr uint32_t DispatchInitiatorUseThreadDimensions = 1u << 5u; /*constexpr uint32_t DispatchInitiatorUseThreadDimensions = 1u << 5u;
auto group_count = [](uint32_t threads, uint32_t group_size) { auto group_count = [](uint32_t threads, uint32_t group_size) {
return (threads == 0 return (threads == 0
? 0u ? 0u
: (threads + std::max(group_size, 1u) - 1u) / std::max(group_size, 1u)); : (threads + std::max(group_size, 1u) - 1u) / std::max(group_size, 1u));
}; };
auto groups_x = thread_group_x; auto groups_x = thread_group_x;
auto groups_y = thread_group_y; auto groups_y = thread_group_y;
auto groups_z = thread_group_z; auto groups_z = thread_group_z;
if ((mode & DispatchInitiatorUseThreadDimensions) != 0) { if ((mode & DispatchInitiatorUseThreadDimensions) != 0) {
groups_x = group_count(thread_group_x, local_x); groups_x = group_count(thread_group_x, local_x);
groups_y = group_count(thread_group_y, local_y); groups_y = group_count(thread_group_y, local_y);
groups_z = group_count(thread_group_z, local_z); groups_z = group_count(thread_group_z, local_z);
} }
const uint64_t invocations = const uint64_t invocations =
static_cast<uint64_t>(groups_x) * groups_y * groups_z * local_x * local_y * local_z; static_cast<uint64_t>(groups_x) * groups_y * groups_z * local_x * local_y * local_z;
if (invocations != 0) { if (invocations != 0) {
BufferFlushAndWait(); BufferFlushAndWait();
} }*/
} }
void CommandProcessor::DispatchIndirect(uint32_t data_offset, uint32_t mode) { void CommandProcessor::DispatchIndirect(uint32_t data_offset, uint32_t mode) {
@@ -1352,6 +1341,9 @@ void CommandProcessor::WriteAtEndOfPipe(uint32_t cache_policy, uint32_t event_wr
value >> 16u); value >> 16u);
Sync::WriteAtEndOfPipeGds32(m_submit_id, CurrentBuffer(), dst, value & 0xffffu, Sync::WriteAtEndOfPipeGds32(m_submit_id, CurrentBuffer(), dst, value & 0xffffu,
value >> 16u); value >> 16u);
if (with_interrupt) {
m_renderer.TriggerEopEvent(interrupt_context_id);
}
return; return;
} }
} else if (eop_event_type == 0x04 && cache_action == 0x00 && event_index == 0x05) { } else if (eop_event_type == 0x04 && cache_action == 0x00 && event_index == 0x05) {
@@ -1554,7 +1546,6 @@ void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index) {
event_index); event_index);
} }
EmitGlobalBarrier(); EmitGlobalBarrier();
SynchronizeGpu();
break; break;
// DbDataWritebackInvalidate, DbMetadataWritebackInvalidate, CbMetadataWritebackInvalidate. // DbDataWritebackInvalidate, DbMetadataWritebackInvalidate, CbMetadataWritebackInvalidate.
case 0x0000002a: case 0x0000002a:
+71 -33
View File
@@ -348,6 +348,16 @@ struct ModeControl {
bool persp_corr_dis = false; bool persp_corr_dis = false;
}; };
struct PolyOffset {
int8_t neg_num_db_bits = -23;
bool db_is_float_fmt = true;
float clamp = 0.0f;
float front_scale = 0.0f;
float front_offset = 0.0f;
float back_scale = 0.0f;
float back_offset = 0.0f;
};
struct BlendControl { struct BlendControl {
uint8_t color_srcblend = 1; uint8_t color_srcblend = 1;
uint8_t color_comb_fcn = 0; uint8_t color_comb_fcn = 0;
@@ -482,6 +492,26 @@ struct VsShaderResource2 {
uint8_t shared_vgprs = 0; uint8_t shared_vgprs = 0;
}; };
struct HsShaderResource1 {
uint8_t vgprs = 0;
uint8_t priority = 0;
uint8_t float_mode = 0;
bool dx10_clamp = false;
bool debug_mode = false;
bool ieee_mode = false;
bool require_forward_progress = false;
bool threadgroup_configuration = false;
uint8_t ls_vgpr_component_count = 0;
bool fp16_overflow = false;
};
struct HsShaderResource2 {
bool scratch_en = false;
uint8_t user_sgpr = 0;
uint16_t lds_size = 0;
uint8_t shared_vgprs = 0;
};
struct PsShaderResource1 { struct PsShaderResource1 {
uint8_t vgprs = 0; uint8_t vgprs = 0;
uint8_t sgprs = 0; uint8_t sgprs = 0;
@@ -500,7 +530,7 @@ struct PsShaderResource2 {
uint8_t user_sgpr = 0; uint8_t user_sgpr = 0;
bool wave_cnt_en = false; bool wave_cnt_en = false;
uint8_t extra_lds_size = 0; uint8_t extra_lds_size = 0;
bool raster_ordered_shading = false; uint8_t raster_ordered_shading = 0;
uint8_t shared_vgprs = 0; uint8_t shared_vgprs = 0;
}; };
@@ -513,22 +543,27 @@ struct PsStageRegisters {
struct CsStageRegisters { struct CsStageRegisters {
uint64_t data_addr = 0; uint64_t data_addr = 0;
uint32_t num_thread_x = 0; uint32_t num_thread_x = 0;
uint32_t num_thread_y = 0; uint32_t num_thread_y = 0;
uint32_t num_thread_z = 0; uint32_t num_thread_z = 0;
uint8_t vgprs = 0; uint8_t vgprs = 0;
uint8_t sgprs = 0; uint8_t sgprs = 0;
uint8_t bulky = 0; uint8_t float_mode = 0;
uint8_t wave_size = 64; bool dx10_clamp = false;
uint8_t scratch_en = 0; bool ieee_mode = false;
uint8_t user_sgpr = 0; bool fp16_overflow = false;
uint8_t tgid_x_en = 0; bool bulky = false;
uint8_t tgid_y_en = 0; bool threadgroup_configuration = false;
uint8_t tgid_z_en = 0; uint8_t wave_size = 64;
uint8_t tg_size_en = 0; bool scratch_en = false;
uint8_t tidig_comp_cnt = 0; uint8_t user_sgpr = 0;
uint8_t lds_size = 0; bool tgid_x_en = false;
bool tgid_y_en = false;
bool tgid_z_en = false;
bool tg_size_en = false;
uint8_t tidig_comp_cnt = 0;
uint16_t lds_size = 0;
}; };
struct EsStageRegisters { struct EsStageRegisters {
@@ -543,24 +578,24 @@ struct LsStageRegisters {
struct HsStageRegisters { struct HsStageRegisters {
uint64_t data_addr = 0; uint64_t data_addr = 0;
VsShaderResource1 rsrc1; HsShaderResource1 rsrc1;
VsShaderResource2 rsrc2; HsShaderResource2 rsrc2;
uint64_t chksum = 0; uint64_t chksum = 0;
}; };
struct GsShaderResource1 { struct GsShaderResource1 {
uint8_t vgprs = 0; uint8_t vgprs = 0;
uint8_t sgprs = 0; uint8_t sgprs = 0;
uint8_t priority = 0; uint8_t priority = 0;
uint8_t float_mode = 0; uint8_t float_mode = 0;
bool dx10_clamp = false; bool dx10_clamp = false;
bool debug_mode = false; bool debug_mode = false;
bool ieee_mode = false; bool ieee_mode = false;
bool cu_group_enable = false; bool cu_group_enable = false;
bool require_forward_progress = false; bool require_forward_progress = false;
bool lds_configuration = false; bool threadgroup_configuration = false;
uint8_t gs_vgpr_component_count = 0; uint8_t gs_vgpr_component_count = 0;
bool fp16_overflow = false; bool fp16_overflow = false;
}; };
struct GsShaderResource2 { struct GsShaderResource2 {
@@ -880,6 +915,8 @@ public:
void SetDepthControl(const DepthControl& control) { m_depth_control = control; } void SetDepthControl(const DepthControl& control) { m_depth_control = control; }
[[nodiscard]] const ModeControl& GetModeControl() const { return m_mode_control; } [[nodiscard]] const ModeControl& GetModeControl() const { return m_mode_control; }
void SetModeControl(const ModeControl& control) { m_mode_control = control; } void SetModeControl(const ModeControl& control) { m_mode_control = control; }
[[nodiscard]] const PolyOffset& GetPolyOffset() const { return m_poly_offset; }
void SetPolyOffset(const PolyOffset& offset) { m_poly_offset = offset; }
[[nodiscard]] const EqaaControl& GetEqaaControl() const { return m_eqaa_control; } [[nodiscard]] const EqaaControl& GetEqaaControl() const { return m_eqaa_control; }
void SetEqaaControl(const EqaaControl& control) { m_eqaa_control = control; } void SetEqaaControl(const EqaaControl& control) { m_eqaa_control = control; }
[[nodiscard]] const StencilControl& GetStencilControl() const { return m_stencil_control; } [[nodiscard]] const StencilControl& GetStencilControl() const { return m_stencil_control; }
@@ -977,6 +1014,7 @@ private:
uint8_t m_stencil_clear_value = 0; uint8_t m_stencil_clear_value = 0;
ModeControl m_mode_control; ModeControl m_mode_control;
PolyOffset m_poly_offset;
EqaaControl m_eqaa_control; EqaaControl m_eqaa_control;
ShaderRegisters m_sh_regs; ShaderRegisters m_sh_regs;
@@ -1034,8 +1072,8 @@ public:
void SetLsShaderResource1(const VsShaderResource1& rsrc1) { m_vs.ls_regs.rsrc1 = rsrc1; } void SetLsShaderResource1(const VsShaderResource1& rsrc1) { m_vs.ls_regs.rsrc1 = rsrc1; }
void SetLsShaderResource2(const VsShaderResource2& rsrc2) { m_vs.ls_regs.rsrc2 = rsrc2; } void SetLsShaderResource2(const VsShaderResource2& rsrc2) { m_vs.ls_regs.rsrc2 = rsrc2; }
void SetHsShaderBase(uint64_t addr) { m_vs.hs_regs.data_addr = addr; } void SetHsShaderBase(uint64_t addr) { m_vs.hs_regs.data_addr = addr; }
void SetHsShaderResource1(const VsShaderResource1& rsrc1) { m_vs.hs_regs.rsrc1 = rsrc1; } void SetHsShaderResource1(const HsShaderResource1& rsrc1) { m_vs.hs_regs.rsrc1 = rsrc1; }
void SetHsShaderResource2(const VsShaderResource2& rsrc2) { m_vs.hs_regs.rsrc2 = rsrc2; } void SetHsShaderResource2(const HsShaderResource2& rsrc2) { m_vs.hs_regs.rsrc2 = rsrc2; }
void SetHsShaderChksum(uint32_t value) { m_vs.hs_regs.chksum = value; } void SetHsShaderChksum(uint32_t value) { m_vs.hs_regs.chksum = value; }
void SetGsShaderBase(uint64_t addr) { m_vs.gs_regs.data_addr = addr; } void SetGsShaderBase(uint64_t addr) { m_vs.gs_regs.data_addr = addr; }
void SetGsShaderResource1(const GsShaderResource1& rsrc1) { m_vs.gs_regs.rsrc1 = rsrc1; } void SetGsShaderResource1(const GsShaderResource1& rsrc1) { m_vs.gs_regs.rsrc1 = rsrc1; }
+131 -87
View File
@@ -578,24 +578,28 @@ constexpr uint32_t DB_HTILE_SURFACE_PREFETCH_HEIGHT_MASK = 0x3F;
constexpr uint32_t DB_HTILE_SURFACE_DST_OUTSIDE_ZERO_TO_ONE_SHIFT = 16; constexpr uint32_t DB_HTILE_SURFACE_DST_OUTSIDE_ZERO_TO_ONE_SHIFT = 16;
constexpr uint32_t DB_HTILE_SURFACE_DST_OUTSIDE_ZERO_TO_ONE_MASK = 0x1; constexpr uint32_t DB_HTILE_SURFACE_DST_OUTSIDE_ZERO_TO_ONE_MASK = 0x1;
constexpr uint32_t DB_SRESULTS_COMPARE_STATE0 = 0x2B0; constexpr uint32_t DB_SRESULTS_COMPARE_STATE0 = 0x2B0;
constexpr uint32_t DB_SRESULTS_COMPARE_STATE1 = 0x2B1; constexpr uint32_t DB_SRESULTS_COMPARE_STATE1 = 0x2B1;
constexpr uint32_t VGT_GS_MAX_VERT_OUT = 0x2CE; constexpr uint32_t VGT_GS_MAX_VERT_OUT = 0x2CE;
constexpr uint32_t GE_NGG_SUBGRP_CNTL = 0x2D3; constexpr uint32_t GE_NGG_SUBGRP_CNTL = 0x2D3;
constexpr uint32_t VGT_TESS_DISTRIBUTION = 0x2D4; constexpr uint32_t VGT_TESS_DISTRIBUTION = 0x2D4;
constexpr uint32_t VGT_SHADER_STAGES_EN = 0x2D5; constexpr uint32_t VGT_SHADER_STAGES_EN = 0x2D5;
constexpr uint32_t VGT_LS_HS_CONFIG = 0x2D6; constexpr uint32_t VGT_LS_HS_CONFIG = 0x2D6;
constexpr uint32_t VGT_TF_PARAM = 0x2DB; constexpr uint32_t VGT_TF_PARAM = 0x2DB;
constexpr uint32_t DB_ALPHA_TO_MASK = 0x2DC; constexpr uint32_t DB_ALPHA_TO_MASK = 0x2DC;
constexpr uint32_t PA_SU_POLY_OFFSET_DB_FMT_CNTL = 0x2DE; constexpr uint32_t PA_SU_POLY_OFFSET_DB_FMT_CNTL = 0x2DE;
constexpr uint32_t PA_SU_POLY_OFFSET_CLAMP = 0x2DF; constexpr uint32_t PA_SU_POLY_OFFSET_DB_FMT_CNTL_NEG_NUM_DB_BITS_SHIFT = 0;
constexpr uint32_t PA_SU_POLY_OFFSET_FRONT_SCALE = 0x2E0; constexpr uint32_t PA_SU_POLY_OFFSET_DB_FMT_CNTL_NEG_NUM_DB_BITS_MASK = 0xFF;
constexpr uint32_t PA_SU_POLY_OFFSET_FRONT_OFFSET = 0x2E1; constexpr uint32_t PA_SU_POLY_OFFSET_DB_FMT_CNTL_DB_IS_FLOAT_FMT_SHIFT = 8;
constexpr uint32_t PA_SU_POLY_OFFSET_BACK_SCALE = 0x2E2; constexpr uint32_t PA_SU_POLY_OFFSET_DB_FMT_CNTL_DB_IS_FLOAT_FMT_MASK = 0x1;
constexpr uint32_t PA_SU_POLY_OFFSET_BACK_OFFSET = 0x2E3; constexpr uint32_t PA_SU_POLY_OFFSET_CLAMP = 0x2DF;
constexpr uint32_t VGT_GS_INSTANCE_CNT = 0x2E4; constexpr uint32_t PA_SU_POLY_OFFSET_FRONT_SCALE = 0x2E0;
constexpr uint32_t PA_SC_CENTROID_PRIORITY_0 = 0x2F5; constexpr uint32_t PA_SU_POLY_OFFSET_FRONT_OFFSET = 0x2E1;
constexpr uint32_t PA_SC_CENTROID_PRIORITY_1 = 0x2F6; constexpr uint32_t PA_SU_POLY_OFFSET_BACK_SCALE = 0x2E2;
constexpr uint32_t PA_SU_POLY_OFFSET_BACK_OFFSET = 0x2E3;
constexpr uint32_t VGT_GS_INSTANCE_CNT = 0x2E4;
constexpr uint32_t PA_SC_CENTROID_PRIORITY_0 = 0x2F5;
constexpr uint32_t PA_SC_CENTROID_PRIORITY_1 = 0x2F6;
constexpr uint32_t PA_SC_AA_CONFIG = 0x2F8; constexpr uint32_t PA_SC_AA_CONFIG = 0x2F8;
constexpr uint32_t PA_SC_AA_CONFIG_MSAA_NUM_SAMPLES_SHIFT = 0; constexpr uint32_t PA_SC_AA_CONFIG_MSAA_NUM_SAMPLES_SHIFT = 0;
@@ -779,21 +783,21 @@ constexpr uint32_t SPI_SHADER_PGM_RSRC1_PS_FWD_PROGRESS_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_PS_FP16_OVFL_SHIFT = 29; constexpr uint32_t SPI_SHADER_PGM_RSRC1_PS_FP16_OVFL_SHIFT = 29;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_PS_FP16_OVFL_MASK = 0x1; constexpr uint32_t SPI_SHADER_PGM_RSRC1_PS_FP16_OVFL_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS = 0xB; constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS = 0xB;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_SCRATCH_EN_SHIFT = 0; constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_SCRATCH_EN_SHIFT = 0;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_SCRATCH_EN_MASK = 0x1; constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_SCRATCH_EN_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_USER_SGPR_SHIFT = 1; constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_USER_SGPR_SHIFT = 1;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_USER_SGPR_MASK = 0x1F; constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_USER_SGPR_MASK = 0x1F;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_WAVE_CNT_EN_SHIFT = 7; constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_WAVE_CNT_EN_SHIFT = 7;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_WAVE_CNT_EN_MASK = 0x1; constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_WAVE_CNT_EN_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_EXTRA_LDS_SIZE_SHIFT = 8; constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_EXTRA_LDS_SIZE_SHIFT = 8;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_EXTRA_LDS_SIZE_MASK = 0xFF; constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_EXTRA_LDS_SIZE_MASK = 0xFF;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_LOAD_INTRAWAVE_COLLISION_SHIFT = 26; constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_RASTER_ORDERED_SHADING_SHIFT = 25;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_LOAD_INTRAWAVE_COLLISION_MASK = 0x1; constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_RASTER_ORDERED_SHADING_MASK = 0x3;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_USER_SGPR_MSB_SHIFT = 27; constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_USER_SGPR_MSB_SHIFT = 27;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_USER_SGPR_MSB_MASK = 0x1; constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_USER_SGPR_MSB_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_SHARED_VGPR_CNT_SHIFT = 28; constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_SHARED_VGPR_CNT_SHIFT = 28;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_SHARED_VGPR_CNT_MASK = 0xF; constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_SHARED_VGPR_CNT_MASK = 0xF;
constexpr uint32_t SPI_SHADER_USER_DATA_PS_0 = 0xC; constexpr uint32_t SPI_SHADER_USER_DATA_PS_0 = 0xC;
constexpr uint32_t SPI_SHADER_USER_DATA_PS_15 = 0x1B; constexpr uint32_t SPI_SHADER_USER_DATA_PS_15 = 0x1B;
@@ -886,61 +890,101 @@ constexpr uint32_t SPI_SHADER_PGM_RSRC2_GS_USER_SGPR_MSB_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_GS_SHARED_VGPR_CNT_SHIFT = 28; constexpr uint32_t SPI_SHADER_PGM_RSRC2_GS_SHARED_VGPR_CNT_SHIFT = 28;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_GS_SHARED_VGPR_CNT_MASK = 0xF; constexpr uint32_t SPI_SHADER_PGM_RSRC2_GS_SHARED_VGPR_CNT_MASK = 0xF;
constexpr uint32_t SPI_SHADER_USER_DATA_GS_0 = 0x8C; constexpr uint32_t SPI_SHADER_USER_DATA_GS_0 = 0x8C;
constexpr uint32_t SPI_SHADER_USER_DATA_GS_15 = 0x9B; constexpr uint32_t SPI_SHADER_USER_DATA_GS_15 = 0x9B;
constexpr uint32_t SPI_SHADER_USER_DATA_GS_31 = 0xAB; constexpr uint32_t SPI_SHADER_USER_DATA_GS_31 = 0xAB;
constexpr uint32_t SPI_SHADER_REQ_CTRL_ESGS = 0xB0; constexpr uint32_t SPI_SHADER_REQ_CTRL_ESGS = 0xB0;
constexpr uint32_t SPI_SHADER_USER_ACCUM_ESGS_0 = 0xB2; constexpr uint32_t SPI_SHADER_USER_ACCUM_ESGS_0 = 0xB2;
constexpr uint32_t SPI_SHADER_USER_ACCUM_ESGS_3 = 0xB5; constexpr uint32_t SPI_SHADER_USER_ACCUM_ESGS_3 = 0xB5;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_ES_GS = 0xBC; constexpr uint32_t SPI_SHADER_PGM_RSRC2_ES_GS = 0xBC;
constexpr uint32_t SPI_SHADER_PGM_LO_ES = 0xC8; constexpr uint32_t SPI_SHADER_PGM_LO_ES = 0xC8;
constexpr uint32_t SPI_SHADER_PGM_HI_ES = 0xC9; constexpr uint32_t SPI_SHADER_PGM_HI_ES = 0xC9;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_ES = 0xCA; constexpr uint32_t SPI_SHADER_PGM_RSRC1_ES = 0xCA;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_ES = 0xCB; constexpr uint32_t SPI_SHADER_PGM_RSRC2_ES = 0xCB;
constexpr uint32_t SPI_SHADER_USER_DATA_ES_0 = 0xCC; constexpr uint32_t SPI_SHADER_USER_DATA_ES_0 = 0xCC;
constexpr uint32_t SPI_SHADER_USER_DATA_ES_15 = 0xDB; constexpr uint32_t SPI_SHADER_USER_DATA_ES_15 = 0xDB;
constexpr uint32_t SPI_SHADER_USER_DATA_ES_31 = 0xEB; constexpr uint32_t SPI_SHADER_USER_DATA_ES_31 = 0xEB;
constexpr uint32_t SPI_SHADER_PGM_CHKSUM_HS = 0x100; constexpr uint32_t SPI_SHADER_PGM_CHKSUM_HS = 0x100;
constexpr uint32_t SPI_SHADER_PGM_RSRC4_HS = 0x101; constexpr uint32_t SPI_SHADER_PGM_RSRC4_HS = 0x101;
constexpr uint32_t SPI_SHADER_USER_DATA_ADDR_LO_HS = 0x102; constexpr uint32_t SPI_SHADER_USER_DATA_ADDR_LO_HS = 0x102;
constexpr uint32_t SPI_SHADER_USER_DATA_ADDR_HI_HS = 0x103; constexpr uint32_t SPI_SHADER_USER_DATA_ADDR_HI_HS = 0x103;
constexpr uint32_t SPI_SHADER_PGM_RSRC3_HS = 0x107; constexpr uint32_t SPI_SHADER_PGM_RSRC3_HS = 0x107;
constexpr uint32_t SPI_SHADER_PGM_LO_HS = 0x108; constexpr uint32_t SPI_SHADER_PGM_LO_HS = 0x108;
constexpr uint32_t SPI_SHADER_PGM_HI_HS = 0x109; constexpr uint32_t SPI_SHADER_PGM_HI_HS = 0x109;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS = 0x10A; constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS = 0x10A;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_HS = 0x10B; constexpr uint32_t SPI_SHADER_PGM_RSRC2_HS = 0x10B;
constexpr uint32_t SPI_SHADER_USER_DATA_HS_0 = 0x10C; constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_VGPRS_SHIFT = 0;
constexpr uint32_t SPI_SHADER_USER_DATA_HS_15 = 0x11B; constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_VGPRS_MASK = 0x3F;
constexpr uint32_t SPI_SHADER_USER_DATA_HS_31 = 0x12B; constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_PRIORITY_SHIFT = 10;
constexpr uint32_t SPI_SHADER_REQ_CTRL_LSHS = 0x130; constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_PRIORITY_MASK = 0x3;
constexpr uint32_t SPI_SHADER_USER_ACCUM_LSHS_0 = 0x132; constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_FLOAT_MODE_SHIFT = 12;
constexpr uint32_t SPI_SHADER_USER_ACCUM_LSHS_3 = 0x135; constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_FLOAT_MODE_MASK = 0xFF;
constexpr uint32_t SPI_SHADER_PGM_LO_LS = 0x148; constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_DX10_CLAMP_SHIFT = 21;
constexpr uint32_t SPI_SHADER_PGM_HI_LS = 0x149; constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_DX10_CLAMP_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_LS = 0x14A; constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_DEBUG_MODE_SHIFT = 22;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_LS = 0x14B; constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_DEBUG_MODE_MASK = 0x1;
constexpr uint32_t COMPUTE_START_X = 0x204; constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_IEEE_MODE_SHIFT = 23;
constexpr uint32_t COMPUTE_START_Y = 0x205; constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_IEEE_MODE_MASK = 0x1;
constexpr uint32_t COMPUTE_START_Z = 0x206; constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_FWD_PROGRESS_SHIFT = 25;
constexpr uint32_t COMPUTE_NUM_THREAD_X = 0x207; constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_FWD_PROGRESS_MASK = 0x1;
constexpr uint32_t COMPUTE_NUM_THREAD_Y = 0x208; constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_WGP_MODE_SHIFT = 26;
constexpr uint32_t COMPUTE_NUM_THREAD_Z = 0x209; constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_WGP_MODE_MASK = 0x1;
constexpr uint32_t COMPUTE_PGM_LO = 0x20C; constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_LS_VGPR_COMP_CNT_SHIFT = 28;
constexpr uint32_t COMPUTE_PGM_HI = 0x20D; constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_LS_VGPR_COMP_CNT_MASK = 0x3;
constexpr uint32_t COMPUTE_TBA_LO = 0x20E; constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_FP16_OVFL_SHIFT = 30;
constexpr uint32_t COMPUTE_TBA_HI = 0x20F; constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_FP16_OVFL_MASK = 0x1;
constexpr uint32_t COMPUTE_TMA_LO = 0x210; constexpr uint32_t SPI_SHADER_PGM_RSRC2_HS_SCRATCH_EN_SHIFT = 0;
constexpr uint32_t COMPUTE_TMA_HI = 0x211; constexpr uint32_t SPI_SHADER_PGM_RSRC2_HS_SCRATCH_EN_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_HS_USER_SGPR_SHIFT = 1;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_HS_USER_SGPR_MASK = 0x1F;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_HS_LDS_SIZE_SHIFT = 18;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_HS_LDS_SIZE_MASK = 0x1FF;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_HS_USER_SGPR_MSB_SHIFT = 27;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_HS_USER_SGPR_MSB_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_HS_SHARED_VGPR_CNT_SHIFT = 28;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_HS_SHARED_VGPR_CNT_MASK = 0xF;
constexpr uint32_t SPI_SHADER_USER_DATA_HS_0 = 0x10C;
constexpr uint32_t SPI_SHADER_USER_DATA_HS_15 = 0x11B;
constexpr uint32_t SPI_SHADER_USER_DATA_HS_31 = 0x12B;
constexpr uint32_t SPI_SHADER_REQ_CTRL_LSHS = 0x130;
constexpr uint32_t SPI_SHADER_USER_ACCUM_LSHS_0 = 0x132;
constexpr uint32_t SPI_SHADER_USER_ACCUM_LSHS_3 = 0x135;
constexpr uint32_t SPI_SHADER_PGM_LO_LS = 0x148;
constexpr uint32_t SPI_SHADER_PGM_HI_LS = 0x149;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_LS = 0x14A;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_LS = 0x14B;
constexpr uint32_t COMPUTE_START_X = 0x204;
constexpr uint32_t COMPUTE_START_Y = 0x205;
constexpr uint32_t COMPUTE_START_Z = 0x206;
constexpr uint32_t COMPUTE_NUM_THREAD_X = 0x207;
constexpr uint32_t COMPUTE_NUM_THREAD_Y = 0x208;
constexpr uint32_t COMPUTE_NUM_THREAD_Z = 0x209;
constexpr uint32_t COMPUTE_PGM_LO = 0x20C;
constexpr uint32_t COMPUTE_PGM_HI = 0x20D;
constexpr uint32_t COMPUTE_TBA_LO = 0x20E;
constexpr uint32_t COMPUTE_TBA_HI = 0x20F;
constexpr uint32_t COMPUTE_TMA_LO = 0x210;
constexpr uint32_t COMPUTE_TMA_HI = 0x211;
constexpr uint32_t COMPUTE_PGM_RSRC1 = 0x212; constexpr uint32_t COMPUTE_PGM_RSRC1 = 0x212;
constexpr uint32_t COMPUTE_PGM_RSRC1_VGPRS_SHIFT = 0; constexpr uint32_t COMPUTE_PGM_RSRC1_VGPRS_SHIFT = 0;
constexpr uint32_t COMPUTE_PGM_RSRC1_VGPRS_MASK = 0x3F; constexpr uint32_t COMPUTE_PGM_RSRC1_VGPRS_MASK = 0x3F;
constexpr uint32_t COMPUTE_PGM_RSRC1_SGPRS_SHIFT = 6; constexpr uint32_t COMPUTE_PGM_RSRC1_SGPRS_SHIFT = 6;
constexpr uint32_t COMPUTE_PGM_RSRC1_SGPRS_MASK = 0xF; constexpr uint32_t COMPUTE_PGM_RSRC1_SGPRS_MASK = 0xF;
constexpr uint32_t COMPUTE_PGM_RSRC1_BULKY_SHIFT = 24; constexpr uint32_t COMPUTE_PGM_RSRC1_FLOAT_MODE_SHIFT = 12;
constexpr uint32_t COMPUTE_PGM_RSRC1_BULKY_MASK = 0x1; constexpr uint32_t COMPUTE_PGM_RSRC1_FLOAT_MODE_MASK = 0xFF;
constexpr uint32_t COMPUTE_PGM_RSRC1_W32_EN_SHIFT = 30; constexpr uint32_t COMPUTE_PGM_RSRC1_DX10_CLAMP_SHIFT = 21;
constexpr uint32_t COMPUTE_PGM_RSRC1_W32_EN_MASK = 0x1; constexpr uint32_t COMPUTE_PGM_RSRC1_DX10_CLAMP_MASK = 0x1;
constexpr uint32_t COMPUTE_PGM_RSRC1_IEEE_MODE_SHIFT = 23;
constexpr uint32_t COMPUTE_PGM_RSRC1_IEEE_MODE_MASK = 0x1;
constexpr uint32_t COMPUTE_PGM_RSRC1_BULKY_SHIFT = 24;
constexpr uint32_t COMPUTE_PGM_RSRC1_BULKY_MASK = 0x1;
constexpr uint32_t COMPUTE_PGM_RSRC1_FP16_OVFL_SHIFT = 26;
constexpr uint32_t COMPUTE_PGM_RSRC1_FP16_OVFL_MASK = 0x1;
constexpr uint32_t COMPUTE_PGM_RSRC1_WGP_MODE_SHIFT = 29;
constexpr uint32_t COMPUTE_PGM_RSRC1_WGP_MODE_MASK = 0x1;
constexpr uint32_t COMPUTE_PGM_RSRC1_W32_EN_SHIFT = 30;
constexpr uint32_t COMPUTE_PGM_RSRC1_W32_EN_MASK = 0x1;
constexpr uint32_t COMPUTE_PGM_RSRC2 = 0x213; constexpr uint32_t COMPUTE_PGM_RSRC2 = 0x213;
constexpr uint32_t COMPUTE_PGM_RSRC2_SCRATCH_EN_SHIFT = 0; constexpr uint32_t COMPUTE_PGM_RSRC2_SCRATCH_EN_SHIFT = 0;
File diff suppressed because it is too large. Load diff
+60 -29
View File
@@ -39,7 +39,7 @@ enum class TileBlockFamily : uint32_t {
}; };
struct TileBlockLayout { struct TileBlockLayout {
TileBlockFamily family = TileBlockFamily::Standard256B; TileBlockFamily family = TileBlockFamily::Count;
uint32_t bytes_per_element = 0; uint32_t bytes_per_element = 0;
uint32_t block_size = 0; uint32_t block_size = 0;
uint32_t block_width = 0; uint32_t block_width = 0;
@@ -47,38 +47,69 @@ struct TileBlockLayout {
uint32_t block_depth = 0; uint32_t block_depth = 0;
}; };
struct TileVolumeLayout { struct TileTextureElementLayout {
TileBlockFamily family = TileBlockFamily::Count; uint32_t bytes = 0;
uint32_t bytes_per_element = 0; uint32_t texel_width = 1;
uint32_t texel_width = 1; uint32_t texel_height = 1;
uint32_t texel_height = 1; };
uint32_t first_tail_level = 0;
uint32_t block_depth = 1; struct TileTextureBlockLayout {
uint64_t block_slice_size = 0; TileBlockLayout block;
uint64_t total_size = 0; uint32_t texel_width = 1;
uint64_t level_offsets[16] = {}; uint32_t texel_height = 1;
uint64_t level_sizes[16] = {}; };
uint32_t tail_x[16] = {};
uint32_t tail_y[16] = {}; enum class TileSurfaceDimension : uint32_t {
uint32_t level_widths[16] = {}; Dim2D,
uint32_t level_heights[16] = {}; Dim3D,
};
struct TileSurfaceDescription {
uint32_t format = 0;
uint32_t tile_mode = 0;
TileSurfaceDimension dimension = TileSurfaceDimension::Dim2D;
uint32_t width = 0;
uint32_t height = 0;
uint32_t depth = 1;
uint32_t levels = 1;
uint32_t layers = 1;
};
// Width/height fields are measured in addressable elements, not texture texels.
struct TileMipLayout {
uint64_t offset = 0;
uint64_t size = 0;
uint32_t width = 0;
uint32_t height = 0;
uint32_t padded_width = 0;
uint32_t padded_height = 0;
uint32_t tail_x = 0;
uint32_t tail_y = 0;
};
struct TileSurfaceLayout {
TileSurfaceDescription description;
TileTextureBlockLayout texture;
uint32_t first_tail_level = 0;
uint64_t block_slice_size = 0;
uint64_t total_size = 0;
TileMipLayout mips[16] = {};
}; };
bool TileGetBlockLayout(TileBlockFamily family, uint32_t bytes_per_element, bool TileGetBlockLayout(TileBlockFamily family, uint32_t bytes_per_element,
TileBlockLayout& layout); TileBlockLayout& layout);
bool TileGetTextureElementLayout(uint32_t format, TileTextureElementLayout& layout);
bool TileGetTextureBlockLayout(uint32_t format, uint32_t tile, bool volume,
TileTextureBlockLayout& layout);
bool TileGetTiledTextureLayout(const TileSurfaceDescription& description,
TileSurfaceLayout& layout);
bool TileGetBlockOffset(const TileBlockLayout& layout, uint32_t x, uint32_t y, uint32_t z, bool TileGetBlockOffset(const TileBlockLayout& layout, uint32_t x, uint32_t y, uint32_t z,
uint32_t& byte_offset); uint32_t& byte_offset);
bool TileGetBlockXor(const TileBlockLayout& layout, uint32_t block_x, uint32_t block_y, bool TileGetBlockXor(const TileBlockLayout& layout, uint32_t block_x, uint32_t block_y,
uint32_t& byte_offset); uint32_t& byte_offset);
bool TileGetBlockXor(const TileBlockLayout& layout, uint32_t block_x, uint32_t block_y, bool TileGetBlockXor(const TileBlockLayout& layout, uint32_t block_x, uint32_t block_y,
uint32_t block_z, uint32_t& byte_offset); uint32_t block_z, uint32_t& byte_offset);
bool TileIsStandard256BTextureSupported(uint32_t format);
bool TileIsStandard4KBTextureSupported(uint32_t format);
bool TileIsStandard64KBTextureSupported(uint32_t format);
bool TileGetTextureVolumeLayout(uint32_t format, uint32_t width, uint32_t height, uint32_t depth,
uint32_t levels, uint32_t tile, TileVolumeLayout& layout);
bool TileGetHtileSize(uint32_t width, uint32_t height, TileSizeAlign& htile_size);
bool TileGetDepthSize(uint32_t width, uint32_t height, uint32_t pitch, uint32_t z_format, bool TileGetDepthSize(uint32_t width, uint32_t height, uint32_t pitch, uint32_t z_format,
uint32_t stencil_format, bool htile, TileSizeAlign& stencil_size, uint32_t stencil_format, bool htile, TileSizeAlign& stencil_size,
TileSizeAlign& htile_size, TileSizeAlign& depth_size, TileSizeAlign& htile_size, TileSizeAlign& depth_size,
@@ -94,13 +125,13 @@ bool TileGetRenderTargetMipLayout(uint32_t width, uint32_t height, uint32_t
uint32_t bytes_per_element, uint32_t levels, uint32_t bytes_per_element, uint32_t levels,
TileSizeAlign& total_size, TileSizeOffset* level_sizes, TileSizeAlign& total_size, TileSizeOffset* level_sizes,
TilePaddedSize* padded_size); TilePaddedSize* padded_size);
void TileGetTextureSize(uint32_t format, uint32_t width, uint32_t height, uint32_t pitch, void TileGetTextureSize(uint32_t format, uint32_t width, uint32_t height, uint32_t levels,
uint32_t levels, uint32_t tile, TileSizeAlign* total_size, uint32_t tile, TileSizeAlign* total_size, TileSizeOffset* level_sizes,
TileSizeOffset* level_sizes, TilePaddedSize* padded_size); TilePaddedSize* padded_size);
void TileGetTextureTotalSize(uint32_t format, uint32_t width, uint32_t height, uint32_t depth, void TileGetTextureTotalSize(uint32_t format, uint32_t width, uint32_t height, uint32_t depth,
uint32_t pitch, uint32_t levels, uint32_t tile, bool volume_texture, uint32_t levels, uint32_t tile, bool volume_texture,
TileSizeAlign& total_size); TileSizeAlign& total_size);
uint32_t TileGetTexturePitch(uint32_t format, uint32_t width, uint32_t levels, uint32_t tile); uint32_t TileGetTexturePitch(uint32_t format, uint32_t width, uint32_t tile);
} // namespace Libs::Graphics } // namespace Libs::Graphics
+2 -8
View File
@@ -79,7 +79,6 @@ RegionManager* MemoryTracker::GetOrCreateRegion(uint64_t index) {
bool MemoryTracker::IsRegionCpuModified(uint64_t vaddr, uint64_t size) { bool MemoryTracker::IsRegionCpuModified(uint64_t vaddr, uint64_t size) {
CheckNotInUploadCallback(); CheckNotInUploadCallback();
std::lock_guard access(m_access_mutex);
return Iterate<true>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) { return Iterate<true>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock); std::scoped_lock lock(manager->lock);
return manager->IsModified<DirtySource::Cpu>(offset, bytes); return manager->IsModified<DirtySource::Cpu>(offset, bytes);
@@ -88,7 +87,6 @@ bool MemoryTracker::IsRegionCpuModified(uint64_t vaddr, uint64_t size) {
bool MemoryTracker::IsRegionGpuModified(uint64_t vaddr, uint64_t size) { bool MemoryTracker::IsRegionGpuModified(uint64_t vaddr, uint64_t size) {
CheckNotInUploadCallback(); CheckNotInUploadCallback();
std::lock_guard access(m_access_mutex);
return Iterate<false>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) { return Iterate<false>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock); std::scoped_lock lock(manager->lock);
return manager->IsModified<DirtySource::Gpu>(offset, bytes); return manager->IsModified<DirtySource::Gpu>(offset, bytes);
@@ -97,7 +95,6 @@ bool MemoryTracker::IsRegionGpuModified(uint64_t vaddr, uint64_t size) {
void MemoryTracker::MarkRegionAsCpuModified(uint64_t vaddr, uint64_t size) { void MemoryTracker::MarkRegionAsCpuModified(uint64_t vaddr, uint64_t size) {
CheckNotInUploadCallback(); CheckNotInUploadCallback();
std::lock_guard access(m_access_mutex);
Iterate<true>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) { Iterate<true>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock); std::scoped_lock lock(manager->lock);
manager->ChangeState<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset, bytes); manager->ChangeState<DirtySource::Cpu, true>(manager->GetCpuAddr() + offset, bytes);
@@ -106,7 +103,6 @@ void MemoryTracker::MarkRegionAsCpuModified(uint64_t vaddr, uint64_t size) {
void MemoryTracker::MarkRegionAsGpuModified(uint64_t vaddr, uint64_t size) { void MemoryTracker::MarkRegionAsGpuModified(uint64_t vaddr, uint64_t size) {
CheckNotInUploadCallback(); CheckNotInUploadCallback();
std::lock_guard access(m_access_mutex);
Iterate<true>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) { Iterate<true>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock); std::scoped_lock lock(manager->lock);
manager->ChangeState<DirtySource::Gpu, true>(manager->GetCpuAddr() + offset, bytes); manager->ChangeState<DirtySource::Gpu, true>(manager->GetCpuAddr() + offset, bytes);
@@ -115,14 +111,13 @@ void MemoryTracker::MarkRegionAsGpuModified(uint64_t vaddr, uint64_t size) {
void MemoryTracker::UnmarkRegionAsGpuModified(uint64_t vaddr, uint64_t size) { void MemoryTracker::UnmarkRegionAsGpuModified(uint64_t vaddr, uint64_t size) {
CheckNotInUploadCallback(); CheckNotInUploadCallback();
std::lock_guard access(m_access_mutex);
Iterate<false>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) { Iterate<false>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock); std::scoped_lock lock(manager->lock);
manager->ChangeState<DirtySource::Gpu, false>(manager->GetCpuAddr() + offset, bytes); manager->ChangeState<DirtySource::Gpu, false>(manager->GetCpuAddr() + offset, bytes);
}); });
} }
void MemoryTracker::UntrackMemoryLocked(uint64_t vaddr, uint64_t size) { void MemoryTracker::UntrackMemoryImpl(uint64_t vaddr, uint64_t size) {
std::vector<RegionManager*> managers; std::vector<RegionManager*> managers;
managers.reserve((vaddr % TRACKER_REGION_SIZE + size + TRACKER_REGION_SIZE - 1) / managers.reserve((vaddr % TRACKER_REGION_SIZE + size + TRACKER_REGION_SIZE - 1) /
TRACKER_REGION_SIZE); TRACKER_REGION_SIZE);
@@ -148,8 +143,7 @@ void MemoryTracker::UntrackMemoryLocked(uint64_t vaddr, uint64_t size) {
void MemoryTracker::UntrackMemory(uint64_t vaddr, uint64_t size) { void MemoryTracker::UntrackMemory(uint64_t vaddr, uint64_t size) {
CheckNotInUploadCallback(); CheckNotInUploadCallback();
std::lock_guard access(m_access_mutex); UntrackMemoryImpl(vaddr, size);
UntrackMemoryLocked(vaddr, size);
} }
} // namespace Libs::Graphics } // namespace Libs::Graphics
+1 -4
View File
@@ -68,7 +68,6 @@ public:
static_assert(std::is_nothrow_invocable_v<Preflight&, uint64_t, uint64_t>); static_assert(std::is_nothrow_invocable_v<Preflight&, uint64_t, uint64_t>);
static_assert(std::is_nothrow_invocable_v<Func&, uint64_t, uint64_t>); static_assert(std::is_nothrow_invocable_v<Func&, uint64_t, uint64_t>);
CheckNotInUploadCallback(); CheckNotInUploadCallback();
std::lock_guard access(m_access_mutex);
std::vector<RegionManager*> managers; std::vector<RegionManager*> managers;
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t, uint64_t) { Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t, uint64_t) {
managers.push_back(manager); managers.push_back(manager);
@@ -109,7 +108,6 @@ public:
static_assert(std::is_nothrow_invocable_v<RangeFunc&, uint64_t, uint64_t>); static_assert(std::is_nothrow_invocable_v<RangeFunc&, uint64_t, uint64_t>);
static_assert(std::is_nothrow_invocable_v<UploadFunc&>); static_assert(std::is_nothrow_invocable_v<UploadFunc&>);
CheckNotInUploadCallback(); CheckNotInUploadCallback();
std::unique_lock access(m_access_mutex);
Iterate<true>(vaddr, size, [](RegionManager*, uint64_t, uint64_t) {}); Iterate<true>(vaddr, size, [](RegionManager*, uint64_t, uint64_t) {});
const auto* previous_upload_owner = std::exchange(s_upload_owner, this); const auto* previous_upload_owner = std::exchange(s_upload_owner, this);
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) { Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
@@ -173,13 +171,12 @@ private:
} }
static void ValidateRange(uint64_t vaddr, uint64_t size); static void ValidateRange(uint64_t vaddr, uint64_t size);
void UntrackMemoryLocked(uint64_t vaddr, uint64_t size); void UntrackMemoryImpl(uint64_t vaddr, uint64_t size);
RegionManager* GetOrCreateRegion(uint64_t index); RegionManager* GetOrCreateRegion(uint64_t index);
std::unique_ptr<std::atomic<RegionManager*>[]> m_regions; std::unique_ptr<std::atomic<RegionManager*>[]> m_regions;
std::vector<std::unique_ptr<RegionManager>> m_region_storage; std::vector<std::unique_ptr<RegionManager>> m_region_storage;
std::mutex m_region_mutex; std::mutex m_region_mutex;
std::mutex m_access_mutex;
PageManager& m_page_manager; PageManager& m_page_manager;
}; };
+39 -49
View File
@@ -5,7 +5,6 @@
#include "common/profiler.h" #include "common/profiler.h"
#include "graphics/guest_gpu/graphicsRun.h" #include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/host_gpu/graphicContext.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/cache/textureCache.h"
#include "graphics/host_gpu/renderer/commandScheduler.h" #include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/renderer/render.h" #include "graphics/host_gpu/renderer/render.h"
@@ -60,15 +59,6 @@ uint64_t BufferCache::AlignUp(uint64_t value) {
return (value + CACHING_PAGE_SIZE - 1) & ~(CACHING_PAGE_SIZE - 1); return (value + CACHING_PAGE_SIZE - 1) & ~(CACHING_PAGE_SIZE - 1);
} }
bool BufferCache::PageOverlaps(uint64_t left, uint64_t left_size, uint64_t right,
uint64_t right_size) noexcept {
const auto left_begin = left & ~(TRACKER_PAGE_SIZE - 1);
const auto left_end = (left + left_size + TRACKER_PAGE_SIZE - 1) & ~(TRACKER_PAGE_SIZE - 1);
const auto right_begin = right & ~(TRACKER_PAGE_SIZE - 1);
const auto right_end = (right + right_size + TRACKER_PAGE_SIZE - 1) & ~(TRACKER_PAGE_SIZE - 1);
return left_begin < right_end && right_begin < left_end;
}
void BufferCache::Upload(CommandBuffer& command, Buffer& destination, uint64_t destination_offset, void BufferCache::Upload(CommandBuffer& command, Buffer& destination, uint64_t destination_offset,
const void* source, uint64_t size) { const void* source, uint64_t size) {
auto* bytes = static_cast<const uint8_t*>(source); auto* bytes = static_cast<const uint8_t*>(source);
@@ -220,8 +210,7 @@ void BufferCache::QueueGarbageDownload(std::span<const DownloadCopy> copies, Ret
} }
BufferCache::BufferCache(GraphicContext& graphics, CommandScheduler& scheduler, BufferCache::BufferCache(GraphicContext& graphics, CommandScheduler& scheduler,
PageManager& page_manager, TextureCache& texture_cache, PageManager& page_manager, TextureCache& texture_cache)
ResourceMutex& resource_mutex)
: m_graphics(graphics), m_scheduler(scheduler), : m_graphics(graphics), m_scheduler(scheduler),
m_gds_buffer(graphics, scheduler, MemoryUsage::Stream, 0, AllFlags, GdsBufferSize), m_gds_buffer(graphics, scheduler, MemoryUsage::Stream, 0, AllFlags, GdsBufferSize),
m_memory_tracker(page_manager), m_memory_tracker(page_manager),
@@ -229,7 +218,7 @@ BufferCache::BufferCache(GraphicContext& graphics, CommandScheduler& scheduler,
m_stream_buffer(graphics, scheduler, MemoryUsage::Stream, 64 * MiB), m_stream_buffer(graphics, scheduler, MemoryUsage::Stream, 64 * MiB),
m_download_buffer(graphics, scheduler, MemoryUsage::Download, 32 * MiB), m_download_buffer(graphics, scheduler, MemoryUsage::Download, 32 * MiB),
m_device_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 128 * MiB), m_device_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 128 * MiB),
m_texture_cache(texture_cache), m_resource_mutex(resource_mutex) { m_texture_cache(texture_cache) {
std::memset(m_gds_buffer.Mapped().data(), 0, static_cast<size_t>(m_gds_buffer.Size())); std::memset(m_gds_buffer.Mapped().data(), 0, static_cast<size_t>(m_gds_buffer.Size()));
m_gds_buffer.Flush(0, m_gds_buffer.Size()); m_gds_buffer.Flush(0, m_gds_buffer.Size());
if (!m_graphics.CanReportMemoryUsage()) { if (!m_graphics.CanReportMemoryUsage()) {
@@ -287,7 +276,7 @@ void BufferCache::InvalidateMemory(uint64_t vaddr, uint64_t size) {
size > TRACKER_ADDRESS_SIZE - vaddr) { size > TRACKER_ADDRESS_SIZE - vaddr) {
EXIT("BufferCache: invalid memory-invalidation range\n"); EXIT("BufferCache: invalid memory-invalidation range\n");
} }
if (!HasPageOverlap(vaddr, size)) { if (!IsRegionRegistered(vaddr, size)) {
return; return;
} }
m_memory_tracker.InvalidateRegion(vaddr, size, m_memory_tracker.InvalidateRegion(vaddr, size,
@@ -304,11 +293,6 @@ void BufferCache::ReadMemory(uint64_t vaddr, uint64_t size, bool is_write) {
"addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n", "addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
vaddr, size); 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( m_scheduler.Context().GetGpu().SendCommandSync(
[this, vaddr, size, is_write] { ReadMemoryOnGpu(vaddr, size, is_write); }); [this, vaddr, size, is_write] { ReadMemoryOnGpu(vaddr, size, is_write); });
} }
@@ -529,7 +513,6 @@ BufferBinding BufferCache::ObtainBuffer(CommandBuffer& command, uint64_t vaddr,
if (command.IsInvalid() || command.IsExecute()) { if (command.IsInvalid() || command.IsExecute()) {
EXIT("BufferCache: buffer request requires a recording command buffer\n"); EXIT("BufferCache: buffer request requires a recording command buffer\n");
} }
std::lock_guard transaction(m_resource_mutex);
if (is_read && !is_written && size <= CACHING_PAGE_SIZE && if (is_read && !is_written && size <= CACHING_PAGE_SIZE &&
!m_memory_tracker.IsRegionGpuModified(vaddr, size) && !m_memory_tracker.IsRegionGpuModified(vaddr, size) &&
@@ -556,25 +539,33 @@ BufferBinding BufferCache::ObtainBuffer(CommandBuffer& command, uint64_t vaddr,
(void)m_texture_cache.InvalidateMemoryFromGPU(vaddr, size, true); (void)m_texture_cache.InvalidateMemoryFromGPU(vaddr, size, true);
} }
FaultSafeCacheLock lock(this, m_mutex); std::shared_ptr<Buffer> owner;
auto& cached = GetOrCreateBuffer(command, vaddr, size); uint64_t offset = 0;
std::vector<std::pair<uint64_t, uint64_t>> uploads; {
m_memory_tracker.ForEachUploadRange( FaultSafeCacheLock lock(this, m_mutex);
vaddr, size, is_written, auto& cached = GetOrCreateBuffer(command, vaddr, size);
[&](uint64_t address, uint64_t bytes) noexcept { uploads.emplace_back(address, bytes); }, std::vector<std::pair<uint64_t, uint64_t>> uploads;
[&]() noexcept { m_memory_tracker.ForEachUploadRange(
for (const auto& [address, bytes]: uploads) { vaddr, size, is_written,
Upload(command, *cached.buffer, cached.buffer->Offset(address), [&](uint64_t address, uint64_t bytes) noexcept {
reinterpret_cast<const void*>(address), bytes); uploads.emplace_back(address, bytes);
} },
}); [&]() noexcept {
if (is_written) { for (const auto& [address, bytes]: uploads) {
m_gpu_modified_ranges.Add(vaddr, size); Upload(command, *cached.buffer, cached.buffer->Offset(address),
reinterpret_cast<const void*>(address), bytes);
}
});
if (is_written) {
m_gpu_modified_ranges.Add(vaddr, size);
}
owner = cached.buffer;
offset = cached.buffer->Offset(vaddr);
} }
if (is_formatted && is_read && !is_written) { if (is_formatted && is_read && !is_written) {
(void)SynchronizeBufferFromImage(*cached.buffer, vaddr, size); (void)SynchronizeBufferFromImage(*owner, vaddr, size);
} }
return {cached.buffer, cached.buffer->Handle(), cached.buffer->Offset(vaddr)}; return {owner, owner->Handle(), offset};
} }
std::shared_ptr<Buffer> BufferCache::ObtainNullBuffer() { std::shared_ptr<Buffer> BufferCache::ObtainNullBuffer() {
@@ -750,8 +741,7 @@ void BufferCache::FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool
} }
(void)m_texture_cache.ClearMeta(vaddr); (void)m_texture_cache.ClearMeta(vaddr);
{ {
std::lock_guard transaction(m_resource_mutex); const auto region = m_texture_cache.QueryRegion(vaddr, size);
const auto region = m_texture_cache.QueryRegion(vaddr, size);
if (!HasGpuDirtyBytes(vaddr, size) && !region.gpu_image_bytes) { if (!HasGpuDirtyBytes(vaddr, size) && !region.gpu_image_bytes) {
if (region.image_bytes) { if (region.image_bytes) {
m_texture_cache.InvalidateMemory(vaddr, size); m_texture_cache.InvalidateMemory(vaddr, size);
@@ -790,8 +780,7 @@ void BufferCache::CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t si
EXIT("BufferCache: invalid or overlapping copy range\n"); EXIT("BufferCache: invalid or overlapping copy range\n");
} }
if (src_memory || dst_memory) { if (src_memory || dst_memory) {
std::lock_guard transaction(m_resource_mutex); const auto src_region =
const auto src_region =
src_memory ? m_texture_cache.QueryRegion(src_vaddr, size) : TextureCache::RegionInfo {}; src_memory ? m_texture_cache.QueryRegion(src_vaddr, size) : TextureCache::RegionInfo {};
const auto dst_region = const auto dst_region =
dst_memory ? m_texture_cache.QueryRegion(dst_vaddr, size) : TextureCache::RegionInfo {}; dst_memory ? m_texture_cache.QueryRegion(dst_vaddr, size) : TextureCache::RegionInfo {};
@@ -842,18 +831,20 @@ void BufferCache::CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t si
destination.CopyFrom(command, source, src.offset, dst.offset, size); destination.CopyFrom(command, source, src.offset, dst.offset, size);
} }
bool BufferCache::HasPageOverlap(uint64_t vaddr, uint64_t size) { bool BufferCache::IsRegionRegistered(uint64_t vaddr, uint64_t size) {
if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE || if (vaddr == 0 || size == 0 || vaddr >= TRACKER_ADDRESS_SIZE ||
size > TRACKER_ADDRESS_SIZE - vaddr) { size > TRACKER_ADDRESS_SIZE - vaddr) {
EXIT("BufferCache: invalid page-overlap query\n"); EXIT("BufferCache: invalid registered-region query\n");
} }
FaultSafeCacheLock lock(this, m_mutex); FaultSafeCacheLock lock(this, m_mutex);
for (const auto& [address, cached]: m_buffers) { // Cached buffers are ordered and non-overlapping. The last buffer beginning before the query
if (PageOverlaps(vaddr, size, address, cached->size)) { // end is therefore the only possible intersection.
return true; const auto candidate = m_buffers.lower_bound(vaddr + size);
} if (candidate == m_buffers.begin()) {
return false;
} }
return false; const auto& [address, cached] = *std::prev(candidate);
return address + cached->size > vaddr;
} }
bool BufferCache::IsRegionGpuModified(uint64_t vaddr, uint64_t size) { bool BufferCache::IsRegionGpuModified(uint64_t vaddr, uint64_t size) {
@@ -870,8 +861,7 @@ bool BufferCache::IsRegionCpuModified(uint64_t vaddr, uint64_t size) {
} }
void BufferCache::RunGarbageCollector() { void BufferCache::RunGarbageCollector() {
std::lock_guard transaction(m_resource_mutex); const auto tick = m_gc_tick++;
const auto tick = m_gc_tick++;
if (m_graphics.CanReportMemoryUsage()) { if (m_graphics.CanReportMemoryUsage()) {
m_total_used_memory = m_graphics.GetDeviceMemoryUsage(); m_total_used_memory = m_graphics.GetDeviceMemoryUsage();
} }
+2 -6
View File
@@ -20,7 +20,6 @@ struct GraphicContext;
class CommandBuffer; class CommandBuffer;
class CommandScheduler; class CommandScheduler;
class TextureCache; class TextureCache;
class ResourceMutex;
struct BufferBinding { struct BufferBinding {
std::shared_ptr<void> owner; std::shared_ptr<void> owner;
@@ -41,7 +40,7 @@ public:
} }
BufferCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager, BufferCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
TextureCache& texture_cache, ResourceMutex& resource_mutex); TextureCache& texture_cache);
~BufferCache(); ~BufferCache();
KYTY_CLASS_NO_COPY(BufferCache); KYTY_CLASS_NO_COPY(BufferCache);
@@ -61,7 +60,7 @@ public:
void FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool is_gds = false); void FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool is_gds = false);
void CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size, bool dst_gds = false, void CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size, bool dst_gds = false,
bool src_gds = false); bool src_gds = false);
[[nodiscard]] bool HasPageOverlap(uint64_t vaddr, uint64_t size); [[nodiscard]] bool IsRegionRegistered(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool HasGpuDirtyBytes(uint64_t vaddr, uint64_t size); [[nodiscard]] bool HasGpuDirtyBytes(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool IsRegionCpuModified(uint64_t vaddr, uint64_t size); [[nodiscard]] bool IsRegionCpuModified(uint64_t vaddr, uint64_t size);
[[nodiscard]] bool IsRegionGpuModified(uint64_t vaddr, uint64_t size); [[nodiscard]] bool IsRegionGpuModified(uint64_t vaddr, uint64_t size);
@@ -84,8 +83,6 @@ private:
[[nodiscard]] static constexpr uint64_t AlignDownload(uint64_t size) noexcept { [[nodiscard]] static constexpr uint64_t AlignDownload(uint64_t size) noexcept {
return (size + DOWNLOAD_ALIGNMENT - 1) & ~(DOWNLOAD_ALIGNMENT - 1); return (size + DOWNLOAD_ALIGNMENT - 1) & ~(DOWNLOAD_ALIGNMENT - 1);
} }
[[nodiscard]] static bool PageOverlaps(uint64_t left, uint64_t left_size, uint64_t right,
uint64_t right_size) noexcept;
[[nodiscard]] static std::pair<uint64_t, uint64_t> DownloadEnvelope(const DownloadCopy& copy); [[nodiscard]] static std::pair<uint64_t, uint64_t> DownloadEnvelope(const DownloadCopy& copy);
[[nodiscard]] static bool ResolveOverlap(CacheRange& merged, CacheRange candidate) noexcept; [[nodiscard]] static bool ResolveOverlap(CacheRange& merged, CacheRange candidate) noexcept;
void Upload(CommandBuffer& command, Buffer& destination, uint64_t destination_offset, void Upload(CommandBuffer& command, Buffer& destination, uint64_t destination_offset,
@@ -112,7 +109,6 @@ private:
StreamBuffer m_download_buffer; StreamBuffer m_download_buffer;
StreamBuffer m_device_buffer; StreamBuffer m_device_buffer;
TextureCache& m_texture_cache; TextureCache& m_texture_cache;
ResourceMutex& m_resource_mutex;
uint64_t m_total_used_memory = 0; uint64_t m_total_used_memory = 0;
uint64_t m_trigger_gc_memory = 1ull * 1024 * 1024 * 1024; uint64_t m_trigger_gc_memory = 1ull * 1024 * 1024 * 1024;
uint64_t m_critical_gc_memory = 2ull * 1024 * 1024 * 1024; uint64_t m_critical_gc_memory = 2ull * 1024 * 1024 * 1024;
@@ -6,9 +6,8 @@
namespace Libs::Graphics { namespace Libs::Graphics {
GpuResourceManager::GpuResourceManager(GraphicContext& graphics, CommandScheduler& scheduler) GpuResourceManager::GpuResourceManager(GraphicContext& graphics, CommandScheduler& scheduler)
: m_scheduler(scheduler), : m_scheduler(scheduler), m_buffer_cache(graphics, scheduler, m_page_manager, m_texture_cache),
m_buffer_cache(graphics, scheduler, m_page_manager, m_texture_cache, m_resource_mutex), m_texture_cache(graphics, scheduler, m_page_manager, m_buffer_cache) {}
m_texture_cache(graphics, scheduler, m_page_manager, m_buffer_cache, m_resource_mutex) {}
GpuResourceManager::~GpuResourceManager() = default; GpuResourceManager::~GpuResourceManager() = default;
@@ -58,11 +57,6 @@ void GpuResourceManager::UnmapMemory(uint64_t vaddr, uint64_t size) {
"addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n", "addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
vaddr, size); vaddr, size);
} }
if (m_resource_mutex.IsOwnedByCurrentThread()) {
EXIT("unsupported memory unmap from a pre-owned resource transaction, "
"addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
vaddr, size);
}
const auto unmap = [this, vaddr, size] { const auto unmap = [this, vaddr, size] {
if (m_scheduler.Active()) { if (m_scheduler.Active()) {
const auto tick = m_scheduler.CurrentTick(); const auto tick = m_scheduler.CurrentTick();
@@ -5,7 +5,6 @@
#include "common/common.h" #include "common/common.h"
#include "graphics/host_gpu/pageManager.h" #include "graphics/host_gpu/pageManager.h"
#include "graphics/host_gpu/renderer/cache/bufferCache.h" #include "graphics/host_gpu/renderer/cache/bufferCache.h"
#include "graphics/host_gpu/renderer/cache/resourceMutex.h"
#include "graphics/host_gpu/renderer/cache/textureCache.h" #include "graphics/host_gpu/renderer/cache/textureCache.h"
#include <cstdint> #include <cstdint>
@@ -35,7 +34,6 @@ public:
private: private:
PageManager m_page_manager; PageManager m_page_manager;
ResourceMutex m_resource_mutex;
CommandScheduler& m_scheduler; CommandScheduler& m_scheduler;
BufferCache m_buffer_cache; BufferCache m_buffer_cache;
TextureCache m_texture_cache; TextureCache m_texture_cache;
-47
View File
@@ -1,47 +0,0 @@
#include "graphics/host_gpu/renderer/cache/resourceMutex.h"
#include "common/assert.h"
namespace Libs::Graphics {
ResourceMutex::~ResourceMutex() {
std::lock_guard state(m_state);
if (m_resource_owner != std::thread::id {}) {
EXIT("ResourceMutex destroyed with an active owner\n");
}
}
void ResourceMutex::lock() {
const auto current = std::this_thread::get_id();
{
std::lock_guard state(m_state);
if (m_resource_owner == current) {
EXIT("recursive resource transaction\n");
}
}
m_resource.lock();
std::lock_guard state(m_state);
if (m_resource_owner != std::thread::id {}) {
EXIT("resource mutex acquired with a stale owner\n");
}
m_resource_owner = current;
}
void ResourceMutex::unlock() {
const auto current = std::this_thread::get_id();
{
std::lock_guard state(m_state);
if (m_resource_owner != current) {
EXIT("resource transaction released without ownership\n");
}
m_resource_owner = {};
}
m_resource.unlock();
}
bool ResourceMutex::IsOwnedByCurrentThread() {
std::lock_guard state(m_state);
return m_resource_owner == std::this_thread::get_id();
}
} // namespace Libs::Graphics
-30
View File
@@ -1,30 +0,0 @@
#ifndef EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RESOURCEMUTEX_H_
#define EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RESOURCEMUTEX_H_
#include "common/common.h"
#include <mutex>
#include <thread>
namespace Libs::Graphics {
// Owner-tracked shared buffer/image transaction.
class ResourceMutex final {
public:
ResourceMutex() = default;
~ResourceMutex();
KYTY_CLASS_NO_COPY(ResourceMutex);
void lock();
void unlock();
[[nodiscard]] bool IsOwnedByCurrentThread();
private:
std::mutex m_resource;
std::mutex m_state;
std::thread::id m_resource_owner;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_HOST_GPU_RENDERER_RESOURCEMUTEX_H_
+52 -73
View File
@@ -8,7 +8,6 @@
#include "graphics/guest_gpu/tile.h" #include "graphics/guest_gpu/tile.h"
#include "graphics/host_gpu/graphicContext.h" #include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/cache/bufferCache.h" #include "graphics/host_gpu/renderer/cache/bufferCache.h"
#include "graphics/host_gpu/renderer/cache/resourceMutex.h"
#include "graphics/host_gpu/renderer/commandScheduler.h" #include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/renderer/image/imageView.h" #include "graphics/host_gpu/renderer/image/imageView.h"
#include "graphics/host_gpu/renderer/image/textureCommon.h" #include "graphics/host_gpu/renderer/image/textureCommon.h"
@@ -56,13 +55,12 @@ private:
} // namespace } // namespace
TextureCache::TextureCache(GraphicContext& graphics, CommandScheduler& scheduler, TextureCache::TextureCache(GraphicContext& graphics, CommandScheduler& scheduler,
PageManager& page_manager, BufferCache& buffer_cache, PageManager& page_manager, BufferCache& buffer_cache)
ResourceMutex& resource_mutex)
: m_graphics(graphics), m_scheduler(scheduler), m_page_manager(page_manager), : m_graphics(graphics), m_scheduler(scheduler), m_page_manager(page_manager),
m_blit_helper(graphics, scheduler), m_blit_helper(graphics, scheduler),
m_tiler(std::make_unique<TileManager>(graphics, scheduler, m_tiler(std::make_unique<TileManager>(graphics, scheduler,
buffer_cache.GetUtilityBuffer(MemoryUsage::Stream))), buffer_cache.GetUtilityBuffer(MemoryUsage::Stream))),
m_buffer_cache(buffer_cache), m_resource_mutex(resource_mutex), m_buffer_cache(buffer_cache),
m_readback_linear_images(Config::ReadbackLinearImagesEnabled()) { m_readback_linear_images(Config::ReadbackLinearImagesEnabled()) {
if (m_graphics.CanReportMemoryUsage()) { if (m_graphics.CanReportMemoryUsage()) {
constexpr int64_t GiB = 1024ll * 1024 * 1024; constexpr int64_t GiB = 1024ll * 1024 * 1024;
@@ -434,9 +432,8 @@ void TextureCache::UntrackImageTail(ImageId id) {
} }
void TextureCache::TrackImageDownload(ImageId id) { void TextureCache::TrackImageDownload(ImageId id) {
std::lock_guard transaction(m_resource_mutex); CacheLock lock(*this, m_lock);
CacheLock lock(*this, m_lock); auto& image = ResolveImage(id);
auto& image = ResolveImage(id);
TrackImageDownloadLocked(id, image); TrackImageDownloadLocked(id, image);
} }
@@ -911,7 +908,6 @@ TextureCache::BuildColorTransfer(const Image& image, BindingType binding,
uint32_t format = info.guest_format; uint32_t format = info.guest_format;
uint32_t layers = info.TransferLayers(); uint32_t layers = info.TransferLayers();
bool volume = info.IsVolume(); bool volume = info.IsVolume();
bool layered = info.IsLayered();
bool allow_depth_tile = direction == TransferDirection::Upload; bool allow_depth_tile = direction == TransferDirection::Upload;
const char* owner = const char* owner =
direction == TransferDirection::Upload ? "TextureCache" : "TextureCache readback"; direction == TransferDirection::Upload ? "TextureCache" : "TextureCache readback";
@@ -940,7 +936,6 @@ TextureCache::BuildColorTransfer(const Image& image, BindingType binding,
format = info.guest_format; format = info.guest_format;
layers = info.resources.layers; layers = info.resources.layers;
volume = false; volume = false;
layered = layers > 1;
allow_depth_tile = false; allow_depth_tile = false;
plan.swap_bgra16 = info.bgra16; plan.swap_bgra16 = info.bgra16;
owner = "VideoOut"; owner = "VideoOut";
@@ -958,19 +953,14 @@ TextureCache::BuildColorTransfer(const Image& image, BindingType binding,
plan.swap_bgra16 = info.bgra16; plan.swap_bgra16 = info.bgra16;
} }
plan.layout = TextureCalcUploadLayout(format, info.extent.width, info.extent.height, plan.layout = TextureCalcUploadLayout(format, info.extent.width, info.extent.height,
info.resources.levels, layers, info.pitch, info.tile_mode, info.resources.levels, layers, info.tile_mode,
info.data.size, allow_depth_tile, volume, owner); info.data.size, allow_depth_tile, volume, owner);
plan.regions = TextureBuildImageCopies(plan.layout, info.extent.width, info.extent.height, plan.regions = TextureBuildImageCopies(plan.layout);
layers, info.resources.levels, layered, volume); plan.tiled = static_cast<Prospero::TileMode>(plan.layout.surface.description.tile_mode) !=
plan.tiled = static_cast<Prospero::TileMode>(plan.layout.tile) != Prospero::TileMode::kLinear; Prospero::TileMode::kLinear;
if (plan.tiled) { if (plan.tiled) {
// CMASK/FMASK decoding is intentionally deferred. if (!TextureBuildGpuTileInfos(info.data.size, plan.regions, plan.layout,
if (plan.layout.tile_family == TileBlockFamily::Depth64KB &&
Prospero::IsFmaskTextureFormat(format)) {
return plan;
}
if (!TextureBuildGpuTileInfos(info.data.size, plan.regions, plan.layout, format, layers,
info.resources.levels, plan.tiles)) { info.resources.levels, plan.tiles)) {
return plan; return plan;
} }
@@ -1017,9 +1007,10 @@ void TextureCache::UploadImage(Image& image, const ImageDesc& desc, Buffer& sour
" size=0x%016" PRIx64 " format=%u tile=%u family=%u extent=%ux%ux%u " " size=0x%016" PRIx64 " format=%u tile=%u family=%u extent=%ux%ux%u "
"pitch=%u levels=%u layers=%u samples=%u\n", "pitch=%u levels=%u layers=%u samples=%u\n",
static_cast<uint32_t>(desc.type), info.data.address, info.data.size, 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.guest_format, info.tile_mode,
info.extent.width, info.extent.height, info.extent.depth, info.pitch, static_cast<uint32_t>(plan.layout.surface.texture.block.family), info.extent.width,
info.resources.levels, info.resources.layers, info.samples); info.extent.height, info.extent.depth, info.pitch, info.resources.levels,
info.resources.layers, info.samples);
} }
TileManager::Result linear {source.Handle(), source_offset, info.data.size}; TileManager::Result linear {source.Handle(), source_offset, info.data.size};
if (plan.tiled) { if (plan.tiled) {
@@ -1152,8 +1143,7 @@ void TextureCache::RefreshImage(ImageId id, const ImageDesc& desc) {
} }
void TextureCache::AssociateStencil(ImageId depth_id, GuestRange stencil) { void TextureCache::AssociateStencil(ImageId depth_id, GuestRange stencil) {
std::lock_guard transaction(m_resource_mutex); CacheLock lock(*this, m_lock);
CacheLock lock(*this, m_lock);
AssociateStencilLocked(depth_id, stencil); AssociateStencilLocked(depth_id, stencil);
} }
@@ -1196,11 +1186,9 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
} }
ImageId result {}; ImageId result {};
bool inserted_new = false;
{ {
std::lock_guard transaction(m_resource_mutex); CacheLock lock(*this, m_lock);
CacheLock lock(*this, m_lock); const auto candidates =
const auto candidates =
FindImagesInRegion(desc.info.data.address, desc.info.data.size, false); FindImagesInRegion(desc.info.data.address, desc.info.data.size, false);
for (const auto id: candidates) { for (const auto id: candidates) {
@@ -1243,19 +1231,12 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
} }
if (!result) { if (!result) {
result = InsertImage(desc.info); result = InsertImage(desc.info);
inserted_new = true;
auto& inserted = ResolveImage(result); auto& inserted = ResolveImage(result);
if (m_buffer_cache.HasGpuDirtyBytes(inserted.info.data.address, if (m_buffer_cache.HasGpuDirtyBytes(inserted.info.data.address,
inserted.info.data.size)) { inserted.info.data.size)) {
inserted.MarkBufferModified(); inserted.MarkBufferModified();
} }
} }
if (inserted_new) {
InitializeImage(result, desc);
} else {
RefreshImage(result, desc);
}
auto& image = ResolveImage(result); auto& image = ResolveImage(result);
if (desc.type == BindingType::VideoOut && if (desc.type == BindingType::VideoOut &&
desc.info.metadata.compression != VideoOutCompression::Uncompressed) { desc.info.metadata.compression != VideoOutCompression::Uncompressed) {
@@ -1280,6 +1261,13 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
return result; return result;
} }
void TextureCache::UpdateImage(ImageId id) {
CacheLock lock(*this, m_lock);
auto& image = ResolveImage(id);
TouchImage(image);
RefreshImage(id, ImageDesc {.info = image.info, .type = UploadBinding(image)});
}
ImageId TextureCache::FindImageFromRange(uint64_t address, uint64_t size, bool ensure_valid) { ImageId TextureCache::FindImageFromRange(uint64_t address, uint64_t size, bool ensure_valid) {
if (!GuestRange {address, size}.Valid()) { if (!GuestRange {address, size}.Valid()) {
return {}; return {};
@@ -1324,22 +1312,24 @@ ImageId TextureCache::FindImageFromRange(uint64_t address, uint64_t size, bool e
} }
vk::ImageView TextureCache::FindTexture(ImageId id, const ImageDesc& desc) { vk::ImageView TextureCache::FindTexture(ImageId id, const ImageDesc& desc) {
std::lock_guard transaction(m_resource_mutex); CacheLock lock(*this, m_lock);
CacheLock lock(*this, m_lock); auto& image = ResolveImage(id);
auto& image = ResolveImage(id);
TouchImage(image); TouchImage(image);
if (!image.info.data.Empty()) { if (!image.info.data.Empty()) {
if (!image.registered || image.depth_id || image.binding.needs_rebind) { if (!image.registered || image.depth_id || image.binding.needs_rebind) {
EXIT("TextureCache: texture requires rediscovery before final acquisition\n"); EXIT("TextureCache: texture requires rediscovery before final acquisition\n");
} }
}
if (desc.type == BindingType::Storage) {
image.MarkGpuModified();
}
if (!image.info.data.Empty()) {
RefreshImage(id, desc); RefreshImage(id, desc);
} }
switch (desc.type) { switch (desc.type) {
case BindingType::Texture: break; case BindingType::Texture: break;
case BindingType::Storage: case BindingType::Storage:
if (image.info.data.Empty()) { if (!image.info.data.Empty()) {
image.MarkGpuModified();
} else {
if (!image.registered || image.depth_id) { if (!image.registered || image.depth_id) {
EXIT("TextureCache: cannot acquire an unavailable storage image\n"); EXIT("TextureCache: cannot acquire an unavailable storage image\n");
} }
@@ -1358,16 +1348,16 @@ vk::ImageView TextureCache::FindRenderTarget(ImageId id, const ImageDesc& desc)
if (desc.type != BindingType::RenderTarget) { if (desc.type != BindingType::RenderTarget) {
EXIT("TextureCache: invalid color-target binding\n"); EXIT("TextureCache: invalid color-target binding\n");
} }
std::lock_guard transaction(m_resource_mutex); CacheLock lock(*this, m_lock);
CacheLock lock(*this, m_lock); auto& image = ResolveImage(id);
auto& image = ResolveImage(id);
if (!image.registered || image.depth_id || image.binding.needs_rebind) { if (!image.registered || image.depth_id || image.binding.needs_rebind) {
EXIT("TextureCache: color target requires rediscovery before final acquisition\n"); EXIT("TextureCache: color target requires rediscovery before final acquisition\n");
} }
TouchImage(image); TouchImage(image);
image.MarkGpuModified();
image.usage.render_target = true;
RefreshImage(id, desc); RefreshImage(id, desc);
CommitGpuWrite(image); CommitGpuWrite(image);
image.usage.render_target = true;
TrackImageDownloadLocked(id, image); TrackImageDownloadLocked(id, image);
const auto view = image.FindView(desc.view_info); const auto view = image.FindView(desc.view_info);
RetainImage(m_scheduler.Current(), id); RetainImage(m_scheduler.Current(), id);
@@ -1378,13 +1368,14 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) {
if (desc.type != BindingType::DepthTarget) { if (desc.type != BindingType::DepthTarget) {
EXIT("TextureCache: invalid depth-target binding\n"); EXIT("TextureCache: invalid depth-target binding\n");
} }
std::lock_guard transaction(m_resource_mutex); CacheLock lock(*this, m_lock);
CacheLock lock(*this, m_lock); auto& image = ResolveImage(id);
auto& image = ResolveImage(id);
if (!image.registered || image.depth_id || image.binding.needs_rebind) { if (!image.registered || image.depth_id || image.binding.needs_rebind) {
EXIT("TextureCache: depth target requires rediscovery before final acquisition\n"); EXIT("TextureCache: depth target requires rediscovery before final acquisition\n");
} }
TouchImage(image); TouchImage(image);
image.MarkGpuModified();
image.usage.depth_target = true;
RefreshImage(id, desc); RefreshImage(id, desc);
if (desc.info.HasMetadata()) { if (desc.info.HasMetadata()) {
image.info.metadata = desc.info.metadata; image.info.metadata = desc.info.metadata;
@@ -1392,7 +1383,6 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) {
MetaDataInfo {.clear_mask = image.info.htile_clear_mask}); MetaDataInfo {.clear_mask = image.info.htile_clear_mask});
} }
CommitGpuWrite(image); CommitGpuWrite(image);
image.usage.depth_target = true;
if (desc.info.HasStencil()) { if (desc.info.HasStencil()) {
AssociateStencilLocked(id, desc.info.stencil); AssociateStencilLocked(id, desc.info.stencil);
} }
@@ -1402,9 +1392,8 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) {
} }
void TextureCache::MarkGpuWritten(ImageId id) { void TextureCache::MarkGpuWritten(ImageId id) {
std::lock_guard transaction(m_resource_mutex); CacheLock lock(*this, m_lock);
CacheLock lock(*this, m_lock); auto& image = ResolveImage(id);
auto& image = ResolveImage(id);
if (!image.registered || image.depth_id) { if (!image.registered || image.depth_id) {
EXIT("TextureCache: cannot mark an unavailable image GPU-written\n"); EXIT("TextureCache: cannot mark an unavailable image GPU-written\n");
} }
@@ -1428,7 +1417,6 @@ bool TextureCache::ClearImageFromBuffer(CommandBuffer& command, uint64_t address
if (command.IsInvalid() || !GuestRange {address, size}.Valid()) { if (command.IsInvalid() || !GuestRange {address, size}.Valid()) {
EXIT("TextureCache: invalid image clear\n"); EXIT("TextureCache: invalid image clear\n");
} }
std::lock_guard transaction(m_resource_mutex);
CacheLock lock(*this, m_lock); CacheLock lock(*this, m_lock);
ImageId selected {}; ImageId selected {};
vk::ImageAspectFlags aspect {}; vk::ImageAspectFlags aspect {};
@@ -1713,14 +1701,9 @@ bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uin
return false; return false;
} }
if (color.tiled) { if (color.tiled) {
const auto binding = m_texture_cache.UploadBinding(image);
const auto format =
binding == TextureCache::BindingType::RenderTarget
? ImageOps::RenderTargetTransferFormat(image.info.bytes_per_block)
: image.info.guest_format;
color.tiles.clear(); color.tiles.clear();
if (!TextureBuildGpuTileInfos(copy_size, color.regions, color.layout, format, if (!TextureBuildGpuTileInfos(copy_size, color.regions, color.layout, levels,
image.info.TransferLayers(), levels, color.tiles)) { color.tiles)) {
return false; return false;
} }
color.linear_size = GetLinearSize(color.tiles); color.linear_size = GetLinearSize(color.tiles);
@@ -1891,9 +1874,8 @@ bool TextureCache::IsMetaCleared(uint64_t address, uint32_t slice) {
} }
bool TextureCache::ClearMeta(uint64_t address) { bool TextureCache::ClearMeta(uint64_t address) {
std::lock_guard transaction(m_resource_mutex); CacheLock lock(*this, m_lock);
CacheLock lock(*this, m_lock); const auto found = m_surface_metas.find(address);
const auto found = m_surface_metas.find(address);
if (found == m_surface_metas.end()) { if (found == m_surface_metas.end()) {
return false; return false;
} }
@@ -1919,9 +1901,8 @@ void TextureCache::UnmapMemory(uint64_t address, uint64_t size) {
if (!GuestRange {address, size}.Valid()) { if (!GuestRange {address, size}.Valid()) {
EXIT("TextureCache: invalid unmap range\n"); EXIT("TextureCache: invalid unmap range\n");
} }
std::lock_guard transaction(m_resource_mutex); CacheLock lock(*this, m_lock);
CacheLock lock(*this, m_lock); auto images = FindImagesInRegion(address, size, false);
auto images = FindImagesInRegion(address, size, false);
if (!images.empty()) { if (!images.empty()) {
m_scheduler.Finish(); m_scheduler.Finish();
} }
@@ -1938,9 +1919,8 @@ void TextureCache::UnmapMemory(uint64_t address, uint64_t size) {
} }
void TextureCache::RunGarbageCollector() { void TextureCache::RunGarbageCollector() {
std::lock_guard transaction(m_resource_mutex); CacheLock lock(*this, m_lock);
CacheLock lock(*this, m_lock); const uint64_t tick = m_gc_tick++;
const uint64_t tick = m_gc_tick++;
if (m_graphics.CanReportMemoryUsage()) { if (m_graphics.CanReportMemoryUsage()) {
m_total_used_memory = m_graphics.GetDeviceMemoryUsage(); m_total_used_memory = m_graphics.GetDeviceMemoryUsage();
} }
@@ -2000,8 +1980,7 @@ void TextureCache::RunGarbageCollector() {
} }
void TextureCache::ProcessDownloadImages() { void TextureCache::ProcessDownloadImages() {
std::lock_guard transaction(m_resource_mutex); CacheLock lock(*this, m_lock);
CacheLock lock(*this, m_lock);
for (const auto id: m_download_images) { for (const auto id: m_download_images) {
const auto owner = ResolveOwner(id); const auto owner = ResolveOwner(id);
if (owner != nullptr && owner->registered && owner->IsGpuModified()) { if (owner != nullptr && owner->registered && owner->IsGpuModified()) {
+2 -3
View File
@@ -26,7 +26,6 @@ class Buffer;
class BufferCache; class BufferCache;
class CommandBuffer; class CommandBuffer;
class CommandScheduler; class CommandScheduler;
class ResourceMutex;
class RenderExecutor; class RenderExecutor;
class StreamBuffer; class StreamBuffer;
class TileManager; class TileManager;
@@ -49,11 +48,12 @@ public:
}; };
TextureCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager, TextureCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
BufferCache& buffer_cache, ResourceMutex& resource_mutex); BufferCache& buffer_cache);
~TextureCache(); ~TextureCache();
KYTY_CLASS_NO_COPY(TextureCache); KYTY_CLASS_NO_COPY(TextureCache);
[[nodiscard]] ImageId FindImage(ImageDesc& desc, bool exact_format = false); [[nodiscard]] ImageId FindImage(ImageDesc& desc, bool exact_format = false);
void UpdateImage(ImageId id);
[[nodiscard]] ImageId FindImageFromRange(uint64_t address, uint64_t size, [[nodiscard]] ImageId FindImageFromRange(uint64_t address, uint64_t size,
bool ensure_valid = true); bool ensure_valid = true);
[[nodiscard]] vk::ImageView FindTexture(ImageId id, const ImageDesc& desc); [[nodiscard]] vk::ImageView FindTexture(ImageId id, const ImageDesc& desc);
@@ -168,7 +168,6 @@ private:
BlitHelper m_blit_helper; BlitHelper m_blit_helper;
std::unique_ptr<TileManager> m_tiler; std::unique_ptr<TileManager> m_tiler;
BufferCache& m_buffer_cache; BufferCache& m_buffer_cache;
ResourceMutex& m_resource_mutex;
std::vector<Slot> m_slots; std::vector<Slot> m_slots;
std::vector<uint32_t> m_free_slots; std::vector<uint32_t> m_free_slots;
ImagePageTable m_image_page_table; ImagePageTable m_image_page_table;
@@ -178,10 +178,10 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
pitch = (rt.pitch.pitch_div8_minus1 + 1u) << 3u; pitch = (rt.pitch.pitch_div8_minus1 + 1u) << 3u;
} else if (tile) { } else if (tile) {
if (volume) { if (volume) {
pitch = TileGetTexturePitch(transfer_format, width, levels, rt.attrib3.tile_mode); pitch = TileGetTexturePitch(transfer_format, width, rt.attrib3.tile_mode);
} else if (standard64) { } else if (standard64) {
pitch = TileGetTexturePitch(Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float), pitch = TileGetTexturePitch(Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float),
width, levels, rt.attrib3.tile_mode); width, rt.attrib3.tile_mode);
} else { } else {
pitch = TileGetRenderTargetPitch(width, bytes_per_element, rt.attrib.num_fragments); pitch = TileGetRenderTargetPitch(width, bytes_per_element, rt.attrib.num_fragments);
} }
@@ -192,13 +192,20 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
pitch = width; pitch = width;
} }
TileSizeOffset mip_sizes[16] {}; TileSizeOffset mip_sizes[16] {};
TilePaddedSize mip_padded[16] {}; TilePaddedSize mip_padded[16] {};
TileVolumeLayout volume_layout {}; TileSurfaceLayout volume_layout {};
uint64_t backing_size = 0; uint64_t backing_size = 0;
if (volume) { if (volume) {
if (!tile || !TileGetTextureVolumeLayout(transfer_format, width, height, depth, levels, const TileSurfaceDescription description {transfer_format,
rt.attrib3.tile_mode, volume_layout)) { rt.attrib3.tile_mode,
TileSurfaceDimension::Dim3D,
width,
height,
depth,
levels,
1};
if (!tile || !TileGetTiledTextureLayout(description, volume_layout)) {
EXIT("unsupported 3D render-target layout: %ux%ux%u levels=%u tile=%u\n", width, height, EXIT("unsupported 3D render-target layout: %ux%ux%u levels=%u tile=%u\n", width, height,
depth, levels, rt.attrib3.tile_mode); depth, levels, rt.attrib3.tile_mode);
} }
@@ -209,7 +216,7 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
bool valid_layout = false; bool valid_layout = false;
if (standard64) { if (standard64) {
TileGetTextureSize(Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float), width, TileGetTextureSize(Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float), width,
height, pitch, levels, rt.attrib3.tile_mode, &layout, mip_sizes, height, levels, rt.attrib3.tile_mode, &layout, mip_sizes,
mip_padded); mip_padded);
valid_layout = layout.size != 0 && layout.align == 65536; valid_layout = layout.size != 0 && layout.align == 65536;
} else { } else {
@@ -289,11 +296,12 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
desc.info.tile_mode = rt.attrib3.tile_mode; desc.info.tile_mode = rt.attrib3.tile_mode;
for (uint32_t level = 0; level < levels; level++) { for (uint32_t level = 0; level < levels; level++) {
if (volume) { if (volume) {
const auto& mip = volume_layout.mips[level];
desc.info.mip_layout[level] = { desc.info.mip_layout[level] = {
volume_layout.level_offsets[level], mip.offset,
volume_layout.level_sizes[level], mip.size,
volume_layout.level_widths[level], mip.padded_width,
volume_layout.level_heights[level], mip.padded_height,
}; };
continue; continue;
} }
@@ -99,7 +99,10 @@ bool CommandScheduler::InDeferredOperation() noexcept {
CommandScheduler::CommandScheduler(RenderContext& context, GraphicContext& graphics) CommandScheduler::CommandScheduler(RenderContext& context, GraphicContext& graphics)
: m_master(graphics), m_context(context), m_graphics(graphics), : m_master(graphics), m_context(context), m_graphics(graphics),
m_priority_thread([this](std::stop_token stop) { PriorityOperationsThread(stop); }) {} m_priority_thread([this](std::stop_token stop) { PriorityOperationsThread(stop); }) {
m_buffers.reserve(CommandBufferGrowStep);
m_buffer_ticks.reserve(CommandBufferGrowStep);
}
CommandScheduler::~CommandScheduler() { CommandScheduler::~CommandScheduler() {
Shutdown(); Shutdown();
@@ -154,10 +157,7 @@ void CommandScheduler::Begin(HW::Context& registers, HW::UserConfig& user_config
m_shaders = &shaders; m_shaders = &shaders;
if (!Active()) { if (!Active()) {
for (auto& buffer: m_buffers) { m_current = static_cast<int>(GrowCommandBuffers());
buffer = std::make_unique<RenderCommandBuffer>(*this);
}
m_current = 0;
} }
BindCurrent(); BindCurrent();
@@ -235,7 +235,13 @@ void CommandScheduler::Wait(uint64_t tick) {
} }
void CommandScheduler::PopPendingOperations() { void CommandScheduler::PopPendingOperations() {
m_master.Refresh(); PopPendingOperations(true);
}
void CommandScheduler::PopPendingOperations(bool refresh_gpu_tick) {
if (refresh_gpu_tick) {
m_master.Refresh();
}
for (;;) { for (;;) {
Common::UniqueFunction<void> callback; Common::UniqueFunction<void> callback;
{ {
@@ -362,7 +368,7 @@ uint64_t CommandScheduler::NextSubmitSequence() noexcept {
} }
void CommandScheduler::CheckActive() const { void CommandScheduler::CheckActive() const {
EXIT_IF(!Active() || m_current >= BufferCount); EXIT_IF(!Active() || static_cast<size_t>(m_current) >= m_buffers.size());
} }
RenderCommandBuffer& CommandScheduler::Current() const { RenderCommandBuffer& CommandScheduler::Current() const {
@@ -384,15 +390,52 @@ CommandBuffer& CommandScheduler::SubmitCurrent(SubmitInfo& submit) {
const auto signal_tick = m_master.NextTick(); const auto signal_tick = m_master.NextTick();
submit.AddSignal(m_master.Handle(), signal_tick); submit.AddSignal(m_master.Handle(), signal_tick);
submitted.Execute(submit); submitted.Execute(submit);
m_recording = false; m_buffer_ticks[static_cast<size_t>(m_current)] = signal_tick;
m_recording = false;
return submitted; return submitted;
} }
int CommandScheduler::FindReusableBuffer(uint64_t gpu_tick) const {
EXIT_IF(m_buffer_ticks.size() != m_buffers.size());
const auto buffer_count = m_buffers.size();
for (size_t offset = 1; offset <= buffer_count; ++offset) {
const auto candidate = (static_cast<size_t>(m_current) + offset) % buffer_count;
if (gpu_tick >= m_buffer_ticks[candidate]) {
return static_cast<int>(candidate);
}
}
return -1;
}
size_t CommandScheduler::GrowCommandBuffers() {
const auto first = m_buffers.size();
const auto end = first + CommandBufferGrowStep;
m_buffers.reserve(end);
m_buffer_ticks.reserve(end);
for (size_t i = first; i < end; ++i) {
m_buffers.emplace_back(std::make_unique<RenderCommandBuffer>(*this));
m_buffer_ticks.push_back(0);
}
return first;
}
void CommandScheduler::BeginNext() { void CommandScheduler::BeginNext() {
EXIT_IF(m_recording); EXIT_IF(m_recording);
m_current = (m_current + 1) % BufferCount;
auto candidate = FindReusableBuffer(m_master.KnownGpuTick());
bool refreshed = false;
if (candidate < 0) {
m_master.Refresh();
refreshed = true;
candidate = FindReusableBuffer(m_master.KnownGpuTick());
}
if (candidate < 0) {
candidate = static_cast<int>(GrowCommandBuffers());
}
m_current = candidate;
Current().WaitForFenceAndReset(); Current().WaitForFenceAndReset();
PopPendingOperations(); PopPendingOperations(!refreshed);
BindCurrent(); BindCurrent();
Current().Begin(); Current().Begin();
m_recording = true; m_recording = true;
@@ -16,6 +16,7 @@
#include <queue> #include <queue>
#include <thread> #include <thread>
#include <vector>
namespace Libs::Graphics { namespace Libs::Graphics {
@@ -31,8 +32,6 @@ struct CommandSlot {
class CommandScheduler { class CommandScheduler {
public: public:
static constexpr int BufferCount = 8;
CommandScheduler(RenderContext& context, GraphicContext& graphics); CommandScheduler(RenderContext& context, GraphicContext& graphics);
~CommandScheduler(); ~CommandScheduler();
KYTY_CLASS_NO_COPY(CommandScheduler); KYTY_CLASS_NO_COPY(CommandScheduler);
@@ -65,6 +64,8 @@ public:
[[nodiscard]] GraphicContext& Graphics() const noexcept { return m_graphics; } [[nodiscard]] GraphicContext& Graphics() const noexcept { return m_graphics; }
private: private:
static constexpr size_t CommandBufferGrowStep = 4;
class CommandPool { class CommandPool {
public: public:
CommandPool() = default; CommandPool() = default;
@@ -94,30 +95,34 @@ private:
void BindCurrent() const; void BindCurrent() const;
CommandBuffer& SubmitCurrent(SubmitInfo& submit); CommandBuffer& SubmitCurrent(SubmitInfo& submit);
void BeginNext(); void BeginNext();
void PopPendingOperations(bool refresh_gpu_tick);
[[nodiscard]] int FindReusableBuffer(uint64_t gpu_tick) const;
[[nodiscard]] size_t GrowCommandBuffers();
void PriorityOperationsThread(std::stop_token stop); void PriorityOperationsThread(std::stop_token stop);
void RunOperation(Common::UniqueFunction<void>&& operation); void RunOperation(Common::UniqueFunction<void>&& operation);
[[nodiscard]] CommandSlot* AllocateCommandBuffer(); [[nodiscard]] CommandSlot* AllocateCommandBuffer();
[[nodiscard]] uint64_t NextSubmitSequence() noexcept; [[nodiscard]] uint64_t NextSubmitSequence() noexcept;
MasterSemaphore m_master; MasterSemaphore m_master;
RenderContext& m_context; RenderContext& m_context;
GraphicContext& m_graphics; GraphicContext& m_graphics;
CommandPool m_command_pool; CommandPool m_command_pool;
std::array<std::unique_ptr<RenderCommandBuffer>, BufferCount> m_buffers; std::vector<std::unique_ptr<RenderCommandBuffer>> m_buffers;
std::queue<PendingOperation> m_pending_operations; std::vector<uint64_t> m_buffer_ticks;
std::queue<PendingOperation> m_priority_operations; std::queue<PendingOperation> m_pending_operations;
std::mutex m_operation_mutex; std::queue<PendingOperation> m_priority_operations;
std::condition_variable m_operation_available; std::mutex m_operation_mutex;
std::jthread m_priority_thread; std::condition_variable m_operation_available;
bool m_priority_active = false; std::jthread m_priority_thread;
uint64_t m_priority_active_tick = 0; bool m_priority_active = false;
OperationState m_operation_state = OperationState::Open; uint64_t m_priority_active_tick = 0;
int m_current = -1; OperationState m_operation_state = OperationState::Open;
bool m_recording = false; int m_current = -1;
HW::Context* m_registers = nullptr; bool m_recording = false;
HW::UserConfig* m_user_config = nullptr; HW::Context* m_registers = nullptr;
HW::Shader* m_shaders = nullptr; HW::UserConfig* m_user_config = nullptr;
std::atomic<uint64_t> m_submit_sequence = 0; HW::Shader* m_shaders = nullptr;
std::atomic<uint64_t> m_submit_sequence = 0;
friend class CommandBuffer; friend class CommandBuffer;
}; };
+16 -17
View File
@@ -10,6 +10,7 @@
#include "graphics/host_gpu/renderer/render.h" #include "graphics/host_gpu/renderer/render.h"
#include <algorithm> #include <algorithm>
#include <array>
#include <atomic> #include <atomic>
#include <cmath> #include <cmath>
#include <fmt/format.h> #include <fmt/format.h>
@@ -686,14 +687,6 @@ static void McCheck(const HW::ModeControl& c) {
} }
EXIT_NOT_IMPLEMENTED(c.polymode_front_ptype != 0 && c.polymode_front_ptype != 2); EXIT_NOT_IMPLEMENTED(c.polymode_front_ptype != 0 && c.polymode_front_ptype != 2);
EXIT_NOT_IMPLEMENTED(c.polymode_back_ptype != 0 && c.polymode_back_ptype != 2); EXIT_NOT_IMPLEMENTED(c.polymode_back_ptype != 0 && c.polymode_back_ptype != 2);
if (c.poly_offset_front_enable || c.poly_offset_back_enable) {
static bool logged = false;
if (!logged) {
LOGF("\t temporary: PA_SU_SC_MODE_CNTL.POLY_OFFSET_*_ENABLE is not implemented; "
"continuing without depth bias\n");
logged = true;
}
}
if (c.vtx_window_offset_enable) { if (c.vtx_window_offset_enable) {
static bool logged = false; static bool logged = false;
if (!logged) { if (!logged) {
@@ -1067,18 +1060,24 @@ static ScissorRect ScissorRectClamp(ScissorRect r, uint32_t width, uint32_t heig
return r; return r;
} }
static constexpr std::array<uint16_t, 16> MakeScissorIntersectionRules() {
std::array<uint16_t, 16> rules {};
for (uint32_t candidate = 0; candidate < rules.size(); candidate++) {
for (uint32_t combination = 0; combination < 16; combination++) {
if ((combination & candidate) == candidate) {
rules[candidate] |= static_cast<uint16_t>(1u << combination);
}
}
}
return rules;
}
static bool ScissorClipRuleToIntersectionMask(uint16_t rule, uint8_t* mask) { static bool ScissorClipRuleToIntersectionMask(uint16_t rule, uint8_t* mask) {
EXIT_IF(mask == nullptr); EXIT_IF(mask == nullptr);
for (uint32_t candidate = 0; candidate < 16; candidate++) { static constexpr auto rules = MakeScissorIntersectionRules();
uint16_t expected = 0; for (uint32_t candidate = 0; candidate < rules.size(); candidate++) {
for (uint32_t combination = 0; combination < 16; combination++) { if (rules[candidate] == rule) {
if ((combination & candidate) == candidate) {
expected |= static_cast<uint16_t>(1u << combination);
}
}
if (expected == rule) {
*mask = static_cast<uint8_t>(candidate); *mask = static_cast<uint8_t>(candidate);
return true; return true;
} }
@@ -38,7 +38,10 @@ namespace Libs::Graphics {
EXIT("unsupported render state; details were printed above\n"); EXIT("unsupported render state; details were printed above\n");
} }
static vk::StencilOp ConvertStencilOp(uint8_t value) { static vk::StencilOp ConvertStencilOp(uint8_t value, uint8_t write_mask, uint8_t op_value) {
if (write_mask == 0) {
return vk::StencilOp::eKeep;
}
switch (static_cast<Prospero::StencilOp>(value)) { switch (static_cast<Prospero::StencilOp>(value)) {
case Prospero::StencilOp::kKeep: return vk::StencilOp::eKeep; case Prospero::StencilOp::kKeep: return vk::StencilOp::eKeep;
case Prospero::StencilOp::kZero: return vk::StencilOp::eZero; case Prospero::StencilOp::kZero: return vk::StencilOp::eZero;
@@ -49,7 +52,17 @@ static vk::StencilOp ConvertStencilOp(uint8_t value) {
case Prospero::StencilOp::kInvert: return vk::StencilOp::eInvert; case Prospero::StencilOp::kInvert: return vk::StencilOp::eInvert;
case Prospero::StencilOp::kAddWrap: return vk::StencilOp::eIncrementAndWrap; case Prospero::StencilOp::kAddWrap: return vk::StencilOp::eIncrementAndWrap;
case Prospero::StencilOp::kSubWrap: return vk::StencilOp::eDecrementAndWrap; case Prospero::StencilOp::kSubWrap: return vk::StencilOp::eDecrementAndWrap;
default: DepthFatal("unsupported stencil operation"); case Prospero::StencilOp::kXor:
if ((write_mask & op_value) == 0) {
return vk::StencilOp::eKeep;
}
if ((write_mask & ~op_value) != 0) {
DepthFatal("unsupported stencil XOR operands: write mask=0x%02" PRIx8
", operation value=0x%02" PRIx8,
write_mask, op_value);
}
return vk::StencilOp::eInvert;
default: DepthFatal("unsupported stencil operation: 0x%02" PRIx8, value);
} }
} }
@@ -283,6 +296,8 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
r.stencil_clear_value = hw.GetStencilClearValue(); r.stencil_clear_value = hw.GetStencilClearValue();
r.stencil_test_enable = has_stencil && dc.stencil_enable; r.stencil_test_enable = has_stencil && dc.stencil_enable;
if (r.stencil_test_enable) { if (r.stencil_test_enable) {
const uint8_t front_write_mask = rc.stencil_clear_enable ? 0 : sm.stencil_writemask;
const uint8_t back_write_mask = rc.stencil_clear_enable ? 0 : sm.stencil_writemask_bf;
if (dc.stencilfunc > static_cast<uint8_t>(vk::CompareOp::eAlways) || if (dc.stencilfunc > static_cast<uint8_t>(vk::CompareOp::eAlways) ||
(dc.backface_enable && (dc.backface_enable &&
dc.stencilfunc_bf > static_cast<uint8_t>(vk::CompareOp::eAlways)) || dc.stencilfunc_bf > static_cast<uint8_t>(vk::CompareOp::eAlways)) ||
@@ -294,19 +309,18 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
DepthFatal("unsupported stencil compare or replacement state"); DepthFatal("unsupported stencil compare or replacement state");
} }
r.stencil_static_front = { r.stencil_static_front = {
ConvertStencilOp(sc.stencil_fail), ConvertStencilOp(sc.stencil_zpass), ConvertStencilOp(sc.stencil_fail, front_write_mask, sm.stencil_opval),
ConvertStencilOp(sc.stencil_zfail), static_cast<vk::CompareOp>(dc.stencilfunc)}; ConvertStencilOp(sc.stencil_zpass, front_write_mask, sm.stencil_opval),
r.stencil_dynamic_front = {sm.stencil_mask, ConvertStencilOp(sc.stencil_zfail, front_write_mask, sm.stencil_opval),
rc.stencil_clear_enable ? 0u : sm.stencil_writemask, static_cast<vk::CompareOp>(dc.stencilfunc)};
sm.stencil_testval}; r.stencil_dynamic_front = {sm.stencil_mask, front_write_mask, sm.stencil_testval};
if (dc.backface_enable) { if (dc.backface_enable) {
r.stencil_static_back = {ConvertStencilOp(sc.stencil_fail_bf), r.stencil_static_back = {
ConvertStencilOp(sc.stencil_zpass_bf), ConvertStencilOp(sc.stencil_fail_bf, back_write_mask, sm.stencil_opval_bf),
ConvertStencilOp(sc.stencil_zfail_bf), ConvertStencilOp(sc.stencil_zpass_bf, back_write_mask, sm.stencil_opval_bf),
static_cast<vk::CompareOp>(dc.stencilfunc_bf)}; ConvertStencilOp(sc.stencil_zfail_bf, back_write_mask, sm.stencil_opval_bf),
r.stencil_dynamic_back = {sm.stencil_mask_bf, static_cast<vk::CompareOp>(dc.stencilfunc_bf)};
rc.stencil_clear_enable ? 0u : sm.stencil_writemask_bf, r.stencil_dynamic_back = {sm.stencil_mask_bf, back_write_mask, sm.stencil_testval_bf};
sm.stencil_testval_bf};
} else { } else {
r.stencil_static_back = r.stencil_static_front; r.stencil_static_back = r.stencil_static_front;
r.stencil_dynamic_back = r.stencil_dynamic_front; r.stencil_dynamic_back = r.stencil_dynamic_front;
@@ -663,7 +663,8 @@ Image::Image(GraphicContext& graphics, CommandScheduler& scheduler, const ImageI
: info(image_info), m_graphics(&graphics), m_scheduler(&scheduler) { : info(image_info), m_graphics(&graphics), m_scheduler(&scheduler) {
KYTY_PROFILER_FUNCTION(); KYTY_PROFILER_FUNCTION();
ImageOps::Validate(info); ImageOps::Validate(info);
m_cpu_dirty = !info.data.Empty(); m_cpu_dirty =
!info.data.Empty() && info.metadata.compression == VideoOutCompression::Uncompressed;
if (info.pixel_format == vk::Format::eUndefined) { if (info.pixel_format == vk::Format::eUndefined) {
return; return;
} }
@@ -22,6 +22,10 @@ struct RenderTargetFormatMapping {
}; };
constexpr RenderTargetFormatMapping kRenderTargetFormats[] = { constexpr RenderTargetFormatMapping kRenderTargetFormats[] = {
{Prospero::ChannelLayout::k8,
Prospero::ChannelType::kUInt,
Prospero::ChannelOrder::kStandard,
{vk::Format::eR8Uint, 1}},
{Prospero::ChannelLayout::k8_8, {Prospero::ChannelLayout::k8_8,
Prospero::ChannelType::kUNorm, Prospero::ChannelType::kUNorm,
Prospero::ChannelOrder::kStandard, Prospero::ChannelOrder::kStandard,
@@ -164,14 +168,6 @@ RenderTargetFormatInfo TextureGetRenderTargetFormat(uint32_t raw_layout, uint32_
namespace { namespace {
static uint64_t GetLevelSrcOffset(const TileSizeOffset& level_size) {
return (level_size.src_size != 0 ? level_size.src_offset : level_size.offset);
}
static uint64_t GetLevelSrcSize(const TileSizeOffset& level_size) {
return (level_size.src_size != 0 ? level_size.src_size : level_size.size);
}
static uint32_t GetTextureLevelDepth(uint32_t depth, uint32_t level, bool volume_texture) { static uint32_t GetTextureLevelDepth(uint32_t depth, uint32_t level, bool volume_texture) {
return volume_texture ? std::max(depth >> level, 1u) : depth; return volume_texture ? std::max(depth >> level, 1u) : depth;
} }
@@ -184,20 +180,6 @@ static size_t GetTextureRegionCount(uint32_t depth, uint64_t levels, bool volume
return count; return count;
} }
uint64_t TextureUploadSliceSourceOffset(const TextureUploadLayout& layout, uint32_t level,
uint32_t slice) {
if (level >= 16 || layout.level_sizes[level].size == 0) {
EXIT("invalid texture upload slice source, level=%u slice=%u\n", level, slice);
}
const auto level_offset = GetLevelSrcOffset(layout.level_sizes[level]);
const auto slice_stride =
layout.source_slice_stride != 0 ? layout.source_slice_stride : layout.slice_stride;
if (slice_stride != 0 && slice > (UINT64_MAX - level_offset) / slice_stride) {
EXIT("texture upload slice source offset overflow, level=%u slice=%u\n", level, slice);
}
return level_offset + static_cast<uint64_t>(slice) * slice_stride;
}
vk::ComponentSwizzle TextureGetComponentSwizzle(uint8_t s) { vk::ComponentSwizzle TextureGetComponentSwizzle(uint8_t s) {
switch (static_cast<Prospero::CompSwizzle>(s)) { switch (static_cast<Prospero::CompSwizzle>(s)) {
case Prospero::CompSwizzle::kZero: return vk::ComponentSwizzle::eZero; case Prospero::CompSwizzle::kZero: return vk::ComponentSwizzle::eZero;
@@ -237,12 +219,11 @@ vk::Format TextureGetFormat(uint32_t fmt) {
namespace { namespace {
uint64_t CalcTextureSliceStride(const TileSizeOffset* level_sizes, uint64_t levels, uint64_t CalcTextureSliceStride(const TextureUploadMipLayout* mips, uint64_t levels,
uint64_t total_size, uint32_t depth) { uint64_t total_size, uint32_t depth) {
uint64_t stride = 0; uint64_t stride = 0;
for (uint32_t i = 0; i < levels; i++) { for (uint32_t i = 0; i < levels; i++) {
stride = stride = std::max(stride, mips[i].offset + mips[i].size);
std::max(stride, static_cast<uint64_t>(level_sizes[i].offset) + level_sizes[i].size);
} }
if (depth > 1 && total_size != 0 && total_size % depth == 0) { if (depth > 1 && total_size != 0 && total_size % depth == 0) {
@@ -255,39 +236,26 @@ uint64_t CalcTextureSliceStride(const TileSizeOffset* level_sizes, uint64_t leve
return stride; return stride;
} }
uint64_t CalcLinearUploadLevelSize(uint32_t fmt, uint32_t pitch, uint32_t height) { uint64_t CalcLinearUploadLevelSize(const TileTextureElementLayout& element, uint32_t pitch,
if (const uint32_t bytes_per_element = Prospero::NumBytesPerElement(fmt); uint32_t height) {
bytes_per_element != 0) { const uint32_t elements_w =
return static_cast<uint64_t>(pitch) * height * bytes_per_element; std::max((pitch + element.texel_width - 1u) / element.texel_width, 1u);
} const uint32_t elements_h =
std::max((height + element.texel_height - 1u) / element.texel_height, 1u);
if (const uint32_t bytes_per_block = Prospero::BlockCompressedBytesPerBlock(fmt); return static_cast<uint64_t>(elements_w) * elements_h * element.bytes;
bytes_per_block != 0) {
const uint32_t blocks_w = std::max((pitch + 3u) / 4u, 1u);
const uint32_t blocks_h = std::max((height + 3u) / 4u, 1u);
return static_cast<uint64_t>(blocks_w) * blocks_h * bytes_per_block;
}
return 0;
} }
uint64_t SetLinearUploadLevels(TileSizeOffset* level_sizes, uint32_t fmt, uint64_t height, uint64_t SetLinearUploadLevels(TextureUploadMipLayout* mips,
const TileTextureElementLayout& element, uint64_t height,
uint64_t levels, uint32_t base_pitch) { uint64_t levels, uint32_t base_pitch) {
uint64_t offset = 0; uint64_t offset = 0;
auto pitch = base_pitch; auto pitch = base_pitch;
auto h = static_cast<uint32_t>(height); auto h = static_cast<uint32_t>(height);
for (uint32_t i = 0; i < levels; i++) { for (uint32_t i = 0; i < levels; i++) {
const auto size = CalcLinearUploadLevelSize(fmt, pitch, h); const auto size = CalcLinearUploadLevelSize(element, pitch, h);
EXIT_NOT_IMPLEMENTED(size > 0xffffffffull); mips[i].size = size;
EXIT_NOT_IMPLEMENTED(offset > 0xffffffffull); mips[i].offset = offset;
level_sizes[i].size = static_cast<uint32_t>(size);
level_sizes[i].offset = static_cast<uint32_t>(offset);
level_sizes[i].src_size = 0;
level_sizes[i].src_offset = 0;
level_sizes[i].x = 0;
level_sizes[i].y = 0;
offset += size; offset += size;
if (pitch > 1) { if (pitch > 1) {
@@ -303,141 +271,123 @@ uint64_t SetLinearUploadLevels(TileSizeOffset* level_sizes, uint32_t fmt, uint64
} // namespace } // namespace
TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint64_t width, uint64_t height, TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint32_t width, uint32_t height,
uint64_t levels, uint32_t depth, uint64_t pitch, uint32_t levels, uint32_t depth, uint32_t tile,
uint64_t tile, uint64_t upload_size, uint64_t upload_size, bool allow_depth_tile,
bool allow_depth_tile, bool volume_texture, bool volume_texture, const char* owner) {
const char* owner) {
TextureUploadLayout layout {}; TextureUploadLayout layout {};
layout.tile = static_cast<uint32_t>(tile); layout.surface.description = {
layout.pitch = static_cast<uint32_t>(pitch); fmt,
layout.texel_block = Prospero::BlockCompressedBytesPerBlock(fmt) != 0 ? 4u : 1u; tile,
layout.volume_texture = volume_texture; volume_texture ? TileSurfaceDimension::Dim3D : TileSurfaceDimension::Dim2D,
width,
height,
volume_texture ? depth : 1u,
levels,
volume_texture ? 1u : depth,
};
const auto& description = layout.surface.description;
const auto tile_mode = static_cast<Prospero::TileMode>(description.tile_mode);
TileTextureElementLayout element {};
if (fmt != 0) { if (fmt == 0) {
if (layout.tile != 0) {
const auto tile_mode = static_cast<Prospero::TileMode>(layout.tile);
switch (tile_mode) {
case Prospero::TileMode::kStandard256B:
if (TileIsStandard256BTextureSupported(fmt)) {
layout.tile_family = TileBlockFamily::Standard256B;
}
break;
case Prospero::TileMode::kStandard4KB:
if (TileIsStandard4KBTextureSupported(fmt)) {
layout.tile_family = TileBlockFamily::Standard4KB;
}
break;
case Prospero::TileMode::kStandard64KB:
if (TileIsStandard64KBTextureSupported(fmt)) {
layout.tile_family = TileBlockFamily::Standard64KB;
}
break;
case Prospero::TileMode::kPrt:
if (TileIsStandard64KBTextureSupported(fmt)) {
layout.tile_family = TileBlockFamily::Prt64KB;
}
break;
case Prospero::TileMode::kRenderTarget:
if (Prospero::RenderTargetBytesPerElement(fmt) != 0) {
layout.tile_family = TileBlockFamily::RenderTarget64KB;
}
break;
case Prospero::TileMode::kDepth:
if (allow_depth_tile && Prospero::RenderTargetBytesPerElement(fmt) != 0) {
layout.tile_family = TileBlockFamily::Depth64KB;
}
break;
default: break;
}
if (layout.tile_family == TileBlockFamily::Count) {
EXIT("%s: unsupported typed tiled upload: fmt=%u tile=%u "
"size=%" PRIu64 " extent=%" PRIu64 "x%" PRIu64 " pitch=%" PRIu64
" levels=%" PRIu64 "\n",
owner, static_cast<uint32_t>(fmt), layout.tile, upload_size, width, height,
pitch, levels);
}
}
layout.pitch = TileGetTexturePitch(fmt, width, levels, layout.tile);
TileGetTextureSize(fmt, width, height, layout.pitch, levels, layout.tile, nullptr,
layout.level_sizes, layout.padded_sizes);
if (static_cast<Prospero::TileMode>(layout.tile) != Prospero::TileMode::kLinear) {
if (layout.volume_texture) {
layout.slice_stride = SetLinearUploadLevels(layout.level_sizes, fmt, height, levels,
static_cast<uint32_t>(width));
} else {
TileSizeOffset tiled_levels[16] {};
std::copy_n(layout.level_sizes, levels, tiled_levels);
layout.source_slice_stride =
CalcTextureSliceStride(tiled_levels, levels, upload_size, depth);
SetLinearUploadLevels(layout.level_sizes, fmt, height, levels, layout.pitch);
for (uint32_t i = 0; i < levels; ++i) {
if (tiled_levels[i].src_size > tiled_levels[i].size) {
layout.first_tail_level = std::min(layout.first_tail_level, i);
}
layout.level_sizes[i].src_offset = GetLevelSrcOffset(tiled_levels[i]);
layout.level_sizes[i].src_size = GetLevelSrcSize(tiled_levels[i]);
layout.level_sizes[i].x = tiled_levels[i].x;
layout.level_sizes[i].y = tiled_levels[i].y;
}
}
} else if (layout.volume_texture) {
layout.slice_stride =
CalcTextureSliceStride(layout.level_sizes, levels, upload_size, depth);
}
} else {
EXIT("%s: legacy texture upload format unsupported: fmt=0 tile=%u size=%" PRIu64 EXIT("%s: legacy texture upload format unsupported: fmt=0 tile=%u size=%" PRIu64
" extent=%" PRIu64 "x%" PRIu64 " pitch=%" PRIu64 " levels=%" PRIu64 "\n", " extent=%ux%u levels=%u\n",
owner, layout.tile, upload_size, width, height, pitch, levels); owner, description.tile_mode, upload_size, width, height, levels);
} }
if (!layout.volume_texture) { switch (tile_mode) {
layout.slice_stride = case Prospero::TileMode::kLinear:
CalcTextureSliceStride(layout.level_sizes, levels, upload_size, depth); if (!TileGetTextureElementLayout(fmt, element)) {
EXIT("%s: unsupported linear texture format: fmt=%u\n", owner, fmt);
}
break;
case Prospero::TileMode::kDepth:
if (!allow_depth_tile || !TileGetTiledTextureLayout(description, layout.surface)) {
EXIT("%s: unsupported typed tiled upload: fmt=%u tile=%u "
"size=%" PRIu64 " extent=%ux%u levels=%u\n",
owner, static_cast<uint32_t>(fmt), description.tile_mode, upload_size, width,
height, levels);
}
break;
default:
if (!TileGetTiledTextureLayout(description, layout.surface)) {
EXIT("%s: unsupported typed tiled upload: fmt=%u tile=%u "
"size=%" PRIu64 " extent=%ux%u levels=%u\n",
owner, static_cast<uint32_t>(fmt), description.tile_mode, upload_size, width,
height, levels);
}
break;
}
if (tile_mode != Prospero::TileMode::kLinear) {
element = {layout.surface.texture.block.bytes_per_element,
layout.surface.texture.texel_width, layout.surface.texture.texel_height};
}
layout.pitch = TileGetTexturePitch(fmt, width, description.tile_mode);
if (tile_mode == Prospero::TileMode::kLinear) {
TileSizeOffset level_sizes[16] {};
TilePaddedSize padded_sizes[16] {};
TileGetTextureSize(fmt, width, height, levels, description.tile_mode, nullptr, level_sizes,
padded_sizes);
for (uint32_t level = 0; level < levels; ++level) {
layout.mips[level] = {level_sizes[level].offset, level_sizes[level].size,
padded_sizes[level].width, padded_sizes[level].height};
}
} else if (volume_texture) {
layout.slice_stride = SetLinearUploadLevels(layout.mips, element, height, levels, width);
} else {
layout.source_slice_stride = layout.surface.block_slice_size;
if (depth > 1 && upload_size != 0 && upload_size % depth == 0) {
layout.source_slice_stride = std::max(layout.source_slice_stride, upload_size / depth);
}
SetLinearUploadLevels(layout.mips, element, height, levels, layout.pitch);
}
if (!volume_texture || tile_mode == Prospero::TileMode::kLinear) {
layout.slice_stride = CalcTextureSliceStride(layout.mips, levels, upload_size, depth);
} }
return layout; return layout;
} }
std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayout& layout, std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayout& layout) {
uint32_t width, uint32_t height, const auto& description = layout.surface.description;
uint32_t depth, uint64_t levels, const bool volume_texture = description.dimension == TileSurfaceDimension::Dim3D;
bool array_texture, bool volume_texture) { const auto depth = volume_texture ? description.depth : description.layers;
uint32_t mip_width = width; uint32_t mip_width = description.width;
uint32_t mip_height = height; uint32_t mip_height = description.height;
uint32_t mip_pitch = volume_texture && static_cast<Prospero::TileMode>(layout.tile) != uint32_t mip_pitch = volume_texture && static_cast<Prospero::TileMode>(description.tile_mode) !=
Prospero::TileMode::kLinear Prospero::TileMode::kLinear
? width ? description.width
: layout.pitch; : layout.pitch;
std::vector<vk::BufferImageCopy> regions; std::vector<vk::BufferImageCopy> regions;
regions.reserve(GetTextureRegionCount(depth, levels, volume_texture)); regions.reserve(GetTextureRegionCount(depth, description.levels, volume_texture));
for (uint32_t i = 0; i < levels; i++) { for (uint32_t i = 0; i < description.levels; i++) {
EXIT_NOT_IMPLEMENTED(layout.level_sizes[i].size == 0); EXIT_NOT_IMPLEMENTED(layout.mips[i].size == 0);
const auto mip_depth = GetTextureLevelDepth(depth, i, volume_texture); const auto mip_depth = GetTextureLevelDepth(depth, i, volume_texture);
for (uint32_t z = 0; z < mip_depth; z++) { for (uint32_t z = 0; z < mip_depth; z++) {
const auto slice_offset = z * layout.slice_stride; const auto slice_offset = z * layout.slice_stride;
vk::BufferImageCopy region {}; vk::BufferImageCopy region {};
region.bufferOffset = layout.level_sizes[i].offset + slice_offset; region.bufferOffset = layout.mips[i].offset + slice_offset;
region.imageSubresource = {vk::ImageAspectFlagBits::eColor, i, array_texture ? z : 0, region.imageSubresource = {vk::ImageAspectFlagBits::eColor, i, volume_texture ? 0u : z,
1}; 1};
region.imageOffset.z = volume_texture ? static_cast<int>(z) : 0; region.imageOffset.z = volume_texture ? static_cast<int>(z) : 0;
region.imageExtent = {mip_width, mip_height, 1}; region.imageExtent = {mip_width, mip_height, 1};
const bool linear = const bool linear = static_cast<Prospero::TileMode>(description.tile_mode) ==
static_cast<Prospero::TileMode>(layout.tile) == Prospero::TileMode::kLinear; Prospero::TileMode::kLinear;
if (linear) { if (linear) {
region.bufferRowLength = layout.padded_sizes[i].width; region.bufferRowLength = layout.mips[i].row_length;
region.bufferImageHeight = layout.padded_sizes[i].height; region.bufferImageHeight = layout.mips[i].image_height;
} else { } else {
const auto align = [](uint32_t value, uint32_t block) { const auto texel_width = layout.surface.texture.texel_width;
const auto align = [](uint32_t value, uint32_t block) {
return ((value + block - 1u) / block) * block; return ((value + block - 1u) / block) * block;
}; };
const auto pitch = align(mip_pitch, layout.texel_block); const auto pitch = align(mip_pitch, texel_width);
region.bufferRowLength = pitch > align(mip_width, layout.texel_block) ? pitch : 0; region.bufferRowLength = pitch > align(mip_width, texel_width) ? pitch : 0;
} }
regions.push_back(region); regions.push_back(region);
} }
@@ -456,24 +406,6 @@ std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayo
return regions; return regions;
} }
struct GpuTileElementLayout {
uint32_t bytes = 0;
uint32_t wide = 1;
uint32_t tall = 1;
};
static bool GetGpuTileElementLayout(uint32_t fmt, GpuTileElementLayout& out) {
if (const auto bytes = Prospero::NumBytesPerElement(fmt); bytes != 0) {
out = {bytes, 1, 1};
return true;
}
if (const auto bytes = Prospero::BlockCompressedBytesPerBlock(fmt); bytes != 0) {
out = {bytes, 4, 4};
return true;
}
return false;
}
static bool SetGpuTileSize(uint64_t offset, uint64_t length, uint64_t capacity, uint64_t& size) { static bool SetGpuTileSize(uint64_t offset, uint64_t length, uint64_t capacity, uint64_t& size) {
if (offset > capacity || length > capacity - offset) { if (offset > capacity || length > capacity - offset) {
return false; return false;
@@ -483,42 +415,32 @@ static bool SetGpuTileSize(uint64_t offset, uint64_t length, uint64_t capacity,
} }
bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferImageCopy>& regions, bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferImageCopy>& regions,
const TextureUploadLayout& layout, uint32_t fmt, uint32_t depth, const TextureUploadLayout& layout, uint32_t levels,
uint64_t levels, std::vector<GpuTileInfo>& out_infos) { std::vector<GpuTileInfo>& out_infos) {
const auto& description = layout.surface.description;
const bool volume_texture = description.dimension == TileSurfaceDimension::Dim3D;
const auto depth = volume_texture ? description.depth : description.layers;
if (tiled_size == 0 || levels == 0 || levels > 16 || depth == 0 || if (tiled_size == 0 || levels == 0 || levels > 16 || depth == 0 ||
regions.size() != GetTextureRegionCount(depth, levels, layout.volume_texture) || regions.size() != GetTextureRegionCount(depth, levels, volume_texture) ||
Prospero::IsFmaskTextureFormat(fmt)) { Prospero::IsFmaskTextureFormat(description.format)) {
return false; return false;
} }
GpuTileElementLayout element {}; const auto& surface = layout.surface;
if (layout.tile_family == TileBlockFamily::RenderTarget64KB || const auto& texture = surface.texture;
layout.tile_family == TileBlockFamily::Depth64KB) { const auto& block = texture.block;
element.bytes = Prospero::RenderTargetBytesPerElement(fmt); if (surface.description.levels < levels || block.bytes_per_element == 0 ||
} else if (!GetGpuTileElementLayout(fmt, element)) { block.family == TileBlockFamily::Count) {
return false;
}
if (element.bytes == 0) {
return false; return false;
} }
std::vector<GpuTileInfo> infos; std::vector<GpuTileInfo> infos;
infos.reserve(regions.size()); infos.reserve(regions.size());
if (layout.volume_texture) { if (volume_texture) {
TileVolumeLayout volume {};
if (!TileGetTextureVolumeLayout(fmt, regions[0].imageExtent.width,
regions[0].imageExtent.height, depth,
static_cast<uint32_t>(levels), layout.tile, volume)) {
return false;
}
element = {volume.bytes_per_element, volume.texel_width, volume.texel_height};
TileBlockLayout block {};
if (!TileGetBlockLayout(volume.family, element.bytes, block)) return false;
size_t region_base = 0; size_t region_base = 0;
for (uint32_t level = 0; level < levels; ++level) { for (uint32_t level = 0; level < levels; ++level) {
const uint32_t mip_depth = GetTextureLevelDepth(depth, level, true); const uint32_t mip_depth = GetTextureLevelDepth(depth, level, true);
const bool tail = level >= volume.first_tail_level; const bool tail = level >= surface.first_tail_level;
const uint64_t linear_stride = layout.slice_stride; const uint64_t linear_stride = layout.slice_stride;
for (uint32_t z = 0; z < mip_depth; z += block.block_depth) { for (uint32_t z = 0; z < mip_depth; z += block.block_depth) {
const uint32_t copy_depth = std::min(block.block_depth, mip_depth - z); const uint32_t copy_depth = std::min(block.block_depth, mip_depth - z);
@@ -533,50 +455,43 @@ bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferI
info.bytes_per_element = block.bytes_per_element; info.bytes_per_element = block.bytes_per_element;
info.linear_offset = region.bufferOffset; info.linear_offset = region.bufferOffset;
info.tiled_offset = info.tiled_offset =
static_cast<uint64_t>(z / block.block_depth) * volume.block_slice_size + static_cast<uint64_t>(z / block.block_depth) * surface.block_slice_size +
volume.level_offsets[level]; surface.mips[level].offset;
const uint64_t linear_span = const uint64_t linear_span =
static_cast<uint64_t>(copy_depth - 1u) * linear_stride + static_cast<uint64_t>(copy_depth - 1u) * linear_stride +
layout.level_sizes[level].size; layout.mips[level].size;
if (!SetGpuTileSize(info.linear_offset, linear_span, UINT64_MAX, if (!SetGpuTileSize(info.linear_offset, linear_span, UINT64_MAX,
info.linear_size) || info.linear_size) ||
!SetGpuTileSize(info.tiled_offset, volume.level_sizes[level], tiled_size, !SetGpuTileSize(info.tiled_offset, surface.mips[level].size, tiled_size,
info.tiled_size)) { info.tiled_size)) {
return false; return false;
} }
info.linear_slice_stride = linear_stride; info.linear_slice_stride = linear_stride;
info.width = info.width = std::max((region.imageExtent.width + texture.texel_width - 1u) /
std::max((region.imageExtent.width + element.wide - 1u) / element.wide, 1u); texture.texel_width,
info.height = std::max((logical_height + element.tall - 1u) / element.tall, 1u); 1u);
info.depth = copy_depth; info.height = std::max(
(logical_height + texture.texel_height - 1u) / texture.texel_height, 1u);
info.depth = copy_depth;
info.surface_z = info.surface_z =
block.block_depth == 1 ? static_cast<uint32_t>(region.imageOffset.z) : 0; block.block_depth == 1 ? static_cast<uint32_t>(region.imageOffset.z) : 0;
info.pitch = std::max((pitch + element.wide - 1u) / element.wide, 1u); info.pitch = std::max((pitch + texture.texel_width - 1u) / texture.texel_width, 1u);
info.tail_x = tail ? volume.tail_x[level] : 0; info.tail_x = tail ? surface.mips[level].tail_x : 0;
info.tail_y = tail ? volume.tail_y[level] : 0; info.tail_y = tail ? surface.mips[level].tail_y : 0;
info.tail = tail; info.tail = tail;
info.tiled_width = volume.level_widths[level]; info.tiled_width = surface.mips[level].padded_width;
info.tiled_height = volume.level_heights[level]; info.tiled_height = surface.mips[level].padded_height;
infos.push_back(info); infos.push_back(info);
} }
region_base += mip_depth; region_base += mip_depth;
} }
} else { } else {
const auto base_family = layout.tile_family;
if (base_family == TileBlockFamily::Count) {
return false;
}
size_t region_index = 0; size_t region_index = 0;
for (uint32_t level = 0; level < levels; level++) { for (uint32_t level = 0; level < levels; level++) {
const auto& level_size = layout.level_sizes[level]; const auto& level_size = layout.mips[level];
const bool tail = level >= layout.first_tail_level; const auto& mip = surface.mips[level];
const auto family = base_family; const bool tail = level >= surface.first_tail_level;
TileBlockLayout block {}; const auto level_depth = GetTextureLevelDepth(depth, level, false);
if (!TileGetBlockLayout(family, element.bytes, block)) {
return false;
}
const auto level_depth = GetTextureLevelDepth(depth, level, layout.volume_texture);
for (uint32_t z = 0; z < level_depth; z++) { for (uint32_t z = 0; z < level_depth; z++) {
const auto& region = regions[region_index++]; const auto& region = regions[region_index++];
const auto pitch = const auto pitch =
@@ -585,39 +500,36 @@ bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferI
? region.bufferImageHeight ? region.bufferImageHeight
: region.imageExtent.height; : region.imageExtent.height;
GpuTileInfo info {}; GpuTileInfo info {};
info.family = block.family; info.family = block.family;
info.bytes_per_element = block.bytes_per_element; info.bytes_per_element = block.bytes_per_element;
info.linear_offset = region.bufferOffset; info.linear_offset = region.bufferOffset;
info.tiled_offset = TextureUploadSliceSourceOffset(layout, level, z); const auto source_stride = layout.source_slice_stride != 0
if (!SetGpuTileSize(info.linear_offset, level_size.size, UINT64_MAX, ? layout.source_slice_stride
info.linear_size) || : surface.block_slice_size;
!SetGpuTileSize(info.tiled_offset, GetLevelSrcSize(level_size), tiled_size, if (z > (UINT64_MAX - mip.offset) / source_stride) {
info.tiled_size)) {
return false; return false;
} }
info.width = info.tiled_offset = mip.offset + static_cast<uint64_t>(z) * source_stride;
std::max((region.imageExtent.width + element.wide - 1u) / element.wide, 1u); if (!SetGpuTileSize(info.linear_offset, level_size.size, UINT64_MAX,
info.height = std::max((logical_height + element.tall - 1u) / element.tall, 1u); info.linear_size) ||
info.surface_z = base_family == TileBlockFamily::RenderTarget64KB || !SetGpuTileSize(info.tiled_offset, mip.size, tiled_size, info.tiled_size)) {
base_family == TileBlockFamily::Depth64KB return false;
}
info.width = std::max((region.imageExtent.width + texture.texel_width - 1u) /
texture.texel_width,
1u);
info.height = std::max(
(logical_height + texture.texel_height - 1u) / texture.texel_height, 1u);
info.surface_z = block.family == TileBlockFamily::RenderTarget64KB ||
block.family == TileBlockFamily::Depth64KB
? region.imageSubresource.baseArrayLayer ? region.imageSubresource.baseArrayLayer
: 0; : 0;
info.pitch = std::max((pitch + element.wide - 1u) / element.wide, 1u); info.pitch = std::max((pitch + texture.texel_width - 1u) / texture.texel_width, 1u);
info.tail = tail; info.tail = tail;
info.tail_x = tail ? level_size.x : 0; info.tail_x = tail ? mip.tail_x : 0;
info.tail_y = tail ? level_size.y : 0; info.tail_y = tail ? mip.tail_y : 0;
info.tiled_width = info.tiled_width = mip.padded_width;
layout.padded_sizes[level].width != 0 info.tiled_height = mip.padded_height;
? std::max((layout.padded_sizes[level].width + element.wide - 1u) /
element.wide,
1u)
: info.pitch;
info.tiled_height =
layout.padded_sizes[level].height != 0
? std::max((layout.padded_sizes[level].height + element.tall - 1u) /
element.tall,
1u)
: info.height;
infos.push_back(info); infos.push_back(info);
} }
} }
@@ -19,34 +19,32 @@ struct RenderTargetFormatInfo {
Prospero::ColorComponentMapping export_mapping; Prospero::ColorComponentMapping export_mapping;
}; };
struct TextureUploadMipLayout {
uint64_t offset = 0;
uint64_t size = 0;
uint32_t row_length = 0;
uint32_t image_height = 0;
};
struct TextureUploadLayout { struct TextureUploadLayout {
uint32_t tile = 0; uint32_t pitch = 0;
uint32_t pitch = 0; uint64_t slice_stride = 0;
uint32_t texel_block = 1; uint64_t source_slice_stride = 0;
uint64_t slice_stride = 0; TileSurfaceLayout surface;
uint64_t source_slice_stride = 0; TextureUploadMipLayout mips[16] = {};
uint32_t first_tail_level = 16;
TileBlockFamily tile_family = TileBlockFamily::Count;
bool volume_texture = false;
TileSizeOffset level_sizes[16] = {};
TilePaddedSize padded_sizes[16] = {};
}; };
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle); vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle);
vk::Format TextureGetFormat(uint32_t fmt); vk::Format TextureGetFormat(uint32_t fmt);
RenderTargetFormatInfo TextureGetRenderTargetFormat(uint32_t layout, uint32_t type, uint32_t order); RenderTargetFormatInfo TextureGetRenderTargetFormat(uint32_t layout, uint32_t type, uint32_t order);
TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint64_t width, uint64_t height, TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint32_t width, uint32_t height,
uint64_t levels, uint32_t depth, uint64_t pitch, uint32_t levels, uint32_t depth, uint32_t tile,
uint64_t tile, uint64_t upload_size, uint64_t upload_size, bool allow_depth_tile,
bool allow_depth_tile, bool volume_texture, bool volume_texture, const char* owner);
const char* owner); std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayout& layout);
std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayout& layout,
uint32_t width, uint32_t height,
uint32_t depth, uint64_t levels,
bool array_texture, bool volume_texture);
bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferImageCopy>& regions, bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferImageCopy>& regions,
const TextureUploadLayout& layout, uint32_t fmt, uint32_t depth, const TextureUploadLayout& layout, uint32_t levels,
uint64_t levels, std::vector<GpuTileInfo>& infos); std::vector<GpuTileInfo>& infos);
} // namespace Libs::Graphics } // namespace Libs::Graphics
@@ -239,7 +239,7 @@ bool IsSupportedDepthTargetDescriptor(const ShaderTextureResource& descriptor, c
const auto samples = multisampled ? 1u << descriptor.LastLevel() : 1u; const auto samples = multisampled ? 1u << descriptor.LastLevel() : 1u;
const auto pitch = const auto pitch =
multisampled ? TileGetDepthPitch(width, image.info.bytes_per_block, descriptor.LastLevel()) multisampled ? TileGetDepthPitch(width, image.info.bytes_per_block, descriptor.LastLevel())
: TileGetTexturePitch(descriptor.Format(), width, 1, descriptor.TileMode()); : TileGetTexturePitch(descriptor.Format(), width, descriptor.TileMode());
const bool supported_2d = type == Prospero::ImageType::kColor2D && const bool supported_2d = type == Prospero::ImageType::kColor2D &&
image.info.resources.layers == 1 && descriptor.Depth() == 0 && image.info.resources.layers == 1 && descriptor.Depth() == 0 &&
descriptor.BaseArray5() == 0; descriptor.BaseArray5() == 0;
@@ -321,7 +321,7 @@ static void ValidateDepthTargetBinding(const ShaderRecompiler::IR::ImageResource
return; return;
} }
const auto descriptor_pitch = const auto descriptor_pitch =
TileGetTexturePitch(descriptor.Format(), static_cast<uint32_t>(descriptor.Width5()) + 1u, 1, TileGetTexturePitch(descriptor.Format(), static_cast<uint32_t>(descriptor.Width5()) + 1u,
descriptor.TileMode()); descriptor.TileMode());
EXIT("unsupported sampled depth target: resource=%d descriptor=%d encoding=%d format=%d " EXIT("unsupported sampled depth target: resource=%d descriptor=%d encoding=%d format=%d "
"kind=%u dimension=%u mip_mode=%u read=%d written=%d atomic=%d compare=%d " "kind=%u dimension=%u mip_mode=%u read=%d written=%d atomic=%d compare=%d "
@@ -373,21 +373,33 @@ static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::Imag
const bool is_3d = resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D && const bool is_3d = resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D &&
descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor3D) && descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor3D) &&
descriptor.BaseArray5() == 0; descriptor.BaseArray5() == 0;
TileBlockLayout depth_block {}; TileTextureBlockLayout tile_layout {};
const auto depth_bpe = Prospero::RenderTargetBytesPerElement(descriptor.Format()); bool supported_tile = false;
const bool supported_depth_tile = switch (static_cast<Prospero::TileMode>(tile)) {
tile == Prospero::GpuEnumValue(Prospero::TileMode::kDepth) && !resource.read && case Prospero::TileMode::kLinear: supported_tile = true; break;
!Prospero::IsFmaskTextureFormat(descriptor.Format()) && (is_2d || is_2d_array) && case Prospero::TileMode::kDepth:
TileGetBlockLayout(TileBlockFamily::Depth64KB, depth_bpe, depth_block); supported_tile =
const bool supported_standard_tile = !resource.read && !Prospero::IsFmaskTextureFormat(descriptor.Format()) &&
(tile == Prospero::GpuEnumValue(Prospero::TileMode::kStandard4KB) && (is_2d || is_2d_array) &&
TileIsStandard4KBTextureSupported(descriptor.Format())) || TileGetTextureBlockLayout(descriptor.Format(), tile, false, tile_layout);
(tile == Prospero::GpuEnumValue(Prospero::TileMode::kStandard64KB) && break;
TileIsStandard64KBTextureSupported(descriptor.Format())); case Prospero::TileMode::kStandard256B:
const bool supported_tile = tile == Prospero::GpuEnumValue(Prospero::TileMode::kLinear) || supported_tile =
tile == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget) || (is_2d || is_2d_array) &&
supported_depth_tile || supported_standard_tile; TileGetTextureBlockLayout(descriptor.Format(), tile, false, tile_layout);
const auto swizzle = descriptor.DstSelXYZW(); break;
case Prospero::TileMode::kStandard4KB:
case Prospero::TileMode::kStandard64KB:
supported_tile =
TileGetTextureBlockLayout(descriptor.Format(), tile, is_3d, tile_layout);
break;
case Prospero::TileMode::kRenderTarget:
supported_tile =
TileGetTextureBlockLayout(descriptor.Format(), tile, false, tile_layout);
break;
default: break;
}
const auto swizzle = descriptor.DstSelXYZW();
const bool supported_swizzle = const bool supported_swizzle =
IsValidImageSwizzle(swizzle) && IsValidImageSwizzle(swizzle) &&
(swizzle == DstSel(4, 5, 6, 7) || !resource.read || resource.atomic); (swizzle == DstSel(4, 5, 6, 7) || !resource.read || resource.atomic);
@@ -493,18 +505,20 @@ static TextureCache::ImageDesc NullTextureDesc(const ShaderRecompiler::IR::Image
static void PopulateTextureMipLayout(ImageInfo& info) { static void PopulateTextureMipLayout(ImageInfo& info) {
if (info.IsVolume() && info.tile_mode != Prospero::GpuEnumValue(Prospero::TileMode::kLinear)) { if (info.IsVolume() && info.tile_mode != Prospero::GpuEnumValue(Prospero::TileMode::kLinear)) {
TileVolumeLayout volume {}; TileSurfaceLayout surface {};
if (!TileGetTextureVolumeLayout(info.guest_format, info.extent.width, info.extent.height, const TileSurfaceDescription description {
info.extent.depth, info.resources.levels, info.tile_mode, info.guest_format, info.tile_mode, TileSurfaceDimension::Dim3D, info.extent.width,
volume)) { info.extent.height, info.extent.depth, info.resources.levels, 1};
if (!TileGetTiledTextureLayout(description, surface)) {
EXIT("unsupported normalized volume texture layout\n"); EXIT("unsupported normalized volume texture layout\n");
} }
for (uint32_t level = 0; level < info.resources.levels; level++) { for (uint32_t level = 0; level < info.resources.levels; level++) {
const auto& mip = surface.mips[level];
info.mip_layout[level] = { info.mip_layout[level] = {
volume.level_offsets[level], mip.offset,
volume.level_sizes[level], mip.size,
volume.level_widths[level], mip.padded_width,
volume.level_heights[level], mip.padded_height,
}; };
} }
return; return;
@@ -512,7 +526,7 @@ static void PopulateTextureMipLayout(ImageInfo& info) {
TileSizeOffset levels[16] {}; TileSizeOffset levels[16] {};
TilePaddedSize padded[16] {}; TilePaddedSize padded[16] {};
TileGetTextureSize(info.guest_format, info.extent.width, info.extent.height, info.pitch, TileGetTextureSize(info.guest_format, info.extent.width, info.extent.height,
info.resources.levels, info.tile_mode, nullptr, levels, padded); info.resources.levels, info.tile_mode, nullptr, levels, padded);
for (uint32_t level = 0; level < info.resources.levels; level++) { for (uint32_t level = 0; level < info.resources.levels; level++) {
const auto offset = const auto offset =
@@ -670,9 +684,9 @@ RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& reso
} }
size.size *= image_layers; size.size *= image_layers;
} else { } else {
pitch = TileGetTexturePitch(format, width, levels, tile); pitch = TileGetTexturePitch(format, width, tile);
TileGetTextureTotalSize(format, width, height, volume ? depth : image_layers, pitch, levels, TileGetTextureTotalSize(format, width, height, volume ? depth : image_layers, levels, tile,
tile, volume, size); volume, size);
} }
EXIT_NOT_IMPLEMENTED(size.size == 0 || size.align == 0 || EXIT_NOT_IMPLEMENTED(size.size == 0 || size.align == 0 ||
(address & (static_cast<uint64_t>(size.align) - 1u)) != 0); (address & (static_cast<uint64_t>(size.align) - 1u)) != 0);
@@ -919,22 +933,21 @@ RenderExecutor::PrepareGraphicsBindings(CommandBuffer& buffer, const ShaderStage
return bindings; return bindings;
} }
static void RetainBindings(CommandBuffer& buffer, static void RetainBindings(CommandBuffer& buffer, DescriptorCache::NativeDescriptors& resources) {
const DescriptorCache::NativeDescriptors& resources) {
if (resources.flattened_srt.owner != nullptr) { if (resources.flattened_srt.owner != nullptr) {
buffer.RetainResourceUntilFence(resources.flattened_srt.owner); buffer.RetainResourceUntilFence(std::move(resources.flattened_srt.owner));
} }
if (resources.user_data.owner != nullptr) { if (resources.user_data.owner != nullptr) {
buffer.RetainResourceUntilFence(resources.user_data.owner); buffer.RetainResourceUntilFence(std::move(resources.user_data.owner));
} }
for (const auto& view: resources.buffers) { for (auto& view: resources.buffers) {
if (view.owner != nullptr) { if (view.owner != nullptr) {
buffer.RetainResourceUntilFence(view.owner); buffer.RetainResourceUntilFence(std::move(view.owner));
} }
} }
for (const auto& view: resources.addresses) { for (auto& view: resources.addresses) {
if (view.owner != nullptr) { if (view.owner != nullptr) {
buffer.RetainResourceUntilFence(view.owner); buffer.RetainResourceUntilFence(std::move(view.owner));
} }
} }
} }
@@ -36,6 +36,22 @@ VulkanMemoryBarrier MakeShaderWriteDependency() {
return barrier; return barrier;
} }
VulkanMemoryBarrier MakeShaderAccessDependency() {
VulkanMemoryBarrier barrier {};
barrier.sType = vk::StructureType::eMemoryBarrier;
barrier.srcAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
barrier.dstAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
return barrier;
}
VulkanMemoryBarrier MakeShaderWriteHazardDependency() {
VulkanMemoryBarrier barrier {};
barrier.sType = vk::StructureType::eMemoryBarrier;
barrier.srcAccessMask = vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite;
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite;
return barrier;
}
vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer) { vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer) {
EXIT_IF(buffer == nullptr); EXIT_IF(buffer == nullptr);
@@ -84,6 +100,21 @@ bool HasShaderBufferWrites(const ShaderStageRuntime& runtime) {
return !CollectShaderBufferWrites(*runtime.program, *runtime.resources).empty(); return !CollectShaderBufferWrites(*runtime.program, *runtime.resources).empty();
} }
void ShaderAccessBarrier(vk::CommandBuffer vk_buffer, vk::PipelineStageFlags source_stages) {
EXIT_IF(vk_buffer == nullptr || !source_stages);
const auto barrier = MakeShaderAccessDependency();
vk_buffer.pipelineBarrier(source_stages, vk::PipelineStageFlagBits::eAllCommands,
vk::DependencyFlags {}, 1, &barrier, 0, nullptr, 0, nullptr);
}
void ShaderWriteHazardBarrier(vk::CommandBuffer vk_buffer,
vk::PipelineStageFlags destination_stages) {
EXIT_IF(vk_buffer == nullptr || !destination_stages);
const auto barrier = MakeShaderWriteHazardDependency();
vk_buffer.pipelineBarrier(vk::PipelineStageFlagBits::eAllCommands, destination_stages,
vk::DependencyFlags {}, 1, &barrier, 0, nullptr, 0, nullptr);
}
void ShaderWriteBarrier(vk::CommandBuffer vk_buffer, vk::PipelineStageFlags source_stages) { void ShaderWriteBarrier(vk::CommandBuffer vk_buffer, vk::PipelineStageFlags source_stages) {
EXIT_IF(vk_buffer == nullptr || !source_stages); EXIT_IF(vk_buffer == nullptr || !source_stages);
const auto barrier = MakeShaderWriteDependency(); const auto barrier = MakeShaderWriteDependency();
@@ -18,12 +18,17 @@ struct ShaderBufferWriteRange {
}; };
vk::PipelineStageFlags ShaderPipelineStages(vk::ShaderStageFlags stages); vk::PipelineStageFlags ShaderPipelineStages(vk::ShaderStageFlags stages);
VulkanMemoryBarrier MakeShaderAccessDependency();
VulkanMemoryBarrier MakeShaderWriteHazardDependency();
VulkanMemoryBarrier MakeShaderWriteDependency(); VulkanMemoryBarrier MakeShaderWriteDependency();
vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer); vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer);
std::vector<ShaderBufferWriteRange> std::vector<ShaderBufferWriteRange>
CollectShaderBufferWrites(const ShaderRecompiler::IR::Program& program, CollectShaderBufferWrites(const ShaderRecompiler::IR::Program& program,
const ShaderRecompiler::IR::ResourceSnapshot& resources); const ShaderRecompiler::IR::ResourceSnapshot& resources);
bool HasShaderBufferWrites(const ShaderStageRuntime& runtime); bool HasShaderBufferWrites(const ShaderStageRuntime& runtime);
void ShaderAccessBarrier(vk::CommandBuffer vk_buffer, vk::PipelineStageFlags source_stages);
void ShaderWriteHazardBarrier(vk::CommandBuffer vk_buffer,
vk::PipelineStageFlags destination_stages);
void ShaderWriteBarrier(vk::CommandBuffer vk_buffer, vk::PipelineStageFlags source_stages); void ShaderWriteBarrier(vk::CommandBuffer vk_buffer, vk::PipelineStageFlags source_stages);
} // namespace Libs::Graphics } // namespace Libs::Graphics
@@ -947,6 +947,8 @@ void CreatePipelineInternal(
vk::DynamicState::eViewport, vk::DynamicState::eViewport,
vk::DynamicState::eScissor, vk::DynamicState::eScissor,
vk::DynamicState::eLineWidth, vk::DynamicState::eLineWidth,
vk::DynamicState::eDepthBiasEnable,
vk::DynamicState::eDepthBias,
vk::DynamicState::eStencilCompareMask, vk::DynamicState::eStencilCompareMask,
vk::DynamicState::eStencilReference, vk::DynamicState::eStencilReference,
vk::DynamicState::eStencilWriteMask, vk::DynamicState::eStencilWriteMask,
@@ -325,9 +325,6 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buf
auto vk_buffer = buffer.Handle(); auto vk_buffer = buffer.Handle();
CommitBindings(buffer, vk::PipelineBindPoint::eCompute, pipeline.pipeline_layout, bindings); CommitBindings(buffer, vk::PipelineBindPoint::eCompute, pipeline.pipeline_layout, bindings);
vk_buffer.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline.pipeline);
vk_buffer.dispatch(thread_group_x, thread_group_y, thread_group_z);
bool has_storage_writes = HasShaderBufferWrites(input_info.stage); bool has_storage_writes = HasShaderBufferWrites(input_info.stage);
has_storage_writes = has_storage_writes =
std::any_of(program.info.images.begin(), program.info.images.end(), std::any_of(program.info.images.begin(), program.info.images.end(),
@@ -338,8 +335,15 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buf
}) || }) ||
has_storage_writes; has_storage_writes;
if (has_storage_writes) { if (has_storage_writes) {
ShaderWriteBarrier(vk_buffer, vk::PipelineStageFlagBits::eComputeShader); // A host fence used to serialize every dispatch. Preserve its read-before-write ordering
// while allowing the queue to execute asynchronously.
ShaderWriteHazardBarrier(vk_buffer, vk::PipelineStageFlagBits::eComputeShader);
} }
vk_buffer.bindPipeline(vk::PipelineBindPoint::eCompute, pipeline.pipeline);
vk_buffer.dispatch(thread_group_x, thread_group_y, thread_group_z);
// The removed host fence also ordered read-only dispatches before later writers.
ShaderAccessBarrier(vk_buffer, vk::PipelineStageFlagBits::eComputeShader);
ResetBindings(); ResetBindings();
} }
+125 -45
View File
@@ -64,12 +64,32 @@ int32_t ResolveVertexOffset(uint32_t index_offset, const ShaderVertexInputInfo&
return 0; return 0;
} }
static std::atomic<uint32_t> g_draw_state_log_count = 0; static std::atomic<uint32_t> g_draw_state_log_count = 0;
static std::atomic<uint32_t> g_draw_input_log_count = 0; static std::atomic<uint32_t> g_draw_input_log_count = 0;
static std::atomic<uint32_t> g_mrt_state_log_count = 0; static std::atomic<uint32_t> g_mrt_state_log_count = 0;
static std::atomic<uint32_t> g_shader_stage_log_count = 0; static std::atomic<uint32_t> g_shader_stage_log_count = 0;
static std::atomic<uint32_t> g_framebuffer_skip_log_count = 0; static std::atomic<uint32_t> g_framebuffer_skip_log_count = 0;
static float ConvertPolygonOffsetConstantFactor(float guest_factor, const HW::PolyOffset& offset,
vk::Format host_depth_format) {
if (offset.db_is_float_fmt) {
return guest_factor;
}
int host_depth_bits = 0;
switch (host_depth_format) {
case vk::Format::eD16Unorm:
case vk::Format::eD16UnormS8Uint: host_depth_bits = 16; break;
case vk::Format::eD24UnormS8Uint: host_depth_bits = 24; break;
default:
// A fixed-point guest bias cannot be represented exactly by a floating-point host
// attachment without VK_EXT_depth_bias_control.
return guest_factor;
}
return std::ldexp(guest_factor, host_depth_bits + offset.neg_num_db_bits);
}
static const char* RenderColorTypeName(RenderColorType type) { static const char* RenderColorTypeName(RenderColorType type) {
switch (type) { switch (type) {
case RenderColorType::NoColorOutput: return "NoColorOutput"; case RenderColorType::NoColorOutput: return "NoColorOutput";
@@ -335,6 +355,22 @@ static PipelineDynamicParameters BuildGraphicsDynamicParams(const RenderCommandB
ret.stencil_front = depth.stencil_dynamic_front; ret.stencil_front = depth.stencil_dynamic_front;
ret.stencil_back = depth.stencil_dynamic_back; ret.stencil_back = depth.stencil_dynamic_back;
const auto& mode = ctx.GetModeControl();
const auto& poly_offset = ctx.GetPolyOffset();
const bool use_front = mode.poly_offset_front_enable && !mode.cull_front;
const bool use_back = mode.poly_offset_back_enable && !mode.cull_back;
ret.depth_bias_enable = use_front || use_back;
if (ret.depth_bias_enable) {
// Vulkan has one bias for both faces. Prefer a visible front face when both are enabled.
const float guest_constant_factor =
use_front ? poly_offset.front_offset : poly_offset.back_offset;
ret.depth_bias_constant_factor =
ConvertPolygonOffsetConstantFactor(guest_constant_factor, poly_offset, depth.format);
ret.depth_bias_clamp = poly_offset.clamp;
ret.depth_bias_slope_factor =
(use_front ? poly_offset.front_scale : poly_offset.back_scale) / 16.0f;
}
// CB_COLOR_CONTROL.operation controls special CB operations, not the normal color component // CB_COLOR_CONTROL.operation controls special CB operations, not the normal color component
// write mask. Use CB_TARGET_MASK here so scanout passes with mode=Disable do not go black. // write mask. Use CB_TARGET_MASK here so scanout passes with mode=Disable do not go black.
for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) { for (uint32_t i = 0; i < RENDER_COLOR_ATTACHMENTS_MAX; i++) {
@@ -379,6 +415,13 @@ static void SetDynamicParams(const RenderCommandBuffer& buffer, vk::CommandBuffe
} }
vk_buffer.setLineWidth(line_width); vk_buffer.setLineWidth(line_width);
vk_buffer.setDepthBiasEnable(dynamic_params.depth_bias_enable ? VK_TRUE : VK_FALSE);
if (dynamic_params.depth_bias_enable) {
vk_buffer.setDepthBias(dynamic_params.depth_bias_constant_factor,
dynamic_params.depth_bias_clamp,
dynamic_params.depth_bias_slope_factor);
}
if (dynamic_params.stencil_test_enable) { if (dynamic_params.stencil_test_enable) {
vk_buffer.setStencilCompareMask(vk::StencilFaceFlagBits::eFront, vk_buffer.setStencilCompareMask(vk::StencilFaceFlagBits::eFront,
dynamic_params.stencil_front.compareMask); dynamic_params.stencil_front.compareMask);
@@ -684,11 +727,24 @@ struct VertexBufferRange {
[[nodiscard]] uint64_t RequestedSize() const { return requested_end - base_address; } [[nodiscard]] uint64_t RequestedSize() const { return requested_end - base_address; }
}; };
static std::vector<BufferBinding> AcquireVertexBuffers(RenderCommandBuffer& buffer, struct PreparedVertexBuffers {
const ShaderVertexInputInfo& vs_input_info) { static constexpr uint32_t MaxBuffers = ShaderVertexInputInfo::RES_MAX;
std::array<vk::Buffer, MaxBuffers> buffers {};
std::array<vk::DeviceSize, MaxBuffers> offsets {};
std::array<std::shared_ptr<void>, MaxBuffers> owners {};
uint32_t count = 0;
uint32_t owner_count = 0;
};
static PreparedVertexBuffers AcquireVertexBuffers(RenderCommandBuffer& buffer,
const ShaderVertexInputInfo& vs_input_info) {
EXIT_IF(vs_input_info.buffers_num < 0 ||
vs_input_info.buffers_num > ShaderVertexInputInfo::RES_MAX);
// Collect the non-empty guest vertex ranges. // Collect the non-empty guest vertex ranges.
std::vector<VertexBufferRange> ranges; std::array<VertexBufferRange, ShaderVertexInputInfo::RES_MAX> ranges {};
ranges.reserve(vs_input_info.buffers_num); uint32_t range_count = 0;
for (int i = 0; i < vs_input_info.buffers_num; i++) { for (int i = 0; i < vs_input_info.buffers_num; i++) {
const auto& vertex = vs_input_info.buffers[i]; const auto& vertex = vs_input_info.buffers[i];
const auto size = VertexBufferDescriptorSize(vertex); const auto size = VertexBufferDescriptorSize(vertex);
@@ -699,27 +755,31 @@ static std::vector<BufferBinding> AcquireVertexBuffers(RenderCommandBuffer&
EXIT("invalid vertex buffer range: addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n", EXIT("invalid vertex buffer range: addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
vertex.addr, size); vertex.addr, size);
} }
ranges.push_back({vertex.addr, vertex.addr + size}); ranges[range_count++] = {vertex.addr, vertex.addr + size};
} }
std::ranges::sort(ranges, [](const VertexBufferRange& left, const VertexBufferRange& right) { std::sort(ranges.begin(), ranges.begin() + range_count,
return left.base_address < right.base_address; [](const VertexBufferRange& left, const VertexBufferRange& right) {
}); return left.base_address < right.base_address;
});
// Merge overlapping or touching ranges before acquiring host buffers. // Merge overlapping or touching ranges before acquiring host buffers.
std::vector<VertexBufferRange> merged_ranges; std::array<VertexBufferRange, ShaderVertexInputInfo::RES_MAX> merged_ranges {};
merged_ranges.reserve(ranges.size()); uint32_t merged_count = 0;
for (const auto& range: ranges) { for (uint32_t i = 0; i < range_count; i++) {
if (!merged_ranges.empty() && merged_ranges.back().requested_end >= range.base_address) { const auto& range = ranges[i];
merged_ranges.back().requested_end = if (merged_count != 0 &&
std::max(merged_ranges.back().requested_end, range.requested_end); merged_ranges[merged_count - 1].requested_end >= range.base_address) {
merged_ranges[merged_count - 1].requested_end =
std::max(merged_ranges[merged_count - 1].requested_end, range.requested_end);
continue; continue;
} }
merged_ranges.push_back(range); merged_ranges[merged_count++] = {range.base_address, range.requested_end};
} }
auto& cache = buffer.GetContext().GetBufferCache(); auto& cache = buffer.GetContext().GetBufferCache();
for (auto& range: merged_ranges) { for (uint32_t i = 0; i < merged_count; i++) {
auto& range = merged_ranges[i];
// PPSA20298 // PPSA20298
const auto size = const auto size =
Libs::LibKernel::Memory::ClampRangeSize(range.base_address, range.RequestedSize()); Libs::LibKernel::Memory::ClampRangeSize(range.base_address, range.RequestedSize());
@@ -728,30 +788,47 @@ static std::vector<BufferBinding> AcquireVertexBuffers(RenderCommandBuffer&
} }
// Rebuild slot bindings, offsetting non-empty slots into their acquired merged range. // Rebuild slot bindings, offsetting non-empty slots into their acquired merged range.
std::vector<BufferBinding> bindings; PreparedVertexBuffers prepared;
bindings.reserve(vs_input_info.buffers_num); prepared.count = static_cast<uint32_t>(vs_input_info.buffers_num);
std::shared_ptr<Buffer> null_owner;
vk::Buffer null_buffer = nullptr;
for (int i = 0; i < vs_input_info.buffers_num; i++) { for (int i = 0; i < vs_input_info.buffers_num; i++) {
const auto& vertex = vs_input_info.buffers[i]; const auto& vertex = vs_input_info.buffers[i];
const auto size = VertexBufferDescriptorSize(vertex); const auto size = VertexBufferDescriptorSize(vertex);
if (size == 0) { if (size == 0) {
auto owner = cache.ObtainNullBuffer(); if (null_owner == nullptr) {
bindings.push_back({owner, owner->Handle(), 0}); null_owner = cache.ObtainNullBuffer();
null_buffer = null_owner->Handle();
}
prepared.buffers[i] = null_buffer;
prepared.offsets[i] = 0;
continue; continue;
} }
const auto range = std::ranges::find_if(merged_ranges, [&](const VertexBufferRange& value) { const auto range = std::find_if(merged_ranges.begin(), merged_ranges.begin() + merged_count,
return vertex.addr >= value.base_address && vertex.addr < value.acquired_end; [&](const VertexBufferRange& value) {
}); return vertex.addr >= value.base_address &&
if (range == merged_ranges.end()) { vertex.addr < value.acquired_end;
});
if (range == merged_ranges.begin() + merged_count) {
EXIT("vertex buffer address is outside the acquired range: addr=0x%016" PRIx64 "\n", EXIT("vertex buffer address is outside the acquired range: addr=0x%016" PRIx64 "\n",
vertex.addr); vertex.addr);
} }
auto binding = range->binding; prepared.buffers[i] = range->binding.buffer;
binding.offset += vertex.addr - range->base_address; prepared.offsets[i] = range->binding.offset + vertex.addr - range->base_address;
bindings.push_back(std::move(binding));
} }
return bindings;
if (null_owner != nullptr) {
prepared.owners[prepared.owner_count++] = std::move(null_owner);
}
for (uint32_t i = 0; i < merged_count; i++) {
if (merged_ranges[i].binding.owner != nullptr) {
EXIT_IF(prepared.owner_count >= PreparedVertexBuffers::MaxBuffers);
prepared.owners[prepared.owner_count++] = std::move(merged_ranges[i].binding.owner);
}
}
return prepared;
} }
static void SetDrawDebugPhase(RenderCommandBuffer& buffer, uint64_t submit_id, static void SetDrawDebugPhase(RenderCommandBuffer& buffer, uint64_t submit_id,
@@ -883,10 +960,9 @@ static void RefreshShaders(RenderCommandBuffer& buffer, const DrawCallInfo& draw
} }
} }
static std::vector<BufferBinding> PrepareVertexBuffers(uint64_t submit_id, static PreparedVertexBuffers PrepareVertexBuffers(uint64_t submit_id, RenderCommandBuffer& buffer,
RenderCommandBuffer& buffer, const DrawCallInfo& draw,
const DrawCallInfo& draw, const ShaderVertexInputInfo& vs_input_info) {
const ShaderVertexInputInfo& vs_input_info) {
EXIT_IF(draw.name == nullptr); EXIT_IF(draw.name == nullptr);
(void)submit_id; (void)submit_id;
@@ -920,24 +996,28 @@ static PreparedIndexBuffer PrepareIndexBuffer(RenderCommandBuffer& buffe
} }
static void CommitVertexBuffers(RenderCommandBuffer& buffer, vk::CommandBuffer vk_buffer, static void CommitVertexBuffers(RenderCommandBuffer& buffer, vk::CommandBuffer vk_buffer,
std::vector<BufferBinding>& bindings) { PreparedVertexBuffers& prepared) {
for (uint32_t slot = 0; slot < bindings.size(); slot++) { for (uint32_t i = 0; i < prepared.owner_count; i++) {
auto& binding = bindings[slot]; if (prepared.owners[i] != nullptr) {
if (binding.owner != nullptr) { buffer.RetainResourceUntilFence(std::move(prepared.owners[i]));
buffer.RetainResourceUntilFence(binding.owner);
} }
EXIT_IF(binding.buffer == nullptr); }
vk_buffer.bindVertexBuffers(slot, 1, &binding.buffer, &binding.offset); for (uint32_t i = 0; i < prepared.count; i++) {
EXIT_IF(prepared.buffers[i] == nullptr);
}
if (prepared.count != 0) {
vk_buffer.bindVertexBuffers(0, prepared.count, prepared.buffers.data(),
prepared.offsets.data());
} }
} }
static void CommitIndexBuffer(RenderCommandBuffer& buffer, vk::CommandBuffer vk_buffer, static void CommitIndexBuffer(RenderCommandBuffer& buffer, vk::CommandBuffer vk_buffer,
const PreparedIndexBuffer& prepared) { PreparedIndexBuffer& prepared) {
if (prepared.size == 0) { if (prepared.size == 0) {
return; return;
} }
if (prepared.owner != nullptr) { if (prepared.owner != nullptr) {
buffer.RetainResourceUntilFence(prepared.owner); buffer.RetainResourceUntilFence(std::move(prepared.owner));
} }
EXIT_IF(prepared.buffer == nullptr); EXIT_IF(prepared.buffer == nullptr);
vk_buffer.bindIndexBuffer(prepared.buffer, prepared.offset, prepared.type); vk_buffer.bindIndexBuffer(prepared.buffer, prepared.offset, prepared.type);
+4 -4
View File
@@ -15,12 +15,16 @@ struct ShaderVertexInputInfo;
struct PipelineDynamicParameters { struct PipelineDynamicParameters {
bool stencil_test_enable = false; bool stencil_test_enable = false;
bool depth_bias_enable = false;
float viewport_scale[3] = {}; float viewport_scale[3] = {};
float viewport_offset[3] = {}; float viewport_offset[3] = {};
int scissor_ltrb[4] = {0}; int scissor_ltrb[4] = {0};
float line_width = 1.0f; float line_width = 1.0f;
float depth_bias_constant_factor = 0.0f;
float depth_bias_clamp = 0.0f;
float depth_bias_slope_factor = 0.0f;
uint32_t color_write_count = 1; uint32_t color_write_count = 1;
bool color_write_enable[RENDER_COLOR_ATTACHMENTS_MAX] = {true, true}; bool color_write_enable[RENDER_COLOR_ATTACHMENTS_MAX] = {true, true};
@@ -33,10 +37,6 @@ struct PipelineDynamicParameters {
static_assert(std::is_trivially_copyable_v<PipelineDynamicParameters>); static_assert(std::is_trivially_copyable_v<PipelineDynamicParameters>);
static_assert(std::is_standard_layout_v<PipelineDynamicParameters>); static_assert(std::is_standard_layout_v<PipelineDynamicParameters>);
static_assert(alignof(PipelineDynamicParameters) == 1); static_assert(alignof(PipelineDynamicParameters) == 1);
static_assert(sizeof(PipelineDynamicParameters) ==
sizeof(bool) + sizeof(float[3]) + sizeof(float[3]) + sizeof(int[4]) + sizeof(float) +
sizeof(uint32_t) + sizeof(bool[RENDER_COLOR_ATTACHMENTS_MAX]) +
sizeof(PipelineStencilDynamicState) * 2);
[[nodiscard]] int32_t ResolveVertexOffset(uint32_t index_offset, [[nodiscard]] int32_t ResolveVertexOffset(uint32_t index_offset,
const ShaderVertexInputInfo& vs_input_info); const ShaderVertexInputInfo& vs_input_info);
@@ -0,0 +1,876 @@
#include "graphics/presentation/imeDialogOverlay.h"
#include "SDL.h"
#include "common/assert.h"
#include "common/stringUtils.h"
#include "graphics/host_gpu/graphicContext.h"
#include "imgui.h"
#include "imgui_impl_vulkan.h"
#include "libs/controller.h"
#include "libs/imeDialog.h"
#include <algorithm>
#include <array>
#include <atomic>
#include <chrono>
#include <cmath>
#include <cstdint>
#include <deque>
#include <mutex>
#include <span>
#include <string>
#include <string_view>
#include <utility>
namespace Libs::Graphics {
namespace {
namespace Ime = Libs::Dialog::ImeDialog;
constexpr size_t INPUT_QUEUE_CAPACITY = 128;
enum class InputKind : uint8_t {
Button,
Axis,
MousePosition,
MouseButton,
MouseWheel,
ResetController
};
struct InputEvent {
InputKind kind;
uint64_t generation;
int id;
float x;
float y;
};
struct VisibilityUpdate {
uint64_t generation;
bool visible;
bool capture_controller;
bool capture_keyboard;
bool multiline;
};
std::atomic<Uint32> g_visibility_event {static_cast<Uint32>(-1)};
std::mutex g_visibility_mutex;
std::mutex g_input_mutex;
std::deque<InputEvent> g_input_events;
std::deque<VisibilityUpdate> g_visibility_updates;
size_t g_missing_visibility_wakeups = 0;
bool g_input_reset_requested = false;
uint16_t g_last_external_keycode = 0;
uint32_t g_last_external_status = 0;
uint64_t g_input_generation = 0;
bool g_input_active = false;
bool g_input_controller = false;
bool g_input_keyboard = false;
bool g_input_multiline = false;
bool g_input_lifecycle_active = false;
bool g_active = false;
bool g_controller_captured = false;
uint64_t g_generation = 0;
void ClearInputEvents() {
std::scoped_lock lock(g_input_mutex);
g_input_events.clear();
g_input_reset_requested = false;
}
void QueueInput(InputEvent event) {
std::scoped_lock lock(g_input_mutex);
const bool replaceable =
event.kind == InputKind::Axis || event.kind == InputKind::MousePosition;
if (replaceable && !g_input_events.empty()) {
auto& last = g_input_events.back();
if (last.generation == event.generation && last.kind == event.kind && last.id == event.id) {
last = event;
return;
}
}
if (g_input_events.size() == INPUT_QUEUE_CAPACITY) {
g_input_events.clear();
g_input_reset_requested = true;
}
g_input_events.push_back(event);
}
void RetryVisibilityWakeup() {
const Uint32 type = g_visibility_event.load(std::memory_order_acquire);
if (type == static_cast<Uint32>(-1)) {
return;
}
std::scoped_lock lock(g_input_mutex);
if (g_missing_visibility_wakeups == 0) {
return;
}
SDL_Event event {};
event.type = type;
if (SDL_PushEvent(&event) > 0) {
g_missing_visibility_wakeups--;
}
}
void OnVisibilityChanged(bool visible, uint64_t generation) {
std::scoped_lock visibility_lock(g_visibility_mutex);
if (!g_input_lifecycle_active) {
return;
}
if (generation < g_generation || (generation == g_generation && visible == g_active)) {
return;
}
g_generation = generation;
bool capture_controller = false;
bool capture_keyboard = false;
bool multiline = false;
if (visible) {
Ime::HostSnapshot snapshot;
if (Ime::GetHostSnapshot(&snapshot) && snapshot.generation == generation) {
capture_controller = (snapshot.disable_device & Ime::DISABLE_DEVICE_CONTROLLER) == 0;
capture_keyboard = (snapshot.disable_device & Ime::DISABLE_DEVICE_EXT_KEYBOARD) == 0;
multiline = (snapshot.option & Ime::OPTION_MULTILINE) != 0;
} else {
visible = false;
}
}
const bool was_controller = std::exchange(g_controller_captured, capture_controller);
if (capture_controller || was_controller) {
Controller::ControllerResetInputState();
}
g_active = visible;
const Uint32 type = g_visibility_event.load(std::memory_order_acquire);
if (type != static_cast<Uint32>(-1)) {
SDL_Event event {};
event.type = type;
std::scoped_lock input_lock(g_input_mutex);
g_visibility_updates.push_back(
{generation, visible, capture_controller, capture_keyboard, multiline});
if (g_missing_visibility_wakeups != 0 || SDL_PushEvent(&event) <= 0) {
g_missing_visibility_wakeups++;
}
}
}
uint32_t ExternalKeyStatus(SDL_Keymod modifiers, bool character_valid) {
uint32_t status = 0x00000001 | (character_valid ? 0x00000002 : 0);
if ((modifiers & KMOD_LCTRL) != 0) status |= 0x00000100;
if ((modifiers & KMOD_LSHIFT) != 0) status |= 0x00000200;
if ((modifiers & KMOD_LALT) != 0) status |= 0x00000400;
if ((modifiers & KMOD_LGUI) != 0) status |= 0x00000800;
if ((modifiers & KMOD_RCTRL) != 0) status |= 0x00001000;
if ((modifiers & KMOD_RSHIFT) != 0) status |= 0x00002000;
if ((modifiers & KMOD_RALT) != 0) status |= 0x00004000;
if ((modifiers & KMOD_RGUI) != 0) status |= 0x00008000;
if ((modifiers & KMOD_NUM) != 0) status |= 0x00010000;
if ((modifiers & KMOD_CAPS) != 0) status |= 0x00020000;
return status;
}
Ime::ExternalInput MakeExternalInput(Ime::ExternalAction action, uint16_t keycode,
uint32_t status) {
Ime::ExternalInput input {};
input.key.keycode = keycode;
input.key.status = status;
input.key.type = 4;
input.action = action;
return input;
}
std::u16string Utf8ToUtf16(std::string_view text) {
std::u16string result;
result.reserve(text.size());
for (size_t i = 0; i < text.size();) {
const auto first = static_cast<uint8_t>(text[i++]);
uint32_t codepoint = 0;
uint32_t remaining = 0;
if (first < 0x80) {
codepoint = first;
} else if ((first & 0xe0) == 0xc0) {
codepoint = first & 0x1f;
remaining = 1;
} else if ((first & 0xf0) == 0xe0) {
codepoint = first & 0x0f;
remaining = 2;
} else if ((first & 0xf8) == 0xf0) {
codepoint = first & 0x07;
remaining = 3;
} else {
continue;
}
if (i + remaining > text.size()) {
break;
}
bool valid = true;
for (uint32_t j = 0; j < remaining; j++) {
const auto next = static_cast<uint8_t>(text[i++]);
if ((next & 0xc0) != 0x80) {
valid = false;
break;
}
codepoint = (codepoint << 6) | (next & 0x3f);
}
if (!valid || codepoint > 0x10ffff || (codepoint >= 0xd800 && codepoint <= 0xdfff)) {
continue;
}
if (codepoint <= 0xffff) {
result.push_back(static_cast<char16_t>(codepoint));
} else {
codepoint -= 0x10000;
result.push_back(static_cast<char16_t>(0xd800 + (codepoint >> 10)));
result.push_back(static_cast<char16_t>(0xdc00 + (codepoint & 0x3ff)));
}
}
return result;
}
std::string VisibleText(const Ime::HostSnapshot& snapshot, size_t max_units) {
const size_t cursor = std::min<size_t>(snapshot.cursor, snapshot.text.size());
const size_t payload_units = max_units > 4 ? max_units - 4 : 1;
size_t begin = cursor > payload_units / 2 ? cursor - payload_units / 2 : 0;
size_t end = std::min(snapshot.text.size(), begin + payload_units);
if (end == snapshot.text.size() && end - begin < payload_units) {
begin = end > payload_units ? end - payload_units : 0;
}
if (begin > 0 && snapshot.text[begin] >= 0xdc00 && snapshot.text[begin] <= 0xdfff) {
begin--;
}
if (end < snapshot.text.size() && end > begin && snapshot.text[end - 1] >= 0xd800 &&
snapshot.text[end - 1] <= 0xdbff) {
end--;
}
std::u16string visible;
if (begin != 0) {
visible.push_back(u'\u2026');
}
const size_t caret = visible.size() + cursor - begin;
if ((snapshot.option & Ime::OPTION_PASSWORD) != 0) {
visible.append(end - begin, u'*');
} else {
visible.append(snapshot.text, begin, end - begin);
std::replace(visible.begin(), visible.end(), u'\n', u'\u21b5');
std::replace(visible.begin(), visible.end(), u'\r', u'\u21b5');
}
visible.insert(std::min(caret, visible.size()), 1, u'|');
if (end != snapshot.text.size()) {
visible.push_back(u'\u2026');
}
return Common::Utf16ToUtf8(visible.c_str());
}
const char* EnterLabel(Ime::EnterLabel label) {
switch (label) {
case Ime::EnterLabel::Send: return "Send";
case Ime::EnterLabel::Search: return "Search";
case Ime::EnterLabel::Go: return "Go";
default: return "Done";
}
}
float AlignmentPivot(Ime::Alignment alignment) {
switch (alignment) {
case Ime::Alignment::Start: return 0.0f;
case Ime::Alignment::End: return 1.0f;
default: return 0.5f;
}
}
ImGuiKey ControllerButtonToKey(int button) {
switch (button) {
case SDL_CONTROLLER_BUTTON_A: return ImGuiKey_GamepadFaceDown;
case SDL_CONTROLLER_BUTTON_B: return ImGuiKey_GamepadFaceRight;
case SDL_CONTROLLER_BUTTON_X: return ImGuiKey_GamepadFaceLeft;
case SDL_CONTROLLER_BUTTON_Y: return ImGuiKey_GamepadFaceUp;
case SDL_CONTROLLER_BUTTON_DPAD_LEFT: return ImGuiKey_GamepadDpadLeft;
case SDL_CONTROLLER_BUTTON_DPAD_RIGHT: return ImGuiKey_GamepadDpadRight;
case SDL_CONTROLLER_BUTTON_DPAD_UP: return ImGuiKey_GamepadDpadUp;
case SDL_CONTROLLER_BUTTON_DPAD_DOWN: return ImGuiKey_GamepadDpadDown;
default: return ImGuiKey_None;
}
}
PFN_vkVoidFunction LoadVulkanFunction(const char* name, void* user_data) {
auto& graphics = *static_cast<GraphicContext*>(user_data);
return graphics.instance.getProcAddr(name);
}
void CheckVulkanResult(VkResult result) {
EXIT_IF(result != VK_SUCCESS);
}
} // namespace
void InitializeImeDialogInput() {
const Uint32 type = SDL_RegisterEvents(1);
EXIT_IF(type == static_cast<Uint32>(-1));
{
std::scoped_lock lock(g_visibility_mutex);
g_input_lifecycle_active = true;
g_visibility_event.store(type, std::memory_order_release);
}
Ime::SetVisibilityCallback(OnVisibilityChanged);
Ime::HostSnapshot snapshot;
if (Ime::GetHostSnapshot(&snapshot)) {
OnVisibilityChanged(true, snapshot.generation);
}
}
void ShutdownImeDialogInput() {
Ime::SetVisibilityCallback(nullptr);
{
std::scoped_lock lock(g_visibility_mutex);
g_input_lifecycle_active = false;
g_active = false;
if (std::exchange(g_controller_captured, false)) {
Controller::ControllerResetInputState();
}
g_visibility_event.store(static_cast<Uint32>(-1), std::memory_order_release);
}
ClearInputEvents();
{
std::scoped_lock lock(g_input_mutex);
g_visibility_updates.clear();
g_missing_visibility_wakeups = 0;
}
g_input_active = false;
g_input_controller = false;
g_input_keyboard = false;
g_input_multiline = false;
if (SDL_IsTextInputActive() == SDL_TRUE) {
SDL_StopTextInput();
}
}
bool ProcessImeDialogInput(const SDL_Event& event) {
RetryVisibilityWakeup();
if (event.type == g_visibility_event.load(std::memory_order_acquire)) {
VisibilityUpdate update {};
{
std::scoped_lock lock(g_input_mutex);
if (g_visibility_updates.empty()) {
return true;
}
update = g_visibility_updates.front();
g_visibility_updates.pop_front();
}
ClearInputEvents();
g_last_external_keycode = 0;
g_last_external_status = 0;
g_input_generation = update.generation;
g_input_active = update.visible;
g_input_controller = update.capture_controller;
g_input_keyboard = update.capture_keyboard;
g_input_multiline = update.multiline;
if (g_input_keyboard) {
SDL_StartTextInput();
} else if (SDL_IsTextInputActive() == SDL_TRUE) {
SDL_StopTextInput();
}
return true;
}
if (event.type == SDL_CONTROLLERDEVICEREMOVED) {
if (g_input_active && g_input_controller) {
QueueInput({InputKind::ResetController, g_input_generation, 0, 0.0f, 0.0f});
}
return false;
}
if (!g_input_active) {
return false;
}
const uint64_t generation = g_input_generation;
const bool controller_event = event.type == SDL_CONTROLLERBUTTONDOWN ||
event.type == SDL_CONTROLLERBUTTONUP ||
event.type == SDL_CONTROLLERAXISMOTION;
if (controller_event && !g_input_controller) {
return false;
}
const bool keyboard_event = event.type == SDL_TEXTINPUT || event.type == SDL_TEXTEDITING ||
event.type == SDL_KEYDOWN || event.type == SDL_KEYUP;
if (keyboard_event && !g_input_keyboard) {
return false;
}
switch (event.type) {
case SDL_TEXTINPUT: {
const auto text = Utf8ToUtf16(event.text.text);
if (!text.empty()) {
auto input = MakeExternalInput(Ime::ExternalAction::Text, g_last_external_keycode,
g_last_external_status | 0x00000002);
input.key.character = text.front();
input.text = text;
Ime::HostQueueExternalInput(generation, std::move(input));
}
return true;
}
case SDL_TEXTEDITING: return true;
case SDL_KEYDOWN: {
g_last_external_keycode = static_cast<uint16_t>(event.key.keysym.scancode);
g_last_external_status =
ExternalKeyStatus(static_cast<SDL_Keymod>(event.key.keysym.mod), false);
auto action = Ime::ExternalAction::Text;
bool queue = true;
if (event.key.keysym.sym == SDLK_BACKSPACE) {
action = Ime::ExternalAction::Backspace;
} else if (event.key.keysym.sym == SDLK_LEFT) {
action = Ime::ExternalAction::MoveLeft;
} else if (event.key.keysym.sym == SDLK_RIGHT) {
action = Ime::ExternalAction::MoveRight;
} else if (event.key.keysym.sym == SDLK_ESCAPE) {
action = Ime::ExternalAction::Cancel;
} else if (event.key.keysym.sym == SDLK_RETURN ||
event.key.keysym.sym == SDLK_KP_ENTER) {
action =
g_input_multiline ? Ime::ExternalAction::Newline : Ime::ExternalAction::Accept;
} else if (event.key.keysym.sym == SDLK_TAB) {
action = Ime::ExternalAction::None;
} else {
queue = false;
}
if (queue) {
Ime::HostQueueExternalInput(
generation,
MakeExternalInput(action, g_last_external_keycode, g_last_external_status));
}
return true;
}
case SDL_KEYUP: return true;
case SDL_CONTROLLERBUTTONDOWN:
case SDL_CONTROLLERBUTTONUP:
QueueInput({InputKind::Button, generation, event.cbutton.button,
event.type == SDL_CONTROLLERBUTTONDOWN ? 1.0f : 0.0f, 0.0f});
return true;
case SDL_CONTROLLERAXISMOTION:
QueueInput({InputKind::Axis, generation, event.caxis.axis,
static_cast<float>(event.caxis.value), 0.0f});
return true;
case SDL_MOUSEMOTION:
QueueInput({InputKind::MousePosition, generation, 0, static_cast<float>(event.motion.x),
static_cast<float>(event.motion.y)});
return true;
case SDL_MOUSEBUTTONDOWN:
case SDL_MOUSEBUTTONUP:
QueueInput({InputKind::MouseButton, generation, event.button.button,
event.type == SDL_MOUSEBUTTONDOWN ? 1.0f : 0.0f, 0.0f});
return true;
case SDL_MOUSEWHEEL:
QueueInput({InputKind::MouseWheel, generation, 0, static_cast<float>(event.wheel.x),
static_cast<float>(event.wheel.y)});
return true;
default: return false;
}
}
struct ImeDialogOverlay::Impl {
explicit Impl(GraphicContext& context): graphics(context) {}
~Impl() {
ReleaseVulkan();
if (imgui_context != nullptr) {
ImGui::DestroyContext(imgui_context);
}
}
void EnsureContext() {
if (imgui_context != nullptr) {
ImGui::SetCurrentContext(imgui_context);
return;
}
IMGUI_CHECKVERSION();
imgui_context = ImGui::CreateContext();
ImGui::SetCurrentContext(imgui_context);
auto& io = ImGui::GetIO();
io.IniFilename = nullptr;
io.LogFilename = nullptr;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
io.BackendPlatformName = "Kyty ImeDialog input";
ImGui::StyleColorsDark();
auto& style = ImGui::GetStyle();
style.WindowRounding = 10.0f;
style.FrameRounding = 6.0f;
style.ItemSpacing = {8.0f, 8.0f};
}
void EnsureVulkan(vk::Format format, uint32_t image_count) {
EnsureContext();
if (vulkan_initialized) {
return;
}
EXIT_IF(image_count < 2);
EXIT_IF(!ImGui_ImplVulkan_LoadFunctions(VK_API_VERSION_1_3, LoadVulkanFunction, &graphics));
const VkFormat color_format = static_cast<VkFormat>(format);
ImGui_ImplVulkan_InitInfo info {};
info.ApiVersion = VK_API_VERSION_1_3;
info.Instance = static_cast<VkInstance>(graphics.instance);
info.PhysicalDevice = static_cast<VkPhysicalDevice>(graphics.physical_device);
info.Device = static_cast<VkDevice>(graphics.device);
info.QueueFamily = graphics.queue_family;
info.Queue = static_cast<VkQueue>(graphics.queue);
info.DescriptorPoolSize = IMGUI_IMPL_VULKAN_MINIMUM_SAMPLED_IMAGE_POOL_SIZE;
info.MinImageCount = image_count;
info.ImageCount = image_count;
info.UseDynamicRendering = true;
info.PipelineInfoMain.MSAASamples = VK_SAMPLE_COUNT_1_BIT;
info.PipelineInfoMain.PipelineRenderingCreateInfo.sType =
VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
info.PipelineInfoMain.PipelineRenderingCreateInfo.colorAttachmentCount = 1;
info.PipelineInfoMain.PipelineRenderingCreateInfo.pColorAttachmentFormats = &color_format;
info.CheckVkResultFn = CheckVulkanResult;
EXIT_IF(!ImGui_ImplVulkan_Init(&info));
vulkan_initialized = true;
}
void DrainInput(uint64_t generation) {
std::deque<InputEvent> events;
bool reset = false;
{
std::scoped_lock lock(g_input_mutex);
events.swap(g_input_events);
reset = g_input_reset_requested;
g_input_reset_requested = false;
}
auto& io = ImGui::GetIO();
if (reset) {
io.ClearEventsQueue();
io.ClearInputKeys();
io.ClearInputMouse();
right_stick = {};
}
for (const auto& event: events) {
if (event.generation != generation) {
continue;
}
switch (event.kind) {
case InputKind::Button: {
const bool down = event.x != 0.0f;
if (down && event.id == SDL_CONTROLLER_BUTTON_B) {
Ime::HostCancel(generation);
} else if (down && event.id == SDL_CONTROLLER_BUTTON_Y) {
Ime::HostBackspace(generation);
}
const ImGuiKey key = ControllerButtonToKey(event.id);
if (key != ImGuiKey_None) {
io.AddKeyEvent(key, down);
}
break;
}
case InputKind::Axis: {
const float value = event.x / (event.x < 0.0f ? 32768.0f : 32767.0f);
if (event.id == SDL_CONTROLLER_AXIS_LEFTX) {
io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickLeft, value < -0.25f,
std::max(-value, 0.0f));
io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickRight, value > 0.25f,
std::max(value, 0.0f));
} else if (event.id == SDL_CONTROLLER_AXIS_LEFTY) {
io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickUp, value < -0.25f,
std::max(-value, 0.0f));
io.AddKeyAnalogEvent(ImGuiKey_GamepadLStickDown, value > 0.25f,
std::max(value, 0.0f));
} else if (event.id == SDL_CONTROLLER_AXIS_RIGHTX) {
right_stick.x = std::abs(value) > 0.2f ? value : 0.0f;
} else if (event.id == SDL_CONTROLLER_AXIS_RIGHTY) {
right_stick.y = std::abs(value) > 0.2f ? value : 0.0f;
}
break;
}
case InputKind::MousePosition: io.AddMousePosEvent(event.x, event.y); break;
case InputKind::MouseButton: {
int button = -1;
if (event.id == SDL_BUTTON_LEFT) button = 0;
if (event.id == SDL_BUTTON_RIGHT) button = 1;
if (event.id == SDL_BUTTON_MIDDLE) button = 2;
if (button >= 0) io.AddMouseButtonEvent(button, event.x != 0.0f);
break;
}
case InputKind::MouseWheel: io.AddMouseWheelEvent(event.x, event.y); break;
case InputKind::ResetController:
io.ClearEventsQueue();
io.ClearInputKeys();
right_stick = {};
break;
}
}
}
void KeyButton(std::string_view label, char16_t value, uint64_t generation, float width,
bool default_focus) {
const bool pressed = ImGui::Button(label.data(), {width, button_height});
if (default_focus) {
ImGui::SetItemDefaultFocus();
}
if (pressed) {
Ime::HostInsertText(generation, std::u16string_view(&value, 1));
}
}
void DrawKeyRows(const Ime::HostSnapshot& snapshot, float width) {
static constexpr std::array<std::string_view, 4> lower = {"1234567890", "qwertyuiop",
"asdfghjkl", "zxcvbnm"};
static constexpr std::array<std::string_view, 4> upper = {"1234567890", "QWERTYUIOP",
"ASDFGHJKL", "ZXCVBNM"};
static constexpr std::array<std::string_view, 3> symbols = {"1234567890", "!@#$%^&*()",
"-_=+/:?."};
static constexpr std::array<std::string_view, 4> number = {"123", "456", "789", "-0."};
std::span<const std::string_view> rows;
if (snapshot.type == Ime::Type::Number) {
rows = number;
} else if (symbol_mode) {
rows = symbols;
} else {
rows = shift ? std::span<const std::string_view>(upper)
: std::span<const std::string_view>(lower);
}
bool first = focus_pending;
size_t row_index = 0;
for (const auto row: rows) {
const float key_width =
std::min(56.0f * ui_scale, (width - 8.0f * (row.size() - 1)) / row.size());
const float row_width = key_width * row.size() + 8.0f * (row.size() - 1);
ImGui::SetCursorPosX((width - row_width) * 0.5f);
ImGui::PushID(static_cast<int>(row_index++));
for (size_t i = 0; i < row.size(); i++) {
ImGui::PushID(static_cast<int>(i));
const char label[2] = {row[i], '\0'};
KeyButton(label, static_cast<char16_t>(row[i]), snapshot.generation, key_width,
first);
first = false;
ImGui::PopID();
if (i + 1 < row.size()) ImGui::SameLine();
}
ImGui::PopID();
}
focus_pending = false;
}
void DrawDialog(const Ime::HostSnapshot& snapshot, vk::Extent2D extent) {
const ImVec2 display(static_cast<float>(extent.width), static_cast<float>(extent.height));
const bool over_2k = (snapshot.option & Ime::OPTION_USE_OVER_2K) != 0;
const float reference_width = over_2k ? 3840.0f : 1920.0f;
const float reference_height = over_2k ? 2160.0f : 1080.0f;
const float scale_x = display.x / reference_width;
const float scale_y = display.y / reference_height;
const float width = static_cast<float>(snapshot.panel_width) * scale_x;
const float height = static_cast<float>(snapshot.panel_height) * scale_y;
ui_scale = std::min(scale_x, scale_y) * (over_2k ? 2.0f : 1.0f);
button_height = std::max(28.0f, 42.0f * ui_scale);
const ImVec2 pivot {AlignmentPivot(snapshot.horizontal_alignment),
AlignmentPivot(snapshot.vertical_alignment)};
if ((snapshot.option & Ime::OPTION_FIXED_POSITION) == 0) {
const float movement = 600.0f * ImGui::GetIO().DeltaTime;
panel_offset.x += right_stick.x * movement;
panel_offset.y += right_stick.y * movement;
}
const ImVec2 base_position {snapshot.posx * scale_x - pivot.x * width,
snapshot.posy * scale_y - pivot.y * height};
ImVec2 position {base_position.x + panel_offset.x, base_position.y + panel_offset.y};
if ((snapshot.option & Ime::OPTION_DISABLE_POSITION_ADJ) == 0) {
position.x = std::clamp(position.x, 0.0f, std::max(display.x - width, 0.0f));
position.y = std::clamp(position.y, 0.0f, std::max(display.y - height, 0.0f));
panel_offset = {position.x - base_position.x, position.y - base_position.y};
}
ImGui::SetNextWindowPos(position, ImGuiCond_Always);
ImGui::SetNextWindowSize({width, height}, ImGuiCond_Always);
constexpr ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoSavedSettings;
ImGui::Begin("##ImeDialog", nullptr, flags);
const std::u16string title = snapshot.title.empty() ? u"Enter text" : snapshot.title;
if (!snapshot.key_panel_visible) {
std::string compact = Common::Utf16ToUtf8(title.c_str()) + ": ";
const float glyph_width = std::max(ImGui::CalcTextSize("M").x, 1.0f);
const float text_width =
std::max(ImGui::GetContentRegionAvail().x - ImGui::CalcTextSize(compact.c_str()).x,
glyph_width);
const size_t visible_units =
std::max<size_t>(6, static_cast<size_t>(text_width / glyph_width));
compact += snapshot.text.empty() && !snapshot.placeholder.empty()
? Common::Utf16ToUtf8(snapshot.placeholder.c_str())
: VisibleText(snapshot, visible_units);
ImGui::TextUnformatted(compact.c_str());
const float compact_height = std::min(button_height, 28.0f * ui_scale);
if (ImGui::Button("Cancel", {90.0f * ui_scale, compact_height})) {
Ime::HostCancel(snapshot.generation);
}
ImGui::SameLine();
if (ImGui::Button(EnterLabel(snapshot.enter_label),
{90.0f * ui_scale, compact_height})) {
Ime::HostAccept(snapshot.generation);
}
ImGui::End();
return;
}
ImGui::TextUnformatted(Common::Utf16ToUtf8(title.c_str()).c_str());
ImGui::Separator();
const float text_height = std::max(
32.0f, ((snapshot.option & Ime::OPTION_MULTILINE) != 0 ? 80.0f : 48.0f) * ui_scale);
ImGui::BeginChild("##ImeText", {0.0f, text_height}, true);
if (snapshot.text.empty() && !snapshot.placeholder.empty()) {
ImGui::TextDisabled("%s", Common::Utf16ToUtf8(snapshot.placeholder.c_str()).c_str());
} else {
const float glyph_width = std::max(ImGui::CalcTextSize("M").x, 1.0f);
const size_t columns = std::max<size_t>(
8, static_cast<size_t>(ImGui::GetContentRegionAvail().x / glyph_width));
const size_t lines =
std::max<size_t>(1, static_cast<size_t>(ImGui::GetContentRegionAvail().y /
ImGui::GetTextLineHeightWithSpacing()));
const auto text = VisibleText(snapshot, columns * lines);
ImGui::TextWrapped("%s", text.c_str());
}
ImGui::EndChild();
ImGui::TextDisabled("%zu / %u", snapshot.text.size(), snapshot.max_text_length);
ImGui::Separator();
const float content_width = ImGui::GetContentRegionAvail().x;
DrawKeyRows(snapshot, content_width);
if (snapshot.type != Ime::Type::Number) {
if (ImGui::Button(shift ? "Lower" : "Shift", {84.0f * ui_scale, button_height})) {
shift = !shift;
}
ImGui::SameLine();
if (ImGui::Button(symbol_mode ? "ABC" : "Symbols", {84.0f * ui_scale, button_height})) {
symbol_mode = !symbol_mode;
}
ImGui::SameLine();
if (ImGui::Button("Space", {120.0f * ui_scale, button_height})) {
Ime::HostInsertText(snapshot.generation, u" ");
}
ImGui::SameLine();
}
const float action_width = snapshot.type == Ime::Type::Number
? std::max((content_width - 16.0f) / 3.0f, 48.0f)
: 100.0f * ui_scale;
if (ImGui::Button("Backspace", {action_width, button_height})) {
Ime::HostBackspace(snapshot.generation);
}
ImGui::SameLine();
if ((snapshot.option & Ime::OPTION_MULTILINE) != 0) {
if (ImGui::Button("Newline", {action_width, button_height})) {
Ime::HostInsertText(snapshot.generation, u"\n");
}
ImGui::SameLine();
}
if (ImGui::Button("Cancel", {action_width, button_height})) {
Ime::HostCancel(snapshot.generation);
}
ImGui::SameLine();
if (ImGui::Button(EnterLabel(snapshot.enter_label), {action_width, button_height})) {
Ime::HostAccept(snapshot.generation);
}
ImGui::End();
}
bool PrepareFrame(vk::Extent2D frame_extent, vk::Format format, uint32_t image_count) {
Ime::HostSnapshot snapshot;
if (!Ime::GetHostSnapshot(&snapshot)) {
return false;
}
const uint64_t prepared_generation = snapshot.generation;
EnsureVulkan(format, image_count);
if (generation != snapshot.generation) {
generation = snapshot.generation;
focus_pending = true;
shift = (snapshot.option & Ime::OPTION_NO_AUTO_CAPITALIZE) == 0;
symbol_mode = false;
panel_offset = {};
right_stick = {};
auto& io = ImGui::GetIO();
io.ClearEventsQueue();
io.ClearInputKeys();
io.ClearInputMouse();
}
DrainInput(snapshot.generation);
auto& io = ImGui::GetIO();
io.DisplaySize = {static_cast<float>(frame_extent.width),
static_cast<float>(frame_extent.height)};
const auto now = std::chrono::steady_clock::now();
io.DeltaTime = last_frame == std::chrono::steady_clock::time_point {}
? 1.0f / 60.0f
: std::clamp(std::chrono::duration<float>(now - last_frame).count(),
1.0f / 1000.0f, 0.1f);
last_frame = now;
ImGui_ImplVulkan_NewFrame();
ImGui::NewFrame();
if (!Ime::GetHostSnapshot(&snapshot) || snapshot.generation != prepared_generation) {
ImGui::EndFrame();
return false;
}
DrawDialog(snapshot, frame_extent);
ImGui::Render();
extent = frame_extent;
return true;
}
void Record(vk::CommandBuffer command, vk::ImageView target) {
vk::RenderingAttachmentInfo color {};
color.sType = vk::StructureType::eRenderingAttachmentInfo;
color.imageView = target;
color.imageLayout = vk::ImageLayout::eColorAttachmentOptimal;
color.loadOp = vk::AttachmentLoadOp::eLoad;
color.storeOp = vk::AttachmentStoreOp::eStore;
vk::RenderingInfo rendering {};
rendering.sType = vk::StructureType::eRenderingInfo;
rendering.renderArea.extent = extent;
rendering.layerCount = 1;
rendering.colorAttachmentCount = 1;
rendering.pColorAttachments = &color;
command.beginRendering(rendering);
{
Common::LockGuard queue_lock(graphics.queue_mutex);
ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(),
static_cast<VkCommandBuffer>(command));
}
command.endRendering();
}
void ReleaseVulkan() {
if (!vulkan_initialized) {
return;
}
ImGui::SetCurrentContext(imgui_context);
ImGui_ImplVulkan_Shutdown();
vulkan_initialized = false;
}
GraphicContext& graphics;
ImGuiContext* imgui_context = nullptr;
bool vulkan_initialized = false;
bool shift = false;
bool symbol_mode = false;
bool focus_pending = true;
float ui_scale = 1.0f;
float button_height = 42.0f;
ImVec2 panel_offset {};
ImVec2 right_stick {};
uint64_t generation = 0;
vk::Extent2D extent {};
std::chrono::steady_clock::time_point last_frame;
};
ImeDialogOverlay::ImeDialogOverlay(GraphicContext& graphics)
: m_impl(std::make_unique<Impl>(graphics)) {}
ImeDialogOverlay::~ImeDialogOverlay() = default;
bool ImeDialogOverlay::PrepareFrame(vk::Extent2D extent, vk::Format format, uint32_t image_count) {
return m_impl->PrepareFrame(extent, format, image_count);
}
void ImeDialogOverlay::Record(vk::CommandBuffer command, vk::ImageView target) {
m_impl->Record(command, target);
}
void ImeDialogOverlay::ReleaseVulkan() {
m_impl->ReleaseVulkan();
}
} // namespace Libs::Graphics
@@ -0,0 +1,36 @@
#ifndef EMULATOR_SRC_GRAPHICS_PRESENTATION_IMEDIALOGOVERLAY_H_
#define EMULATOR_SRC_GRAPHICS_PRESENTATION_IMEDIALOGOVERLAY_H_
#include "common/common.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <memory>
union SDL_Event;
namespace Libs::Graphics {
struct GraphicContext;
void InitializeImeDialogInput();
void ShutdownImeDialogInput();
bool ProcessImeDialogInput(const SDL_Event& event);
class ImeDialogOverlay final {
public:
explicit ImeDialogOverlay(GraphicContext& graphics);
~ImeDialogOverlay();
KYTY_CLASS_NO_COPY(ImeDialogOverlay);
[[nodiscard]] bool PrepareFrame(vk::Extent2D extent, vk::Format format, uint32_t image_count);
void Record(vk::CommandBuffer command, vk::ImageView target);
void ReleaseVulkan();
private:
struct Impl;
std::unique_ptr<Impl> m_impl;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_PRESENTATION_IMEDIALOGOVERLAY_H_
+1
View File
@@ -25,6 +25,7 @@ public:
CommandBuffer* producer = nullptr); CommandBuffer* producer = nullptr);
[[nodiscard]] Frame* PrepareLastFrame(); [[nodiscard]] Frame* PrepareLastFrame();
[[nodiscard]] bool IsGuestPaused() const noexcept; [[nodiscard]] bool IsGuestPaused() const noexcept;
[[nodiscard]] bool NeedsImeRefresh() const noexcept;
[[nodiscard]] RenderContext& Renderer() const noexcept; [[nodiscard]] RenderContext& Renderer() const noexcept;
void Present(Frame& frame, bool reuse = false); void Present(Frame& frame, bool reuse = false);
void Discard(Frame& frame); void Discard(Frame& frame);
+2 -4
View File
@@ -128,8 +128,7 @@ static bool BindRenderDocApi(HMODULE module) {
g_api = static_cast<RenderDocApi*>(api); g_api = static_cast<RenderDocApi*>(api);
g_api->SetCaptureFilePathTemplate("_RenderDoc/kyty"); g_api->SetCaptureFilePathTemplate("_RenderDoc/kyty");
static RenderDocInputButton capture_keys[] = {eRENDERDOC_Key_F1}; g_api->SetCaptureKeys(nullptr, 0);
g_api->SetCaptureKeys(capture_keys, 1);
g_api->UnloadCrashHandler(); g_api->UnloadCrashHandler();
char module_path[MAX_PATH] = {}; char module_path[MAX_PATH] = {};
@@ -170,8 +169,7 @@ static bool BindRenderDocApi(HMODULE module) {
g_api = static_cast<RenderDocApi*>(api); g_api = static_cast<RenderDocApi*>(api);
g_api->SetCaptureFilePathTemplate("_RenderDoc/kyty"); g_api->SetCaptureFilePathTemplate("_RenderDoc/kyty");
static RenderDocInputButton capture_keys[] = {eRENDERDOC_Key_F1}; g_api->SetCaptureKeys(nullptr, 0);
g_api->SetCaptureKeys(capture_keys, 1);
g_api->UnloadCrashHandler(); g_api->UnloadCrashHandler();
Dl_info info {}; Dl_info info {};
+24 -11
View File
@@ -537,10 +537,10 @@ Graphics::ImageInfo BufferAttributeGroup::ImageInfo(const VideoOutBuffer& buffer
const auto tile_mode = const auto tile_mode =
Graphics::Prospero::GpuEnumValue(Graphics::Prospero::TileMode::kRenderTarget); Graphics::Prospero::GpuEnumValue(Graphics::Prospero::TileMode::kRenderTarget);
const auto pitch = const auto pitch =
Graphics::TileGetTexturePitch(pixel_format.guest_format, attribute.width, 1, tile_mode); Graphics::TileGetTexturePitch(pixel_format.guest_format, attribute.width, tile_mode);
Graphics::TileSizeAlign total {}; Graphics::TileSizeAlign total {};
Graphics::TileGetTextureTotalSize(pixel_format.guest_format, attribute.width, attribute.height, Graphics::TileGetTextureTotalSize(pixel_format.guest_format, attribute.width, attribute.height,
1, pitch, 1, tile_mode, false, total); 1, 1, tile_mode, false, total);
if (total.size == 0 || total.align != 65536 || if (total.size == 0 || total.align != 65536 ||
(buffer.data_address & (total.align - 1u)) != 0) { (buffer.data_address & (total.align - 1u)) != 0) {
EXIT("invalid video-out surface footprint or alignment\n"); EXIT("invalid video-out surface footprint or alignment\n");
@@ -788,14 +788,10 @@ void VideoOutDriver::Impl::PresentThread(std::stop_token token) {
while (!token.stop_requested()) { while (!token.stop_requested()) {
const auto sleep_begin = Common::Timer::QueryPerformanceCounter(); const auto sleep_begin = Common::Timer::QueryPerformanceCounter();
if (total_wait > 0) { if (total_wait > 0) {
const auto sleep_end = sleep_begin + static_cast<uint64_t>(total_wait); const auto remaining_us =
auto now = sleep_begin; (static_cast<uint64_t>(total_wait) * 1000000u + frequency - 1) / frequency;
while (!token.stop_requested() && now < sleep_end) { Common::Thread::SleepMicro(static_cast<uint32_t>(
const auto remaining_us = (sleep_end - now) * 1000000u / frequency; std::clamp<uint64_t>(remaining_us, 1, std::numeric_limits<uint32_t>::max())));
Common::Thread::SleepMicro(
static_cast<uint32_t>(std::clamp<uint64_t>(remaining_us, 1, 1000)));
now = Common::Timer::QueryPerformanceCounter();
}
} }
if (token.stop_requested()) { if (token.stop_requested()) {
break; break;
@@ -817,7 +813,24 @@ void VideoOutDriver::Impl::PresentThread(std::stop_token token) {
} }
VblankBegin(); VblankBegin();
const bool presented = m_flip_queue.Flip(0); bool presented = m_flip_queue.Flip(0);
if (!presented && m_presenter.NeedsImeRefresh()) {
if (auto* frame = m_presenter.PrepareLastFrame(); frame != nullptr) {
m_presenter.Present(*frame, true);
presented = true;
} else {
uint32_t width = 0;
uint32_t height = 0;
{
Common::LockGuard lock(m_mutex);
width = m_video_out_ctx[0].width;
height = m_video_out_ctx[0].height;
}
auto& blank = m_presenter.PrepareBlankFrame(width, height, true);
m_presenter.Present(blank);
presented = true;
}
}
if (!presented && total_wait < 0) { if (!presented && total_wait < 0) {
bool any_open = false; bool any_open = false;
uint32_t width = 0; uint32_t width = 0;
+74 -31
View File
@@ -31,11 +31,13 @@
#include "graphics/host_gpu/renderer/renderContext.h" #include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vma.h" #include "graphics/host_gpu/vma.h"
#include "graphics/host_gpu/vulkanCommon.h" #include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/presentation/imeDialogOverlay.h"
#include "graphics/presentation/presenter.h" #include "graphics/presentation/presenter.h"
#include "graphics/presentation/renderDoc.h" #include "graphics/presentation/renderDoc.h"
#include "graphics/presentation/videoOut.h" #include "graphics/presentation/videoOut.h"
#include "graphics/presentation/window/windowInternal.h" #include "graphics/presentation/window/windowInternal.h"
#include "libs/controller.h" #include "libs/controller.h"
#include "libs/imeDialog.h"
#include "loader/systemContent.h" #include "loader/systemContent.h"
#include <algorithm> #include <algorithm>
@@ -329,8 +331,9 @@ public:
void Create(); void Create();
void Recreate(bool surface_lost = false); void Recreate(bool surface_lost = false);
[[nodiscard]] Status AcquireNextImage(); [[nodiscard]] Status AcquireNextImage();
void RecordPresentCommands(CommandBuffer& command, VulkanImage& source); [[nodiscard]] bool PrepareImeOverlay();
void Submit(CommandBuffer& command); void RecordPresentCommands(CommandBuffer& command, VulkanImage& source, bool draw_ime_overlay);
void Submit(CommandBuffer& command);
[[nodiscard]] Status Present(); [[nodiscard]] Status Present();
[[nodiscard]] uint32_t ImageCount() const noexcept { [[nodiscard]] uint32_t ImageCount() const noexcept {
@@ -342,16 +345,17 @@ private:
void Destroy(); void Destroy();
void RefreshSurfaceSize(); void RefreshSurfaceSize();
WindowContext& m_window; WindowContext& m_window;
vk::SwapchainKHR m_handle = nullptr; vk::SwapchainKHR m_handle = nullptr;
vk::Format m_format = vk::Format::eUndefined; vk::Format m_format = vk::Format::eUndefined;
vk::Extent2D m_extent {}; vk::Extent2D m_extent {};
std::vector<vk::Image> m_images; std::vector<vk::Image> m_images;
std::vector<vk::ImageView> m_image_views; std::vector<vk::ImageView> m_image_views;
std::vector<vk::Semaphore> m_image_acquired; std::vector<vk::Semaphore> m_image_acquired;
std::vector<vk::Semaphore> m_render_complete; std::vector<vk::Semaphore> m_render_complete;
uint32_t m_image_index = static_cast<uint32_t>(-1); std::unique_ptr<ImeDialogOverlay> m_ime_overlay;
uint32_t m_frame_index = 0; uint32_t m_image_index = static_cast<uint32_t>(-1);
uint32_t m_frame_index = 0;
}; };
struct Presenter::Impl { struct Presenter::Impl {
@@ -383,17 +387,20 @@ struct Presenter::Impl {
desc.view_info.usage = vk::ImageUsageFlagBits::eTransferSrc; desc.view_info.usage = vk::ImageUsageFlagBits::eTransferSrc;
desc.type = TextureCache::BindingType::VideoOut; desc.type = TextureCache::BindingType::VideoOut;
auto& cache = renderer.GetTextureCache(); auto& cache = renderer.GetTextureCache();
auto& image = cache.GetImage(cache.FindImage(desc)); const auto image_id = cache.FindImage(desc);
auto& image = cache.GetImage(image_id);
image.usage.video_out = true; image.usage.video_out = true;
cache.UpdateImage(image_id);
return image; return image;
} }
RenderContext& renderer; RenderContext& renderer;
WindowContext& window; WindowContext& window;
Swapchain swapchain; Swapchain swapchain;
CommandScheduler present_scheduler; CommandScheduler present_scheduler;
FramePool frames; FramePool frames;
std::atomic<uint64_t> presented_ime_revision {0};
}; };
void Swapchain::Create() { void Swapchain::Create() {
@@ -522,6 +529,9 @@ void Swapchain::Destroy() {
Common::LockGuard queue_lock(graphics.queue_mutex); Common::LockGuard queue_lock(graphics.queue_mutex);
RequireVulkanSuccess(graphics.queue.waitIdle(), "wait for swapchain queue"); RequireVulkanSuccess(graphics.queue.waitIdle(), "wait for swapchain queue");
} }
if (m_ime_overlay != nullptr) {
m_ime_overlay->ReleaseVulkan();
}
for (const auto semaphore: m_image_acquired) { for (const auto semaphore: m_image_acquired) {
if (semaphore != nullptr) { if (semaphore != nullptr) {
@@ -606,7 +616,15 @@ Swapchain::Status Swapchain::AcquireNextImage() {
return Status::Success; return Status::Success;
} }
void Swapchain::RecordPresentCommands(CommandBuffer& command, VulkanImage& source) { bool Swapchain::PrepareImeOverlay() {
if (m_ime_overlay == nullptr) {
m_ime_overlay = std::make_unique<ImeDialogOverlay>(m_window.graphic_ctx);
}
return m_ime_overlay->PrepareFrame(m_extent, m_format, ImageCount());
}
void Swapchain::RecordPresentCommands(CommandBuffer& command, VulkanImage& source,
bool draw_ime_overlay) {
if (source.state.layout != vk::ImageLayout::eTransferSrcOptimal) { if (source.state.layout != vk::ImageLayout::eTransferSrcOptimal) {
EXIT("invalid prepared presentation image, vk_image=%p layout=%d\n", EXIT("invalid prepared presentation image, vk_image=%p layout=%d\n",
static_cast<void*>(source.image), static_cast<int>(source.state.layout)); static_cast<void*>(source.image), static_cast<int>(source.state.layout));
@@ -652,11 +670,14 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, VulkanImage& sourc
vk::Filter::eLinear); vk::Filter::eLinear);
vk::ImageMemoryBarrier to_present {}; vk::ImageMemoryBarrier to_present {};
to_present.sType = vk::StructureType::eImageMemoryBarrier; to_present.sType = vk::StructureType::eImageMemoryBarrier;
to_present.srcAccessMask = vk::AccessFlagBits::eTransferWrite; to_present.srcAccessMask = vk::AccessFlagBits::eTransferWrite;
to_present.dstAccessMask = vk::AccessFlagBits::eMemoryRead; to_present.dstAccessMask = draw_ime_overlay ? vk::AccessFlagBits::eColorAttachmentRead |
to_present.oldLayout = vk::ImageLayout::eTransferDstOptimal; vk::AccessFlagBits::eColorAttachmentWrite
to_present.newLayout = vk::ImageLayout::ePresentSrcKHR; : vk::AccessFlagBits::eMemoryRead;
to_present.oldLayout = vk::ImageLayout::eTransferDstOptimal;
to_present.newLayout = draw_ime_overlay ? vk::ImageLayout::eColorAttachmentOptimal
: vk::ImageLayout::ePresentSrcKHR;
to_present.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; to_present.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
to_present.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; to_present.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
to_present.image = m_images[m_image_index]; to_present.image = m_images[m_image_index];
@@ -665,9 +686,22 @@ void Swapchain::RecordPresentCommands(CommandBuffer& command, VulkanImage& sourc
to_present.subresourceRange.levelCount = 1; to_present.subresourceRange.levelCount = 1;
to_present.subresourceRange.baseArrayLayer = 0; to_present.subresourceRange.baseArrayLayer = 0;
to_present.subresourceRange.layerCount = 1; to_present.subresourceRange.layerCount = 1;
vk_command.pipelineBarrier( vk_command.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer,
vk::PipelineStageFlagBits::eAllCommands, vk::PipelineStageFlagBits::eAllCommands, draw_ime_overlay ? vk::PipelineStageFlagBits::eColorAttachmentOutput
vk::DependencyFlagBits::eByRegion, 0, nullptr, 0, nullptr, 1, &to_present); : vk::PipelineStageFlagBits::eAllCommands,
vk::DependencyFlagBits::eByRegion, 0, nullptr, 0, nullptr, 1,
&to_present);
if (draw_ime_overlay) {
m_ime_overlay->Record(vk_command, m_image_views[m_image_index]);
to_present.srcAccessMask = vk::AccessFlagBits::eColorAttachmentWrite;
to_present.dstAccessMask = vk::AccessFlagBits::eMemoryRead;
to_present.oldLayout = vk::ImageLayout::eColorAttachmentOptimal;
to_present.newLayout = vk::ImageLayout::ePresentSrcKHR;
vk_command.pipelineBarrier(vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::PipelineStageFlagBits::eAllCommands,
vk::DependencyFlagBits::eByRegion, 0, nullptr, 0, nullptr, 1,
&to_present);
}
command.End(); command.End();
} }
@@ -772,6 +806,12 @@ bool Presenter::IsGuestPaused() const noexcept {
return m_impl->window.loop.paused.load(std::memory_order_acquire); return m_impl->window.loop.paused.load(std::memory_order_acquire);
} }
bool Presenter::NeedsImeRefresh() const noexcept {
const auto visual = Dialog::ImeDialog::GetVisualState();
return visual.active ||
visual.revision != m_impl->presented_ime_revision.load(std::memory_order_acquire);
}
RenderContext& Presenter::Renderer() const noexcept { RenderContext& Presenter::Renderer() const noexcept {
return m_impl->renderer; return m_impl->renderer;
} }
@@ -803,7 +843,8 @@ void Presenter::Present(Frame& frame, bool reuse) {
m_impl->RecoverSwapchain(Swapchain::Status::Recreate); m_impl->RecoverSwapchain(Swapchain::Status::Recreate);
} }
auto& swapchain = m_impl->swapchain; const auto ime_visual = Dialog::ImeDialog::GetVisualState();
auto& swapchain = m_impl->swapchain;
for (uint32_t attempt = 0; attempt < 2; attempt++) { for (uint32_t attempt = 0; attempt < 2; attempt++) {
auto status = swapchain.AcquireNextImage(); auto status = swapchain.AcquireNextImage();
if (status != Swapchain::Status::Success) { if (status != Swapchain::Status::Success) {
@@ -816,8 +857,9 @@ void Presenter::Present(Frame& frame, bool reuse) {
{ {
Common::LockGuard render_lock(m_impl->renderer.GetMutex()); Common::LockGuard render_lock(m_impl->renderer.GetMutex());
frame.present_commands->WaitForFenceAndReset(); frame.present_commands->WaitForFenceAndReset();
auto& command = *frame.present_commands; auto& command = *frame.present_commands;
swapchain.RecordPresentCommands(command, frame.image); const bool draw_ime_overlay = ime_visual.active && swapchain.PrepareImeOverlay();
swapchain.RecordPresentCommands(command, frame.image, draw_ime_overlay);
swapchain.Submit(command); swapchain.Submit(command);
} }
status = swapchain.Present(); status = swapchain.Present();
@@ -827,6 +869,7 @@ void Presenter::Present(Frame& frame, bool reuse) {
} }
RenderDocOnPresent(); RenderDocOnPresent();
m_impl->presented_ime_revision.store(ime_visual.revision, std::memory_order_release);
window.UpdateTitle(); window.UpdateTitle();
m_impl->frames.Release(&frame, true); m_impl->frames.Release(&frame, true);
return; return;
@@ -31,6 +31,7 @@
#include "graphics/host_gpu/renderer/renderContext.h" #include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vma.h" #include "graphics/host_gpu/vma.h"
#include "graphics/host_gpu/vulkanCommon.h" #include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/presentation/imeDialogOverlay.h"
#include "graphics/presentation/presenter.h" #include "graphics/presentation/presenter.h"
#include "graphics/presentation/renderDoc.h" #include "graphics/presentation/renderDoc.h"
#include "graphics/presentation/videoOut.h" #include "graphics/presentation/videoOut.h"
@@ -259,6 +260,10 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
LOGF("sampleRateShading is not supported\n"); LOGF("sampleRateShading is not supported\n");
skip_device = true; skip_device = true;
} }
if (device_features2.features.depthBiasClamp != VK_TRUE) {
LOGF("depthBiasClamp is not supported\n");
skip_device = true;
}
if (device_features2.features.fragmentStoresAndAtomics != VK_TRUE) { if (device_features2.features.fragmentStoresAndAtomics != VK_TRUE) {
LOGF("fragmentStoresAndAtomics is not supported\n"); LOGF("fragmentStoresAndAtomics is not supported\n");
@@ -548,6 +553,7 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
EXIT_NOT_IMPLEMENTED(required_features13.synchronization2 == VK_TRUE && EXIT_NOT_IMPLEMENTED(required_features13.synchronization2 == VK_TRUE &&
supported_features13.synchronization2 != VK_TRUE); supported_features13.synchronization2 != VK_TRUE);
EXIT_NOT_IMPLEMENTED(supported_features2.features.sampleRateShading != VK_TRUE); EXIT_NOT_IMPLEMENTED(supported_features2.features.sampleRateShading != VK_TRUE);
EXIT_NOT_IMPLEMENTED(supported_features2.features.depthBiasClamp != VK_TRUE);
features12.timelineSemaphore = VK_TRUE; features12.timelineSemaphore = VK_TRUE;
vk::PhysicalDeviceFeatures device_features {}; vk::PhysicalDeviceFeatures device_features {};
@@ -563,6 +569,7 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
device_features.independentBlend = VK_TRUE; device_features.independentBlend = VK_TRUE;
device_features.tessellationShader = VK_TRUE; device_features.tessellationShader = VK_TRUE;
device_features.sampleRateShading = VK_TRUE; device_features.sampleRateShading = VK_TRUE;
device_features.depthBiasClamp = VK_TRUE;
graphics.sample_rate_shading_enabled = true; graphics.sample_rate_shading_enabled = true;
device_features.vertexPipelineStoresAndAtomics = device_features.vertexPipelineStoresAndAtomics =
supported_features2.features.vertexPipelineStoresAndAtomics; supported_features2.features.vertexPipelineStoresAndAtomics;
@@ -1008,6 +1015,7 @@ void WindowContext::RecreateSurface() {
} }
WindowContext::~WindowContext() { WindowContext::~WindowContext() {
ShutdownImeDialogInput();
presenter.reset(); presenter.reset();
LibKernel::Memory::InstallGpuResources(nullptr); LibKernel::Memory::InstallGpuResources(nullptr);
render_context.reset(); render_context.reset();
+11 -14
View File
@@ -31,6 +31,7 @@
#include "graphics/host_gpu/renderer/render.h" #include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/vma.h" #include "graphics/host_gpu/vma.h"
#include "graphics/host_gpu/vulkanCommon.h" #include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/presentation/imeDialogOverlay.h"
#include "graphics/presentation/renderDoc.h" #include "graphics/presentation/renderDoc.h"
#include "graphics/presentation/window/hostInput.h" #include "graphics/presentation/window/hostInput.h"
#include "graphics/presentation/window/windowInternal.h" #include "graphics/presentation/window/windowInternal.h"
@@ -476,6 +477,9 @@ void WindowContext::ProcessEvent(double time_s) {
auto& game = loop; auto& game = loop;
auto* event = &game.event; auto* event = &game.event;
EXIT_IF(SDL_GetEventState(SDL_DISPLAYEVENT) != SDL_ENABLE); EXIT_IF(SDL_GetEventState(SDL_DISPLAYEVENT) != SDL_ENABLE);
if (ProcessImeDialogInput(*event)) {
return;
}
switch (event->type) { switch (event->type) {
case SDL_QUIT: GameEventQuit(game); break; case SDL_QUIT: GameEventQuit(game); break;
@@ -748,26 +752,18 @@ void WindowContext::Run() {
#if defined(__APPLE__) #if defined(__APPLE__)
DrainMainThreadTasks(); DrainMainThreadTasks();
#endif #endif
if (SDL_PollEvent(&loop.event) != 0) {
ProcessEvent(timer.GetTimeS());
continue;
}
if (loop.paused.load(std::memory_order_acquire)) { if (loop.paused.load(std::memory_order_acquire)) {
if (!timer.IsPaused()) { if (!timer.IsPaused()) {
timer.Pause(); timer.Pause();
} }
if (SDL_WaitEvent(&loop.event) == 0) { } else if (timer.IsPaused()) {
EXIT("%s\n", SDL_GetError());
}
ProcessEvent(timer.GetTimeS());
continue;
}
if (timer.IsPaused()) {
timer.Resume(); timer.Resume();
} }
Common::Thread::SleepMicro(1000);
if (SDL_WaitEvent(&loop.event) == 0) {
EXIT("%s\n", SDL_GetError());
}
ProcessEvent(timer.GetTimeS());
} }
} }
@@ -787,6 +783,7 @@ static void WindowCreate(WindowContext& context) {
EXIT("%s\n", SDL_GetError()); EXIT("%s\n", SDL_GetError());
} }
HostInputInit(); HostInputInit();
InitializeImeDialogInput();
LOGF("WindowCreate(): width = %d, height = %d\n", width, height); LOGF("WindowCreate(): width = %d, height = %d\n", width, height);
@@ -84,6 +84,8 @@ bool InstructionMaySplitSpirvBlock(const IR::Instruction& inst) {
case IR::Opcode::AtomicAndU32: case IR::Opcode::AtomicAndU32:
case IR::Opcode::AtomicOrU32: case IR::Opcode::AtomicOrU32:
case IR::Opcode::AtomicXorU32: case IR::Opcode::AtomicXorU32:
case IR::Opcode::AtomicFMinF32:
case IR::Opcode::AtomicFMaxF32:
case IR::Opcode::FlatLoadUbyte: case IR::Opcode::FlatLoadUbyte:
case IR::Opcode::FlatLoadSbyte: case IR::Opcode::FlatLoadSbyte:
case IR::Opcode::FlatLoadUshort: case IR::Opcode::FlatLoadUshort:
@@ -57,6 +57,8 @@ constexpr MemoryOpcodeInfo MUBUF_OPS[] = {
{0x39u, Opcode::BufferAtomicAnd, 1, 32}, {0x39u, Opcode::BufferAtomicAnd, 1, 32},
{0x3au, Opcode::BufferAtomicOr, 1, 32}, {0x3au, Opcode::BufferAtomicOr, 1, 32},
{0x3bu, Opcode::BufferAtomicXor, 1, 32}, {0x3bu, Opcode::BufferAtomicXor, 1, 32},
{0x3fu, Opcode::BufferAtomicFMin, 1, 32},
{0x40u, Opcode::BufferAtomicFMax, 1, 32},
}; };
constexpr MemoryOpcodeInfo MTBUF_OPS[] = { constexpr MemoryOpcodeInfo MTBUF_OPS[] = {
@@ -0,0 +1,45 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_DECOMPILER_OPCODETABLE_H_
#define EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_DECOMPILER_OPCODETABLE_H_
#include "graphics/shader/recompiler/decompiler/ShaderDecoder.h"
#include <cstddef>
namespace Libs::Graphics::ShaderRecompiler::Decoder::Detail {
struct OpcodeMap {
uint32_t encoding = 0;
Opcode decoded = Opcode::Unknown;
};
template <typename Entry, size_t N>
constexpr const Entry* FindOpcode(const Entry (&table)[N], uint32_t encoding) {
for (const auto& entry: table) {
if (entry.encoding == encoding) {
return &entry;
}
}
return nullptr;
}
template <typename Entry, size_t N>
constexpr Opcode LookupOpcode(const Entry (&table)[N], uint32_t encoding) {
const auto* entry = FindOpcode(table, encoding);
return entry != nullptr ? entry->decoded : Opcode::Unsupported;
}
template <typename Entry, size_t N>
constexpr bool HasUniqueEncodings(const Entry (&table)[N]) {
for (size_t i = 0; i < N; i++) {
for (size_t j = i + 1; j < N; j++) {
if (table[i].encoding == table[j].encoding) {
return false;
}
}
}
return true;
}
} // namespace Libs::Graphics::ShaderRecompiler::Decoder::Detail
#endif // EMULATOR_INCLUDE_EMULATOR_GRAPHICS_SHADER_RECOMPILER_DECOMPILER_OPCODETABLE_H_
@@ -1,14 +1,11 @@
#include "graphics/shader/recompiler/decompiler/ScalarAluOps.h" #include "graphics/shader/recompiler/decompiler/ScalarAluOps.h"
#include <iterator> #include "graphics/shader/recompiler/decompiler/OpcodeTable.h"
namespace Libs::Graphics::ShaderRecompiler::Decoder { namespace Libs::Graphics::ShaderRecompiler::Decoder {
namespace { namespace {
struct OpcodeMap { using Detail::OpcodeMap;
uint32_t opcode = 0;
Opcode ir = Opcode::Unknown;
};
constexpr OpcodeMap SOP2_OPS[] = { constexpr OpcodeMap SOP2_OPS[] = {
{0x00u, Opcode::SAddU32}, {0x01u, Opcode::SSubU32}, {0x02u, Opcode::SAddI32}, {0x00u, Opcode::SAddU32}, {0x01u, Opcode::SSubU32}, {0x02u, Opcode::SAddI32},
@@ -58,7 +55,8 @@ constexpr OpcodeMap SOPC_OPS[] = {
{0x03u, Opcode::SCmpGeI32}, {0x04u, Opcode::SCmpLtI32}, {0x05u, Opcode::SCmpLeI32}, {0x03u, Opcode::SCmpGeI32}, {0x04u, Opcode::SCmpLtI32}, {0x05u, Opcode::SCmpLeI32},
{0x06u, Opcode::SCmpEqU32}, {0x07u, Opcode::SCmpLgU32}, {0x08u, Opcode::SCmpGtU32}, {0x06u, Opcode::SCmpEqU32}, {0x07u, Opcode::SCmpLgU32}, {0x08u, Opcode::SCmpGtU32},
{0x09u, Opcode::SCmpGeU32}, {0x0au, Opcode::SCmpLtU32}, {0x0bu, Opcode::SCmpLeU32}, {0x09u, Opcode::SCmpGeU32}, {0x0au, Opcode::SCmpLtU32}, {0x0bu, Opcode::SCmpLeU32},
{0x0cu, Opcode::SBitcmp0B32}, {0x0du, Opcode::SBitcmp1B32}, {0x13u, Opcode::SCmpLgU64}, {0x0cu, Opcode::SBitcmp0B32}, {0x0du, Opcode::SBitcmp1B32}, {0x12u, Opcode::SCmpEqU64},
{0x13u, Opcode::SCmpLgU64},
}; };
constexpr OpcodeMap SOPK_OPS[] = { constexpr OpcodeMap SOPK_OPS[] = {
@@ -78,15 +76,11 @@ constexpr OpcodeMap SOPP_OPS[] = {
{0x0au, Opcode::SBarrier}, {0x0cu, Opcode::SWaitcnt}, {0x0eu, Opcode::SSleep}, {0x0au, Opcode::SBarrier}, {0x0cu, Opcode::SWaitcnt}, {0x0eu, Opcode::SSleep},
{0x10u, Opcode::SSendmsg}, {0x16u, Opcode::STtraceData}, {0x20u, Opcode::SInstPrefetch}, {0x10u, Opcode::SSendmsg}, {0x16u, Opcode::STtraceData}, {0x20u, Opcode::SInstPrefetch},
}; };
static_assert(Detail::HasUniqueEncodings(SOP1_OPS));
Opcode Lookup(const OpcodeMap* ops, uint32_t count, uint32_t opcode) { static_assert(Detail::HasUniqueEncodings(SOP2_OPS));
for (uint32_t i = 0; i < count; i++) { static_assert(Detail::HasUniqueEncodings(SOPK_OPS));
if (ops[i].opcode == opcode) { static_assert(Detail::HasUniqueEncodings(SOPC_OPS));
return ops[i].ir; static_assert(Detail::HasUniqueEncodings(SOPP_OPS));
}
}
return Opcode::Unsupported;
}
bool DecodeBinarySources(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index, bool DecodeBinarySources(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index,
Instruction& inst, uint32_t ssrc0, uint32_t ssrc1, std::string* error) { Instruction& inst, uint32_t ssrc0, uint32_t ssrc1, std::string* error) {
@@ -111,7 +105,7 @@ bool DecodeSop1(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
inst.word = word; inst.word = word;
inst.family = Family::SOP1; inst.family = Family::SOP1;
inst.opcode_id = opcode; inst.opcode_id = opcode;
inst.opcode = Lookup(SOP1_OPS, static_cast<uint32_t>(std::size(SOP1_OPS)), opcode); inst.opcode = Detail::LookupOpcode(SOP1_OPS, opcode);
SetRawWords(inst, code, word_index, 1); SetRawWords(inst, code, word_index, 1);
if (inst.opcode == Opcode::Unsupported) { if (inst.opcode == Opcode::Unsupported) {
@@ -153,7 +147,7 @@ bool DecodeSop2(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
inst.word = word; inst.word = word;
inst.family = Family::SOP2; inst.family = Family::SOP2;
inst.opcode_id = opcode; inst.opcode_id = opcode;
inst.opcode = Lookup(SOP2_OPS, static_cast<uint32_t>(std::size(SOP2_OPS)), opcode); inst.opcode = Detail::LookupOpcode(SOP2_OPS, opcode);
SetRawWords(inst, code, word_index, 1); SetRawWords(inst, code, word_index, 1);
if (inst.opcode == Opcode::Unsupported) { if (inst.opcode == Opcode::Unsupported) {
@@ -180,7 +174,7 @@ bool DecodeSopk(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
inst.word = word; inst.word = word;
inst.family = Family::SOPK; inst.family = Family::SOPK;
inst.opcode_id = opcode; inst.opcode_id = opcode;
inst.opcode = Lookup(SOPK_OPS, static_cast<uint32_t>(std::size(SOPK_OPS)), opcode); inst.opcode = Detail::LookupOpcode(SOPK_OPS, opcode);
inst.src0.kind = OperandKind::IntegerInlineConstant; inst.src0.kind = OperandKind::IntegerInlineConstant;
inst.src0.signed_val = imm; inst.src0.signed_val = imm;
inst.src0.value = static_cast<uint32_t>(imm); inst.src0.value = static_cast<uint32_t>(imm);
@@ -237,7 +231,7 @@ bool DecodeSopc(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
inst.word = word; inst.word = word;
inst.family = Family::SOPC; inst.family = Family::SOPC;
inst.opcode_id = opcode; inst.opcode_id = opcode;
inst.opcode = Lookup(SOPC_OPS, static_cast<uint32_t>(std::size(SOPC_OPS)), opcode); inst.opcode = Detail::LookupOpcode(SOPC_OPS, opcode);
inst.dst.kind = OperandKind::Scc; inst.dst.kind = OperandKind::Scc;
SetRawWords(inst, code, word_index, 1); SetRawWords(inst, code, word_index, 1);
@@ -261,7 +255,7 @@ bool DecodeSopp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
inst.word = word; inst.word = word;
inst.family = Family::SOPP; inst.family = Family::SOPP;
inst.opcode_id = opcode; inst.opcode_id = opcode;
inst.opcode = Lookup(SOPP_OPS, static_cast<uint32_t>(std::size(SOPP_OPS)), opcode); inst.opcode = Detail::LookupOpcode(SOPP_OPS, opcode);
inst.src0.kind = OperandKind::LiteralConstant; inst.src0.kind = OperandKind::LiteralConstant;
inst.src0.value = simm; inst.src0.value = simm;
inst.src0.signed_val = static_cast<int16_t>(simm); inst.src0.signed_val = static_cast<int16_t>(simm);
@@ -77,6 +77,16 @@ std::string FormatBinary(const Instruction& inst) {
return text; return text;
} }
std::string FormatSources(const Instruction& inst) {
std::string text = fmt::format("0x{:08x}: {}", inst.pc, OpcodeToString(inst.opcode));
const Operand* sources[] = {&inst.src0, &inst.src1, &inst.src2, &inst.src3};
for (uint32_t i = 0; i < inst.src_count && i < 4u; i++) {
text += i == 0 ? " " : ", ";
text += OperandToString(*sources[i]);
}
return text;
}
std::string FormatMemory(const Instruction& inst) { std::string FormatMemory(const Instruction& inst) {
std::string text = fmt::format("0x{:08x}: {} {}", inst.pc, OpcodeToString(inst.opcode).c_str(), std::string text = fmt::format("0x{:08x}: {} {}", inst.pc, OpcodeToString(inst.opcode).c_str(),
OperandToString(inst.dst).c_str()); OperandToString(inst.dst).c_str());
@@ -511,6 +521,7 @@ std::string OpcodeToString(Opcode opcode) {
case Opcode::SCmpGeU32: return "s_cmp_ge_u32"; case Opcode::SCmpGeU32: return "s_cmp_ge_u32";
case Opcode::SCmpLtU32: return "s_cmp_lt_u32"; case Opcode::SCmpLtU32: return "s_cmp_lt_u32";
case Opcode::SCmpLeU32: return "s_cmp_le_u32"; case Opcode::SCmpLeU32: return "s_cmp_le_u32";
case Opcode::SCmpEqU64: return "s_cmp_eq_u64";
case Opcode::SCmpLgU64: return "s_cmp_lg_u64"; case Opcode::SCmpLgU64: return "s_cmp_lg_u64";
case Opcode::VNop: return "v_nop"; case Opcode::VNop: return "v_nop";
case Opcode::VMovB32: return "v_mov_b32"; case Opcode::VMovB32: return "v_mov_b32";
@@ -766,6 +777,7 @@ std::string OpcodeToString(Opcode opcode) {
case Opcode::VCmpNeU32: return "v_cmp_ne_u32"; case Opcode::VCmpNeU32: return "v_cmp_ne_u32";
case Opcode::VCmpGeU32: return "v_cmp_ge_u32"; case Opcode::VCmpGeU32: return "v_cmp_ge_u32";
case Opcode::VCmpTU32: return "v_cmp_t_u32"; case Opcode::VCmpTU32: return "v_cmp_t_u32";
case Opcode::VCmpEqI64: return "v_cmp_eq_i64";
case Opcode::VCmpNeU64: return "v_cmp_ne_u64"; case Opcode::VCmpNeU64: return "v_cmp_ne_u64";
case Opcode::VCmpxLtU32: return "v_cmpx_lt_u32"; case Opcode::VCmpxLtU32: return "v_cmpx_lt_u32";
case Opcode::VCmpxEqU32: return "v_cmpx_eq_u32"; case Opcode::VCmpxEqU32: return "v_cmpx_eq_u32";
@@ -823,6 +835,8 @@ std::string OpcodeToString(Opcode opcode) {
case Opcode::BufferAtomicAnd: return "buffer_atomic_and"; case Opcode::BufferAtomicAnd: return "buffer_atomic_and";
case Opcode::BufferAtomicOr: return "buffer_atomic_or"; case Opcode::BufferAtomicOr: return "buffer_atomic_or";
case Opcode::BufferAtomicXor: return "buffer_atomic_xor"; case Opcode::BufferAtomicXor: return "buffer_atomic_xor";
case Opcode::BufferAtomicFMin: return "buffer_atomic_fmin";
case Opcode::BufferAtomicFMax: return "buffer_atomic_fmax";
case Opcode::FlatLoadUbyte: return "flat_load_ubyte"; case Opcode::FlatLoadUbyte: return "flat_load_ubyte";
case Opcode::FlatLoadSbyte: return "flat_load_sbyte"; case Opcode::FlatLoadSbyte: return "flat_load_sbyte";
case Opcode::FlatLoadUshort: return "flat_load_ushort"; case Opcode::FlatLoadUshort: return "flat_load_ushort";
@@ -983,6 +997,9 @@ std::string InstructionToString(const Instruction& inst) {
inst.pc, FamilyToString(inst.family).c_str(), inst.opcode_id, inst.pc, FamilyToString(inst.family).c_str(), inst.opcode_id,
RawWordsToString(inst).c_str(), inst.unsupported_reason.c_str()); RawWordsToString(inst).c_str(), inst.unsupported_reason.c_str());
} }
if (inst.family == Family::SOPC) {
return WithUnsupportedReason(inst, FormatSources(inst));
}
switch (inst.opcode) { switch (inst.opcode) {
case Opcode::SMovB32: case Opcode::SMovB32:
@@ -1110,6 +1127,8 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::BufferAtomicAnd: case Opcode::BufferAtomicAnd:
case Opcode::BufferAtomicOr: case Opcode::BufferAtomicOr:
case Opcode::BufferAtomicXor: case Opcode::BufferAtomicXor:
case Opcode::BufferAtomicFMin:
case Opcode::BufferAtomicFMax:
case Opcode::BufferLoadSbyte: case Opcode::BufferLoadSbyte:
case Opcode::BufferLoadSshort: case Opcode::BufferLoadSshort:
case Opcode::FlatLoadUbyte: case Opcode::FlatLoadUbyte:
@@ -1171,25 +1190,6 @@ std::string InstructionToString(const Instruction& inst) {
case Opcode::DsWriteB128: case Opcode::DsWriteB128:
case Opcode::DsWriteAddtidB32: case Opcode::DsWriteAddtidB32:
case Opcode::DsReadAddtidB32: return WithUnsupportedReason(inst, FormatMemory(inst)); case Opcode::DsReadAddtidB32: return WithUnsupportedReason(inst, FormatMemory(inst));
case Opcode::SCmpEqU32:
case Opcode::SCmpEqI32:
case Opcode::SCmpLgU32:
case Opcode::SCmpLgI32:
case Opcode::SCmpGtU32:
case Opcode::SCmpGtI32:
case Opcode::SCmpGeU32:
case Opcode::SCmpGeI32:
case Opcode::SCmpLtU32:
case Opcode::SCmpLtI32:
case Opcode::SCmpLeU32:
case Opcode::SCmpLeI32:
case Opcode::SBitcmp0B32:
case Opcode::SBitcmp1B32:
case Opcode::SCmpLgU64:
return WithUnsupportedReason(inst, fmt::format("0x{:08x}: {} {}, {}", inst.pc,
OpcodeToString(inst.opcode).c_str(),
OperandToString(inst.src0).c_str(),
OperandToString(inst.src1).c_str()));
default: return WithUnsupportedReason(inst, FormatBinary(inst)); default: return WithUnsupportedReason(inst, FormatBinary(inst));
} }
} }
@@ -118,6 +118,7 @@ enum class Opcode {
SCmpGeU32, SCmpGeU32,
SCmpLtU32, SCmpLtU32,
SCmpLeU32, SCmpLeU32,
SCmpEqU64,
SCmpLgU64, SCmpLgU64,
VNop, VNop,
@@ -374,6 +375,7 @@ enum class Opcode {
VCmpNeU32, VCmpNeU32,
VCmpGeU32, VCmpGeU32,
VCmpTU32, VCmpTU32,
VCmpEqI64,
VCmpNeU64, VCmpNeU64,
VCmpxLtU32, VCmpxLtU32,
VCmpxEqU32, VCmpxEqU32,
@@ -432,6 +434,8 @@ enum class Opcode {
BufferAtomicAnd, BufferAtomicAnd,
BufferAtomicOr, BufferAtomicOr,
BufferAtomicXor, BufferAtomicXor,
BufferAtomicFMin,
BufferAtomicFMax,
FlatLoadUbyte, FlatLoadUbyte,
FlatLoadSbyte, FlatLoadSbyte,
FlatLoadUshort, FlatLoadUshort,
@@ -1,43 +1,91 @@
#include "graphics/shader/recompiler/decompiler/VectorAluOps.h" #include "graphics/shader/recompiler/decompiler/VectorAluOps.h"
#include "graphics/shader/recompiler/decompiler/OpcodeTable.h"
#include <fmt/format.h> #include <fmt/format.h>
#include <iterator>
namespace Libs::Graphics::ShaderRecompiler::Decoder { namespace Libs::Graphics::ShaderRecompiler::Decoder {
namespace { namespace {
struct OpcodeMap { using Detail::OpcodeMap;
uint32_t opcode = 0;
Opcode ir = Opcode::Unknown; // These profiles describe the selectors that the current lowering implements. They are not
// architectural SDWA legality classes.
enum class Vop2SdwaProfile {
None,
Cndmask,
Float32,
Float16,
IntegerFullDestination,
IntegerPartialDestination,
ReverseLogicalRight,
ReverseLogicalLeft,
Bitwise,
Count,
}; };
constexpr OpcodeMap VOP2_OPS[] = { struct Vop2OpcodeInfo {
{0x01u, Opcode::VCndmaskB32}, {0x02u, Opcode::VDot2cF32F16}, uint32_t encoding = 0;
{0x03u, Opcode::VAddF32}, {0x04u, Opcode::VSubF32}, Opcode decoded = Opcode::Unknown;
{0x05u, Opcode::VSubrevF32}, {0x08u, Opcode::VMulF32}, Vop2SdwaProfile sdwa_profile = Vop2SdwaProfile::None;
{0x09u, Opcode::VMulI32I24}, {0x0bu, Opcode::VMulU32U24},
{0x0fu, Opcode::VMinF32}, {0x10u, Opcode::VMaxF32},
{0x11u, Opcode::VMinI32}, {0x12u, Opcode::VMaxI32},
{0x13u, Opcode::VMinU32}, {0x14u, Opcode::VMaxU32},
{0x15u, Opcode::VLshrB32}, {0x16u, Opcode::VLshrrevB32},
{0x17u, Opcode::VAshrI32}, {0x18u, Opcode::VAshrrevI32},
{0x19u, Opcode::VLshlB32}, {0x1au, Opcode::VLshlrevB32},
{0x1bu, Opcode::VAndB32}, {0x1cu, Opcode::VOrB32},
{0x1du, Opcode::VXorB32}, {0x1eu, Opcode::VXnorB32},
{0x1fu, Opcode::VMacF32}, {0x20u, Opcode::VMadmkF32},
{0x21u, Opcode::VMadakF32}, {0x22u, Opcode::VBcntU32B32},
{0x23u, Opcode::VMbcntLoU32B32}, {0x24u, Opcode::VMbcntHiU32B32},
{0x25u, Opcode::VAddNcU32}, {0x28u, Opcode::VAddcU32},
{0x26u, Opcode::VSubNcU32}, {0x27u, Opcode::VSubrevNcU32},
{0x2bu, Opcode::VMacF32}, {0x2cu, Opcode::VMadmkF32},
{0x2du, Opcode::VMadakF32}, {0x2fu, Opcode::VCvtPkrtzF16F32},
{0x32u, Opcode::VAddF16}, {0x33u, Opcode::VSubF16},
{0x34u, Opcode::VSubrevF16}, {0x35u, Opcode::VMulF16},
{0x36u, Opcode::VFmacF16}, {0x37u, Opcode::VFmamkF16},
{0x38u, Opcode::VFmaakF16}, {0x39u, Opcode::VMaxF16},
{0x3au, Opcode::VMinF16}, {0x3cu, Opcode::VPkFmacF16},
}; };
constexpr Vop2OpcodeInfo VOP2_OPS[] = {
{0x01u, Opcode::VCndmaskB32, Vop2SdwaProfile::Cndmask},
{0x02u, Opcode::VDot2cF32F16},
{0x03u, Opcode::VAddF32, Vop2SdwaProfile::Float32},
{0x04u, Opcode::VSubF32, Vop2SdwaProfile::Float32},
{0x05u, Opcode::VSubrevF32},
{0x08u, Opcode::VMulF32, Vop2SdwaProfile::Float32},
{0x09u, Opcode::VMulI32I24, Vop2SdwaProfile::IntegerFullDestination},
{0x0bu, Opcode::VMulU32U24, Vop2SdwaProfile::IntegerFullDestination},
{0x0fu, Opcode::VMinF32},
{0x10u, Opcode::VMaxF32},
{0x11u, Opcode::VMinI32},
{0x12u, Opcode::VMaxI32},
{0x13u, Opcode::VMinU32, Vop2SdwaProfile::IntegerPartialDestination},
{0x14u, Opcode::VMaxU32, Vop2SdwaProfile::IntegerFullDestination},
{0x15u, Opcode::VLshrB32},
{0x16u, Opcode::VLshrrevB32, Vop2SdwaProfile::ReverseLogicalRight},
{0x17u, Opcode::VAshrI32},
{0x18u, Opcode::VAshrrevI32},
{0x19u, Opcode::VLshlB32},
{0x1au, Opcode::VLshlrevB32, Vop2SdwaProfile::ReverseLogicalLeft},
{0x1bu, Opcode::VAndB32, Vop2SdwaProfile::Bitwise},
{0x1cu, Opcode::VOrB32, Vop2SdwaProfile::Bitwise},
{0x1du, Opcode::VXorB32, Vop2SdwaProfile::Bitwise},
{0x1eu, Opcode::VXnorB32, Vop2SdwaProfile::Bitwise},
{0x1fu, Opcode::VMacF32},
{0x20u, Opcode::VMadmkF32},
{0x21u, Opcode::VMadakF32},
{0x22u, Opcode::VBcntU32B32},
{0x23u, Opcode::VMbcntLoU32B32},
{0x24u, Opcode::VMbcntHiU32B32},
{0x25u, Opcode::VAddNcU32, Vop2SdwaProfile::IntegerFullDestination},
{0x28u, Opcode::VAddcU32},
{0x26u, Opcode::VSubNcU32, Vop2SdwaProfile::IntegerPartialDestination},
{0x27u, Opcode::VSubrevNcU32, Vop2SdwaProfile::IntegerFullDestination},
{0x2bu, Opcode::VMacF32},
{0x2cu, Opcode::VMadmkF32},
{0x2du, Opcode::VMadakF32},
{0x2fu, Opcode::VCvtPkrtzF16F32},
{0x32u, Opcode::VAddF16, Vop2SdwaProfile::Float16},
{0x33u, Opcode::VSubF16, Vop2SdwaProfile::Float16},
{0x34u, Opcode::VSubrevF16, Vop2SdwaProfile::Float16},
{0x35u, Opcode::VMulF16, Vop2SdwaProfile::Float16},
{0x36u, Opcode::VFmacF16},
{0x37u, Opcode::VFmamkF16},
{0x38u, Opcode::VFmaakF16},
{0x39u, Opcode::VMaxF16, Vop2SdwaProfile::Float16},
{0x3au, Opcode::VMinF16, Vop2SdwaProfile::Float16},
{0x3cu, Opcode::VPkFmacF16},
};
static_assert(Detail::HasUniqueEncodings(VOP2_OPS));
constexpr Opcode LookupVop2Opcode(uint32_t encoding) {
return Detail::LookupOpcode(VOP2_OPS, encoding);
}
constexpr OpcodeMap VOP1_OPS[] = { constexpr OpcodeMap VOP1_OPS[] = {
{0x00u, Opcode::VNop}, {0x00u, Opcode::VNop},
{0x01u, Opcode::VMovB32}, {0x01u, Opcode::VMovB32},
@@ -135,37 +183,59 @@ constexpr OpcodeMap VOP3_ENCODED_VOP1_OPS[] = {
{0x5eu, Opcode::VRndneF16}, {0x5eu, Opcode::VRndneF16},
}; };
constexpr OpcodeMap VOPC_OPS[] = { struct VopcOpcodeInfo {
{0x00u, Opcode::VCmpFF32}, {0x01u, Opcode::VCmpLtF32}, {0x02u, Opcode::VCmpEqF32}, uint32_t encoding = 0;
{0x03u, Opcode::VCmpLeF32}, {0x04u, Opcode::VCmpGtF32}, {0x05u, Opcode::VCmpLgF32}, Opcode decoded = Opcode::Unknown;
{0x06u, Opcode::VCmpGeF32}, {0x07u, Opcode::VCmpOF32}, {0x08u, Opcode::VCmpUF32}, bool supports_dpp = true;
{0x09u, Opcode::VCmpNgeF32}, {0x0au, Opcode::VCmpNlgF32}, {0x0bu, Opcode::VCmpNgtF32},
{0x0cu, Opcode::VCmpNleF32}, {0x0du, Opcode::VCmpNeqF32}, {0x0eu, Opcode::VCmpNltF32},
{0x0fu, Opcode::VCmpTruF32}, {0x11u, Opcode::VCmpxLtF32}, {0x12u, Opcode::VCmpxEqF32},
{0x13u, Opcode::VCmpxLeF32}, {0x14u, Opcode::VCmpxGtF32}, {0x15u, Opcode::VCmpxLgF32},
{0x16u, Opcode::VCmpxGeF32}, {0x19u, Opcode::VCmpxNgeF32}, {0x1au, Opcode::VCmpxNlgF32},
{0x1bu, Opcode::VCmpxNgtF32}, {0x1cu, Opcode::VCmpxNleF32}, {0x1du, Opcode::VCmpxNeqF32},
{0x1eu, Opcode::VCmpxNltF32}, {0x80u, Opcode::VCmpFI32}, {0x81u, Opcode::VCmpLtI32},
{0x82u, Opcode::VCmpEqI32}, {0x83u, Opcode::VCmpLeI32}, {0x84u, Opcode::VCmpGtI32},
{0x85u, Opcode::VCmpNeI32}, {0x86u, Opcode::VCmpGeI32}, {0x87u, Opcode::VCmpTI32},
{0x88u, Opcode::VCmpClassF32}, {0x89u, Opcode::VCmpLtI16}, {0x8au, Opcode::VCmpEqI16},
{0x8bu, Opcode::VCmpLeI16}, {0x8cu, Opcode::VCmpGtI16}, {0x8du, Opcode::VCmpNeI16},
{0x8eu, Opcode::VCmpGeI16}, {0x91u, Opcode::VCmpxLtI32}, {0x92u, Opcode::VCmpxEqI32},
{0x93u, Opcode::VCmpxLeI32}, {0x94u, Opcode::VCmpxGtI32}, {0x95u, Opcode::VCmpxNeI32},
{0x96u, Opcode::VCmpxGeI32}, {0xa9u, Opcode::VCmpLtU16}, {0xaau, Opcode::VCmpEqU16},
{0xabu, Opcode::VCmpLeU16}, {0xacu, Opcode::VCmpGtU16}, {0xadu, Opcode::VCmpNeU16},
{0xaeu, Opcode::VCmpGeU16}, {0xc0u, Opcode::VCmpFU32}, {0xc1u, Opcode::VCmpLtU32},
{0xc2u, Opcode::VCmpEqU32}, {0xc3u, Opcode::VCmpLeU32}, {0xc4u, Opcode::VCmpGtU32},
{0xc5u, Opcode::VCmpNeU32}, {0xc6u, Opcode::VCmpGeU32}, {0xc7u, Opcode::VCmpTU32},
{0xd1u, Opcode::VCmpxLtU32}, {0xd2u, Opcode::VCmpxEqU32}, {0xd3u, Opcode::VCmpxLeU32},
{0xd4u, Opcode::VCmpxGtU32}, {0xd5u, Opcode::VCmpxNeU32}, {0xd6u, Opcode::VCmpxGeU32},
{0xe5u, Opcode::VCmpNeU64}, {0xc9u, Opcode::VCmpLtF16}, {0xcau, Opcode::VCmpEqF16},
{0xcbu, Opcode::VCmpLeF16}, {0xccu, Opcode::VCmpGtF16}, {0xcdu, Opcode::VCmpLgF16},
{0xceu, Opcode::VCmpGeF16}, {0xedu, Opcode::VCmpNeqF16}, {0xd9u, Opcode::VCmpxLtF16},
{0xdau, Opcode::VCmpxEqF16}, {0xdbu, Opcode::VCmpxLeF16}, {0xdcu, Opcode::VCmpxGtF16},
{0xdeu, Opcode::VCmpxGeF16}, {0xfdu, Opcode::VCmpxNeqF16}, {0xfeu, Opcode::VCmpxNltF16},
}; };
constexpr VopcOpcodeInfo VOPC_OPS[] = {
{0x00u, Opcode::VCmpFF32}, {0x01u, Opcode::VCmpLtF32},
{0x02u, Opcode::VCmpEqF32}, {0x03u, Opcode::VCmpLeF32},
{0x04u, Opcode::VCmpGtF32}, {0x05u, Opcode::VCmpLgF32},
{0x06u, Opcode::VCmpGeF32}, {0x07u, Opcode::VCmpOF32},
{0x08u, Opcode::VCmpUF32}, {0x09u, Opcode::VCmpNgeF32},
{0x0au, Opcode::VCmpNlgF32}, {0x0bu, Opcode::VCmpNgtF32},
{0x0cu, Opcode::VCmpNleF32}, {0x0du, Opcode::VCmpNeqF32},
{0x0eu, Opcode::VCmpNltF32}, {0x0fu, Opcode::VCmpTruF32},
{0x11u, Opcode::VCmpxLtF32}, {0x12u, Opcode::VCmpxEqF32},
{0x13u, Opcode::VCmpxLeF32}, {0x14u, Opcode::VCmpxGtF32},
{0x15u, Opcode::VCmpxLgF32}, {0x16u, Opcode::VCmpxGeF32},
{0x19u, Opcode::VCmpxNgeF32}, {0x1au, Opcode::VCmpxNlgF32},
{0x1bu, Opcode::VCmpxNgtF32}, {0x1cu, Opcode::VCmpxNleF32},
{0x1du, Opcode::VCmpxNeqF32}, {0x1eu, Opcode::VCmpxNltF32},
{0x80u, Opcode::VCmpFI32}, {0x81u, Opcode::VCmpLtI32},
{0x82u, Opcode::VCmpEqI32}, {0x83u, Opcode::VCmpLeI32},
{0x84u, Opcode::VCmpGtI32}, {0x85u, Opcode::VCmpNeI32},
{0x86u, Opcode::VCmpGeI32}, {0x87u, Opcode::VCmpTI32},
{0x88u, Opcode::VCmpClassF32}, {0x89u, Opcode::VCmpLtI16},
{0x8au, Opcode::VCmpEqI16}, {0x8bu, Opcode::VCmpLeI16},
{0x8cu, Opcode::VCmpGtI16}, {0x8du, Opcode::VCmpNeI16},
{0x8eu, Opcode::VCmpGeI16}, {0x91u, Opcode::VCmpxLtI32},
{0x92u, Opcode::VCmpxEqI32}, {0x93u, Opcode::VCmpxLeI32},
{0x94u, Opcode::VCmpxGtI32}, {0x95u, Opcode::VCmpxNeI32},
{0x96u, Opcode::VCmpxGeI32}, {0xa9u, Opcode::VCmpLtU16},
{0xaau, Opcode::VCmpEqU16}, {0xabu, Opcode::VCmpLeU16},
{0xacu, Opcode::VCmpGtU16}, {0xadu, Opcode::VCmpNeU16},
{0xaeu, Opcode::VCmpGeU16}, {0xc0u, Opcode::VCmpFU32},
{0xc1u, Opcode::VCmpLtU32}, {0xc2u, Opcode::VCmpEqU32},
{0xc3u, Opcode::VCmpLeU32}, {0xc4u, Opcode::VCmpGtU32},
{0xc5u, Opcode::VCmpNeU32}, {0xc6u, Opcode::VCmpGeU32},
{0xc7u, Opcode::VCmpTU32}, {0xa2u, Opcode::VCmpEqI64, false},
{0xd1u, Opcode::VCmpxLtU32}, {0xd2u, Opcode::VCmpxEqU32},
{0xd3u, Opcode::VCmpxLeU32}, {0xd4u, Opcode::VCmpxGtU32},
{0xd5u, Opcode::VCmpxNeU32}, {0xd6u, Opcode::VCmpxGeU32},
{0xe5u, Opcode::VCmpNeU64, false}, {0xc9u, Opcode::VCmpLtF16},
{0xcau, Opcode::VCmpEqF16}, {0xcbu, Opcode::VCmpLeF16},
{0xccu, Opcode::VCmpGtF16}, {0xcdu, Opcode::VCmpLgF16},
{0xceu, Opcode::VCmpGeF16}, {0xedu, Opcode::VCmpNeqF16},
{0xd9u, Opcode::VCmpxLtF16}, {0xdau, Opcode::VCmpxEqF16},
{0xdbu, Opcode::VCmpxLeF16}, {0xdcu, Opcode::VCmpxGtF16},
{0xdeu, Opcode::VCmpxGeF16}, {0xfdu, Opcode::VCmpxNeqF16},
{0xfeu, Opcode::VCmpxNltF16},
};
static_assert(Detail::HasUniqueEncodings(VOPC_OPS));
constexpr OpcodeMap VOP3_OPS[] = { constexpr OpcodeMap VOP3_OPS[] = {
{0x141u, Opcode::VMadF32}, {0x142u, Opcode::VMadI32I24}, {0x141u, Opcode::VMadF32}, {0x142u, Opcode::VMadI32I24},
{0x143u, Opcode::VMadU32U24}, {0x176u, Opcode::VMadU64U32}, {0x143u, Opcode::VMadU32U24}, {0x176u, Opcode::VMadU64U32},
@@ -214,16 +284,6 @@ constexpr OpcodeMap VOP3P_OPS[] = {
{0x22u, Opcode::VMadMixhiF16}, {0x22u, Opcode::VMadMixhiF16},
}; };
Opcode Lookup(const OpcodeMap* ops, uint32_t count, uint32_t opcode) {
for (uint32_t i = 0; i < count; i++) {
const auto& op = ops[i];
if (op.opcode == opcode) {
return op.ir;
}
}
return Opcode::Unsupported;
}
bool IsVop2LiteralMadOpcode(uint32_t opcode) { bool IsVop2LiteralMadOpcode(uint32_t opcode) {
return opcode == 0x20u || opcode == 0x21u || opcode == 0x2cu || opcode == 0x2du; return opcode == 0x20u || opcode == 0x21u || opcode == 0x2cu || opcode == 0x2du;
} }
@@ -234,24 +294,23 @@ bool IsUnsupportedVop3EncodedVop2Alias(uint32_t opcode) {
} }
Opcode LookupVop3Opcode(uint32_t opcode) { Opcode LookupVop3Opcode(uint32_t opcode) {
const auto direct = Lookup(VOP3_OPS, static_cast<uint32_t>(std::size(VOP3_OPS)), opcode); const auto direct = Detail::LookupOpcode(VOP3_OPS, opcode);
if (direct != Opcode::Unsupported) { if (direct != Opcode::Unsupported) {
return direct; return direct;
} }
if (opcode <= 0xffu) { if (opcode <= 0xffu) {
return Lookup(VOPC_OPS, static_cast<uint32_t>(std::size(VOPC_OPS)), opcode); return Detail::LookupOpcode(VOPC_OPS, opcode);
} }
if (opcode >= 0x100u && opcode <= 0x13fu) { if (opcode >= 0x100u && opcode <= 0x13fu) {
if (IsUnsupportedVop3EncodedVop2Alias(opcode - 0x100u)) { if (IsUnsupportedVop3EncodedVop2Alias(opcode - 0x100u)) {
return Opcode::Unsupported; return Opcode::Unsupported;
} }
return Lookup(VOP2_OPS, static_cast<uint32_t>(std::size(VOP2_OPS)), opcode - 0x100u); return LookupVop2Opcode(opcode - 0x100u);
} }
if (opcode >= 0x180u && opcode <= 0x1ffu) { if (opcode >= 0x180u && opcode <= 0x1ffu) {
return Lookup(VOP3_ENCODED_VOP1_OPS, return Detail::LookupOpcode(VOP3_ENCODED_VOP1_OPS, opcode - 0x180u);
static_cast<uint32_t>(std::size(VOP3_ENCODED_VOP1_OPS)), opcode - 0x180u);
} }
return Lookup(VOP3_OPS, static_cast<uint32_t>(std::size(VOP3_OPS)), opcode); return Detail::LookupOpcode(VOP3_OPS, opcode);
} }
bool IsVop3EncodedVopc(uint32_t opcode) { bool IsVop3EncodedVopc(uint32_t opcode) {
@@ -791,7 +850,6 @@ Vop2SdwaFields DecodeVop2SdwaFields(uint32_t modifier) {
} }
struct Vop2SdwaRule { struct Vop2SdwaRule {
Opcode opcode = Opcode::Unknown;
uint32_t dst_selectors = SdwaSelFull(); uint32_t dst_selectors = SdwaSelFull();
uint32_t src0_selectors = SdwaSelFull(); uint32_t src0_selectors = SdwaSelFull();
uint32_t src1_selectors = SdwaSelFull(); uint32_t src1_selectors = SdwaSelFull();
@@ -804,46 +862,27 @@ constexpr uint32_t SdwaSelAll() {
} }
constexpr Vop2SdwaRule VOP2_SDWA_RULES[] = { constexpr Vop2SdwaRule VOP2_SDWA_RULES[] = {
{Opcode::VCndmaskB32, SdwaSelWords(), SdwaSelWords() | SdwaSelFull(), {},
SdwaSelWords() | SdwaSelFull(), true, false}, {SdwaSelWords() | SdwaSelFull(), SdwaSelWords() | SdwaSelFull(), SdwaSelWords() | SdwaSelFull(),
{Opcode::VAddF32, SdwaSelFull(), SdwaSelFull(), SdwaSelFull(), false, true}, true, false},
{Opcode::VSubF32, SdwaSelFull(), SdwaSelFull(), SdwaSelFull(), false, true}, {SdwaSelFull(), SdwaSelFull(), SdwaSelFull(), false, true},
{Opcode::VMulF32, SdwaSelFull(), SdwaSelFull(), SdwaSelFull(), false, true}, {SdwaSelWords() | SdwaSelFull(), SdwaSelWords() | SdwaSelFull(), SdwaSelWords() | SdwaSelFull(),
{Opcode::VAddF16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords() | SdwaSelFull(), true, true},
SdwaSelWords() | SdwaSelFull(), true, true}, {SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
{Opcode::VSubF16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords() | SdwaSelFull(), {SdwaSelAll(), SdwaSelAll(), SdwaSelAll(), true, false},
SdwaSelWords() | SdwaSelFull(), true, true}, {SdwaSelFull(), SdwaSelFull(), SdwaSelWords() | SdwaSelFull(), false, false},
{Opcode::VSubrevF16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords() | SdwaSelFull(), {SdwaSelFull(), SdwaSelFull(), SdwaSelAll(), false, false},
SdwaSelWords() | SdwaSelFull(), true, true}, {SdwaSelWords() | SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), true, false},
{Opcode::VMulF16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords() | SdwaSelFull(),
SdwaSelWords() | SdwaSelFull(), true, true},
{Opcode::VMaxF16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords() | SdwaSelFull(),
SdwaSelWords() | SdwaSelFull(), true, true},
{Opcode::VMinF16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords() | SdwaSelFull(),
SdwaSelWords() | SdwaSelFull(), true, true},
{Opcode::VMulI32I24, SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
{Opcode::VMulU32U24, SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
{Opcode::VMinU32, SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
{Opcode::VMaxU32, SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
{Opcode::VLshrrevB32, SdwaSelFull(), SdwaSelFull(), SdwaSelWords() | SdwaSelFull(), false,
false},
{Opcode::VLshlrevB32, SdwaSelFull(), SdwaSelFull(), SdwaSelAll(), false, false},
{Opcode::VAndB32, SdwaSelWords() | SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), true, false},
{Opcode::VOrB32, SdwaSelWords() | SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), true, false},
{Opcode::VXorB32, SdwaSelWords() | SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), true, false},
{Opcode::VXnorB32, SdwaSelWords() | SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), true, false},
{Opcode::VAddNcU32, SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
{Opcode::VSubNcU32, SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
{Opcode::VSubrevNcU32, SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false},
}; };
static_assert(sizeof(VOP2_SDWA_RULES) / sizeof(VOP2_SDWA_RULES[0]) ==
static_cast<size_t>(Vop2SdwaProfile::Count));
const Vop2SdwaRule* FindVop2SdwaRule(Opcode opcode) { const Vop2SdwaRule* FindVop2SdwaRule(uint32_t encoding) {
for (const auto& rule: VOP2_SDWA_RULES) { const auto* info = Detail::FindOpcode(VOP2_OPS, encoding);
if (rule.opcode == opcode) { if (info == nullptr || info->sdwa_profile == Vop2SdwaProfile::None) {
return &rule; return nullptr;
}
} }
return nullptr; return &VOP2_SDWA_RULES[static_cast<size_t>(info->sdwa_profile)];
} }
bool IsVop2SdwaDestinationSupported(const Vop2SdwaRule& rule, const Vop2SdwaFields& fields) { bool IsVop2SdwaDestinationSupported(const Vop2SdwaRule& rule, const Vop2SdwaFields& fields) {
@@ -881,7 +920,7 @@ bool ValidateVop2Sdwa(Instruction& inst, uint32_t opcode, const Vop2SdwaFields&
return true; return true;
} }
const auto* rule = FindVop2SdwaRule(inst.opcode); const auto* rule = FindVop2SdwaRule(opcode);
if (rule == nullptr) { if (rule == nullptr) {
SetUnsupported(inst, Family::VOP2, opcode, SetUnsupported(inst, Family::VOP2, opcode,
"VOP2 SDWA modifier is not supported for opcode"); "VOP2 SDWA modifier is not supported for opcode");
@@ -1135,6 +1174,11 @@ bool DecodeVopcDpp(uint32_t pc, std::span<const uint32_t> code, uint32_t word_in
SetUnsupported(inst, Family::VOPC, opcode, "VOPC opcode is not implemented"); SetUnsupported(inst, Family::VOPC, opcode, "VOPC opcode is not implemented");
return true; return true;
} }
const auto* info = Detail::FindOpcode(VOPC_OPS, opcode);
if (info == nullptr || !info->supports_dpp) {
SetUnsupported(inst, Family::VOPC, opcode, "VOPC DPP modifier is not supported for opcode");
return true;
}
if (!DecodeScalarSource(src0 + 256u, pc, inst.src0, error) || if (!DecodeScalarSource(src0 + 256u, pc, inst.src0, error) ||
!DecodeVectorGpr(vsrc1, inst.src1, error)) { !DecodeVectorGpr(vsrc1, inst.src1, error)) {
return false; return false;
@@ -1427,7 +1471,7 @@ bool DecodeVop2(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
inst.word = word; inst.word = word;
inst.family = Family::VOP2; inst.family = Family::VOP2;
inst.opcode_id = opcode; inst.opcode_id = opcode;
inst.opcode = Lookup(VOP2_OPS, static_cast<uint32_t>(std::size(VOP2_OPS)), opcode); inst.opcode = LookupVop2Opcode(opcode);
SetRawWords(inst, code, word_index, 1); SetRawWords(inst, code, word_index, 1);
switch (opcode) { switch (opcode) {
@@ -1468,7 +1512,7 @@ bool DecodeVop1(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
inst.word = word; inst.word = word;
inst.family = Family::VOP1; inst.family = Family::VOP1;
inst.opcode_id = opcode; inst.opcode_id = opcode;
inst.opcode = Lookup(VOP1_OPS, static_cast<uint32_t>(std::size(VOP1_OPS)), opcode); inst.opcode = Detail::LookupOpcode(VOP1_OPS, opcode);
SetRawWords(inst, code, word_index, 1); SetRawWords(inst, code, word_index, 1);
if (inst.opcode == Opcode::Unsupported) { if (inst.opcode == Opcode::Unsupported) {
@@ -1509,7 +1553,7 @@ bool DecodeVopc(uint32_t pc, std::span<const uint32_t> code, uint32_t word_index
inst.word = word; inst.word = word;
inst.family = Family::VOPC; inst.family = Family::VOPC;
inst.opcode_id = opcode; inst.opcode_id = opcode;
inst.opcode = Lookup(VOPC_OPS, static_cast<uint32_t>(std::size(VOPC_OPS)), opcode); inst.opcode = Detail::LookupOpcode(VOPC_OPS, opcode);
inst.dst.kind = IsVopcCompareExec(inst.opcode) ? OperandKind::ExecLo : OperandKind::VccLo; inst.dst.kind = IsVopcCompareExec(inst.opcode) ? OperandKind::ExecLo : OperandKind::VccLo;
SetRawWords(inst, code, word_index, 1); SetRawWords(inst, code, word_index, 1);
@@ -1723,7 +1767,7 @@ bool DecodeVop3p(uint32_t pc, std::span<const uint32_t> code, uint32_t word_inde
inst.word = word0; inst.word = word0;
inst.family = Family::VOP3P; inst.family = Family::VOP3P;
inst.opcode_id = opcode; inst.opcode_id = opcode;
inst.opcode = Lookup(VOP3P_OPS, static_cast<uint32_t>(std::size(VOP3P_OPS)), opcode); inst.opcode = Detail::LookupOpcode(VOP3P_OPS, opcode);
SetRawWords(inst, code, word_index, 2); SetRawWords(inst, code, word_index, 2);
if (inst.opcode == Opcode::Unsupported) { if (inst.opcode == Opcode::Unsupported) {
@@ -131,7 +131,9 @@ bool IsAtomic(IR::Opcode op) {
case IR::Opcode::AtomicUMaxU32: case IR::Opcode::AtomicUMaxU32:
case IR::Opcode::AtomicAndU32: case IR::Opcode::AtomicAndU32:
case IR::Opcode::AtomicOrU32: case IR::Opcode::AtomicOrU32:
case IR::Opcode::AtomicXorU32: return true; case IR::Opcode::AtomicXorU32:
case IR::Opcode::AtomicFMinF32:
case IR::Opcode::AtomicFMaxF32: return true;
default: return false; default: return false;
} }
} }
@@ -141,6 +143,9 @@ uint32_t BufferAddressOperandCount(const IR::Instruction& inst) {
} }
bool ValidateInstructionContract(const IR::Instruction& inst, std::string* error) { bool ValidateInstructionContract(const IR::Instruction& inst, std::string* error) {
if (static_cast<size_t>(inst.op) >= static_cast<size_t>(IR::Opcode::Count)) {
return Fail(error, "IR instruction opcode is out of range");
}
if (inst.src_count > std::size(inst.src)) { if (inst.src_count > std::size(inst.src)) {
return Fail(error, "instruction source count exceeds fixed IR storage"); return Fail(error, "instruction source count exceeds fixed IR storage");
} }
@@ -200,6 +205,12 @@ bool ValidateInstructionContract(const IR::Instruction& inst, std::string* error
inst.src_count != 2)) { inst.src_count != 2)) {
return Fail(error, "storage image instruction has an invalid resource class"); return Fail(error, "storage image instruction has an invalid resource class");
} }
const auto float_atomic =
inst.op == IR::Opcode::AtomicFMinF32 || inst.op == IR::Opcode::AtomicFMaxF32;
if (float_atomic &&
(kind != IR::ResourceKind::Buffer || inst.src_count != buffer_address_count + 1u)) {
return Fail(error, "floating-point atomic instruction must target a buffer");
}
if (IsAtomic(inst.op) && if (IsAtomic(inst.op) &&
!((kind == IR::ResourceKind::Buffer && inst.src_count == buffer_address_count + 1u) || !((kind == IR::ResourceKind::Buffer && inst.src_count == buffer_address_count + 1u) ||
((kind == IR::ResourceKind::Lds || kind == IR::ResourceKind::Gds) && ((kind == IR::ResourceKind::Lds || kind == IR::ResourceKind::Gds) &&
@@ -389,7 +400,7 @@ bool ProgramRequiresExactSubgroupSize(const IR::Program& program) {
if (Emitter::InstructionHasDppSource(inst)) { if (Emitter::InstructionHasDppSource(inst)) {
return true; return true;
} }
if (Emitter::IsCompareOpcode(inst.op) && !Emitter::IsSccOperand(inst.dst)) { if (IR::IsCompareOpcode(inst.op) && !Emitter::IsSccOperand(inst.dst)) {
return true; return true;
} }
if ((IsExecDestination(inst.dst) || IsExecDestination(inst.dst2)) && if ((IsExecDestination(inst.dst) || IsExecDestination(inst.dst2)) &&
@@ -488,6 +499,13 @@ bool EmitProgram(const IR::Program& program, const IR::ResourceSnapshot& resourc
AllocateBlockLabels(state, program); AllocateBlockLabels(state, program);
AllocateDispatcherState(state, program); AllocateDispatcherState(state, program);
EmitFunction(state, program); EmitFunction(state, program);
if (state.unsupported_ir_instruction) {
char pc[9] {};
std::snprintf(pc, sizeof(pc), "%08x", state.unsupported_ir_pc);
return Fail(error, "SPIR-V emitter has no handler for IR opcode " +
std::string(IR::OpcodeName(state.unsupported_ir_opcode)) +
" at pc 0x" + pc);
}
ReportReserveExceeded(program, "registers", state.registers.size()); ReportReserveExceeded(program, "registers", state.registers.size());
ReportReserveExceeded(program, "inputs", state.inputs.size()); ReportReserveExceeded(program, "inputs", state.inputs.size());
@@ -1486,17 +1486,20 @@ void EmitBitCompareB32(EmitterState& state, const IR::Instruction& inst, bool bi
EmitStoreU32(state, inst.dst, ret); EmitStoreU32(state, inst.dst, ret);
} }
void EmitCompareNeU64(EmitterState& state, const IR::Instruction& inst) { void EmitCompareU64(EmitterState& state, const IR::Instruction& inst) {
const auto lhs_low = EmitSequentialValueLoad(state, inst.src[0], 0); const bool equal = inst.op == IR::Opcode::CompareEqU64;
const auto lhs_high = EmitSequentialValueLoad(state, inst.src[0], 1); const auto lhs_low = EmitSequentialValueLoad(state, inst.src[0], 0);
const auto rhs_low = EmitSequentialValueLoad(state, inst.src[1], 0); const auto lhs_high = EmitSequentialValueLoad(state, inst.src[0], 1);
const auto rhs_high = EmitSequentialValueLoad(state, inst.src[1], 1); const auto rhs_low = EmitSequentialValueLoad(state, inst.src[1], 0);
const auto ne_low = state.builder.AllocateId(); const auto rhs_high = EmitSequentialValueLoad(state, inst.src[1], 1);
const auto ne_high = state.builder.AllocateId(); const auto low = state.builder.AllocateId();
const auto cond = state.builder.AllocateId(); const auto high = state.builder.AllocateId();
state.builder.AddFunction({OpINotEqual, state.bool_type, ne_low, lhs_low, rhs_low}); const auto cond = state.builder.AllocateId();
state.builder.AddFunction({OpINotEqual, state.bool_type, ne_high, lhs_high, rhs_high}); const auto compare_op = equal ? OpIEqual : OpINotEqual;
state.builder.AddFunction({OpLogicalOr, state.bool_type, cond, ne_low, ne_high}); state.builder.AddFunction({compare_op, state.bool_type, low, lhs_low, rhs_low});
state.builder.AddFunction({compare_op, state.bool_type, high, lhs_high, rhs_high});
state.builder.AddFunction(
{equal ? OpLogicalAnd : OpLogicalOr, state.bool_type, cond, low, high});
EmitCompareResult(state, inst.dst, cond); EmitCompareResult(state, inst.dst, cond);
} }
@@ -75,8 +75,6 @@ bool IsSccOperand(const IR::Operand& operand) {
return operand.kind == IR::OperandKind::Register && operand.reg.file == IR::RegisterFile::Scc; return operand.kind == IR::OperandKind::Register && operand.reg.file == IR::RegisterFile::Scc;
} }
bool IsCompareOpcode(IR::Opcode op);
void CollectMaskStateRegisters(std::vector<RegisterBinding>& registers) { void CollectMaskStateRegisters(std::vector<RegisterBinding>& registers) {
CollectRegister(registers, {IR::RegisterFile::Exec, 0}); CollectRegister(registers, {IR::RegisterFile::Exec, 0});
CollectRegister(registers, {IR::RegisterFile::Exec, 1}); CollectRegister(registers, {IR::RegisterFile::Exec, 1});
@@ -189,11 +187,11 @@ void CollectRegisters(const IR::Program& program, std::vector<RegisterBinding>&
if (inst.op == IR::Opcode::BitCountU64 || inst.op == IR::Opcode::FindMsbFromHighU64) { if (inst.op == IR::Opcode::BitCountU64 || inst.op == IR::Opcode::FindMsbFromHighU64) {
CollectSequentialRegisters(registers, inst.src[0], 2); CollectSequentialRegisters(registers, inst.src[0], 2);
} }
if (inst.op == IR::Opcode::CompareNeU64) { if (IR::IsCompare64Opcode(inst.op)) {
CollectSequentialRegisters(registers, inst.src[0], 2); CollectSequentialRegisters(registers, inst.src[0], 2);
CollectSequentialRegisters(registers, inst.src[1], 2); CollectSequentialRegisters(registers, inst.src[1], 2);
} }
if (IsCompareOpcode(inst.op) && inst.dst.kind == IR::OperandKind::Register && if (IR::IsCompareOpcode(inst.op) && inst.dst.kind == IR::OperandKind::Register &&
inst.dst.reg.file != IR::RegisterFile::Scc) { inst.dst.reg.file != IR::RegisterFile::Scc) {
CollectSequentialRegisters(registers, inst.dst, 2); CollectSequentialRegisters(registers, inst.dst, 2);
} }
@@ -384,93 +382,6 @@ bool ProgramNeedsSubgroupShuffle(const IR::Program& program) {
return false; return false;
} }
bool IsCompareOpcode(IR::Opcode op) {
switch (op) {
case IR::Opcode::CompareFalse:
case IR::Opcode::CompareTrue:
case IR::Opcode::CompareEqU32:
case IR::Opcode::CompareNeU32:
case IR::Opcode::CompareGtU32:
case IR::Opcode::CompareGeU32:
case IR::Opcode::CompareLtU32:
case IR::Opcode::CompareLeU32:
case IR::Opcode::CompareNeU64:
case IR::Opcode::CompareMaskEqU32:
case IR::Opcode::CompareMaskNeU32:
case IR::Opcode::CompareMaskGtU32:
case IR::Opcode::CompareMaskGeU32:
case IR::Opcode::CompareMaskLtU32:
case IR::Opcode::CompareMaskLeU32:
case IR::Opcode::CompareEqI32:
case IR::Opcode::CompareNeI32:
case IR::Opcode::CompareGtI32:
case IR::Opcode::CompareGeI32:
case IR::Opcode::CompareLtI32:
case IR::Opcode::CompareLeI32:
case IR::Opcode::CompareEqI16:
case IR::Opcode::CompareNeI16:
case IR::Opcode::CompareGtI16:
case IR::Opcode::CompareGeI16:
case IR::Opcode::CompareLtI16:
case IR::Opcode::CompareLeI16:
case IR::Opcode::CompareMaskEqI32:
case IR::Opcode::CompareMaskNeI32:
case IR::Opcode::CompareMaskGtI32:
case IR::Opcode::CompareMaskGeI32:
case IR::Opcode::CompareMaskLtI32:
case IR::Opcode::CompareMaskLeI32:
case IR::Opcode::CompareEqU16:
case IR::Opcode::CompareNeU16:
case IR::Opcode::CompareGtU16:
case IR::Opcode::CompareGeU16:
case IR::Opcode::CompareLtU16:
case IR::Opcode::CompareLeU16:
case IR::Opcode::CompareEqF32:
case IR::Opcode::CompareNeF32:
case IR::Opcode::CompareGtF32:
case IR::Opcode::CompareGeF32:
case IR::Opcode::CompareLtF32:
case IR::Opcode::CompareLeF32:
case IR::Opcode::CompareOrderedF32:
case IR::Opcode::CompareUnorderedF32:
case IR::Opcode::CompareUnordEqF32:
case IR::Opcode::CompareUnordNeF32:
case IR::Opcode::CompareUnordGtF32:
case IR::Opcode::CompareUnordGeF32:
case IR::Opcode::CompareUnordLtF32:
case IR::Opcode::CompareUnordLeF32:
case IR::Opcode::CompareClassF32:
case IR::Opcode::CompareEqF16:
case IR::Opcode::CompareNeF16:
case IR::Opcode::CompareGtF16:
case IR::Opcode::CompareGeF16:
case IR::Opcode::CompareLtF16:
case IR::Opcode::CompareLeF16:
case IR::Opcode::CompareUnordNeF16:
case IR::Opcode::CompareMaskEqF16:
case IR::Opcode::CompareMaskNeF16:
case IR::Opcode::CompareMaskGtF16:
case IR::Opcode::CompareMaskGeF16:
case IR::Opcode::CompareMaskLtF16:
case IR::Opcode::CompareMaskLeF16:
case IR::Opcode::CompareMaskUnordNeF16:
case IR::Opcode::CompareMaskUnordGeF16:
case IR::Opcode::CompareMaskEqF32:
case IR::Opcode::CompareMaskNeF32:
case IR::Opcode::CompareMaskGtF32:
case IR::Opcode::CompareMaskGeF32:
case IR::Opcode::CompareMaskLtF32:
case IR::Opcode::CompareMaskLeF32:
case IR::Opcode::CompareMaskUnordEqF32:
case IR::Opcode::CompareMaskUnordNeF32:
case IR::Opcode::CompareMaskUnordGtF32:
case IR::Opcode::CompareMaskUnordGeF32:
case IR::Opcode::CompareMaskUnordLtF32:
case IR::Opcode::CompareMaskUnordLeF32: return true;
default: return false;
}
}
bool ProgramNeedsSubgroupLocalInvocationId(const IR::Program& program) { bool ProgramNeedsSubgroupLocalInvocationId(const IR::Program& program) {
for (const auto& block: program.blocks) { for (const auto& block: program.blocks) {
for (const auto& inst: block.instructions) { for (const auto& inst: block.instructions) {
@@ -481,7 +392,7 @@ bool ProgramNeedsSubgroupLocalInvocationId(const IR::Program& program) {
InstructionHasDppSource(inst)) { InstructionHasDppSource(inst)) {
return true; return true;
} }
if (IsCompareOpcode(inst.op)) { if (IR::IsCompareOpcode(inst.op)) {
return true; return true;
} }
if (inst.dst.kind == IR::OperandKind::Register && if (inst.dst.kind == IR::OperandKind::Register &&
@@ -516,7 +516,8 @@ void EmitInstruction(EmitterState& state, const IR::Instruction& inst) {
case IR::Opcode::CompareGeU32: EmitCompareU32(state, inst, OpUGreaterThanEqual); break; case IR::Opcode::CompareGeU32: EmitCompareU32(state, inst, OpUGreaterThanEqual); break;
case IR::Opcode::CompareLtU32: EmitCompareU32(state, inst, OpULessThan); break; case IR::Opcode::CompareLtU32: EmitCompareU32(state, inst, OpULessThan); break;
case IR::Opcode::CompareLeU32: EmitCompareU32(state, inst, OpULessThanEqual); break; case IR::Opcode::CompareLeU32: EmitCompareU32(state, inst, OpULessThanEqual); break;
case IR::Opcode::CompareNeU64: EmitCompareNeU64(state, inst); break; case IR::Opcode::CompareEqU64:
case IR::Opcode::CompareNeU64: EmitCompareU64(state, inst); break;
case IR::Opcode::CompareMaskEqU32: EmitCompareMaskU32(state, inst, OpIEqual); break; case IR::Opcode::CompareMaskEqU32: EmitCompareMaskU32(state, inst, OpIEqual); break;
case IR::Opcode::CompareMaskNeU32: EmitCompareMaskU32(state, inst, OpINotEqual); break; case IR::Opcode::CompareMaskNeU32: EmitCompareMaskU32(state, inst, OpINotEqual); break;
case IR::Opcode::CompareMaskGtU32: EmitCompareMaskU32(state, inst, OpUGreaterThan); break; case IR::Opcode::CompareMaskGtU32: EmitCompareMaskU32(state, inst, OpUGreaterThan); break;
@@ -774,6 +775,12 @@ void EmitInstruction(EmitterState& state, const IR::Instruction& inst) {
case IR::Opcode::AtomicXorU32: case IR::Opcode::AtomicXorU32:
EmitGuardedByExec(state, [&]() { EmitAtomicU32(state, inst, OpAtomicXor); }); EmitGuardedByExec(state, [&]() { EmitAtomicU32(state, inst, OpAtomicXor); });
break; break;
case IR::Opcode::AtomicFMinF32:
EmitGuardedByExec(state, [&]() { EmitAtomicFMinF32(state, inst); });
break;
case IR::Opcode::AtomicFMaxF32:
EmitGuardedByExec(state, [&]() { EmitAtomicFMaxF32(state, inst); });
break;
case IR::Opcode::FlatLoadUbyte: EmitFlatLoadUbyte(state, inst); break; case IR::Opcode::FlatLoadUbyte: EmitFlatLoadUbyte(state, inst); break;
case IR::Opcode::FlatLoadSbyte: EmitFlatLoadSbyte(state, inst); break; case IR::Opcode::FlatLoadSbyte: EmitFlatLoadSbyte(state, inst); break;
case IR::Opcode::FlatLoadUshort: EmitFlatLoadUshort(state, inst); break; case IR::Opcode::FlatLoadUshort: EmitFlatLoadUshort(state, inst); break;
@@ -850,7 +857,13 @@ void EmitInstruction(EmitterState& state, const IR::Instruction& inst) {
} }
EmitGuardedByExec(state, [&]() { EmitExport(state, inst); }); EmitGuardedByExec(state, [&]() { EmitExport(state, inst); });
break; break;
default: break; default:
if (!state.unsupported_ir_instruction) {
state.unsupported_ir_instruction = true;
state.unsupported_ir_opcode = inst.op;
state.unsupported_ir_pc = inst.pc;
}
break;
} }
} }
@@ -414,6 +414,9 @@ struct EmitterState {
bool needs_image_gather_extended = false; bool needs_image_gather_extended = false;
bool needs_function_lds = false; bool needs_function_lds = false;
bool needs_pixel_valid_mask = false; bool needs_pixel_valid_mask = false;
bool unsupported_ir_instruction = false;
IR::Opcode unsupported_ir_opcode = IR::Opcode::Count;
uint32_t unsupported_ir_pc = 0;
std::vector<RegisterBinding> registers; std::vector<RegisterBinding> registers;
std::vector<InputBinding> inputs; std::vector<InputBinding> inputs;
std::vector<OutputBinding> outputs; std::vector<OutputBinding> outputs;
@@ -611,8 +614,6 @@ bool IsMaskRegisterFile(IR::RegisterFile file);
bool IsSccOperand(const IR::Operand& operand); bool IsSccOperand(const IR::Operand& operand);
bool IsCompareOpcode(IR::Opcode op);
void CollectMaskStateRegisters(std::vector<RegisterBinding>& registers); void CollectMaskStateRegisters(std::vector<RegisterBinding>& registers);
void CollectSequentialRegisters(std::vector<RegisterBinding>& registers, const IR::Operand& base, void CollectSequentialRegisters(std::vector<RegisterBinding>& registers, const IR::Operand& base,
@@ -651,8 +652,6 @@ bool ProgramNeedsSubgroupBallot(const IR::Program& program);
bool ProgramNeedsSubgroupShuffle(const IR::Program& program); bool ProgramNeedsSubgroupShuffle(const IR::Program& program);
bool IsCompareOpcode(IR::Opcode op);
bool ProgramNeedsSubgroupLocalInvocationId(const IR::Program& program); bool ProgramNeedsSubgroupLocalInvocationId(const IR::Program& program);
uint32_t PointerForRegister(const EmitterState& state, IR::Register reg); uint32_t PointerForRegister(const EmitterState& state, IR::Register reg);
@@ -1015,6 +1014,9 @@ void EmitDeviceAtomicMemoryBarrier(EmitterState& state);
void EmitAtomicU32(EmitterState& state, const IR::Instruction& inst, uint32_t opcode); void EmitAtomicU32(EmitterState& state, const IR::Instruction& inst, uint32_t opcode);
void EmitAtomicFMinF32(EmitterState& state, const IR::Instruction& inst);
void EmitAtomicFMaxF32(EmitterState& state, const IR::Instruction& inst);
void EmitSLoadDword(EmitterState& state, const IR::Instruction& inst); void EmitSLoadDword(EmitterState& state, const IR::Instruction& inst);
void EmitLoadSrtDword(EmitterState& state, const IR::Instruction& inst); void EmitLoadSrtDword(EmitterState& state, const IR::Instruction& inst);
@@ -1345,7 +1347,7 @@ void EmitCompareI16(EmitterState& state, const IR::Instruction& inst, uint32_t o
void EmitBitCompareB32(EmitterState& state, const IR::Instruction& inst, bool bit_set); void EmitBitCompareB32(EmitterState& state, const IR::Instruction& inst, bool bit_set);
void EmitCompareNeU64(EmitterState& state, const IR::Instruction& inst); void EmitCompareU64(EmitterState& state, const IR::Instruction& inst);
void EmitCompareConstant(EmitterState& state, const IR::Instruction& inst, bool value); void EmitCompareConstant(EmitterState& state, const IR::Instruction& inst, bool value);
@@ -1577,19 +1579,22 @@ uint32_t EmitValueOrZeroIfCondition(EmitterState& state, uint32_t condition, Fn&
} }
const auto then_label = state.builder.AllocateId(); const auto then_label = state.builder.AllocateId();
const auto then_exit = state.builder.AllocateId();
const auto else_label = state.builder.AllocateId(); const auto else_label = state.builder.AllocateId();
const auto merge_label = state.builder.AllocateId(); const auto merge_label = state.builder.AllocateId();
state.builder.AddFunction({OpSelectionMerge, merge_label, SelectionControlNone}); state.builder.AddFunction({OpSelectionMerge, merge_label, SelectionControlNone});
state.builder.AddFunction({OpBranchConditional, condition, then_label, else_label}); state.builder.AddFunction({OpBranchConditional, condition, then_label, else_label});
state.builder.AddFunction({OpLabel, then_label}); state.builder.AddFunction({OpLabel, then_label});
const auto then_value = fn(); const auto then_value = fn();
state.builder.AddFunction({OpBranch, then_exit});
state.builder.AddFunction({OpLabel, then_exit});
state.builder.AddFunction({OpBranch, merge_label}); state.builder.AddFunction({OpBranch, merge_label});
state.builder.AddFunction({OpLabel, else_label}); state.builder.AddFunction({OpLabel, else_label});
state.builder.AddFunction({OpBranch, merge_label}); state.builder.AddFunction({OpBranch, merge_label});
state.builder.AddFunction({OpLabel, merge_label}); state.builder.AddFunction({OpLabel, merge_label});
const auto value = state.builder.AllocateId(); const auto value = state.builder.AllocateId();
state.builder.AddFunction( state.builder.AddFunction(
{OpPhi, state.uint_type, value, then_value, then_label, ConstantU32(state, 0), else_label}); {OpPhi, state.uint_type, value, then_value, then_exit, ConstantU32(state, 0), else_label});
return value; return value;
} }
@@ -640,8 +640,8 @@ void EmitMemoryStoreU32(EmitterState& state, const IR::Instruction& inst, IR::Re
} }
template <typename Fn> template <typename Fn>
void EmitAtomicUpdateU32(EmitterState& state, uint32_t pointer, IR::ResourceKind kind, uint32_t EmitAtomicUpdateU32(EmitterState& state, uint32_t pointer, IR::ResourceKind kind,
Fn&& desired_value) { Fn&& desired_value) {
const auto scope = kind == IR::ResourceKind::Lds ? ScopeWorkgroup : ScopeDevice; const auto scope = kind == IR::ResourceKind::Lds ? ScopeWorkgroup : ScopeDevice;
const auto memory = kind == IR::ResourceKind::Lds ? MemorySemanticsWorkgroupMemory const auto memory = kind == IR::ResourceKind::Lds ? MemorySemanticsWorkgroupMemory
: MemorySemanticsUniformMemory; : MemorySemanticsUniformMemory;
@@ -675,6 +675,7 @@ void EmitAtomicUpdateU32(EmitterState& state, uint32_t pointer, IR::ResourceKind
const auto semantics = MemorySemanticsAcquireRelease | memory; const auto semantics = MemorySemanticsAcquireRelease | memory;
state.builder.AddFunction( state.builder.AddFunction(
{OpMemoryBarrier, ConstantU32(state, scope), ConstantU32(state, semantics)}); {OpMemoryBarrier, ConstantU32(state, scope), ConstantU32(state, semantics)});
return observed;
} }
void EmitMemoryStoreSubDwordU32(EmitterState& state, const IR::Instruction& inst, void EmitMemoryStoreSubDwordU32(EmitterState& state, const IR::Instruction& inst,
@@ -1181,6 +1182,79 @@ void EmitAtomicU32(EmitterState& state, const IR::Instruction& inst, uint32_t op
EmitStoreU32(state, inst.dst, old); EmitStoreU32(state, inst.dst, old);
} }
namespace {
uint32_t EmitF32BitsOrderedCompare(EmitterState& state, uint32_t lhs_bits, uint32_t rhs_bits,
uint32_t comparison_opcode) {
struct ClassifiedBits {
uint32_t nan = 0;
uint32_t zero = 0;
uint32_t key = 0;
};
const auto Classify = [&](uint32_t bits) {
ClassifiedBits cls;
const auto abs_bits = EmitAndConstant(state, bits, 0x7fffffffu);
const auto exponent_bits = EmitAndConstant(state, abs_bits, 0x7f800000u);
const auto mantissa_bits = EmitAndConstant(state, abs_bits, 0x007fffffu);
const auto exponent_max =
EmitCompareU32Constant(state, OpIEqual, exponent_bits, 0x7f800000u);
const auto mantissa_nonzero = EmitCompareU32Constant(state, OpINotEqual, mantissa_bits, 0);
const auto negative = EmitCompareU32Constant(state, OpINotEqual,
EmitAndConstant(state, bits, 0x80000000u), 0);
const auto negative_key = state.builder.AllocateId();
const auto positive_key = state.builder.AllocateId();
// Map all non-NaN IEEE-754 encodings to monotonically increasing unsigned keys.
state.builder.AddFunction({OpNot, state.uint_type, negative_key, bits});
state.builder.AddFunction(
{OpBitwiseXor, state.uint_type, positive_key, bits, ConstantU32(state, 0x80000000u)});
cls.nan = EmitLogicalAndBool(state, exponent_max, mantissa_nonzero);
cls.zero = EmitCompareU32Constant(state, OpIEqual, abs_bits, 0);
cls.key = EmitSelectValueU32(state, negative, negative_key, positive_key);
return cls;
};
const auto lhs = Classify(lhs_bits);
const auto rhs = Classify(rhs_bits);
const auto any_nan = EmitLogicalOrBool(state, lhs.nan, rhs.nan);
// The key transform orders -0 below +0, while the floating comparison treats them as equal.
const auto both_zero = EmitLogicalAndBool(state, lhs.zero, rhs.zero);
const auto ordered_nonzero =
EmitLogicalNotBool(state, EmitLogicalOrBool(state, any_nan, both_zero));
const auto comparison = state.builder.AllocateId();
state.builder.AddFunction({comparison_opcode, state.bool_type, comparison, lhs.key, rhs.key});
return EmitLogicalAndBool(state, ordered_nonzero, comparison);
}
void EmitAtomicFMinMaxF32(EmitterState& state, const IR::Instruction& inst,
uint32_t comparison_opcode) {
const auto index =
EmitMemoryDwordIndex(state, inst, inst.memory, 1, AddressSourceCount(inst, 1));
const auto in_bounds = EmitStorageBufferElementInBounds(state, inst.memory, index, inst.pc);
const auto src_u32 = EmitValueLoad(state, inst.src[0]);
const auto old = EmitValueOrZeroIfCondition(state, in_bounds, [&]() {
const auto pointer = EmitStorageBufferElementPointer(state, inst.memory, index, inst.pc);
return EmitAtomicUpdateU32(state, pointer, inst.memory.kind, [&](uint32_t old_u32) {
const auto replace_old = state.builder.AllocateId();
const auto replace =
EmitF32BitsOrderedCompare(state, src_u32, old_u32, comparison_opcode);
state.builder.AddFunction(
{OpSelect, state.uint_type, replace_old, replace, src_u32, old_u32});
return replace_old;
});
});
EmitStoreU32(state, inst.dst, old);
}
} // namespace
void EmitAtomicFMinF32(EmitterState& state, const IR::Instruction& inst) {
EmitAtomicFMinMaxF32(state, inst, OpULessThan);
}
void EmitAtomicFMaxF32(EmitterState& state, const IR::Instruction& inst) {
EmitAtomicFMinMaxF32(state, inst, OpUGreaterThan);
}
void EmitSLoadDword(EmitterState& state, const IR::Instruction& inst) { void EmitSLoadDword(EmitterState& state, const IR::Instruction& inst) {
if (state.address_memory_variable == 0) { if (state.address_memory_variable == 0) {
ExitDescriptorBindingFailure(state, IR::DescriptorBindingKind::AddressMemory, ExitDescriptorBindingFailure(state, IR::DescriptorBindingKind::AddressMemory,
@@ -30,7 +30,9 @@ bool IsAtomic(Opcode op) {
case Opcode::AtomicUMaxU32: case Opcode::AtomicUMaxU32:
case Opcode::AtomicAndU32: case Opcode::AtomicAndU32:
case Opcode::AtomicOrU32: case Opcode::AtomicOrU32:
case Opcode::AtomicXorU32: return true; case Opcode::AtomicXorU32:
case Opcode::AtomicFMinF32:
case Opcode::AtomicFMaxF32: return true;
default: return false; default: return false;
} }
} }
+44 -12
View File
@@ -490,13 +490,26 @@ bool IsScalarMinMaxOpcode(Decoder::Opcode opcode) {
} }
} }
bool ResolveIrOpcode(Decoder::Opcode decoded, Opcode& lowered, std::string* error) {
const auto opcode = LookupIrOpcode(decoded);
if (!opcode.has_value()) {
if (error != nullptr) {
*error = fmt::format("decoded opcode has no IR lowering: {}",
Decoder::OpcodeToString(decoded));
}
return false;
}
lowered = *opcode;
return true;
}
bool LowerScalarMinMaxWithScc(const Decoder::Instruction& decoded, BasicBlock& block, bool LowerScalarMinMaxWithScc(const Decoder::Instruction& decoded, BasicBlock& block,
std::string* error) { std::string* error) {
Instruction result; Instruction result;
result.pc = decoded.pc; result.pc = decoded.pc;
result.op = LookupIrOpcode(decoded.opcode);
result.src_count = 2; result.src_count = 2;
if (!LowerRegisterOperand(decoded.dst, result.dst, error) || if (!ResolveIrOpcode(decoded.opcode, result.op, error) ||
!LowerRegisterOperand(decoded.dst, result.dst, error) ||
!LowerSourceOperand(decoded.src0, result.src[0], error) || !LowerSourceOperand(decoded.src0, result.src[0], error) ||
!LowerSourceOperand(decoded.src1, result.src[1], error)) { !LowerSourceOperand(decoded.src1, result.src[1], error)) {
return false; return false;
@@ -962,7 +975,12 @@ bool LowerControlInstruction(const Decoder::Instruction& decoded, BasicBlock& bl
return LowerControlMarker(decoded, block, Opcode::TtraceData, true, error); return LowerControlMarker(decoded, block, Opcode::TtraceData, true, error);
case Decoder::Opcode::SInstPrefetch: case Decoder::Opcode::SInstPrefetch:
return LowerControlMarker(decoded, block, Opcode::InstPrefetch, true, error); return LowerControlMarker(decoded, block, Opcode::InstPrefetch, true, error);
default: return false; default:
if (error != nullptr) {
*error = fmt::format("control opcode has no specialized IR lowering: {}",
Decoder::OpcodeToString(decoded.opcode));
}
return false;
} }
} }
@@ -1051,6 +1069,18 @@ bool IsTerminatorOpcode(Decoder::Opcode opcode) {
bool LowerImplemented(const Decoder::Instruction& decoded, BasicBlock& block, std::string* error); bool LowerImplemented(const Decoder::Instruction& decoded, BasicBlock& block, std::string* error);
bool IsMemoryFamily(Decoder::Family family) {
switch (family) {
case Decoder::Family::SMEM:
case Decoder::Family::MUBUF:
case Decoder::Family::MTBUF:
case Decoder::Family::FLAT:
case Decoder::Family::DS:
case Decoder::Family::MIMG: return true;
default: return false;
}
}
bool LowerDecodedInstruction(const Decoder::Instruction& inst, BasicBlock& block, bool LowerDecodedInstruction(const Decoder::Instruction& inst, BasicBlock& block,
std::string* error) { std::string* error) {
switch (inst.opcode) { switch (inst.opcode) {
@@ -1104,6 +1134,15 @@ bool LowerDecodedInstruction(const Decoder::Instruction& inst, BasicBlock& block
if (IsMemoryOpcode(inst.opcode)) { if (IsMemoryOpcode(inst.opcode)) {
return LowerMemoryInstruction(inst, block, error); return LowerMemoryInstruction(inst, block, error);
} }
// Memory instructions require specialized lowering to populate MemoryInfo. Never let a
// newly decoded memory-family opcode fall through to generic register-only IR.
if (IsMemoryFamily(inst.family)) {
if (error != nullptr) {
*error = fmt::format("decoded memory-family opcode has no specialized IR lowering: {}",
Decoder::OpcodeToString(inst.opcode));
}
return false;
}
if (ScalarShiftLeftAddAmount(inst.opcode) != 0) { if (ScalarShiftLeftAddAmount(inst.opcode) != 0) {
return LowerScalarShiftLeftAdd(inst, block, error); return LowerScalarShiftLeftAdd(inst, block, error);
} }
@@ -1116,23 +1155,16 @@ bool LowerDecodedInstruction(const Decoder::Instruction& inst, BasicBlock& block
if (VectorByteConvertIndex(inst.opcode) <= 3u) { if (VectorByteConvertIndex(inst.opcode) <= 3u) {
return LowerVectorByteConvert(inst, block, error); return LowerVectorByteConvert(inst, block, error);
} }
if (!IsImplemented(inst.opcode)) {
if (error != nullptr) {
*error = fmt::format("decoded opcode has no IR lowering yet: {}",
Decoder::InstructionToString(inst).c_str());
}
return false;
}
return LowerImplemented(inst, block, error); return LowerImplemented(inst, block, error);
} }
bool LowerImplemented(const Decoder::Instruction& decoded, BasicBlock& block, std::string* error) { bool LowerImplemented(const Decoder::Instruction& decoded, BasicBlock& block, std::string* error) {
Instruction inst; Instruction inst;
inst.pc = decoded.pc; inst.pc = decoded.pc;
inst.op = LookupIrOpcode(decoded.opcode);
inst.src_count = decoded.src_count; inst.src_count = decoded.src_count;
if (!LowerRegisterOperand(decoded.dst, inst.dst, error)) { if (!ResolveIrOpcode(decoded.opcode, inst.op, error) ||
!LowerRegisterOperand(decoded.dst, inst.dst, error)) {
return false; return false;
} }
ApplyDppDestinationMask(decoded, inst.dst); ApplyDppDestinationMask(decoded, inst.dst);
+35 -337
View File
@@ -8,350 +8,48 @@
#include "graphics/shader/shader.h" #include "graphics/shader/shader.h"
#include <array> #include <array>
#include <string_view>
#include <vector> #include <vector>
namespace Libs::Graphics::ShaderRecompiler::IR { namespace Libs::Graphics::ShaderRecompiler::IR {
enum class Opcode { enum class Opcode {
ControlNop, #define IR_OPCODE(name, category) name,
Waitcnt, #include "graphics/shader/recompiler/ir/ShaderIROpcodes.inc"
Barrier, #undef IR_OPCODE
Sendmsg, Count,
TtraceData,
InstPrefetch,
MoveU32,
MoveF32Bits,
MoveRelDestU32,
MoveRelSourceU32,
MoveU64,
WqmB64,
SaveexecB32,
SaveexecB64,
ReadFirstLaneU32,
ReadLaneU32,
WriteLaneU32,
Permlane16B32,
Permlanex16B32,
AbsI32,
IAddU32,
IAddCarryU32,
ISubBorrowU32,
ScalarAddCarryU32,
ScalarSubBorrowU32,
ScalarSubBorrowCarryU32,
ScalarSignedAddOverflowI32,
ScalarSignedSubOverflowI32,
ScalarShiftLeftAddCarryU32,
ISubU32,
IMulU32,
UMulHighU32,
SMulHighI32,
IMadI24U32,
UMadU24U32,
UMadU64U32,
SadU32,
IAdd3U32,
IMulI24U32,
UMulU24U32,
IMinI32,
IMaxI32,
IMin3I32,
IMax3I32,
IMed3I32,
UMinU32,
UMaxU32,
UMin3U32,
UMax3U32,
UMed3U32,
BitwiseAndU32,
BitwiseAndU64,
BitwiseAndNotU32,
BitwiseAndNotU64,
BitwiseOrU32,
BitwiseOrU64,
BitwiseOrNotU32,
BitwiseOrNotU64,
BitwiseAndOrU32,
BitwiseOr3U32,
BitwiseXorU32,
BitwiseXorU64,
BitwiseXor3U32,
BitwiseNandU32,
BitwiseNandU64,
BitwiseNorU32,
BitwiseNorU64,
BitwiseXnorU32,
BitwiseXnorU64,
BitwiseNotU32,
BitwiseNotU64,
BitClearU32,
BitSetU32,
BitReverseU32,
BitCountU32,
BitCountU64,
BitReplicateB64B32,
BitCountAddU32,
MaskedBitCountLowU32,
MaskedBitCountHighU32,
FindLsbU32,
FindMsbFromHighU32,
FindMsbFromHighU64,
BitFieldMaskU32,
BitFieldMaskU64,
BitFieldExtractU32,
BitFieldExtractU64,
BitFieldExtract3U32,
BitFieldExtract3I32,
BitFieldInsertSelectU32,
BitCompare0B32,
BitCompare1B32,
AlignBitU32,
ShiftLeftAddU32,
AddShiftLeftU32,
XorAddU32,
ShiftLeftOrU32,
ShiftLeftLogicalU32,
ShiftLeftLogicalU64,
ShiftLeftLogicalU16,
ShiftRightLogicalU32,
ShiftRightLogicalU64,
ShiftRightLogicalU16,
ShiftRightArithmeticI32,
ShiftRightArithmeticI16,
SelectU32,
SelectMaskU32,
SelectF32Bits,
SelectMaskF32Bits,
SelectU64,
PackLowLowU16,
PackLowHighU16,
PackHighHighU16,
CompareFalse,
CompareTrue,
CompareEqU32,
CompareNeU32,
CompareGtU32,
CompareGeU32,
CompareLtU32,
CompareLeU32,
CompareNeU64,
CompareMaskEqU32,
CompareMaskNeU32,
CompareMaskGtU32,
CompareMaskGeU32,
CompareMaskLtU32,
CompareMaskLeU32,
CompareEqI32,
CompareNeI32,
CompareGtI32,
CompareGeI32,
CompareLtI32,
CompareLeI32,
CompareEqI16,
CompareNeI16,
CompareGtI16,
CompareGeI16,
CompareLtI16,
CompareLeI16,
CompareMaskEqI32,
CompareMaskNeI32,
CompareMaskGtI32,
CompareMaskGeI32,
CompareMaskLtI32,
CompareMaskLeI32,
CompareEqU16,
CompareNeU16,
CompareGtU16,
CompareGeU16,
CompareLtU16,
CompareLeU16,
CompareEqF32,
CompareNeF32,
CompareGtF32,
CompareGeF32,
CompareLtF32,
CompareLeF32,
CompareOrderedF32,
CompareUnorderedF32,
CompareUnordEqF32,
CompareUnordNeF32,
CompareUnordGtF32,
CompareUnordGeF32,
CompareUnordLtF32,
CompareUnordLeF32,
CompareClassF32,
CompareEqF16,
CompareNeF16,
CompareGtF16,
CompareGeF16,
CompareLtF16,
CompareLeF16,
CompareUnordNeF16,
CompareMaskEqF16,
CompareMaskNeF16,
CompareMaskGtF16,
CompareMaskGeF16,
CompareMaskLtF16,
CompareMaskLeF16,
CompareMaskUnordNeF16,
CompareMaskUnordGeF16,
CompareMaskEqF32,
CompareMaskNeF32,
CompareMaskGtF32,
CompareMaskGeF32,
CompareMaskLtF32,
CompareMaskLeF32,
CompareMaskUnordEqF32,
CompareMaskUnordNeF32,
CompareMaskUnordGtF32,
CompareMaskUnordGeF32,
CompareMaskUnordLtF32,
CompareMaskUnordLeF32,
ConvertByteU32ToF32,
ConvertU32ToF32,
ConvertI32ToF32,
ConvertF32ToU32,
ConvertF32ToI32,
ConvertF32ToF16,
ConvertF16ToF32,
ConvertU16ToF16,
ConvertF16ToU16,
ConvertI16ToF16,
ConvertF16ToI16,
ConvertRoundPlusInfF32ToI32,
ConvertFloorF32ToI32,
ConvertI4ToOffsetF32,
LdexpF32,
PackF32ToF16Rtz,
PackSnorm2x16F32,
PackUnorm2x16F32,
PackU16U32,
PackU8F32,
PackB32F16,
PackedMadI16,
PackedMulLoU16,
PackedAddI16,
PackedSubI16,
PackedLshlrevB16,
PackedLshrrevB16,
PackedAshrrevI16,
PackedMaxI16,
PackedMinI16,
PackedMadU16,
PackedAddU16,
PackedSubU16,
PackedMaxU16,
PackedMinU16,
PackedAddF16,
PackedMulF16,
PackedMinF16,
PackedMaxF16,
PackedFmaF16,
AddF16,
SubF16,
MulF16,
MinF16,
MaxF16,
FmaF16,
MadMixF16,
IAddU16,
ISubI16,
IMinI16,
IMaxI16,
UMinU16,
UMaxU16,
RcpF32,
FractF32,
TruncF32,
CeilF32,
RoundEvenF32,
FloorF32,
Exp2F32,
Log2F32,
InverseSqrtF32,
SqrtF32,
RcpF16,
SqrtF16,
InverseSqrtF16,
Log2F16,
Exp2F16,
FloorF16,
CeilF16,
TruncF16,
RoundEvenF16,
SinF32,
CosF32,
CubeIdF32,
CubeScF32,
CubeTcF32,
CubeMaF32,
FAddF32,
FSubF32,
FMulF32,
FMinF32,
FMaxF32,
FMadF32,
Dot2AccF32F16,
FMin3F32,
FMax3F32,
FMed3F32,
Min3F16,
Max3F16,
Med3F16,
LoadSrtDword,
SLoadDword,
SBufferLoadDword,
BufferLoadUbyte,
BufferLoadSbyte,
BufferLoadUshort,
BufferLoadSshort,
BufferLoadDword,
BufferStoreByte,
BufferStoreShort,
BufferStoreDword,
AtomicSwapU32,
AtomicAddU32,
AtomicSubU32,
AtomicSMinI32,
AtomicUMinU32,
AtomicSMaxI32,
AtomicUMaxU32,
AtomicAndU32,
AtomicOrU32,
AtomicXorU32,
FlatLoadUbyte,
FlatLoadSbyte,
FlatLoadUshort,
FlatLoadSshort,
FlatLoadDword,
FlatStoreByte,
FlatStoreShort,
FlatStoreDword,
DsReadUbyte,
DsReadSbyte,
DsReadUshort,
DsReadSshort,
DsReadB32,
DsWriteByte,
DsWriteShort,
DsWriteB32,
DsMinF32,
DsMaxF32,
DsSwizzleB32,
DsConsume,
DsAppend,
DsWriteAddtidB32,
DsReadAddtidB32,
ImageGetResinfo,
ImageGetLod,
ImageLoad,
ImageStore,
ImageSample,
ImageGather4,
LoadInputF32,
Export,
}; };
enum class OpcodeClass { General, Compare, Compare64 };
struct OpcodeInfo {
std::string_view name;
OpcodeClass opcode_class = OpcodeClass::General;
};
inline constexpr OpcodeInfo OPCODE_INFO[] = {
#define IR_OPCODE(name, category) {#name, OpcodeClass::category},
#include "graphics/shader/recompiler/ir/ShaderIROpcodes.inc"
#undef IR_OPCODE
};
static_assert(sizeof(OPCODE_INFO) / sizeof(OPCODE_INFO[0]) == static_cast<size_t>(Opcode::Count));
constexpr const OpcodeInfo& GetOpcodeInfo(Opcode opcode) {
return OPCODE_INFO[static_cast<size_t>(opcode)];
}
constexpr std::string_view OpcodeName(Opcode opcode) {
return GetOpcodeInfo(opcode).name;
}
constexpr bool IsCompareOpcode(Opcode opcode) {
return GetOpcodeInfo(opcode).opcode_class != OpcodeClass::General;
}
constexpr bool IsCompare64Opcode(Opcode opcode) {
return GetOpcodeInfo(opcode).opcode_class == OpcodeClass::Compare64;
}
enum class RegisterFile { Scalar, Vector, Vcc, Exec, Scc, M0 }; enum class RegisterFile { Scalar, Vector, Vcc, Exec, Scc, M0 };
struct Register { struct Register {
@@ -3,16 +3,17 @@
#include "graphics/shader/recompiler/ir/ShaderIR.h" #include "graphics/shader/recompiler/ir/ShaderIR.h"
#include <optional>
namespace Libs::Graphics::ShaderRecompiler::IR { namespace Libs::Graphics::ShaderRecompiler::IR {
void SetError(std::string* error, const char* message); void SetError(std::string* error, const char* message);
Opcode LookupIrOpcode(Decoder::Opcode opcode); std::optional<Opcode> LookupIrOpcode(Decoder::Opcode opcode);
bool IsImplemented(Decoder::Opcode opcode); bool IsReversedBinary(Decoder::Opcode opcode);
bool IsReversedBinary(Decoder::Opcode opcode); bool IsVectorCarryOutOpcode(Decoder::Opcode opcode);
bool IsVectorCarryOutOpcode(Decoder::Opcode opcode); bool ScalarResultWritesSccNonZero(Decoder::Opcode opcode);
bool ScalarResultWritesSccNonZero(Decoder::Opcode opcode); bool ScalarResultIs64Bit(Decoder::Opcode opcode);
bool ScalarResultIs64Bit(Decoder::Opcode opcode);
Operand MakeSccOperand(); Operand MakeSccOperand();
Operand MakeImmediateU32(uint32_t value); Operand MakeImmediateU32(uint32_t value);
@@ -157,345 +157,7 @@ std::string InstructionToString(const Instruction& inst) {
text += ExportInfoToString(inst.export_info); text += ExportInfoToString(inst.export_info);
return text; return text;
} }
switch (inst.op) { text += OpcodeName(inst.op);
case Opcode::ControlNop: text += "ControlNop"; break;
case Opcode::Waitcnt: text += "Waitcnt"; break;
case Opcode::Barrier: text += "Barrier"; break;
case Opcode::Sendmsg: text += "Sendmsg"; break;
case Opcode::TtraceData: text += "TtraceData"; break;
case Opcode::InstPrefetch: text += "InstPrefetch"; break;
case Opcode::MoveU32: text += "MoveU32"; break;
case Opcode::MoveF32Bits: text += "MoveF32Bits"; break;
case Opcode::MoveRelDestU32: text += "MoveRelDestU32"; break;
case Opcode::MoveRelSourceU32: text += "MoveRelSourceU32"; break;
case Opcode::MoveU64: text += "MoveU64"; break;
case Opcode::WqmB64: text += "WqmB64"; break;
case Opcode::SaveexecB32: text += "SaveexecB32"; break;
case Opcode::SaveexecB64: text += "SaveexecB64"; break;
case Opcode::ReadFirstLaneU32: text += "ReadFirstLaneU32"; break;
case Opcode::ReadLaneU32: text += "ReadLaneU32"; break;
case Opcode::WriteLaneU32: text += "WriteLaneU32"; break;
case Opcode::Permlane16B32: text += "Permlane16B32"; break;
case Opcode::Permlanex16B32: text += "Permlanex16B32"; break;
case Opcode::AbsI32: text += "AbsI32"; break;
case Opcode::IAddU32: text += "IAddU32"; break;
case Opcode::IAddCarryU32: text += "IAddCarryU32"; break;
case Opcode::ISubBorrowU32: text += "ISubBorrowU32"; break;
case Opcode::ScalarAddCarryU32: text += "ScalarAddCarryU32"; break;
case Opcode::ScalarSubBorrowU32: text += "ScalarSubBorrowU32"; break;
case Opcode::ScalarSubBorrowCarryU32: text += "ScalarSubBorrowCarryU32"; break;
case Opcode::ScalarSignedAddOverflowI32: text += "ScalarSignedAddOverflowI32"; break;
case Opcode::ScalarSignedSubOverflowI32: text += "ScalarSignedSubOverflowI32"; break;
case Opcode::ScalarShiftLeftAddCarryU32: text += "ScalarShiftLeftAddCarryU32"; break;
case Opcode::ISubU32: text += "ISubU32"; break;
case Opcode::IMulU32: text += "IMulU32"; break;
case Opcode::UMulHighU32: text += "UMulHighU32"; break;
case Opcode::SMulHighI32: text += "SMulHighI32"; break;
case Opcode::IMadI24U32: text += "IMadI24U32"; break;
case Opcode::UMadU24U32: text += "UMadU24U32"; break;
case Opcode::UMadU64U32: text += "UMadU64U32"; break;
case Opcode::SadU32: text += "SadU32"; break;
case Opcode::IAdd3U32: text += "IAdd3U32"; break;
case Opcode::IMulI24U32: text += "IMulI24U32"; break;
case Opcode::UMulU24U32: text += "UMulU24U32"; break;
case Opcode::IMinI32: text += "IMinI32"; break;
case Opcode::IMaxI32: text += "IMaxI32"; break;
case Opcode::IMin3I32: text += "IMin3I32"; break;
case Opcode::IMax3I32: text += "IMax3I32"; break;
case Opcode::IMed3I32: text += "IMed3I32"; break;
case Opcode::UMinU32: text += "UMinU32"; break;
case Opcode::UMaxU32: text += "UMaxU32"; break;
case Opcode::UMin3U32: text += "UMin3U32"; break;
case Opcode::UMax3U32: text += "UMax3U32"; break;
case Opcode::UMed3U32: text += "UMed3U32"; break;
case Opcode::BitwiseAndU32: text += "BitwiseAndU32"; break;
case Opcode::BitwiseAndU64: text += "BitwiseAndU64"; break;
case Opcode::BitwiseAndNotU32: text += "BitwiseAndNotU32"; break;
case Opcode::BitwiseAndNotU64: text += "BitwiseAndNotU64"; break;
case Opcode::BitwiseOrU32: text += "BitwiseOrU32"; break;
case Opcode::BitwiseOrU64: text += "BitwiseOrU64"; break;
case Opcode::BitwiseOrNotU32: text += "BitwiseOrNotU32"; break;
case Opcode::BitwiseOrNotU64: text += "BitwiseOrNotU64"; break;
case Opcode::BitwiseAndOrU32: text += "BitwiseAndOrU32"; break;
case Opcode::BitwiseOr3U32: text += "BitwiseOr3U32"; break;
case Opcode::BitwiseXorU32: text += "BitwiseXorU32"; break;
case Opcode::BitwiseXorU64: text += "BitwiseXorU64"; break;
case Opcode::BitwiseXor3U32: text += "BitwiseXor3U32"; break;
case Opcode::BitwiseNandU32: text += "BitwiseNandU32"; break;
case Opcode::BitwiseNandU64: text += "BitwiseNandU64"; break;
case Opcode::BitwiseNorU32: text += "BitwiseNorU32"; break;
case Opcode::BitwiseNorU64: text += "BitwiseNorU64"; break;
case Opcode::BitwiseXnorU32: text += "BitwiseXnorU32"; break;
case Opcode::BitwiseXnorU64: text += "BitwiseXnorU64"; break;
case Opcode::BitwiseNotU32: text += "BitwiseNotU32"; break;
case Opcode::BitwiseNotU64: text += "BitwiseNotU64"; break;
case Opcode::BitClearU32: text += "BitClearU32"; break;
case Opcode::BitSetU32: text += "BitSetU32"; break;
case Opcode::BitReverseU32: text += "BitReverseU32"; break;
case Opcode::BitCountU32: text += "BitCountU32"; break;
case Opcode::BitCountU64: text += "BitCountU64"; break;
case Opcode::BitReplicateB64B32: text += "BitReplicateB64B32"; break;
case Opcode::BitCountAddU32: text += "BitCountAddU32"; break;
case Opcode::MaskedBitCountLowU32: text += "MaskedBitCountLowU32"; break;
case Opcode::MaskedBitCountHighU32: text += "MaskedBitCountHighU32"; break;
case Opcode::FindLsbU32: text += "FindLsbU32"; break;
case Opcode::FindMsbFromHighU32: text += "FindMsbFromHighU32"; break;
case Opcode::FindMsbFromHighU64: text += "FindMsbFromHighU64"; break;
case Opcode::BitFieldMaskU32: text += "BitFieldMaskU32"; break;
case Opcode::BitFieldMaskU64: text += "BitFieldMaskU64"; break;
case Opcode::BitFieldExtractU32: text += "BitFieldExtractU32"; break;
case Opcode::BitFieldExtractU64: text += "BitFieldExtractU64"; break;
case Opcode::BitFieldExtract3U32: text += "BitFieldExtract3U32"; break;
case Opcode::BitFieldExtract3I32: text += "BitFieldExtract3I32"; break;
case Opcode::BitFieldInsertSelectU32: text += "BitFieldInsertSelectU32"; break;
case Opcode::BitCompare0B32: text += "BitCompare0B32"; break;
case Opcode::BitCompare1B32: text += "BitCompare1B32"; break;
case Opcode::AlignBitU32: text += "AlignBitU32"; break;
case Opcode::ShiftLeftAddU32: text += "ShiftLeftAddU32"; break;
case Opcode::AddShiftLeftU32: text += "AddShiftLeftU32"; break;
case Opcode::XorAddU32: text += "XorAddU32"; break;
case Opcode::ShiftLeftOrU32: text += "ShiftLeftOrU32"; break;
case Opcode::ShiftLeftLogicalU32: text += "ShiftLeftLogicalU32"; break;
case Opcode::ShiftLeftLogicalU64: text += "ShiftLeftLogicalU64"; break;
case Opcode::ShiftLeftLogicalU16: text += "ShiftLeftLogicalU16"; break;
case Opcode::ShiftRightLogicalU32: text += "ShiftRightLogicalU32"; break;
case Opcode::ShiftRightLogicalU64: text += "ShiftRightLogicalU64"; break;
case Opcode::ShiftRightLogicalU16: text += "ShiftRightLogicalU16"; break;
case Opcode::ShiftRightArithmeticI32: text += "ShiftRightArithmeticI32"; break;
case Opcode::ShiftRightArithmeticI16: text += "ShiftRightArithmeticI16"; break;
case Opcode::SelectU32: text += "SelectU32"; break;
case Opcode::SelectMaskU32: text += "SelectMaskU32"; break;
case Opcode::SelectF32Bits: text += "SelectF32Bits"; break;
case Opcode::SelectMaskF32Bits: text += "SelectMaskF32Bits"; break;
case Opcode::SelectU64: text += "SelectU64"; break;
case Opcode::PackLowLowU16: text += "PackLowLowU16"; break;
case Opcode::PackLowHighU16: text += "PackLowHighU16"; break;
case Opcode::PackHighHighU16: text += "PackHighHighU16"; break;
case Opcode::CompareFalse: text += "CompareFalse"; break;
case Opcode::CompareTrue: text += "CompareTrue"; break;
case Opcode::CompareEqU32: text += "CompareEqU32"; break;
case Opcode::CompareNeU32: text += "CompareNeU32"; break;
case Opcode::CompareGtU32: text += "CompareGtU32"; break;
case Opcode::CompareGeU32: text += "CompareGeU32"; break;
case Opcode::CompareLtU32: text += "CompareLtU32"; break;
case Opcode::CompareLeU32: text += "CompareLeU32"; break;
case Opcode::CompareNeU64: text += "CompareNeU64"; break;
case Opcode::CompareMaskEqU32: text += "CompareMaskEqU32"; break;
case Opcode::CompareMaskNeU32: text += "CompareMaskNeU32"; break;
case Opcode::CompareMaskGtU32: text += "CompareMaskGtU32"; break;
case Opcode::CompareMaskGeU32: text += "CompareMaskGeU32"; break;
case Opcode::CompareMaskLtU32: text += "CompareMaskLtU32"; break;
case Opcode::CompareMaskLeU32: text += "CompareMaskLeU32"; break;
case Opcode::CompareEqI32: text += "CompareEqI32"; break;
case Opcode::CompareNeI32: text += "CompareNeI32"; break;
case Opcode::CompareGtI32: text += "CompareGtI32"; break;
case Opcode::CompareGeI32: text += "CompareGeI32"; break;
case Opcode::CompareLtI32: text += "CompareLtI32"; break;
case Opcode::CompareLeI32: text += "CompareLeI32"; break;
case Opcode::CompareEqI16: text += "CompareEqI16"; break;
case Opcode::CompareNeI16: text += "CompareNeI16"; break;
case Opcode::CompareGtI16: text += "CompareGtI16"; break;
case Opcode::CompareGeI16: text += "CompareGeI16"; break;
case Opcode::CompareLtI16: text += "CompareLtI16"; break;
case Opcode::CompareLeI16: text += "CompareLeI16"; break;
case Opcode::CompareMaskEqI32: text += "CompareMaskEqI32"; break;
case Opcode::CompareMaskNeI32: text += "CompareMaskNeI32"; break;
case Opcode::CompareMaskGtI32: text += "CompareMaskGtI32"; break;
case Opcode::CompareMaskGeI32: text += "CompareMaskGeI32"; break;
case Opcode::CompareMaskLtI32: text += "CompareMaskLtI32"; break;
case Opcode::CompareMaskLeI32: text += "CompareMaskLeI32"; break;
case Opcode::CompareEqU16: text += "CompareEqU16"; break;
case Opcode::CompareNeU16: text += "CompareNeU16"; break;
case Opcode::CompareGtU16: text += "CompareGtU16"; break;
case Opcode::CompareGeU16: text += "CompareGeU16"; break;
case Opcode::CompareLtU16: text += "CompareLtU16"; break;
case Opcode::CompareLeU16: text += "CompareLeU16"; break;
case Opcode::CompareEqF32: text += "CompareEqF32"; break;
case Opcode::CompareNeF32: text += "CompareNeF32"; break;
case Opcode::CompareGtF32: text += "CompareGtF32"; break;
case Opcode::CompareGeF32: text += "CompareGeF32"; break;
case Opcode::CompareLtF32: text += "CompareLtF32"; break;
case Opcode::CompareLeF32: text += "CompareLeF32"; break;
case Opcode::CompareOrderedF32: text += "CompareOrderedF32"; break;
case Opcode::CompareUnorderedF32: text += "CompareUnorderedF32"; break;
case Opcode::CompareUnordEqF32: text += "CompareUnordEqF32"; break;
case Opcode::CompareUnordNeF32: text += "CompareUnordNeF32"; break;
case Opcode::CompareUnordGtF32: text += "CompareUnordGtF32"; break;
case Opcode::CompareUnordGeF32: text += "CompareUnordGeF32"; break;
case Opcode::CompareUnordLtF32: text += "CompareUnordLtF32"; break;
case Opcode::CompareUnordLeF32: text += "CompareUnordLeF32"; break;
case Opcode::CompareClassF32: text += "CompareClassF32"; break;
case Opcode::CompareEqF16: text += "CompareEqF16"; break;
case Opcode::CompareNeF16: text += "CompareNeF16"; break;
case Opcode::CompareGtF16: text += "CompareGtF16"; break;
case Opcode::CompareGeF16: text += "CompareGeF16"; break;
case Opcode::CompareLtF16: text += "CompareLtF16"; break;
case Opcode::CompareLeF16: text += "CompareLeF16"; break;
case Opcode::CompareUnordNeF16: text += "CompareUnordNeF16"; break;
case Opcode::CompareMaskEqF16: text += "CompareMaskEqF16"; break;
case Opcode::CompareMaskNeF16: text += "CompareMaskNeF16"; break;
case Opcode::CompareMaskGtF16: text += "CompareMaskGtF16"; break;
case Opcode::CompareMaskGeF16: text += "CompareMaskGeF16"; break;
case Opcode::CompareMaskLtF16: text += "CompareMaskLtF16"; break;
case Opcode::CompareMaskLeF16: text += "CompareMaskLeF16"; break;
case Opcode::CompareMaskUnordNeF16: text += "CompareMaskUnordNeF16"; break;
case Opcode::CompareMaskUnordGeF16: text += "CompareMaskUnordGeF16"; break;
case Opcode::CompareMaskEqF32: text += "CompareMaskEqF32"; break;
case Opcode::CompareMaskNeF32: text += "CompareMaskNeF32"; break;
case Opcode::CompareMaskGtF32: text += "CompareMaskGtF32"; break;
case Opcode::CompareMaskGeF32: text += "CompareMaskGeF32"; break;
case Opcode::CompareMaskLtF32: text += "CompareMaskLtF32"; break;
case Opcode::CompareMaskLeF32: text += "CompareMaskLeF32"; break;
case Opcode::CompareMaskUnordEqF32: text += "CompareMaskUnordEqF32"; break;
case Opcode::CompareMaskUnordNeF32: text += "CompareMaskUnordNeF32"; break;
case Opcode::CompareMaskUnordGtF32: text += "CompareMaskUnordGtF32"; break;
case Opcode::CompareMaskUnordGeF32: text += "CompareMaskUnordGeF32"; break;
case Opcode::CompareMaskUnordLtF32: text += "CompareMaskUnordLtF32"; break;
case Opcode::CompareMaskUnordLeF32: text += "CompareMaskUnordLeF32"; break;
case Opcode::ConvertByteU32ToF32: text += "ConvertByteU32ToF32"; break;
case Opcode::ConvertU32ToF32: text += "ConvertU32ToF32"; break;
case Opcode::ConvertI32ToF32: text += "ConvertI32ToF32"; break;
case Opcode::ConvertF32ToU32: text += "ConvertF32ToU32"; break;
case Opcode::ConvertF32ToI32: text += "ConvertF32ToI32"; break;
case Opcode::ConvertF32ToF16: text += "ConvertF32ToF16"; break;
case Opcode::ConvertF16ToF32: text += "ConvertF16ToF32"; break;
case Opcode::ConvertU16ToF16: text += "ConvertU16ToF16"; break;
case Opcode::ConvertF16ToU16: text += "ConvertF16ToU16"; break;
case Opcode::ConvertI16ToF16: text += "ConvertI16ToF16"; break;
case Opcode::ConvertF16ToI16: text += "ConvertF16ToI16"; break;
case Opcode::ConvertRoundPlusInfF32ToI32: text += "ConvertRoundPlusInfF32ToI32"; break;
case Opcode::ConvertFloorF32ToI32: text += "ConvertFloorF32ToI32"; break;
case Opcode::ConvertI4ToOffsetF32: text += "ConvertI4ToOffsetF32"; break;
case Opcode::LdexpF32: text += "LdexpF32"; break;
case Opcode::PackF32ToF16Rtz: text += "PackF32ToF16Rtz"; break;
case Opcode::PackSnorm2x16F32: text += "PackSnorm2x16F32"; break;
case Opcode::PackUnorm2x16F32: text += "PackUnorm2x16F32"; break;
case Opcode::PackU16U32: text += "PackU16U32"; break;
case Opcode::PackU8F32: text += "PackU8F32"; break;
case Opcode::PackB32F16: text += "PackB32F16"; break;
case Opcode::PackedMadI16: text += "PackedMadI16"; break;
case Opcode::PackedMulLoU16: text += "PackedMulLoU16"; break;
case Opcode::PackedAddI16: text += "PackedAddI16"; break;
case Opcode::PackedSubI16: text += "PackedSubI16"; break;
case Opcode::PackedLshlrevB16: text += "PackedLshlrevB16"; break;
case Opcode::PackedLshrrevB16: text += "PackedLshrrevB16"; break;
case Opcode::PackedAshrrevI16: text += "PackedAshrrevI16"; break;
case Opcode::PackedMaxI16: text += "PackedMaxI16"; break;
case Opcode::PackedMinI16: text += "PackedMinI16"; break;
case Opcode::PackedMadU16: text += "PackedMadU16"; break;
case Opcode::PackedAddU16: text += "PackedAddU16"; break;
case Opcode::PackedSubU16: text += "PackedSubU16"; break;
case Opcode::PackedMaxU16: text += "PackedMaxU16"; break;
case Opcode::PackedMinU16: text += "PackedMinU16"; break;
case Opcode::PackedAddF16: text += "PackedAddF16"; break;
case Opcode::PackedMulF16: text += "PackedMulF16"; break;
case Opcode::PackedMinF16: text += "PackedMinF16"; break;
case Opcode::PackedMaxF16: text += "PackedMaxF16"; break;
case Opcode::PackedFmaF16: text += "PackedFmaF16"; break;
case Opcode::AddF16: text += "AddF16"; break;
case Opcode::SubF16: text += "SubF16"; break;
case Opcode::MulF16: text += "MulF16"; break;
case Opcode::MinF16: text += "MinF16"; break;
case Opcode::MaxF16: text += "MaxF16"; break;
case Opcode::FmaF16: text += "FmaF16"; break;
case Opcode::MadMixF16: text += "MadMixF16"; break;
case Opcode::IAddU16: text += "IAddU16"; break;
case Opcode::ISubI16: text += "ISubI16"; break;
case Opcode::IMinI16: text += "IMinI16"; break;
case Opcode::IMaxI16: text += "IMaxI16"; break;
case Opcode::UMinU16: text += "UMinU16"; break;
case Opcode::UMaxU16: text += "UMaxU16"; break;
case Opcode::RcpF32: text += "RcpF32"; break;
case Opcode::FractF32: text += "FractF32"; break;
case Opcode::TruncF32: text += "TruncF32"; break;
case Opcode::CeilF32: text += "CeilF32"; break;
case Opcode::RoundEvenF32: text += "RoundEvenF32"; break;
case Opcode::FloorF32: text += "FloorF32"; break;
case Opcode::Exp2F32: text += "Exp2F32"; break;
case Opcode::Log2F32: text += "Log2F32"; break;
case Opcode::InverseSqrtF32: text += "InverseSqrtF32"; break;
case Opcode::SqrtF32: text += "SqrtF32"; break;
case Opcode::RcpF16: text += "RcpF16"; break;
case Opcode::SqrtF16: text += "SqrtF16"; break;
case Opcode::InverseSqrtF16: text += "InverseSqrtF16"; break;
case Opcode::Log2F16: text += "Log2F16"; break;
case Opcode::Exp2F16: text += "Exp2F16"; break;
case Opcode::FloorF16: text += "FloorF16"; break;
case Opcode::CeilF16: text += "CeilF16"; break;
case Opcode::TruncF16: text += "TruncF16"; break;
case Opcode::RoundEvenF16: text += "RoundEvenF16"; break;
case Opcode::SinF32: text += "SinF32"; break;
case Opcode::CosF32: text += "CosF32"; break;
case Opcode::CubeIdF32: text += "CubeIdF32"; break;
case Opcode::CubeScF32: text += "CubeScF32"; break;
case Opcode::CubeTcF32: text += "CubeTcF32"; break;
case Opcode::CubeMaF32: text += "CubeMaF32"; break;
case Opcode::FAddF32: text += "FAddF32"; break;
case Opcode::FSubF32: text += "FSubF32"; break;
case Opcode::FMulF32: text += "FMulF32"; break;
case Opcode::FMinF32: text += "FMinF32"; break;
case Opcode::FMaxF32: text += "FMaxF32"; break;
case Opcode::FMadF32: text += "FMadF32"; break;
case Opcode::Dot2AccF32F16: text += "Dot2AccF32F16"; break;
case Opcode::FMin3F32: text += "FMin3F32"; break;
case Opcode::FMax3F32: text += "FMax3F32"; break;
case Opcode::FMed3F32: text += "FMed3F32"; break;
case Opcode::Min3F16: text += "Min3F16"; break;
case Opcode::Max3F16: text += "Max3F16"; break;
case Opcode::Med3F16: text += "Med3F16"; break;
case Opcode::LoadSrtDword: text += "LoadSrtDword"; break;
case Opcode::SLoadDword: text += "SLoadDword"; break;
case Opcode::SBufferLoadDword: text += "SBufferLoadDword"; break;
case Opcode::BufferLoadUbyte: text += "BufferLoadUbyte"; break;
case Opcode::BufferLoadSbyte: text += "BufferLoadSbyte"; break;
case Opcode::BufferLoadUshort: text += "BufferLoadUshort"; break;
case Opcode::BufferLoadSshort: text += "BufferLoadSshort"; break;
case Opcode::BufferLoadDword: text += "BufferLoadDword"; break;
case Opcode::BufferStoreByte: text += "BufferStoreByte"; break;
case Opcode::BufferStoreShort: text += "BufferStoreShort"; break;
case Opcode::BufferStoreDword: text += "BufferStoreDword"; break;
case Opcode::AtomicSwapU32: text += "AtomicSwapU32"; break;
case Opcode::AtomicAddU32: text += "AtomicAddU32"; break;
case Opcode::AtomicSubU32: text += "AtomicSubU32"; break;
case Opcode::AtomicSMinI32: text += "AtomicSMinI32"; break;
case Opcode::AtomicUMinU32: text += "AtomicUMinU32"; break;
case Opcode::AtomicSMaxI32: text += "AtomicSMaxI32"; break;
case Opcode::AtomicUMaxU32: text += "AtomicUMaxU32"; break;
case Opcode::AtomicAndU32: text += "AtomicAndU32"; break;
case Opcode::AtomicOrU32: text += "AtomicOrU32"; break;
case Opcode::AtomicXorU32: text += "AtomicXorU32"; break;
case Opcode::FlatLoadUbyte: text += "FlatLoadUbyte"; break;
case Opcode::FlatLoadSbyte: text += "FlatLoadSbyte"; break;
case Opcode::FlatLoadUshort: text += "FlatLoadUshort"; break;
case Opcode::FlatLoadSshort: text += "FlatLoadSshort"; break;
case Opcode::FlatLoadDword: text += "FlatLoadDword"; break;
case Opcode::FlatStoreByte: text += "FlatStoreByte"; break;
case Opcode::FlatStoreShort: text += "FlatStoreShort"; break;
case Opcode::FlatStoreDword: text += "FlatStoreDword"; break;
case Opcode::DsReadUbyte: text += "DsReadUbyte"; break;
case Opcode::DsReadSbyte: text += "DsReadSbyte"; break;
case Opcode::DsReadUshort: text += "DsReadUshort"; break;
case Opcode::DsReadSshort: text += "DsReadSshort"; break;
case Opcode::DsReadB32: text += "DsReadB32"; break;
case Opcode::DsWriteByte: text += "DsWriteByte"; break;
case Opcode::DsWriteShort: text += "DsWriteShort"; break;
case Opcode::DsWriteB32: text += "DsWriteB32"; break;
case Opcode::DsMinF32: text += "DsMinF32"; break;
case Opcode::DsMaxF32: text += "DsMaxF32"; break;
case Opcode::DsSwizzleB32: text += "DsSwizzleB32"; break;
case Opcode::DsConsume: text += "DsConsume"; break;
case Opcode::DsAppend: text += "DsAppend"; break;
case Opcode::DsWriteAddtidB32: text += "DsWriteAddtidB32"; break;
case Opcode::DsReadAddtidB32: text += "DsReadAddtidB32"; break;
case Opcode::ImageGetResinfo: text += "ImageGetResinfo"; break;
case Opcode::ImageGetLod: text += "ImageGetLod"; break;
case Opcode::ImageLoad: text += "ImageLoad"; break;
case Opcode::ImageStore: text += "ImageStore"; break;
case Opcode::ImageSample: text += "ImageSample"; break;
case Opcode::ImageGather4: text += "ImageGather4"; break;
case Opcode::LoadInputF32: text += "LoadInputF32"; break;
case Opcode::Export: text += "Export"; break;
}
text += " "; text += " ";
text += OperandToString(inst.dst); text += OperandToString(inst.dst);
if (inst.dst2.kind != OperandKind::Null) { if (inst.dst2.kind != OperandKind::Null) {
@@ -569,6 +569,10 @@ bool LowerMemoryInstruction(const Decoder::Instruction& decoded, BasicBlock& blo
return LowerBufferAtomicDword(decoded, block, Opcode::AtomicOrU32, error); return LowerBufferAtomicDword(decoded, block, Opcode::AtomicOrU32, error);
case Decoder::Opcode::BufferAtomicXor: case Decoder::Opcode::BufferAtomicXor:
return LowerBufferAtomicDword(decoded, block, Opcode::AtomicXorU32, error); return LowerBufferAtomicDword(decoded, block, Opcode::AtomicXorU32, error);
case Decoder::Opcode::BufferAtomicFMin:
return LowerBufferAtomicDword(decoded, block, Opcode::AtomicFMinF32, error);
case Decoder::Opcode::BufferAtomicFMax:
return LowerBufferAtomicDword(decoded, block, Opcode::AtomicFMaxF32, error);
case Decoder::Opcode::FlatLoadUbyte: case Decoder::Opcode::FlatLoadUbyte:
case Decoder::Opcode::FlatLoadSbyte: case Decoder::Opcode::FlatLoadSbyte:
case Decoder::Opcode::FlatLoadUshort: case Decoder::Opcode::FlatLoadUshort:
@@ -673,7 +677,12 @@ bool LowerMemoryInstruction(const Decoder::Instruction& decoded, BasicBlock& blo
case Decoder::Opcode::ImageAtomicAnd: case Decoder::Opcode::ImageAtomicAnd:
case Decoder::Opcode::ImageAtomicOr: case Decoder::Opcode::ImageAtomicOr:
case Decoder::Opcode::ImageAtomicXor: return LowerImageAtomicU32(decoded, block, error); case Decoder::Opcode::ImageAtomicXor: return LowerImageAtomicU32(decoded, block, error);
default: return false; default:
if (error != nullptr) {
*error = fmt::format("memory opcode has no specialized IR lowering: {}",
Decoder::OpcodeToString(decoded.opcode));
}
return false;
} }
} }
@@ -729,6 +738,8 @@ bool IsMemoryOpcode(Decoder::Opcode opcode) {
case Decoder::Opcode::BufferAtomicAnd: case Decoder::Opcode::BufferAtomicAnd:
case Decoder::Opcode::BufferAtomicOr: case Decoder::Opcode::BufferAtomicOr:
case Decoder::Opcode::BufferAtomicXor: case Decoder::Opcode::BufferAtomicXor:
case Decoder::Opcode::BufferAtomicFMin:
case Decoder::Opcode::BufferAtomicFMax:
case Decoder::Opcode::FlatLoadUbyte: case Decoder::Opcode::FlatLoadUbyte:
case Decoder::Opcode::FlatLoadSbyte: case Decoder::Opcode::FlatLoadSbyte:
case Decoder::Opcode::FlatLoadUshort: case Decoder::Opcode::FlatLoadUshort:
@@ -82,6 +82,7 @@ constexpr LowerMap LOWER_OPS[] = {
{Decoder::Opcode::SCmpGeU32, Opcode::CompareGeU32}, {Decoder::Opcode::SCmpGeU32, Opcode::CompareGeU32},
{Decoder::Opcode::SCmpLtU32, Opcode::CompareLtU32}, {Decoder::Opcode::SCmpLtU32, Opcode::CompareLtU32},
{Decoder::Opcode::SCmpLeU32, Opcode::CompareLeU32}, {Decoder::Opcode::SCmpLeU32, Opcode::CompareLeU32},
{Decoder::Opcode::SCmpEqU64, Opcode::CompareEqU64},
{Decoder::Opcode::SCmpLgU64, Opcode::CompareNeU64}, {Decoder::Opcode::SCmpLgU64, Opcode::CompareNeU64},
{Decoder::Opcode::VReadfirstlaneB32, Opcode::ReadFirstLaneU32}, {Decoder::Opcode::VReadfirstlaneB32, Opcode::ReadFirstLaneU32},
{Decoder::Opcode::VReadlaneB32, Opcode::ReadLaneU32}, {Decoder::Opcode::VReadlaneB32, Opcode::ReadLaneU32},
@@ -325,6 +326,7 @@ constexpr LowerMap LOWER_OPS[] = {
{Decoder::Opcode::VCmpNeU32, Opcode::CompareNeU32}, {Decoder::Opcode::VCmpNeU32, Opcode::CompareNeU32},
{Decoder::Opcode::VCmpGeU32, Opcode::CompareGeU32}, {Decoder::Opcode::VCmpGeU32, Opcode::CompareGeU32},
{Decoder::Opcode::VCmpTU32, Opcode::CompareTrue}, {Decoder::Opcode::VCmpTU32, Opcode::CompareTrue},
{Decoder::Opcode::VCmpEqI64, Opcode::CompareEqU64},
{Decoder::Opcode::VCmpNeU64, Opcode::CompareNeU64}, {Decoder::Opcode::VCmpNeU64, Opcode::CompareNeU64},
{Decoder::Opcode::VCmpxLtU32, Opcode::CompareMaskLtU32}, {Decoder::Opcode::VCmpxLtU32, Opcode::CompareMaskLtU32},
{Decoder::Opcode::VCmpxEqU32, Opcode::CompareMaskEqU32}, {Decoder::Opcode::VCmpxEqU32, Opcode::CompareMaskEqU32},
@@ -350,6 +352,8 @@ constexpr LowerMap LOWER_OPS[] = {
{Decoder::Opcode::BufferAtomicAnd, Opcode::AtomicAndU32}, {Decoder::Opcode::BufferAtomicAnd, Opcode::AtomicAndU32},
{Decoder::Opcode::BufferAtomicOr, Opcode::AtomicOrU32}, {Decoder::Opcode::BufferAtomicOr, Opcode::AtomicOrU32},
{Decoder::Opcode::BufferAtomicXor, Opcode::AtomicXorU32}, {Decoder::Opcode::BufferAtomicXor, Opcode::AtomicXorU32},
{Decoder::Opcode::BufferAtomicFMin, Opcode::AtomicFMinF32},
{Decoder::Opcode::BufferAtomicFMax, Opcode::AtomicFMaxF32},
{Decoder::Opcode::FlatLoadUbyte, Opcode::FlatLoadUbyte}, {Decoder::Opcode::FlatLoadUbyte, Opcode::FlatLoadUbyte},
{Decoder::Opcode::FlatLoadSbyte, Opcode::FlatLoadSbyte}, {Decoder::Opcode::FlatLoadSbyte, Opcode::FlatLoadSbyte},
{Decoder::Opcode::FlatLoadSshort, Opcode::FlatLoadSshort}, {Decoder::Opcode::FlatLoadSshort, Opcode::FlatLoadSshort},
@@ -432,22 +436,13 @@ void SetError(std::string* error, const char* message) {
} }
} }
Opcode LookupIrOpcode(Decoder::Opcode opcode) { std::optional<Opcode> LookupIrOpcode(Decoder::Opcode opcode) {
for (const auto& op: LOWER_OPS) { for (const auto& op: LOWER_OPS) {
if (op.decoded == opcode) { if (op.decoded == opcode) {
return op.ir; return op.ir;
} }
} }
return Opcode::MoveU32; return std::nullopt;
}
bool IsImplemented(Decoder::Opcode opcode) {
for (const auto& op: LOWER_OPS) {
if (op.decoded == opcode) {
return true;
}
}
return false;
} }
bool IsReversedBinary(Decoder::Opcode opcode) { bool IsReversedBinary(Decoder::Opcode opcode) {
@@ -0,0 +1,342 @@
// Central IR opcode catalog. Keep semantic categories here so enum identity, logging,
// and generic analysis cannot drift apart.
IR_OPCODE(ControlNop, General)
IR_OPCODE(Waitcnt, General)
IR_OPCODE(Barrier, General)
IR_OPCODE(Sendmsg, General)
IR_OPCODE(TtraceData, General)
IR_OPCODE(InstPrefetch, General)
IR_OPCODE(MoveU32, General)
IR_OPCODE(MoveF32Bits, General)
IR_OPCODE(MoveRelDestU32, General)
IR_OPCODE(MoveRelSourceU32, General)
IR_OPCODE(MoveU64, General)
IR_OPCODE(WqmB64, General)
IR_OPCODE(SaveexecB32, General)
IR_OPCODE(SaveexecB64, General)
IR_OPCODE(ReadFirstLaneU32, General)
IR_OPCODE(ReadLaneU32, General)
IR_OPCODE(WriteLaneU32, General)
IR_OPCODE(Permlane16B32, General)
IR_OPCODE(Permlanex16B32, General)
IR_OPCODE(AbsI32, General)
IR_OPCODE(IAddU32, General)
IR_OPCODE(IAddCarryU32, General)
IR_OPCODE(ISubBorrowU32, General)
IR_OPCODE(ScalarAddCarryU32, General)
IR_OPCODE(ScalarSubBorrowU32, General)
IR_OPCODE(ScalarSubBorrowCarryU32, General)
IR_OPCODE(ScalarSignedAddOverflowI32, General)
IR_OPCODE(ScalarSignedSubOverflowI32, General)
IR_OPCODE(ScalarShiftLeftAddCarryU32, General)
IR_OPCODE(ISubU32, General)
IR_OPCODE(IMulU32, General)
IR_OPCODE(UMulHighU32, General)
IR_OPCODE(SMulHighI32, General)
IR_OPCODE(IMadI24U32, General)
IR_OPCODE(UMadU24U32, General)
IR_OPCODE(UMadU64U32, General)
IR_OPCODE(SadU32, General)
IR_OPCODE(IAdd3U32, General)
IR_OPCODE(IMulI24U32, General)
IR_OPCODE(UMulU24U32, General)
IR_OPCODE(IMinI32, General)
IR_OPCODE(IMaxI32, General)
IR_OPCODE(IMin3I32, General)
IR_OPCODE(IMax3I32, General)
IR_OPCODE(IMed3I32, General)
IR_OPCODE(UMinU32, General)
IR_OPCODE(UMaxU32, General)
IR_OPCODE(UMin3U32, General)
IR_OPCODE(UMax3U32, General)
IR_OPCODE(UMed3U32, General)
IR_OPCODE(BitwiseAndU32, General)
IR_OPCODE(BitwiseAndU64, General)
IR_OPCODE(BitwiseAndNotU32, General)
IR_OPCODE(BitwiseAndNotU64, General)
IR_OPCODE(BitwiseOrU32, General)
IR_OPCODE(BitwiseOrU64, General)
IR_OPCODE(BitwiseOrNotU32, General)
IR_OPCODE(BitwiseOrNotU64, General)
IR_OPCODE(BitwiseAndOrU32, General)
IR_OPCODE(BitwiseOr3U32, General)
IR_OPCODE(BitwiseXorU32, General)
IR_OPCODE(BitwiseXorU64, General)
IR_OPCODE(BitwiseXor3U32, General)
IR_OPCODE(BitwiseNandU32, General)
IR_OPCODE(BitwiseNandU64, General)
IR_OPCODE(BitwiseNorU32, General)
IR_OPCODE(BitwiseNorU64, General)
IR_OPCODE(BitwiseXnorU32, General)
IR_OPCODE(BitwiseXnorU64, General)
IR_OPCODE(BitwiseNotU32, General)
IR_OPCODE(BitwiseNotU64, General)
IR_OPCODE(BitClearU32, General)
IR_OPCODE(BitSetU32, General)
IR_OPCODE(BitReverseU32, General)
IR_OPCODE(BitCountU32, General)
IR_OPCODE(BitCountU64, General)
IR_OPCODE(BitReplicateB64B32, General)
IR_OPCODE(BitCountAddU32, General)
IR_OPCODE(MaskedBitCountLowU32, General)
IR_OPCODE(MaskedBitCountHighU32, General)
IR_OPCODE(FindLsbU32, General)
IR_OPCODE(FindMsbFromHighU32, General)
IR_OPCODE(FindMsbFromHighU64, General)
IR_OPCODE(BitFieldMaskU32, General)
IR_OPCODE(BitFieldMaskU64, General)
IR_OPCODE(BitFieldExtractU32, General)
IR_OPCODE(BitFieldExtractU64, General)
IR_OPCODE(BitFieldExtract3U32, General)
IR_OPCODE(BitFieldExtract3I32, General)
IR_OPCODE(BitFieldInsertSelectU32, General)
IR_OPCODE(BitCompare0B32, General)
IR_OPCODE(BitCompare1B32, General)
IR_OPCODE(AlignBitU32, General)
IR_OPCODE(ShiftLeftAddU32, General)
IR_OPCODE(AddShiftLeftU32, General)
IR_OPCODE(XorAddU32, General)
IR_OPCODE(ShiftLeftOrU32, General)
IR_OPCODE(ShiftLeftLogicalU32, General)
IR_OPCODE(ShiftLeftLogicalU64, General)
IR_OPCODE(ShiftLeftLogicalU16, General)
IR_OPCODE(ShiftRightLogicalU32, General)
IR_OPCODE(ShiftRightLogicalU64, General)
IR_OPCODE(ShiftRightLogicalU16, General)
IR_OPCODE(ShiftRightArithmeticI32, General)
IR_OPCODE(ShiftRightArithmeticI16, General)
IR_OPCODE(SelectU32, General)
IR_OPCODE(SelectMaskU32, General)
IR_OPCODE(SelectF32Bits, General)
IR_OPCODE(SelectMaskF32Bits, General)
IR_OPCODE(SelectU64, General)
IR_OPCODE(PackLowLowU16, General)
IR_OPCODE(PackLowHighU16, General)
IR_OPCODE(PackHighHighU16, General)
IR_OPCODE(CompareFalse, Compare)
IR_OPCODE(CompareTrue, Compare)
IR_OPCODE(CompareEqU32, Compare)
IR_OPCODE(CompareNeU32, Compare)
IR_OPCODE(CompareGtU32, Compare)
IR_OPCODE(CompareGeU32, Compare)
IR_OPCODE(CompareLtU32, Compare)
IR_OPCODE(CompareLeU32, Compare)
IR_OPCODE(CompareEqU64, Compare64)
IR_OPCODE(CompareNeU64, Compare64)
IR_OPCODE(CompareMaskEqU32, Compare)
IR_OPCODE(CompareMaskNeU32, Compare)
IR_OPCODE(CompareMaskGtU32, Compare)
IR_OPCODE(CompareMaskGeU32, Compare)
IR_OPCODE(CompareMaskLtU32, Compare)
IR_OPCODE(CompareMaskLeU32, Compare)
IR_OPCODE(CompareEqI32, Compare)
IR_OPCODE(CompareNeI32, Compare)
IR_OPCODE(CompareGtI32, Compare)
IR_OPCODE(CompareGeI32, Compare)
IR_OPCODE(CompareLtI32, Compare)
IR_OPCODE(CompareLeI32, Compare)
IR_OPCODE(CompareEqI16, Compare)
IR_OPCODE(CompareNeI16, Compare)
IR_OPCODE(CompareGtI16, Compare)
IR_OPCODE(CompareGeI16, Compare)
IR_OPCODE(CompareLtI16, Compare)
IR_OPCODE(CompareLeI16, Compare)
IR_OPCODE(CompareMaskEqI32, Compare)
IR_OPCODE(CompareMaskNeI32, Compare)
IR_OPCODE(CompareMaskGtI32, Compare)
IR_OPCODE(CompareMaskGeI32, Compare)
IR_OPCODE(CompareMaskLtI32, Compare)
IR_OPCODE(CompareMaskLeI32, Compare)
IR_OPCODE(CompareEqU16, Compare)
IR_OPCODE(CompareNeU16, Compare)
IR_OPCODE(CompareGtU16, Compare)
IR_OPCODE(CompareGeU16, Compare)
IR_OPCODE(CompareLtU16, Compare)
IR_OPCODE(CompareLeU16, Compare)
IR_OPCODE(CompareEqF32, Compare)
IR_OPCODE(CompareNeF32, Compare)
IR_OPCODE(CompareGtF32, Compare)
IR_OPCODE(CompareGeF32, Compare)
IR_OPCODE(CompareLtF32, Compare)
IR_OPCODE(CompareLeF32, Compare)
IR_OPCODE(CompareOrderedF32, Compare)
IR_OPCODE(CompareUnorderedF32, Compare)
IR_OPCODE(CompareUnordEqF32, Compare)
IR_OPCODE(CompareUnordNeF32, Compare)
IR_OPCODE(CompareUnordGtF32, Compare)
IR_OPCODE(CompareUnordGeF32, Compare)
IR_OPCODE(CompareUnordLtF32, Compare)
IR_OPCODE(CompareUnordLeF32, Compare)
IR_OPCODE(CompareClassF32, Compare)
IR_OPCODE(CompareEqF16, Compare)
IR_OPCODE(CompareNeF16, Compare)
IR_OPCODE(CompareGtF16, Compare)
IR_OPCODE(CompareGeF16, Compare)
IR_OPCODE(CompareLtF16, Compare)
IR_OPCODE(CompareLeF16, Compare)
IR_OPCODE(CompareUnordNeF16, Compare)
IR_OPCODE(CompareMaskEqF16, Compare)
IR_OPCODE(CompareMaskNeF16, Compare)
IR_OPCODE(CompareMaskGtF16, Compare)
IR_OPCODE(CompareMaskGeF16, Compare)
IR_OPCODE(CompareMaskLtF16, Compare)
IR_OPCODE(CompareMaskLeF16, Compare)
IR_OPCODE(CompareMaskUnordNeF16, Compare)
IR_OPCODE(CompareMaskUnordGeF16, Compare)
IR_OPCODE(CompareMaskEqF32, Compare)
IR_OPCODE(CompareMaskNeF32, Compare)
IR_OPCODE(CompareMaskGtF32, Compare)
IR_OPCODE(CompareMaskGeF32, Compare)
IR_OPCODE(CompareMaskLtF32, Compare)
IR_OPCODE(CompareMaskLeF32, Compare)
IR_OPCODE(CompareMaskUnordEqF32, Compare)
IR_OPCODE(CompareMaskUnordNeF32, Compare)
IR_OPCODE(CompareMaskUnordGtF32, Compare)
IR_OPCODE(CompareMaskUnordGeF32, Compare)
IR_OPCODE(CompareMaskUnordLtF32, Compare)
IR_OPCODE(CompareMaskUnordLeF32, Compare)
IR_OPCODE(ConvertByteU32ToF32, General)
IR_OPCODE(ConvertU32ToF32, General)
IR_OPCODE(ConvertI32ToF32, General)
IR_OPCODE(ConvertF32ToU32, General)
IR_OPCODE(ConvertF32ToI32, General)
IR_OPCODE(ConvertF32ToF16, General)
IR_OPCODE(ConvertF16ToF32, General)
IR_OPCODE(ConvertU16ToF16, General)
IR_OPCODE(ConvertF16ToU16, General)
IR_OPCODE(ConvertI16ToF16, General)
IR_OPCODE(ConvertF16ToI16, General)
IR_OPCODE(ConvertRoundPlusInfF32ToI32, General)
IR_OPCODE(ConvertFloorF32ToI32, General)
IR_OPCODE(ConvertI4ToOffsetF32, General)
IR_OPCODE(LdexpF32, General)
IR_OPCODE(PackF32ToF16Rtz, General)
IR_OPCODE(PackSnorm2x16F32, General)
IR_OPCODE(PackUnorm2x16F32, General)
IR_OPCODE(PackU16U32, General)
IR_OPCODE(PackU8F32, General)
IR_OPCODE(PackB32F16, General)
IR_OPCODE(PackedMadI16, General)
IR_OPCODE(PackedMulLoU16, General)
IR_OPCODE(PackedAddI16, General)
IR_OPCODE(PackedSubI16, General)
IR_OPCODE(PackedLshlrevB16, General)
IR_OPCODE(PackedLshrrevB16, General)
IR_OPCODE(PackedAshrrevI16, General)
IR_OPCODE(PackedMaxI16, General)
IR_OPCODE(PackedMinI16, General)
IR_OPCODE(PackedMadU16, General)
IR_OPCODE(PackedAddU16, General)
IR_OPCODE(PackedSubU16, General)
IR_OPCODE(PackedMaxU16, General)
IR_OPCODE(PackedMinU16, General)
IR_OPCODE(PackedAddF16, General)
IR_OPCODE(PackedMulF16, General)
IR_OPCODE(PackedMinF16, General)
IR_OPCODE(PackedMaxF16, General)
IR_OPCODE(PackedFmaF16, General)
IR_OPCODE(AddF16, General)
IR_OPCODE(SubF16, General)
IR_OPCODE(MulF16, General)
IR_OPCODE(MinF16, General)
IR_OPCODE(MaxF16, General)
IR_OPCODE(FmaF16, General)
IR_OPCODE(MadMixF16, General)
IR_OPCODE(IAddU16, General)
IR_OPCODE(ISubI16, General)
IR_OPCODE(IMinI16, General)
IR_OPCODE(IMaxI16, General)
IR_OPCODE(UMinU16, General)
IR_OPCODE(UMaxU16, General)
IR_OPCODE(RcpF32, General)
IR_OPCODE(FractF32, General)
IR_OPCODE(TruncF32, General)
IR_OPCODE(CeilF32, General)
IR_OPCODE(RoundEvenF32, General)
IR_OPCODE(FloorF32, General)
IR_OPCODE(Exp2F32, General)
IR_OPCODE(Log2F32, General)
IR_OPCODE(InverseSqrtF32, General)
IR_OPCODE(SqrtF32, General)
IR_OPCODE(RcpF16, General)
IR_OPCODE(SqrtF16, General)
IR_OPCODE(InverseSqrtF16, General)
IR_OPCODE(Log2F16, General)
IR_OPCODE(Exp2F16, General)
IR_OPCODE(FloorF16, General)
IR_OPCODE(CeilF16, General)
IR_OPCODE(TruncF16, General)
IR_OPCODE(RoundEvenF16, General)
IR_OPCODE(SinF32, General)
IR_OPCODE(CosF32, General)
IR_OPCODE(CubeIdF32, General)
IR_OPCODE(CubeScF32, General)
IR_OPCODE(CubeTcF32, General)
IR_OPCODE(CubeMaF32, General)
IR_OPCODE(FAddF32, General)
IR_OPCODE(FSubF32, General)
IR_OPCODE(FMulF32, General)
IR_OPCODE(FMinF32, General)
IR_OPCODE(FMaxF32, General)
IR_OPCODE(FMadF32, General)
IR_OPCODE(Dot2AccF32F16, General)
IR_OPCODE(FMin3F32, General)
IR_OPCODE(FMax3F32, General)
IR_OPCODE(FMed3F32, General)
IR_OPCODE(Min3F16, General)
IR_OPCODE(Max3F16, General)
IR_OPCODE(Med3F16, General)
IR_OPCODE(LoadSrtDword, General)
IR_OPCODE(SLoadDword, General)
IR_OPCODE(SBufferLoadDword, General)
IR_OPCODE(BufferLoadUbyte, General)
IR_OPCODE(BufferLoadSbyte, General)
IR_OPCODE(BufferLoadUshort, General)
IR_OPCODE(BufferLoadSshort, General)
IR_OPCODE(BufferLoadDword, General)
IR_OPCODE(BufferStoreByte, General)
IR_OPCODE(BufferStoreShort, General)
IR_OPCODE(BufferStoreDword, General)
IR_OPCODE(AtomicSwapU32, General)
IR_OPCODE(AtomicAddU32, General)
IR_OPCODE(AtomicSubU32, General)
IR_OPCODE(AtomicSMinI32, General)
IR_OPCODE(AtomicUMinU32, General)
IR_OPCODE(AtomicSMaxI32, General)
IR_OPCODE(AtomicUMaxU32, General)
IR_OPCODE(AtomicAndU32, General)
IR_OPCODE(AtomicOrU32, General)
IR_OPCODE(AtomicXorU32, General)
IR_OPCODE(AtomicFMinF32, General)
IR_OPCODE(AtomicFMaxF32, General)
IR_OPCODE(FlatLoadUbyte, General)
IR_OPCODE(FlatLoadSbyte, General)
IR_OPCODE(FlatLoadUshort, General)
IR_OPCODE(FlatLoadSshort, General)
IR_OPCODE(FlatLoadDword, General)
IR_OPCODE(FlatStoreByte, General)
IR_OPCODE(FlatStoreShort, General)
IR_OPCODE(FlatStoreDword, General)
IR_OPCODE(DsReadUbyte, General)
IR_OPCODE(DsReadSbyte, General)
IR_OPCODE(DsReadUshort, General)
IR_OPCODE(DsReadSshort, General)
IR_OPCODE(DsReadB32, General)
IR_OPCODE(DsWriteByte, General)
IR_OPCODE(DsWriteShort, General)
IR_OPCODE(DsWriteB32, General)
IR_OPCODE(DsMinF32, General)
IR_OPCODE(DsMaxF32, General)
IR_OPCODE(DsSwizzleB32, General)
IR_OPCODE(DsConsume, General)
IR_OPCODE(DsAppend, General)
IR_OPCODE(DsWriteAddtidB32, General)
IR_OPCODE(DsReadAddtidB32, General)
IR_OPCODE(ImageGetResinfo, General)
IR_OPCODE(ImageGetLod, General)
IR_OPCODE(ImageLoad, General)
IR_OPCODE(ImageStore, General)
IR_OPCODE(ImageSample, General)
IR_OPCODE(ImageGather4, General)
IR_OPCODE(LoadInputF32, General)
IR_OPCODE(Export, General)
@@ -562,8 +562,7 @@ static bool EvaluateRuntimeSourcesImpl(const Program&
const auto dynamic = const auto dynamic =
std::find(program.srt.dynamic_sources.begin(), program.srt.dynamic_sources.end(), std::find(program.srt.dynamic_sources.begin(), program.srt.dynamic_sources.end(),
request.source) != program.srt.dynamic_sources.end(); request.source) != program.srt.dynamic_sources.end();
if (descriptor == nullptr || dynamic || if (descriptor == nullptr || dynamic) {
!DescriptorSourceResolved(program, request.source)) {
if (error != nullptr) { if (error != nullptr) {
*error = Diagnostic(program, request.use_pc, *error = Diagnostic(program, request.use_pc,
fmt::format("descriptor source {} is {}", request.source, fmt::format("descriptor source {} is {}", request.source,
+2 -2
View File
@@ -302,7 +302,7 @@ static void vs_check(const HW::VertexShaderInfo& vs, const HW::ShaderRegisters&
EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.ieee_mode != false); EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.ieee_mode != false);
EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.cu_group_enable != false); EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.cu_group_enable != false);
EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.require_forward_progress != false); EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.require_forward_progress != false);
EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.lds_configuration != false); EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.threadgroup_configuration != false);
EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.gs_vgpr_component_count != 3); EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.gs_vgpr_component_count != 3);
EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.fp16_overflow != false); EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc1.fp16_overflow != false);
EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc2.scratch_en != false); EXIT_NOT_IMPLEMENTED(vs.gs_regs.rsrc2.scratch_en != false);
@@ -374,7 +374,7 @@ static void ps_check(const HW::PsStageRegisters& ps, const HW::ShaderRegisters&
ps.rsrc2.extra_lds_size); ps.rsrc2.extra_lds_size);
} }
} }
EXIT_NOT_IMPLEMENTED(ps.rsrc2.raster_ordered_shading != false); EXIT_NOT_IMPLEMENTED(ps.rsrc2.raster_ordered_shading != 0);
EXIT_NOT_IMPLEMENTED(ps.rsrc2.shared_vgprs != 0); EXIT_NOT_IMPLEMENTED(ps.rsrc2.shared_vgprs != 0);
if (sh.shader_z_format != 0x00000000 && sh.shader_z_format != 0x00000001 && if (sh.shader_z_format != 0x00000000 && sh.shader_z_format != 0x00000001 &&
+1 -3
View File
@@ -803,7 +803,7 @@ int64_t KYTY_SYSV_ABI KernelLseek(int d, int64_t offset, int whence) {
return KERNEL_ERROR_ESPIPE; return KERNEL_ERROR_ESPIPE;
} }
file->mutex.Lock(); Common::LockGuard lock(file->mutex);
bool is_invalid = file->f.IsInvalid(); bool is_invalid = file->f.IsInvalid();
@@ -828,8 +828,6 @@ int64_t KYTY_SYSV_ABI KernelLseek(int d, int64_t offset, int whence) {
EXIT_IF(pos != offset); EXIT_IF(pos != offset);
file->mutex.Unlock();
if (is_invalid) { if (is_invalid) {
LOGF("\tfile is invalid\n"); LOGF("\tfile is invalid\n");
return KERNEL_ERROR_EIO; return KERNEL_ERROR_EIO;
+23 -2
View File
@@ -22,6 +22,13 @@ static bool GuestFreeRangeContains(uint64_t base, uint64_t range_size, uint64_t
return candidate <= range_end && size <= range_end - candidate; return candidate <= range_end && size <= range_end - candidate;
} }
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
static bool CoalescePlaceholders(uint64_t vaddr, uint64_t size) {
return size != 0 && VirtualFree(reinterpret_cast<void*>(vaddr), size,
MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS) != 0;
}
#endif
class GuestBackingStore { class GuestBackingStore {
public: public:
enum class FailureReason { enum class FailureReason {
@@ -264,6 +271,19 @@ public:
removed.push_back(entry); removed.push_back(entry);
} }
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
// Each unmapped file view becomes a separate Windows placeholder. The address-space owner
// records this contiguous guest span as one free range, so normalize fragmented backing
// views into one placeholder before publishing it there.
if (removed.size() > 1 && !CoalescePlaceholders(vaddr, size)) {
for (auto it = removed.rbegin(); it != removed.rend(); ++it) {
EXIT_IF(!MapExistingPlaceholderFixed(it->vaddr, it->size, it->backing_offset,
it->mode));
}
return false;
}
#endif
if (placeholder_preserved != nullptr) { if (placeholder_preserved != nullptr) {
*placeholder_preserved = true; *placeholder_preserved = true;
} }
@@ -832,6 +852,8 @@ public:
EXIT_IF(VirtualFree(reinterpret_cast<void*>(region_vaddr), region_size, EXIT_IF(VirtualFree(reinterpret_cast<void*>(region_vaddr), region_size,
MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER) == 0); MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER) == 0);
} }
// ReleaseCommitted also publishes the aggregate span as one logical free range.
EXIT_IF(regions.size() > 1 && !CoalescePlaceholders(vaddr, size));
#else #else
for (const auto& [region_vaddr, region_size]: regions) { for (const auto& [region_vaddr, region_size]: regions) {
RestorePlaceholder(region_vaddr, region_size); RestorePlaceholder(region_vaddr, region_size);
@@ -1252,8 +1274,7 @@ private:
if (base != it->first || end != it->first + it->second) { if (base != it->first || end != it->first + it->second) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS #if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (VirtualFree(reinterpret_cast<void*>(base), end - base, if (CoalescePlaceholders(base, end - base)) {
MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS) != 0) {
m_free.erase(first, last); m_free.erase(first, last);
m_free.emplace(base, end - base); m_free.emplace(base, end - base);
} }
+24 -7
View File
@@ -1849,8 +1849,9 @@ static int PthreadMutexInitNamed(PthreadMutex* mutex, const PthreadMutexattr* at
} }
} }
int KYTY_SYSV_ABI PthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr) { int KYTY_SYSV_ABI PthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr,
return PthreadMutexInitNamed(mutex, attr, ""); const char* name) {
return PthreadMutexInitNamed(mutex, attr, name);
} }
int KYTY_SYSV_ABI PthreadMutexDestroy(PthreadMutex* mutex) { int KYTY_SYSV_ABI PthreadMutexDestroy(PthreadMutex* mutex) {
@@ -2443,8 +2444,9 @@ static int PthreadRwlockInitNamed(PthreadRwlock* rwlock, const PthreadRwlockattr
return OK; return OK;
} }
int KYTY_SYSV_ABI PthreadRwlockInit(PthreadRwlock* rwlock, const PthreadRwlockattr* attr) { int KYTY_SYSV_ABI PthreadRwlockInit(PthreadRwlock* rwlock, const PthreadRwlockattr* attr,
return PthreadRwlockInitNamed(rwlock, attr, ""); const char* name) {
return PthreadRwlockInitNamed(rwlock, attr, name);
} }
static PthreadRwlockPrivate::Reader* RwlockFindReader(PthreadRwlock rwlock, Pthread thread) { static PthreadRwlockPrivate::Reader* RwlockFindReader(PthreadRwlock rwlock, Pthread thread) {
@@ -2938,8 +2940,9 @@ static int PthreadCondInitNamed(PthreadCond* cond, const PthreadCondattr* attr,
} }
} }
int KYTY_SYSV_ABI PthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr) { int KYTY_SYSV_ABI PthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr,
return PthreadCondInitNamed(cond, attr, ""); const char* name) {
return PthreadCondInitNamed(cond, attr, name);
} }
int KYTY_SYSV_ABI PthreadCondSignal(PthreadCond* cond) { int KYTY_SYSV_ABI PthreadCondSignal(PthreadCond* cond) {
@@ -4417,6 +4420,13 @@ int KYTY_SYSV_ABI pthread_cond_signal(LibKernel::PthreadCond* cond) {
return POSIX_PTHREAD_CALL(LibKernel::PthreadCondSignal(cond)); return POSIX_PTHREAD_CALL(LibKernel::PthreadCondSignal(cond));
} }
int KYTY_SYSV_ABI pthread_cond_init(LibKernel::PthreadCond* cond,
const LibKernel::PthreadCondattr* attr) {
PRINT_NAME();
return POSIX_PTHREAD_CALL(LibKernel::PthreadCondInit(cond, attr, nullptr));
}
int KYTY_SYSV_ABI pthread_condattr_setclock(LibKernel::PthreadCondattr* attr, int KYTY_SYSV_ABI pthread_condattr_setclock(LibKernel::PthreadCondattr* attr,
LibKernel::KernelClockid clock_id) { LibKernel::KernelClockid clock_id) {
PRINT_NAME(); PRINT_NAME();
@@ -4525,6 +4535,13 @@ int KYTY_SYSV_ABI pthread_rwlock_destroy(LibKernel::PthreadRwlock* rwlock) {
return POSIX_PTHREAD_CALL(LibKernel::PthreadRwlockDestroy(rwlock)); return POSIX_PTHREAD_CALL(LibKernel::PthreadRwlockDestroy(rwlock));
} }
int KYTY_SYSV_ABI pthread_rwlock_init(LibKernel::PthreadRwlock* rwlock,
const LibKernel::PthreadRwlockattr* attr) {
PRINT_NAME();
return POSIX_PTHREAD_CALL(LibKernel::PthreadRwlockInit(rwlock, attr, nullptr));
}
int KYTY_SYSV_ABI pthread_key_create(LibKernel::PthreadKey* key, int KYTY_SYSV_ABI pthread_key_create(LibKernel::PthreadKey* key,
LibKernel::pthread_key_destructor_func_t destructor) { LibKernel::pthread_key_destructor_func_t destructor) {
PRINT_NAME(); PRINT_NAME();
@@ -4560,7 +4577,7 @@ int KYTY_SYSV_ABI pthread_mutex_init(LibKernel::PthreadMutex* mutex,
const LibKernel::PthreadMutexattr* attr) { const LibKernel::PthreadMutexattr* attr) {
PRINT_NAME(); PRINT_NAME();
return POSIX_PTHREAD_CALL(LibKernel::PthreadMutexInit(mutex, attr)); return POSIX_PTHREAD_CALL(LibKernel::PthreadMutexInit(mutex, attr, nullptr));
} }
int KYTY_SYSV_ABI pthread_mutexattr_init(LibKernel::PthreadMutexattr* attr) { int KYTY_SYSV_ABI pthread_mutexattr_init(LibKernel::PthreadMutexattr* attr) {
+7 -3
View File
@@ -77,7 +77,8 @@ int KYTY_SYSV_ABI PthreadMutexattrInit(PthreadMutexattr* attr);
int KYTY_SYSV_ABI PthreadMutexattrDestroy(PthreadMutexattr* attr); int KYTY_SYSV_ABI PthreadMutexattrDestroy(PthreadMutexattr* attr);
int KYTY_SYSV_ABI PthreadMutexattrSettype(PthreadMutexattr* attr, int type); int KYTY_SYSV_ABI PthreadMutexattrSettype(PthreadMutexattr* attr, int type);
int KYTY_SYSV_ABI PthreadMutexattrSetprotocol(PthreadMutexattr* attr, int protocol); int KYTY_SYSV_ABI PthreadMutexattrSetprotocol(PthreadMutexattr* attr, int protocol);
int KYTY_SYSV_ABI PthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr); int KYTY_SYSV_ABI PthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr,
const char* name);
int KYTY_SYSV_ABI PthreadMutexDestroy(PthreadMutex* mutex); int KYTY_SYSV_ABI PthreadMutexDestroy(PthreadMutex* mutex);
int KYTY_SYSV_ABI PthreadMutexLock(PthreadMutex* mutex); int KYTY_SYSV_ABI PthreadMutexLock(PthreadMutex* mutex);
int KYTY_SYSV_ABI PthreadMutexTrylock(PthreadMutex* mutex); int KYTY_SYSV_ABI PthreadMutexTrylock(PthreadMutex* mutex);
@@ -159,7 +160,8 @@ int KYTY_SYSV_ABI PthreadAttrSetstackaddr(PthreadAttr* attr, void* addr);
int KYTY_SYSV_ABI PthreadAttrSetstacksize(PthreadAttr* attr, size_t stack_size); int KYTY_SYSV_ABI PthreadAttrSetstacksize(PthreadAttr* attr, size_t stack_size);
int KYTY_SYSV_ABI PthreadRwlockDestroy(PthreadRwlock* rwlock); int KYTY_SYSV_ABI PthreadRwlockDestroy(PthreadRwlock* rwlock);
int KYTY_SYSV_ABI PthreadRwlockInit(PthreadRwlock* rwlock, const PthreadRwlockattr* attr); int KYTY_SYSV_ABI PthreadRwlockInit(PthreadRwlock* rwlock, const PthreadRwlockattr* attr,
const char* name);
int KYTY_SYSV_ABI PthreadRwlockRdlock(PthreadRwlock* rwlock); int KYTY_SYSV_ABI PthreadRwlockRdlock(PthreadRwlock* rwlock);
int KYTY_SYSV_ABI PthreadRwlockTimedrdlock(PthreadRwlock* rwlock, KernelUseconds usec); int KYTY_SYSV_ABI PthreadRwlockTimedrdlock(PthreadRwlock* rwlock, KernelUseconds usec);
int KYTY_SYSV_ABI PthreadRwlockTimedwrlock(PthreadRwlock* rwlock, KernelUseconds usec); int KYTY_SYSV_ABI PthreadRwlockTimedwrlock(PthreadRwlock* rwlock, KernelUseconds usec);
@@ -177,7 +179,7 @@ int KYTY_SYSV_ABI PthreadCondattrInit(PthreadCondattr* attr);
int KYTY_SYSV_ABI PthreadCondattrSetclock(PthreadCondattr* attr, KernelClockid clock_id); int KYTY_SYSV_ABI PthreadCondattrSetclock(PthreadCondattr* attr, KernelClockid clock_id);
int KYTY_SYSV_ABI PthreadCondBroadcast(PthreadCond* cond); int KYTY_SYSV_ABI PthreadCondBroadcast(PthreadCond* cond);
int KYTY_SYSV_ABI PthreadCondDestroy(PthreadCond* cond); int KYTY_SYSV_ABI PthreadCondDestroy(PthreadCond* cond);
int KYTY_SYSV_ABI PthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr); int KYTY_SYSV_ABI PthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr, const char* name);
int KYTY_SYSV_ABI PthreadCondSignal(PthreadCond* cond); int KYTY_SYSV_ABI PthreadCondSignal(PthreadCond* cond);
int KYTY_SYSV_ABI PthreadCondSignalto(PthreadCond* cond, Pthread thread); int KYTY_SYSV_ABI PthreadCondSignalto(PthreadCond* cond, Pthread thread);
int KYTY_SYSV_ABI PthreadCondTimedwait(PthreadCond* cond, PthreadMutex* mutex, KernelUseconds usec); int KYTY_SYSV_ABI PthreadCondTimedwait(PthreadCond* cond, PthreadMutex* mutex, KernelUseconds usec);
@@ -267,6 +269,8 @@ int KYTY_SYSV_ABI pthread_rwlock_rdlock(LibKernel::PthreadRwlock* rwlock);
int KYTY_SYSV_ABI pthread_rwlock_unlock(LibKernel::PthreadRwlock* rwlock); int KYTY_SYSV_ABI pthread_rwlock_unlock(LibKernel::PthreadRwlock* rwlock);
int KYTY_SYSV_ABI pthread_rwlock_wrlock(LibKernel::PthreadRwlock* rwlock); int KYTY_SYSV_ABI pthread_rwlock_wrlock(LibKernel::PthreadRwlock* rwlock);
int KYTY_SYSV_ABI pthread_rwlock_destroy(LibKernel::PthreadRwlock* rwlock); int KYTY_SYSV_ABI pthread_rwlock_destroy(LibKernel::PthreadRwlock* rwlock);
int KYTY_SYSV_ABI pthread_rwlock_init(LibKernel::PthreadRwlock* rwlock,
const LibKernel::PthreadRwlockattr* attr);
int KYTY_SYSV_ABI pthread_key_create(LibKernel::PthreadKey* key, int KYTY_SYSV_ABI pthread_key_create(LibKernel::PthreadKey* key,
LibKernel::pthread_key_destructor_func_t destructor); LibKernel::pthread_key_destructor_func_t destructor);
int KYTY_SYSV_ABI pthread_key_delete(LibKernel::PthreadKey key); int KYTY_SYSV_ABI pthread_key_delete(LibKernel::PthreadKey key);
@@ -88,6 +88,16 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1">
<widget class="QCheckBox" name="checkBox_red_zone_protection">
<property name="toolTip">
<string>Helps prevent crashes in some games on Windows</string>
</property>
<property name="text">
<string>Windows SysV red zone crash protection (Experimental)</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</item> </item>
+16 -3
View File
@@ -88,7 +88,7 @@ public:
bool fullscreen_enabled = false; bool fullscreen_enabled = false;
int vblank_frequency = 60; int vblank_frequency = 60;
int console_language = DEFAULT_CONSOLE_LANGUAGE; int console_language = DEFAULT_CONSOLE_LANGUAGE;
bool vulkan_validation_enabled = true; bool vulkan_validation_enabled = false;
bool shader_validation_enabled = true; bool shader_validation_enabled = true;
ShaderOptimizationType shader_optimization_type = ShaderOptimizationType::Performance; ShaderOptimizationType shader_optimization_type = ShaderOptimizationType::Performance;
ShaderLogDirection shader_log_direction = ShaderLogDirection::Silent; ShaderLogDirection shader_log_direction = ShaderLogDirection::Silent;
@@ -99,7 +99,10 @@ public:
QString printf_output_file = "_kyty.txt"; QString printf_output_file = "_kyty.txt";
ProfilerDirection profiler_direction = ProfilerDirection::None; ProfilerDirection profiler_direction = ProfilerDirection::None;
bool renderdoc_enabled = false; bool renderdoc_enabled = false;
QStringList host_input_mapping; #if defined(_WIN32)
bool red_zone_protection_enabled = false;
#endif
QStringList host_input_mapping;
QString elf = QStringLiteral("eboot.bin"); QString elf = QStringLiteral("eboot.bin");
@@ -119,7 +122,10 @@ public:
printf_output_file = other.printf_output_file; printf_output_file = other.printf_output_file;
profiler_direction = other.profiler_direction; profiler_direction = other.profiler_direction;
renderdoc_enabled = other.renderdoc_enabled; renderdoc_enabled = other.renderdoc_enabled;
host_input_mapping = other.host_input_mapping; #if defined(_WIN32)
red_zone_protection_enabled = other.red_zone_protection_enabled;
#endif
host_input_mapping = other.host_input_mapping;
} }
void CopyFrom(const Configuration& other) { void CopyFrom(const Configuration& other) {
@@ -156,6 +162,9 @@ public:
KYTY_CFG_SET(printf_output_file); KYTY_CFG_SET(printf_output_file);
KYTY_CFG_SET(profiler_direction); KYTY_CFG_SET(profiler_direction);
KYTY_CFG_SET(renderdoc_enabled); KYTY_CFG_SET(renderdoc_enabled);
#if defined(_WIN32)
KYTY_CFG_SET(red_zone_protection_enabled);
#endif
s->setValue("host_input_mapping", host_input_mapping); s->setValue("host_input_mapping", host_input_mapping);
KYTY_CFG_SET(elf); KYTY_CFG_SET(elf);
} }
@@ -183,6 +192,10 @@ public:
KYTY_CFG_GET(printf_output_file); KYTY_CFG_GET(printf_output_file);
KYTY_CFG_GET(profiler_direction); KYTY_CFG_GET(profiler_direction);
KYTY_CFG_GET(renderdoc_enabled); KYTY_CFG_GET(renderdoc_enabled);
#if defined(_WIN32)
red_zone_protection_enabled =
s->value("red_zone_protection_enabled", red_zone_protection_enabled).toBool();
#endif
host_input_mapping = s->value("host_input_mapping", host_input_mapping).toStringList(); host_input_mapping = s->value("host_input_mapping", host_input_mapping).toStringList();
elf = s->value("elf", elf).toString(); elf = s->value("elf", elf).toString();
} }
+9 -1
View File
@@ -164,6 +164,11 @@ void ConfigurationEditDialog::Init(const Configuration& info) {
m_ui->checkBox_shader_validation->setChecked(info.shader_validation_enabled); m_ui->checkBox_shader_validation->setChecked(info.shader_validation_enabled);
m_ui->checkBox_vulkan_validation->setChecked(info.vulkan_validation_enabled); m_ui->checkBox_vulkan_validation->setChecked(info.vulkan_validation_enabled);
m_ui->checkBox_renderdoc_capture->setChecked(info.renderdoc_enabled); m_ui->checkBox_renderdoc_capture->setChecked(info.renderdoc_enabled);
#if defined(_WIN32)
m_ui->checkBox_red_zone_protection->setChecked(info.red_zone_protection_enabled);
#else
m_ui->checkBox_red_zone_protection->setVisible(false);
#endif
ListInit(m_ui->comboBox_shader_optimization_type, info.shader_optimization_type); ListInit(m_ui->comboBox_shader_optimization_type, info.shader_optimization_type);
ListInit(m_ui->comboBox_shader_log_direction, info.shader_log_direction); ListInit(m_ui->comboBox_shader_log_direction, info.shader_log_direction);
m_ui->lineEdit_shader_log_folder->setText(info.shader_log_folder); m_ui->lineEdit_shader_log_folder->setText(info.shader_log_folder);
@@ -286,7 +291,10 @@ static void UpdateInfo(Configuration& info, Ui::ConfigurationEditDialog& ui) {
info.vulkan_validation_enabled = ui.checkBox_vulkan_validation->isChecked(); info.vulkan_validation_enabled = ui.checkBox_vulkan_validation->isChecked();
info.shader_validation_enabled = ui.checkBox_shader_validation->isChecked(); info.shader_validation_enabled = ui.checkBox_shader_validation->isChecked();
info.renderdoc_enabled = ui.checkBox_renderdoc_capture->isChecked(); info.renderdoc_enabled = ui.checkBox_renderdoc_capture->isChecked();
info.shader_optimization_type = TextToEnum<Configuration::ShaderOptimizationType>( #if defined(_WIN32)
info.red_zone_protection_enabled = ui.checkBox_red_zone_protection->isChecked();
#endif
info.shader_optimization_type = TextToEnum<Configuration::ShaderOptimizationType>(
ui.comboBox_shader_optimization_type->currentText()); ui.comboBox_shader_optimization_type->currentText());
info.shader_log_direction = TextToEnum<Configuration::ShaderLogDirection>( info.shader_log_direction = TextToEnum<Configuration::ShaderLogDirection>(
ui.comboBox_shader_log_direction->currentText()); ui.comboBox_shader_log_direction->currentText());
+5
View File
@@ -220,6 +220,11 @@ static QStringList CreateEmulatorArgs(const Configuration& info) {
args << "--printf-output-file" << info.printf_output_file; args << "--printf-output-file" << info.printf_output_file;
args << "--profiler-direction" << EnumToText(info.profiler_direction); args << "--profiler-direction" << EnumToText(info.profiler_direction);
args << "--spirv-debug-printf" << "false"; args << "--spirv-debug-printf" << "false";
#if defined(_WIN32)
if (info.red_zone_protection_enabled) {
args << "--redzone";
}
#endif
for (const auto& binding: info.host_input_mapping) { for (const auto& binding: info.host_input_mapping) {
args << "--keymap" << binding; args << "--keymap" << binding;
} }
+19 -5
View File
@@ -82,6 +82,7 @@ public:
Id AudioOutOpen(int type, uint32_t samples_num, uint32_t freq, Format format); Id AudioOutOpen(int type, uint32_t samples_num, uint32_t freq, Format format);
bool AudioOutClose(Id handle); bool AudioOutClose(Id handle);
bool AudioOutValid(Id handle); bool AudioOutValid(Id handle);
bool AudioOutHasDevice(Id handle);
bool AudioOutSetVolume(Id handle, uint32_t bitflag, const int* volume); bool AudioOutSetVolume(Id handle, uint32_t bitflag, const int* volume);
uint32_t AudioOutOutputs(OutputParam* params, uint32_t num, bool blocking = true); uint32_t AudioOutOutputs(OutputParam* params, uint32_t num, bool blocking = true);
bool AudioOutGetStatus(Id handle, int* type, int* channels_num); bool AudioOutGetStatus(Id handle, int* type, int* channels_num);
@@ -152,6 +153,10 @@ void AudioOutClose(int handle) {
} }
} }
bool AudioOutHasDevice(int handle) {
return g_audio != nullptr && handle > 0 && g_audio->AudioOutHasDevice(Audio::Id(handle));
}
uint32_t AudioOutOutputs(const OutputParam* params, uint32_t num, bool blocking) { uint32_t AudioOutOutputs(const OutputParam* params, uint32_t num, bool blocking) {
if (g_audio == nullptr || params == nullptr || num == 0) { if (g_audio == nullptr || params == nullptr || num == 0) {
return 0; return 0;
@@ -451,6 +456,13 @@ bool Audio::AudioOutValid(Id handle) {
m_out_ports[handle.GetId()].used); m_out_ports[handle.GetId()].used);
} }
bool Audio::AudioOutHasDevice(Id handle) {
Common::LockGuard lock(m_mutex);
return (handle.GetId() >= 0 && handle.GetId() < OUT_PORTS_MAX &&
m_out_ports[handle.GetId()].used && m_out_ports[handle.GetId()].audio_device != 0);
}
bool Audio::AudioOutGetStatus(Id handle, int* type, int* channels_num) { bool Audio::AudioOutGetStatus(Id handle, int* type, int* channels_num) {
Common::LockGuard lock(m_mutex); Common::LockGuard lock(m_mutex);
@@ -516,16 +528,18 @@ uint32_t Audio::AudioOutOutputs(OutputParam* params, uint32_t num, bool blocking
max_wait_time = (wait_time > max_wait_time ? wait_time : max_wait_time); max_wait_time = (wait_time > max_wait_time ? wait_time : max_wait_time);
} }
bool all_ports_have_device = true; bool any_port_has_device = false;
for (uint32_t i = 0; i < num; i++) { for (uint32_t i = 0; i < num; i++) {
if (m_out_ports[params[i].handle.GetId()].audio_device == 0) { if (m_out_ports[params[i].handle.GetId()].audio_device != 0) {
all_ports_have_device = false; any_port_has_device = true;
break; break;
} }
} }
// Device-backed ports are paced by the SDL queue above. // One real output device is enough to pace the whole synchronized batch. Applying the fallback
if (blocking && max_wait_time != 0 && !all_ports_have_device) { // when a vibration port is present would rate-limit the device-backed ports to exactly 1x and
// prevent their SDL queues from building an underrun cushion.
if (blocking && max_wait_time != 0 && !any_port_has_device) {
Common::Thread::SleepMicro(max_wait_time); Common::Thread::SleepMicro(max_wait_time);
} }
+1
View File
@@ -26,6 +26,7 @@ static constexpr int OUT_PORTS_MAX = 32;
int AudioOutOpen(int type, uint32_t samples_num, uint32_t freq, Format format); int AudioOutOpen(int type, uint32_t samples_num, uint32_t freq, Format format);
void AudioOutClose(int handle); void AudioOutClose(int handle);
bool AudioOutHasDevice(int handle);
uint32_t AudioOutOutputs(const OutputParam* params, uint32_t num, bool blocking = true); uint32_t AudioOutOutputs(const OutputParam* params, uint32_t num, bool blocking = true);
} // namespace Libs::Audio::AudioInternal } // namespace Libs::Audio::AudioInternal
+15
View File
@@ -56,6 +56,7 @@ public:
void Disconnect(int id); void Disconnect(int id);
void Button(int id, uint32_t button, bool down); void Button(int id, uint32_t button, bool down);
void Axis(int id, Axis axis, int value); void Axis(int id, Axis axis, int value);
void ResetInputState();
void GetConnectionInfo(bool* flag, int* count); void GetConnectionInfo(bool* flag, int* count);
void ReadState(ControllerState* state, bool* flag, int* count); void ReadState(ControllerState* state, bool* flag, int* count);
int ReadStates(ControllerState* states, int states_num, bool* flag, int* count); int ReadStates(ControllerState* states, int states_num, bool* flag, int* count);
@@ -256,6 +257,15 @@ void GameController::Axis(int id, Controller::Axis axis, int value) {
} }
} }
void GameController::ResetInputState() {
Common::LockGuard lock(m_mutex);
ControllerState state {};
state.time = LibKernel::KernelGetProcessTime();
m_states_num = 0;
m_first_state = 0;
AddState(state);
}
void GameController::GetConnectionInfo(bool* flag, int* count) { void GameController::GetConnectionInfo(bool* flag, int* count) {
EXIT_IF(flag == nullptr); EXIT_IF(flag == nullptr);
EXIT_IF(count == nullptr); EXIT_IF(count == nullptr);
@@ -334,6 +344,11 @@ void ControllerAxis(int id, Axis axis, int value) {
g_controller->Axis(id, axis, value); g_controller->Axis(id, axis, value);
} }
void ControllerResetInputState() {
EXIT_IF(g_controller == nullptr);
g_controller->ResetInputState();
}
int KYTY_SYSV_ABI PadInit() { int KYTY_SYSV_ABI PadInit() {
PRINT_NAME(); PRINT_NAME();
+1
View File
@@ -53,6 +53,7 @@ void ControllerConnect(int id);
void ControllerDisconnect(int id); void ControllerDisconnect(int id);
void ControllerButton(int id, uint32_t button, bool down); void ControllerButton(int id, uint32_t button, bool down);
void ControllerAxis(int id, Axis axis, int value); void ControllerAxis(int id, Axis axis, int value);
void ControllerResetInputState();
int KYTY_SYSV_ABI PadInit(); int KYTY_SYSV_ABI PadInit();
int KYTY_SYSV_ABI PadOpen(int user_id, int type, int index, const void* param); int KYTY_SYSV_ABI PadOpen(int user_id, int type, int index, const void* param);
-14
View File
@@ -23,20 +23,6 @@ int KYTY_SYSV_ABI CommonDialogInitialize() {
} // namespace CommonDialog } // namespace CommonDialog
namespace ImeDialog {
LIB_NAME("ImeDialog", "ImeDialog");
constexpr int IME_STATUS_NONE = 0;
int KYTY_SYSV_ABI ImeDialogGetStatus() {
PRINT_NAME();
return IME_STATUS_NONE;
}
} // namespace ImeDialog
namespace LoginDialog { namespace LoginDialog {
LIB_NAME("LoginDialog", "LoginDialog"); LIB_NAME("LoginDialog", "LoginDialog");
-6
View File
@@ -12,12 +12,6 @@ int KYTY_SYSV_ABI CommonDialogInitialize();
} // namespace CommonDialog } // namespace CommonDialog
namespace ImeDialog {
int KYTY_SYSV_ABI ImeDialogGetStatus();
} // namespace ImeDialog
namespace LoginDialog { namespace LoginDialog {
int KYTY_SYSV_ABI LoginDialogInitialize(); int KYTY_SYSV_ABI LoginDialogInitialize();
+781
View File
@@ -0,0 +1,781 @@
#include "libs/imeDialog.h"
#include "libs/errno.h"
#include <algorithm>
#include <atomic>
#include <cmath>
#include <cstring>
#include <mutex>
#include <vector>
namespace Libs::Dialog::ImeDialog {
namespace {
constexpr int Error(uint32_t value) {
return static_cast<int32_t>(value);
}
constexpr int ERROR_BUSY = Error(0x80bc0001);
constexpr int ERROR_INVALID_USER_ID = Error(0x80bc0010);
constexpr int ERROR_INVALID_TYPE = Error(0x80bc0011);
constexpr int ERROR_INVALID_LANGUAGES = Error(0x80bc0012);
constexpr int ERROR_INVALID_ENTER_LABEL = Error(0x80bc0013);
constexpr int ERROR_INVALID_INPUT_METHOD = Error(0x80bc0014);
constexpr int ERROR_INVALID_OPTION = Error(0x80bc0015);
constexpr int ERROR_INVALID_MAX_TEXT_LENGTH = Error(0x80bc0016);
constexpr int ERROR_INVALID_TEXT_BUFFER = Error(0x80bc0017);
constexpr int ERROR_INVALID_POSX = Error(0x80bc0018);
constexpr int ERROR_INVALID_POSY = Error(0x80bc0019);
constexpr int ERROR_INVALID_HALIGN = Error(0x80bc001a);
constexpr int ERROR_INVALID_VALIGN = Error(0x80bc001b);
constexpr int ERROR_INVALID_EXTENDED = Error(0x80bc001c);
constexpr int ERROR_INVALID_PARAM = Error(0x80bc0030);
constexpr int ERROR_INVALID_ADDRESS = Error(0x80bc0031);
constexpr int ERROR_INVALID_RESERVED = Error(0x80bc0032);
constexpr int ERROR_INVALID_TITLE = Error(0x80bc0101);
constexpr int ERROR_NOT_RUNNING = Error(0x80bc0105);
constexpr int ERROR_NOT_FINISHED = Error(0x80bc0106);
constexpr int ERROR_NOT_IN_USE = Error(0x80bc0107);
constexpr uint32_t VALID_OPTIONS = 0x00007bff;
constexpr uint32_t VALID_EXTENDED_OPTIONS = 0x00005fde;
constexpr uint32_t VALID_EXT_KEYBOARD_MODE = 0x1c000003;
constexpr uint64_t VALID_LANGUAGES = 0x00000001ff1fffffULL;
struct State {
Status status = Status::None;
EndStatus end_status = EndStatus::Ok;
uint64_t generation = 0;
uint64_t revision = 0;
Param param {};
ExtendedParam extended {};
bool input_changed = false;
bool commit_pending = false;
uint32_t cursor = 0;
std::u16string original_text;
std::u16string current_text;
std::u16string title;
std::u16string placeholder;
std::vector<ExternalInput> external_inputs;
};
std::mutex g_mutex;
State g_state;
std::atomic<Status> g_status {Status::None};
std::atomic<uint64_t> g_revision {0};
std::atomic<VisibilityCallback> g_visibility_callback {nullptr};
bool AllZero(const int8_t* data, size_t size) {
return std::all_of(data, data + size, [](int8_t value) { return value == 0; });
}
bool IsValidUtf16(std::u16string_view text);
bool ReadBounded(const char16_t* text, uint32_t limit, std::u16string* out) {
out->clear();
if (text == nullptr) {
return true;
}
out->reserve(limit);
for (uint32_t i = 0; i <= limit; ++i) {
const char16_t value = text[i];
if (value == u'\0') {
return IsValidUtf16(*out);
}
if (i == limit) {
return false;
}
out->push_back(value);
}
return false;
}
bool IsValidUtf16(std::u16string_view text) {
for (size_t i = 0; i < text.size(); i++) {
const char16_t current = text[i];
if (current >= 0xd800 && current <= 0xdbff) {
if (++i >= text.size() || text[i] < 0xdc00 || text[i] > 0xdfff) {
return false;
}
} else if (current >= 0xdc00 && current <= 0xdfff) {
return false;
}
}
return true;
}
bool IsAllowedInput(char16_t value, Type type, uint32_t option) {
if (value == u'\r') {
return false;
}
if (value == u'\n') {
return (option & OPTION_MULTILINE) != 0;
}
if (value == u'\0') {
return false;
}
if (type == Type::Number) {
return (value >= u'0' && value <= u'9') || value == u',' || value == u'-' || value == u'.';
}
if (type == Type::BasicLatin) {
return value >= u' ' && value <= u'~';
}
return true;
}
char16_t HidCharacter(uint16_t keycode, uint32_t status) {
const bool shift = (status & 0x00002200) != 0;
const bool caps = (status & 0x00020000) != 0;
if (keycode >= 4 && keycode <= 29) {
const bool upper = shift != caps;
return static_cast<char16_t>((upper ? u'A' : u'a') + keycode - 4);
}
if (keycode >= 30 && keycode <= 39) {
static constexpr char16_t plain[] = u"1234567890";
static constexpr char16_t shifted[] = u"!@#$%^&*()";
return (shift ? shifted : plain)[keycode - 30];
}
if (keycode == 44) {
return u' ';
}
if (keycode >= 45 && keycode <= 56) {
static constexpr char16_t plain[] = u"-=[]\\#;'`,./";
static constexpr char16_t shifted[] = u"_+{}|~:\"~<>?";
return (shift ? shifted : plain)[keycode - 45];
}
if (keycode >= 89 && keycode <= 98) {
static constexpr char16_t keypad[] = u"1234567890";
return keypad[keycode - 89];
}
if (keycode == 99) {
return u'.';
}
return u'\0';
}
bool ApplyKeyboardFilterOutput(ExternalInput* input, uint16_t keycode, uint32_t status,
bool multiline) {
constexpr uint32_t KEYCODE_VALID = 0x00000001;
constexpr uint32_t CHARACTER_VALID = 0x00000002;
if (keycode == input->key.keycode && status == input->key.status) {
return true;
}
if ((status & KEYCODE_VALID) == 0 || keycode == 0) {
return input->action == ExternalAction::Text && (status & CHARACTER_VALID) != 0;
}
input->key.keycode = keycode;
input->key.status = status;
input->text.clear();
switch (keycode) {
case 40:
case 88:
case 158:
input->action = multiline ? ExternalAction::Newline : ExternalAction::Accept;
break;
case 41: input->action = ExternalAction::Cancel; break;
case 42:
case 187: input->action = ExternalAction::Backspace; break;
case 43:
case 186: input->action = ExternalAction::None; break;
case 79: input->action = ExternalAction::MoveRight; break;
case 80: input->action = ExternalAction::MoveLeft; break;
default: {
const char16_t character = HidCharacter(keycode, status);
if (character == u'\0') {
return false;
}
input->action = ExternalAction::Text;
input->key.character = character;
input->text.push_back(character);
break;
}
}
return true;
}
void ClampText(std::u16string* text, uint32_t limit) {
if (text->size() <= limit) {
return;
}
text->resize(limit);
if (!text->empty() && text->back() >= 0xd800 && text->back() <= 0xdbff) {
text->pop_back();
}
}
uint32_t NormalizeCursor(std::u16string_view text, uint32_t cursor) {
cursor = std::min<uint32_t>(cursor, static_cast<uint32_t>(text.size()));
if (cursor > 0 && cursor < text.size() && text[cursor - 1] >= 0xd800 &&
text[cursor - 1] <= 0xdbff && text[cursor] >= 0xdc00 && text[cursor] <= 0xdfff) {
cursor++;
}
return cursor;
}
void WriteGuestText(const State& state, const std::u16string& text) {
if (state.param.input_text_buffer == nullptr) {
return;
}
const size_t size = std::min<size_t>(text.size(), state.param.max_text_length);
std::memcpy(state.param.input_text_buffer, text.data(), size * sizeof(char16_t));
state.param.input_text_buffer[size] = u'\0';
}
void NotifyVisibility(bool visible, uint64_t generation) {
if (const auto callback = g_visibility_callback.load(std::memory_order_acquire);
callback != nullptr) {
callback(visible, generation);
}
}
int ValidateExtended(const ExtendedParam* extended) {
if (extended == nullptr) {
return OK;
}
if ((extended->option & ~VALID_EXTENDED_OPTIONS) != 0 ||
((extended->option & 0x00004000) != 0 && (extended->option & 0x00000080) == 0) ||
extended->priority > 3 || extended->disable_device > 7 ||
(extended->ext_keyboard_mode & ~VALID_EXT_KEYBOARD_MODE) != 0 ||
!AllZero(extended->reserved, sizeof(extended->reserved))) {
return ERROR_INVALID_EXTENDED;
}
return OK;
}
int ValidateParam(const Param* param, const ExtendedParam* extended, std::u16string* initial,
std::u16string* title, std::u16string* placeholder) {
if (param == nullptr) {
return ERROR_INVALID_ADDRESS;
}
if (static_cast<uint32_t>(param->type) > static_cast<uint32_t>(Type::Number)) {
return ERROR_INVALID_TYPE;
}
if ((param->option & ~VALID_OPTIONS) != 0) {
return ERROR_INVALID_OPTION;
}
if ((param->supported_languages & ~VALID_LANGUAGES) != 0) {
return ERROR_INVALID_LANGUAGES;
}
const bool over_2k = (param->option & OPTION_USE_OVER_2K) != 0;
const float max_x = over_2k ? 3840.0f : 1920.0f;
const float max_y = over_2k ? 2160.0f : 1080.0f;
if (!std::isfinite(param->posx) || param->posx < 0.0f || param->posx >= max_x) {
return ERROR_INVALID_POSX;
}
if (!std::isfinite(param->posy) || param->posy < 0.0f || param->posy >= max_y) {
return ERROR_INVALID_POSY;
}
if (static_cast<uint32_t>(param->horizontal_alignment) > 2) {
return ERROR_INVALID_HALIGN;
}
if (static_cast<uint32_t>(param->vertical_alignment) > 2) {
return ERROR_INVALID_VALIGN;
}
const bool multiline = (param->option & OPTION_MULTILINE) != 0;
const bool password = (param->option & OPTION_PASSWORD) != 0;
if ((multiline && password) ||
(multiline && param->type != Type::Default && param->type != Type::BasicLatin) ||
(password && param->type != Type::BasicLatin && param->type != Type::Number)) {
return ERROR_INVALID_PARAM;
}
if (param->user_id < 0 || param->user_id == 0xff) {
return ERROR_INVALID_USER_ID;
}
if (!AllZero(param->reserved, sizeof(param->reserved))) {
return ERROR_INVALID_RESERVED;
}
if (param->input_text_buffer == nullptr) {
return ERROR_INVALID_TEXT_BUFFER;
}
const int extended_result = ValidateExtended(extended);
if (extended_result != OK) {
return extended_result;
}
if (static_cast<uint32_t>(param->enter_label) > static_cast<uint32_t>(EnterLabel::Go)) {
return ERROR_INVALID_ENTER_LABEL;
}
if (param->input_method != 0) {
return ERROR_INVALID_INPUT_METHOD;
}
if (param->max_text_length == 0 || param->max_text_length > IME_DIALOG_MAX_TEXT_LENGTH) {
return ERROR_INVALID_MAX_TEXT_LENGTH;
}
if (!ReadBounded(param->input_text_buffer, param->max_text_length, initial)) {
return ERROR_INVALID_TEXT_BUFFER;
}
if (!ReadBounded(param->title, IME_DIALOG_MAX_TITLE_LENGTH, title)) {
return ERROR_INVALID_TITLE;
}
if (!ReadBounded(param->placeholder, IME_DIALOG_MAX_PLACEHOLDER_LENGTH, placeholder)) {
return ERROR_INVALID_PARAM;
}
return OK;
}
void ComputePanelSize(const Param& param, const ExtendedParam* extended, uint32_t* width,
uint32_t* height) {
const bool multiline = (param.option & OPTION_MULTILINE) != 0;
const bool hide_keyboard = extended != nullptr && (param.option & OPTION_EXT_KEYBOARD) != 0 &&
(extended->option & 0x00000400) != 0;
if (param.type == Type::Number) {
*width = 370;
*height = hide_keyboard ? 102 : 522;
} else if (param.type == Type::BasicLatin) {
*width = 793;
if (hide_keyboard) {
*height = multiline ? 203 : 103;
} else {
*height = multiline ? 628 : 528;
}
} else {
*width = 793;
if (hide_keyboard) {
*height = multiline ? 268 : 168;
} else {
*height = multiline ? 628 : 528;
}
}
if ((param.option & OPTION_USE_OVER_2K) != 0) {
*width *= 2;
*height *= 2;
}
}
void ApplyFilterAndCommit() {
TextFilter filter = nullptr;
std::u16string source;
uint32_t max_length = 0;
uint64_t generation = 0;
uint64_t revision = 0;
{
std::scoped_lock lock(g_mutex);
if (g_state.status == Status::None || (!g_state.input_changed && !g_state.commit_pending)) {
return;
}
filter = g_state.input_changed ? g_state.param.filter : nullptr;
source = g_state.current_text;
max_length = g_state.param.max_text_length;
generation = g_state.generation;
revision = g_state.revision;
g_state.input_changed = false;
}
if (filter != nullptr) {
std::vector<char16_t> output(IME_DIALOG_MAX_TEXT_LENGTH + 1, u'\0');
uint32_t output_length = IME_DIALOG_MAX_TEXT_LENGTH;
if (filter(output.data(), &output_length, source.c_str(),
static_cast<uint32_t>(source.size())) == 0 &&
output_length <= IME_DIALOG_MAX_TEXT_LENGTH &&
IsValidUtf16(std::u16string_view(output.data(), output_length))) {
source.assign(output.data(), output.data() + output_length);
ClampText(&source, max_length);
}
}
std::scoped_lock lock(g_mutex);
if (g_state.generation != generation || g_state.revision != revision ||
g_state.status == Status::None) {
if (g_state.generation == generation &&
(g_state.status == Status::Running ||
(g_state.status == Status::Finished && g_state.end_status == EndStatus::Ok))) {
g_state.input_changed = true;
g_state.commit_pending = true;
}
return;
}
if (!g_state.input_changed) {
g_state.current_text = std::move(source);
g_state.cursor = NormalizeCursor(g_state.current_text, g_state.cursor);
}
const auto& committed =
g_state.status == Status::Finished && g_state.end_status != EndStatus::Ok
? g_state.original_text
: g_state.current_text;
WriteGuestText(g_state, committed);
g_state.commit_pending = false;
}
bool MatchRunningGeneration(uint64_t generation) {
return g_state.status == Status::Running && g_state.generation == generation;
}
bool FinishFromHost(uint64_t generation, EndStatus end_status) {
uint64_t notify_generation = 0;
{
std::scoped_lock lock(g_mutex);
if (!MatchRunningGeneration(generation)) {
return false;
}
g_state.status = Status::Finished;
g_state.end_status = end_status;
g_state.commit_pending = true;
g_state.revision++;
notify_generation = g_state.generation;
g_status.store(Status::Finished, std::memory_order_release);
g_revision.store(g_state.revision, std::memory_order_release);
}
NotifyVisibility(false, notify_generation);
return true;
}
void ApplyExternalInputs() {
std::vector<ExternalInput> inputs;
ExtKeyboardFilter filter = nullptr;
uint64_t generation = 0;
int32_t user_id = 0;
bool multiline = false;
{
std::scoped_lock lock(g_mutex);
if (g_state.status != Status::Running || g_state.external_inputs.empty()) {
return;
}
inputs.swap(g_state.external_inputs);
filter = g_state.extended.ext_keyboard_filter;
generation = g_state.generation;
user_id = g_state.param.user_id;
multiline = (g_state.param.option & OPTION_MULTILINE) != 0;
}
for (auto& input: inputs) {
{
std::scoped_lock lock(g_mutex);
if (!MatchRunningGeneration(generation)) {
break;
}
}
input.key.user_id = user_id;
bool accepted = true;
if (filter != nullptr) {
uint16_t output_keycode = input.key.keycode;
uint32_t output_status = input.key.status;
if (filter(&input.key, &output_keycode, &output_status, nullptr) == 0) {
accepted =
ApplyKeyboardFilterOutput(&input, output_keycode, output_status, multiline);
}
}
if (!accepted) {
continue;
}
switch (input.action) {
case ExternalAction::None: break;
case ExternalAction::Text: HostInsertText(generation, input.text); break;
case ExternalAction::Backspace: HostBackspace(generation); break;
case ExternalAction::MoveLeft: HostMoveCursor(generation, -1); break;
case ExternalAction::MoveRight: HostMoveCursor(generation, 1); break;
case ExternalAction::Cancel: HostCancel(generation); break;
case ExternalAction::Accept: HostAccept(generation); break;
case ExternalAction::Newline: HostInsertText(generation, u"\n"); break;
}
}
}
} // namespace
int KYTY_SYSV_ABI ImeDialogGetPanelSize(const Param* param, uint32_t* width, uint32_t* height) {
return ImeDialogGetPanelSizeExtended(param, nullptr, width, height);
}
int KYTY_SYSV_ABI ImeDialogGetPanelSizeExtended(const Param* param, const ExtendedParam* extended,
uint32_t* width, uint32_t* height) {
if (param == nullptr || width == nullptr || height == nullptr) {
return ERROR_INVALID_ADDRESS;
}
if (static_cast<uint32_t>(param->type) > static_cast<uint32_t>(Type::Number)) {
return ERROR_INVALID_TYPE;
}
if ((param->option & ~VALID_OPTIONS) != 0) {
return ERROR_INVALID_OPTION;
}
if ((param->supported_languages & ~VALID_LANGUAGES) != 0) {
return ERROR_INVALID_LANGUAGES;
}
const int extended_result = ValidateExtended(extended);
if (extended_result != OK) {
return extended_result;
}
ComputePanelSize(*param, extended, width, height);
return OK;
}
int KYTY_SYSV_ABI ImeDialogInit(const Param* param, const ExtendedParam* extended) {
if (g_status.load(std::memory_order_acquire) != Status::None) {
return ERROR_BUSY;
}
std::u16string initial;
std::u16string title;
std::u16string placeholder;
const int validation = ValidateParam(param, extended, &initial, &title, &placeholder);
if (validation != OK) {
return validation;
}
uint64_t generation = 0;
{
std::scoped_lock lock(g_mutex);
if (g_state.status != Status::None) {
return ERROR_BUSY;
}
const uint64_t next_generation = g_state.generation + 1;
const uint64_t next_revision = g_state.revision + 1;
g_state = {};
g_state.status = Status::Running;
g_state.generation = next_generation;
g_state.revision = next_revision;
g_state.param = *param;
if (extended != nullptr) {
g_state.extended = *extended;
}
g_state.original_text = initial;
g_state.current_text = std::move(initial);
g_state.cursor = static_cast<uint32_t>(g_state.current_text.size());
g_state.title = std::move(title);
g_state.placeholder = std::move(placeholder);
g_state.commit_pending = true;
generation = g_state.generation;
g_status.store(Status::Running, std::memory_order_release);
g_revision.store(g_state.revision, std::memory_order_release);
}
NotifyVisibility(true, generation);
return OK;
}
int KYTY_SYSV_ABI ImeDialogGetStatus() {
ApplyExternalInputs();
ApplyFilterAndCommit();
return static_cast<int>(g_status.load(std::memory_order_acquire));
}
int KYTY_SYSV_ABI ImeDialogAbort() {
uint64_t generation = 0;
{
std::scoped_lock lock(g_mutex);
if (g_state.status == Status::None) {
return ERROR_NOT_IN_USE;
}
if (g_state.status != Status::Running) {
return ERROR_NOT_RUNNING;
}
generation = g_state.generation;
}
if (!FinishFromHost(generation, EndStatus::Aborted)) {
return ERROR_NOT_RUNNING;
}
ApplyFilterAndCommit();
return OK;
}
int KYTY_SYSV_ABI ImeDialogGetResult(Result* result) {
{
std::scoped_lock lock(g_mutex);
if (g_state.status == Status::None) {
return ERROR_NOT_IN_USE;
}
}
if (result == nullptr) {
return ERROR_INVALID_ADDRESS;
}
if (!AllZero(result->reserved, sizeof(result->reserved))) {
return ERROR_INVALID_RESERVED;
}
{
std::scoped_lock lock(g_mutex);
if (g_state.status != Status::Finished) {
return ERROR_NOT_FINISHED;
}
}
ApplyFilterAndCommit();
std::scoped_lock lock(g_mutex);
if (g_state.status == Status::None) {
return ERROR_NOT_IN_USE;
}
if (g_state.status != Status::Finished) {
return ERROR_NOT_FINISHED;
}
result->endstatus = g_state.end_status;
return OK;
}
int KYTY_SYSV_ABI ImeDialogTerm() {
{
std::scoped_lock lock(g_mutex);
if (g_state.status == Status::None) {
return ERROR_NOT_IN_USE;
}
if (g_state.status != Status::Finished) {
return ERROR_NOT_FINISHED;
}
}
ApplyFilterAndCommit();
std::scoped_lock lock(g_mutex);
if (g_state.status == Status::None) {
return ERROR_NOT_IN_USE;
}
if (g_state.status != Status::Finished) {
return ERROR_NOT_FINISHED;
}
const uint64_t generation = g_state.generation;
const uint64_t revision = g_state.revision;
g_state = {};
g_state.generation = generation;
g_state.revision = revision;
g_status.store(Status::None, std::memory_order_release);
return OK;
}
int KYTY_SYSV_ABI ImeDialogGetPanelPositionAndForm(PositionAndForm* form) {
std::scoped_lock lock(g_mutex);
if (g_state.status == Status::None) {
return ERROR_NOT_IN_USE;
}
if (form == nullptr) {
return ERROR_INVALID_ADDRESS;
}
form->type = 2;
form->posx = g_state.param.posx;
form->posy = g_state.param.posy;
form->horizontal_alignment = g_state.param.horizontal_alignment;
form->vertical_alignment = g_state.param.vertical_alignment;
ComputePanelSize(g_state.param, &g_state.extended, &form->width, &form->height);
return OK;
}
VisualState GetVisualState() noexcept {
return {g_status.load(std::memory_order_acquire) == Status::Running,
g_revision.load(std::memory_order_acquire)};
}
void SetVisibilityCallback(VisibilityCallback callback) noexcept {
g_visibility_callback.store(callback, std::memory_order_release);
}
bool GetHostSnapshot(HostSnapshot* snapshot) {
if (snapshot == nullptr) {
return false;
}
std::scoped_lock lock(g_mutex);
if (g_state.status != Status::Running) {
return false;
}
snapshot->generation = g_state.generation;
snapshot->type = g_state.param.type;
snapshot->enter_label = g_state.param.enter_label;
snapshot->option = g_state.param.option;
snapshot->max_text_length = g_state.param.max_text_length;
snapshot->cursor = g_state.cursor;
snapshot->disable_device = g_state.extended.disable_device;
snapshot->key_panel_visible = (g_state.param.option & OPTION_EXT_KEYBOARD) == 0 ||
(g_state.extended.option & 0x00000400) == 0;
snapshot->posx = g_state.param.posx;
snapshot->posy = g_state.param.posy;
snapshot->horizontal_alignment = g_state.param.horizontal_alignment;
snapshot->vertical_alignment = g_state.param.vertical_alignment;
ComputePanelSize(g_state.param, &g_state.extended, &snapshot->panel_width,
&snapshot->panel_height);
snapshot->text = g_state.current_text;
snapshot->title = g_state.title;
snapshot->placeholder = g_state.placeholder;
return true;
}
bool HostInsertText(uint64_t generation, std::u16string_view text) {
std::scoped_lock lock(g_mutex);
if (!MatchRunningGeneration(generation) || text.empty()) {
return false;
}
if (!IsValidUtf16(text)) {
return false;
}
std::u16string allowed;
allowed.reserve(text.size());
for (const char16_t value: text) {
if (IsAllowedInput(value, g_state.param.type, g_state.param.option)) {
allowed.push_back(value);
}
}
if (allowed.empty()) {
return false;
}
const size_t available = g_state.param.max_text_length - g_state.current_text.size();
if (available == 0) {
return false;
}
std::u16string insertion(std::u16string_view(allowed).substr(0, available));
if (insertion.size() < allowed.size() && !insertion.empty() && insertion.back() >= 0xd800 &&
insertion.back() <= 0xdbff) {
insertion.pop_back();
}
if (insertion.empty()) {
return false;
}
std::u16string candidate = g_state.current_text;
candidate.insert(g_state.cursor, insertion);
g_state.current_text = std::move(candidate);
g_state.cursor += static_cast<uint32_t>(insertion.size());
g_state.input_changed = true;
g_state.commit_pending = true;
return true;
}
bool HostBackspace(uint64_t generation) {
std::scoped_lock lock(g_mutex);
if (!MatchRunningGeneration(generation) || g_state.cursor == 0) {
return false;
}
uint32_t first = g_state.cursor - 1;
if (first > 0 && g_state.current_text[first] >= 0xdc00 &&
g_state.current_text[first] <= 0xdfff && g_state.current_text[first - 1] >= 0xd800 &&
g_state.current_text[first - 1] <= 0xdbff) {
first--;
}
g_state.current_text.erase(first, g_state.cursor - first);
g_state.cursor = first;
g_state.input_changed = true;
g_state.commit_pending = true;
return true;
}
bool HostMoveCursor(uint64_t generation, int delta) {
std::scoped_lock lock(g_mutex);
if (!MatchRunningGeneration(generation) || delta == 0) {
return false;
}
int next = std::clamp(static_cast<int>(g_state.cursor) + delta, 0,
static_cast<int>(g_state.current_text.size()));
if (delta < 0 && next > 0 && next < static_cast<int>(g_state.current_text.size()) &&
g_state.current_text[next] >= 0xdc00 && g_state.current_text[next] <= 0xdfff &&
g_state.current_text[next - 1] >= 0xd800 && g_state.current_text[next - 1] <= 0xdbff) {
next--;
} else if (delta > 0 && next > 0 && next < static_cast<int>(g_state.current_text.size()) &&
g_state.current_text[next - 1] >= 0xd800 &&
g_state.current_text[next - 1] <= 0xdbff && g_state.current_text[next] >= 0xdc00 &&
g_state.current_text[next] <= 0xdfff) {
next++;
}
if (next == static_cast<int>(g_state.cursor)) {
return false;
}
g_state.cursor = static_cast<uint32_t>(next);
return true;
}
bool HostAccept(uint64_t generation) {
return FinishFromHost(generation, EndStatus::Ok);
}
bool HostCancel(uint64_t generation) {
return FinishFromHost(generation, EndStatus::UserCanceled);
}
bool HostQueueExternalInput(uint64_t generation, ExternalInput input) {
std::scoped_lock lock(g_mutex);
if (!MatchRunningGeneration(generation) || g_state.external_inputs.size() >= 128) {
return false;
}
g_state.external_inputs.push_back(std::move(input));
return true;
}
} // namespace Libs::Dialog::ImeDialog
+199
View File
@@ -0,0 +1,199 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_LIBS_IMEDIALOG_H_
#define EMULATOR_INCLUDE_EMULATOR_LIBS_IMEDIALOG_H_
#include "common/abi.h"
#include <cstddef>
#include <cstdint>
#include <string>
#include <string_view>
namespace Libs::Dialog::ImeDialog {
constexpr uint32_t IME_DIALOG_MAX_TEXT_LENGTH = 2048;
constexpr uint32_t IME_DIALOG_MAX_TITLE_LENGTH = 128;
constexpr uint32_t IME_DIALOG_MAX_PLACEHOLDER_LENGTH = 64;
enum class Status : uint32_t { None = 0, Running = 1, Finished = 2 };
enum class EndStatus : uint32_t { Ok = 0, UserCanceled = 1, Aborted = 2 };
enum class Type : uint32_t { Default = 0, BasicLatin = 1, Url = 2, Mail = 3, Number = 4 };
enum class EnterLabel : uint32_t { Default = 0, Send = 1, Search = 2, Go = 3 };
enum class Alignment : uint32_t { Start = 0, Center = 1, End = 2 };
enum Option : uint32_t {
OPTION_MULTILINE = 0x00000001,
OPTION_NO_AUTO_CAPITALIZE = 0x00000002,
OPTION_PASSWORD = 0x00000004,
OPTION_LANGUAGES_FORCED = 0x00000008,
OPTION_EXT_KEYBOARD = 0x00000010,
OPTION_NO_LEARNING = 0x00000020,
OPTION_FIXED_POSITION = 0x00000040,
OPTION_DISABLE_COPY_PASTE = 0x00000080,
OPTION_DISABLE_RESUME = 0x00000100,
OPTION_DISABLE_AUTO_SPACE = 0x00000200,
OPTION_DISABLE_POSITION_ADJ = 0x00000800,
OPTION_EXPANDED_PREEDIT = 0x00001000,
OPTION_JAPANESE_CAPS_LOCK = 0x00002000,
OPTION_USE_OVER_2K = 0x00004000,
};
enum DisableDevice : uint32_t {
DISABLE_DEVICE_CONTROLLER = 0x00000001,
DISABLE_DEVICE_EXT_KEYBOARD = 0x00000002,
DISABLE_DEVICE_REMOTE_OSK = 0x00000004,
};
struct Color {
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
};
struct Keycode;
using TextFilter = int32_t(KYTY_SYSV_ABI*)(char16_t* out_text, uint32_t* out_text_length,
const char16_t* source_text,
uint32_t source_text_length);
using ExtKeyboardFilter = int(KYTY_SYSV_ABI*)(const Keycode* source_keycode, uint16_t* out_keycode,
uint32_t* out_status, void* reserved);
struct Param {
int32_t user_id;
Type type;
uint64_t supported_languages;
EnterLabel enter_label;
uint32_t input_method;
TextFilter filter;
uint32_t option;
uint32_t max_text_length;
char16_t* input_text_buffer;
float posx;
float posy;
Alignment horizontal_alignment;
Alignment vertical_alignment;
const char16_t* placeholder;
const char16_t* title;
int8_t reserved[16];
};
struct Result {
EndStatus endstatus;
int8_t reserved[12];
};
struct ExtendedParam {
uint32_t option;
Color color_base;
Color color_line;
Color color_text_field;
Color color_preedit;
Color color_button_default;
Color color_button_function;
Color color_button_symbol;
Color color_text;
Color color_special;
uint32_t priority;
const char* additional_dictionary_path;
ExtKeyboardFilter ext_keyboard_filter;
uint32_t disable_device;
uint32_t ext_keyboard_mode;
int8_t reserved[60];
};
struct PositionAndForm {
uint32_t type;
float posx;
float posy;
Alignment horizontal_alignment;
Alignment vertical_alignment;
uint32_t width;
uint32_t height;
};
struct Keycode {
uint16_t keycode;
char16_t character;
uint32_t status;
uint32_t type;
int32_t user_id;
uint32_t resource_id;
uint64_t timestamp;
};
enum class ExternalAction : uint8_t {
None,
Text,
Backspace,
MoveLeft,
MoveRight,
Cancel,
Accept,
Newline,
};
struct ExternalInput {
Keycode key;
ExternalAction action;
std::u16string text;
};
static_assert(sizeof(Param) == 0x60);
static_assert(offsetof(Param, input_text_buffer) == 0x28);
static_assert(offsetof(Param, title) == 0x48);
static_assert(sizeof(Result) == 0x10);
static_assert(sizeof(ExtendedParam) == 0x88);
static_assert(offsetof(ExtendedParam, additional_dictionary_path) == 0x30);
static_assert(sizeof(PositionAndForm) == 0x1c);
static_assert(sizeof(Keycode) == 0x20);
struct VisualState {
bool active;
uint64_t revision;
};
struct HostSnapshot {
uint64_t generation;
Type type;
EnterLabel enter_label;
uint32_t option;
uint32_t max_text_length;
uint32_t cursor;
uint32_t disable_device;
bool key_panel_visible;
float posx;
float posy;
Alignment horizontal_alignment;
Alignment vertical_alignment;
uint32_t panel_width;
uint32_t panel_height;
std::u16string text;
std::u16string title;
std::u16string placeholder;
};
using VisibilityCallback = void (*)(bool visible, uint64_t generation);
int KYTY_SYSV_ABI ImeDialogGetPanelSize(const Param* param, uint32_t* width, uint32_t* height);
int KYTY_SYSV_ABI ImeDialogGetPanelSizeExtended(const Param* param, const ExtendedParam* extended,
uint32_t* width, uint32_t* height);
int KYTY_SYSV_ABI ImeDialogInit(const Param* param, const ExtendedParam* extended);
int KYTY_SYSV_ABI ImeDialogGetStatus();
int KYTY_SYSV_ABI ImeDialogAbort();
int KYTY_SYSV_ABI ImeDialogGetResult(Result* result);
int KYTY_SYSV_ABI ImeDialogTerm();
int KYTY_SYSV_ABI ImeDialogGetPanelPositionAndForm(PositionAndForm* form);
VisualState GetVisualState() noexcept;
void SetVisibilityCallback(VisibilityCallback callback) noexcept;
bool GetHostSnapshot(HostSnapshot* snapshot);
bool HostInsertText(uint64_t generation, std::u16string_view text);
bool HostBackspace(uint64_t generation);
bool HostMoveCursor(uint64_t generation, int delta);
bool HostAccept(uint64_t generation);
bool HostCancel(uint64_t generation);
bool HostQueueExternalInput(uint64_t generation, ExternalInput input);
} // namespace Libs::Dialog::ImeDialog
#endif // EMULATOR_INCLUDE_EMULATOR_LIBS_IMEDIALOG_H_
+21 -2
View File
@@ -336,6 +336,17 @@ static AudioOut2PortStateEntry* audioout2_find_port_locked(AudioOut2PortHandle p
return nullptr; return nullptr;
} }
static bool audioout2_context_has_queueable_device(AudioOut2ContextHandle ctx) {
Common::LockGuard lock(g_audioout2_port_mutex);
for (const auto& state: g_audioout2_ports) {
if (state.used && state.context == ctx && state.audio_handle > 0 &&
state.pcm_data != nullptr && AudioInternal::AudioOutHasDevice(state.audio_handle)) {
return true;
}
}
return false;
}
static void audioout2_queue_context_audio(AudioOut2ContextHandle ctx, bool blocking) { static void audioout2_queue_context_audio(AudioOut2ContextHandle ctx, bool blocking) {
std::vector<AudioInternal::OutputParam> params; std::vector<AudioInternal::OutputParam> params;
params.reserve(AudioInternal::OUT_PORTS_MAX); params.reserve(AudioInternal::OUT_PORTS_MAX);
@@ -493,15 +504,23 @@ int KYTY_SYSV_ABI AudioOut2ContextPush(AudioOut2ContextHandle ctx, uint32_t bloc
uint32_t sleep_micros = audioout2_grain_micros(512); uint32_t sleep_micros = audioout2_grain_micros(512);
for (;;) { for (;;) {
// Only a synchronous submission carrying PCM to a real device can rely on the SDL queue for
// pacing. Async pushes must retain queue-depth backpressure, and a handle without PCM (or a
// vibration/failed-open handle) has no downstream operation that can block this call.
const bool use_device_clock =
blocking != 0 && audioout2_context_has_queueable_device(ctx);
g_audioout2_context_mutex.Lock(); g_audioout2_context_mutex.Lock();
if (auto* state = audioout2_find_context_locked(ctx); state != nullptr) { if (auto* state = audioout2_find_context_locked(ctx); state != nullptr) {
audioout2_update_context_locked(state); audioout2_update_context_locked(state);
sleep_micros = audioout2_grain_micros(state->num_grains); sleep_micros = audioout2_grain_micros(state->num_grains);
if (state->queued < state->queue_depth) { if (state->queued < state->queue_depth || use_device_clock) {
if (state->queued == 0) { if (state->queued == 0) {
state->last_update = LibKernel::KernelGetProcessTime(); state->last_update = LibKernel::KernelGetProcessTime();
} }
state->queued++; if (state->queued < state->queue_depth) {
state->queued++;
}
g_audioout2_context_mutex.Unlock(); g_audioout2_context_mutex.Unlock();
audioout2_queue_context_audio(ctx, blocking != 0); audioout2_queue_context_audio(ctx, blocking != 0);
return OK; return OK;
+4 -4
View File
@@ -481,13 +481,13 @@ static KYTY_SYSV_ABI int MtxInit(LibKernel::PthreadMutex* mtx, int type) {
constexpr int mtx_recursive = 0x100; constexpr int mtx_recursive = 0x100;
if ((type & mtx_recursive) == 0) { if ((type & mtx_recursive) == 0) {
return mtx_result(LibKernel::PthreadMutexInit(mtx, nullptr)); return mtx_result(LibKernel::PthreadMutexInit(mtx, nullptr, nullptr));
} }
LibKernel::PthreadMutexattr attr = nullptr; LibKernel::PthreadMutexattr attr = nullptr;
int result = LibKernel::PthreadMutexattrInit(&attr); int result = LibKernel::PthreadMutexattrInit(&attr);
result = (result == OK ? LibKernel::PthreadMutexattrSettype(&attr, 2) : result); result = (result == OK ? LibKernel::PthreadMutexattrSettype(&attr, 2) : result);
result = (result == OK ? LibKernel::PthreadMutexInit(mtx, &attr) : result); result = (result == OK ? LibKernel::PthreadMutexInit(mtx, &attr, nullptr) : result);
(void)LibKernel::PthreadMutexattrDestroy(&attr); (void)LibKernel::PthreadMutexattrDestroy(&attr);
return mtx_result(result); return mtx_result(result);
@@ -659,8 +659,8 @@ void KYTY_SYSV_ABI LibcHeapGetTraceInfo(Info* info) {
info->mstate_table = g_mstate_table; info->mstate_table = g_mstate_table;
} }
int KYTY_SYSV_ABI LibcInternalExtCxaThreadAtexit(thread_atexit_destructor_t destructor, void* object, int KYTY_SYSV_ABI LibcInternalExtCxaThreadAtexit(thread_atexit_destructor_t destructor,
void* /*module_id*/) { void* object, void* /*module_id*/) {
PRINT_NAME(); PRINT_NAME();
g_thread_atexit_destructors.push_back({destructor, object}); g_thread_atexit_destructors.push_back({destructor, object});
+8
View File
@@ -1,5 +1,6 @@
#include "common/abi.h" #include "common/abi.h"
#include "libs/dialog.h" #include "libs/dialog.h"
#include "libs/imeDialog.h"
#include "libs/libs.h" #include "libs/libs.h"
#include "loader/symbolDatabase.h" #include "loader/symbolDatabase.h"
@@ -24,7 +25,14 @@ LIB_VERSION("ImeDialog", 1, "ImeDialog", 1, 1);
namespace ImeDialog = Dialog::ImeDialog; namespace ImeDialog = Dialog::ImeDialog;
LIB_DEFINE(InitDialog_1_ImeDialog) { LIB_DEFINE(InitDialog_1_ImeDialog) {
LIB_FUNC("CRD+jSErEJQ", ImeDialog::ImeDialogGetPanelSizeExtended);
LIB_FUNC("IADmD4tScBY", ImeDialog::ImeDialogGetStatus); LIB_FUNC("IADmD4tScBY", ImeDialog::ImeDialogGetStatus);
LIB_FUNC("gyTyVn+bXMw", ImeDialog::ImeDialogTerm);
LIB_FUNC("x01jxu+vxlc", ImeDialog::ImeDialogGetResult);
LIB_FUNC("wqsJvRXwl58", ImeDialog::ImeDialogGetPanelSize);
LIB_FUNC("NUeBrN7hzf0", ImeDialog::ImeDialogInit);
LIB_FUNC("oBmw4xrmfKs", ImeDialog::ImeDialogAbort);
LIB_FUNC("8jqzzPioYl8", ImeDialog::ImeDialogGetPanelPositionAndForm);
} }
} // namespace LibImeDialog } // namespace LibImeDialog
+5 -62
View File
@@ -516,6 +516,7 @@ static void QueuePendingSignal(Pthread thread, int signum) {
PthreadQueuePendingSignal(thread, signum); PthreadQueuePendingSignal(thread, signum);
} }
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
static void WaitForSignalDispatch(Pthread thread, int signum) { static void WaitForSignalDispatch(Pthread thread, int signum) {
constexpr auto DISPATCH_WAIT_STEP = std::chrono::microseconds(1000); constexpr auto DISPATCH_WAIT_STEP = std::chrono::microseconds(1000);
constexpr auto DISPATCH_WAIT_MAX = std::chrono::milliseconds(2); constexpr auto DISPATCH_WAIT_MAX = std::chrono::milliseconds(2);
@@ -526,6 +527,7 @@ static void WaitForSignalDispatch(Pthread thread, int signum) {
waited += DISPATCH_WAIT_STEP; waited += DISPATCH_WAIT_STEP;
} }
} }
#endif
static bool TakePendingSignal(Pthread thread, int signum) { static bool TakePendingSignal(Pthread thread, int signum) {
return PthreadTakePendingSignal(thread, signum); return PthreadTakePendingSignal(thread, signum);
@@ -1070,60 +1072,6 @@ static void SignalApcHandler(void* arg1, void* arg2, void* /*arg3*/, PCONTEXT co
} }
} }
static bool DispatchSignalWithSuspendedThreadContext(HANDLE target_thread, Pthread thread,
int signum, exception_handler_func_t handler) {
if (target_thread == nullptr || thread == nullptr || handler == nullptr) {
return false;
}
if (!PthreadHasPendingSignal(thread, signum)) {
return true;
}
if (SuspendThread(target_thread) == static_cast<DWORD>(-1)) {
return false;
}
bool dispatched = false;
CONTEXT host_ctx {};
host_ctx.ContextFlags = CONTEXT_FULL | CONTEXT_SEGMENTS;
if (GetThreadContext(target_thread, &host_ctx) != FALSE) {
if (TakePendingSignal(thread, signum)) {
auto ctx = CreateSignalUcontext(&host_ctx);
auto guest_context = IsGuestCodeAddress(ctx.uc_mcontext.mc_rip);
HANDLE helper_thread_handle = nullptr;
if (guest_context) {
if (DuplicateHandle(GetCurrentProcess(), target_thread, GetCurrentProcess(),
&helper_thread_handle, THREAD_SUSPEND_RESUME, FALSE,
0) == FALSE) {
ResumeThread(target_thread);
return false;
}
} else {
SanitizeNonGuestSignalUcontext(&ctx, thread);
ResumeThread(target_thread);
}
std::thread([helper_thread_handle, thread, signum, handler, ctx]() mutable {
SignalDispatchScope scope;
auto* previous_self = PthreadSwapSelfForSignal(thread);
handler(signum, &ctx);
PthreadSwapSelfForSignal(previous_self);
if (helper_thread_handle != nullptr) {
ResumeThread(helper_thread_handle);
CloseHandle(helper_thread_handle);
}
}).detach();
dispatched = true;
return true;
}
dispatched = true;
}
ResumeThread(target_thread);
return dispatched;
}
#endif #endif
void KernelDispatchPendingSignalForCurrentThread() { void KernelDispatchPendingSignalForCurrentThread() {
@@ -1206,8 +1154,7 @@ static int KYTY_SYSV_ABI KernelRaiseException(Pthread thread, int signum) {
} }
HANDLE target_thread = HANDLE target_thread =
OpenThread(THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT, FALSE, OpenThread(THREAD_SET_CONTEXT, FALSE, static_cast<DWORD>(target_thread_id));
static_cast<DWORD>(target_thread_id));
if (target_thread == nullptr) { if (target_thread == nullptr) {
return KERNEL_ERROR_EINVAL; return KERNEL_ERROR_EINVAL;
} }
@@ -1231,10 +1178,6 @@ static int KYTY_SYSV_ABI KernelRaiseException(Pthread thread, int signum) {
Common::CondVar::SignalThread(PthreadGetUniqueId(thread)); Common::CondVar::SignalThread(PthreadGetUniqueId(thread));
PthreadWakeForSignal(thread); PthreadWakeForSignal(thread);
WaitForSignalDispatch(thread, signum);
if (PthreadHasPendingSignal(thread, signum)) {
DispatchSignalWithSuspendedThreadContext(target_thread, thread, signum, handler);
}
CloseHandle(target_thread); CloseHandle(target_thread);
return OK; return OK;
#elif defined(__x86_64__) #elif defined(__x86_64__)
@@ -2229,9 +2172,11 @@ LIB_DEFINE(InitLibKernel_1_Posix) {
LIB_FUNC("ltCfaGr2JGE", Posix::pthread_mutex_destroy); LIB_FUNC("ltCfaGr2JGE", Posix::pthread_mutex_destroy);
LIB_FUNC("mkx2fVhNMsg", Posix::pthread_cond_broadcast); LIB_FUNC("mkx2fVhNMsg", Posix::pthread_cond_broadcast);
LIB_FUNC("2MOy+rUfuhQ", Posix::pthread_cond_signal); LIB_FUNC("2MOy+rUfuhQ", Posix::pthread_cond_signal);
LIB_FUNC("0TyVk4MSLt0", Posix::pthread_cond_init);
LIB_FUNC("Op8TBGY5KHg", Posix::pthread_cond_wait); LIB_FUNC("Op8TBGY5KHg", Posix::pthread_cond_wait);
LIB_FUNC("Z4QosVuAsA0", Posix::pthread_once); LIB_FUNC("Z4QosVuAsA0", Posix::pthread_once);
LIB_FUNC("1471ajPzxh0", Posix::pthread_rwlock_destroy); LIB_FUNC("1471ajPzxh0", Posix::pthread_rwlock_destroy);
LIB_FUNC("ytQULN-nhL4", Posix::pthread_rwlock_init);
LIB_FUNC("sIlRvQqsN2Y", Posix::pthread_rwlock_wrlock); LIB_FUNC("sIlRvQqsN2Y", Posix::pthread_rwlock_wrlock);
LIB_FUNC("SFxTMOfuCkE", LibKernel::PthreadRwlockTryrdlock); LIB_FUNC("SFxTMOfuCkE", LibKernel::PthreadRwlockTryrdlock);
LIB_FUNC("XhWHn6P5R7U", LibKernel::PthreadRwlockTrywrlock); LIB_FUNC("XhWHn6P5R7U", LibKernel::PthreadRwlockTrywrlock);
@@ -3268,7 +3213,6 @@ LIB_DEFINE(InitLibKernel_1_Pthread) {
LIB_FUNC("FXPWHNk8Of0", LibKernel::PthreadAttrGetschedparam); LIB_FUNC("FXPWHNk8Of0", LibKernel::PthreadAttrGetschedparam);
LIB_FUNC("6ULAa0fq4jA", LibKernel::PthreadRwlockInit); LIB_FUNC("6ULAa0fq4jA", LibKernel::PthreadRwlockInit);
LIB_FUNC("ytQULN-nhL4", LibKernel::PthreadRwlockInit);
LIB_FUNC("BB+kb08Tl9A", LibKernel::PthreadRwlockDestroy); LIB_FUNC("BB+kb08Tl9A", LibKernel::PthreadRwlockDestroy);
LIB_FUNC("Ox9i0c7L5w0", LibKernel::PthreadRwlockRdlock); LIB_FUNC("Ox9i0c7L5w0", LibKernel::PthreadRwlockRdlock);
LIB_FUNC("iGjsr1WAtI0", LibKernel::PthreadRwlockRdlock); LIB_FUNC("iGjsr1WAtI0", LibKernel::PthreadRwlockRdlock);
@@ -3284,7 +3228,6 @@ LIB_DEFINE(InitLibKernel_1_Pthread) {
LIB_FUNC("h-OifiouBd8", LibKernel::PthreadRwlockattrSettype); LIB_FUNC("h-OifiouBd8", LibKernel::PthreadRwlockattrSettype);
LIB_FUNC("2Tb92quprl0", LibKernel::PthreadCondInit); LIB_FUNC("2Tb92quprl0", LibKernel::PthreadCondInit);
LIB_FUNC("0TyVk4MSLt0", LibKernel::PthreadCondInit);
LIB_FUNC("g+PZd2hiacg", LibKernel::PthreadCondDestroy); LIB_FUNC("g+PZd2hiacg", LibKernel::PthreadCondDestroy);
LIB_FUNC("RXXqi4CtF8w", LibKernel::PthreadCondDestroy); LIB_FUNC("RXXqi4CtF8w", LibKernel::PthreadCondDestroy);
LIB_FUNC("WKAXJ4XBPQ4", LibKernel::PthreadCondWait); LIB_FUNC("WKAXJ4XBPQ4", LibKernel::PthreadCondWait);
+1
View File
@@ -53,6 +53,7 @@ constexpr Elf64_Half ET_DYNAMIC = 0xfe18; // Shared
constexpr Elf64_Word PT_LOAD = 1; constexpr Elf64_Word PT_LOAD = 1;
constexpr Elf64_Word PT_DYNAMIC = 2; constexpr Elf64_Word PT_DYNAMIC = 2;
constexpr Elf64_Word PT_TLS = 7; constexpr Elf64_Word PT_TLS = 7;
constexpr Elf64_Word PT_GNU_EH_FRAME = 0x6474e550;
constexpr Elf64_Word PT_OS_DYNLIBDATA = 0x61000000; constexpr Elf64_Word PT_OS_DYNLIBDATA = 0x61000000;
constexpr Elf64_Word PT_OS_PROCPARAM = 0x61000001; constexpr Elf64_Word PT_OS_PROCPARAM = 0x61000001;
constexpr Elf64_Word PT_OS_RELRO = 0x61000010; constexpr Elf64_Word PT_OS_RELRO = 0x61000010;
File diff suppressed because it is too large. Load diff
+39
View File
@@ -0,0 +1,39 @@
// SPDX-FileCopyrightText: Copyright 2026 shadPS4 Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#ifndef KYTY_LOADER_RED_ZONE_PATCHER_H_
#define KYTY_LOADER_RED_ZONE_PATCHER_H_
#include "common/common.h"
#include <span>
#include <vector>
namespace Loader {
struct RedZonePatchResult {
uint64_t function_count = 0;
uint64_t instruction_count = 0;
uint64_t red_zone_function_count = 0;
uint64_t memory_instruction_count = 0;
uint64_t short_memory_instruction_count = 0;
uint64_t patched_memory_instruction_count = 0;
uint64_t stack_dependent_memory_instruction_count = 0;
uint64_t control_flow_memory_instruction_count = 0;
uint64_t unrelocatable_memory_instruction_count = 0;
uint64_t indirect_red_zone_function_count = 0;
};
void RegisterRedZonePatchModule(void* module_ptr, uint64_t module_size, void* trampoline_area_ptr,
uint64_t trampoline_area_size);
void UnregisterRedZonePatchModule(void* module_ptr);
RedZonePatchResult PatchRedZoneMemoryInstructions(uint64_t segment_addr, uint64_t segment_size,
std::span<const uintptr_t> function_starts);
bool DecodeEhFrameFunctionStarts(uint64_t eh_frame_header_addr, uint64_t eh_frame_header_size,
std::vector<uintptr_t>* function_starts);
} // namespace Loader
#endif /* KYTY_LOADER_RED_ZONE_PATCHER_H_ */
+73 -1
View File
@@ -19,6 +19,7 @@
#include "loader/elf.h" #include "loader/elf.h"
#include "loader/gamePatch.h" #include "loader/gamePatch.h"
#include "loader/jit.h" #include "loader/jit.h"
#include "loader/redZonePatcher.h"
#include "loader/symbolDatabase.h" #include "loader/symbolDatabase.h"
#include "loader/x64InstructionEmulator.h" #include "loader/x64InstructionEmulator.h"
@@ -2202,17 +2203,39 @@ void RuntimeLinker::LoadProgramToMemory(Program* program) {
EXIT_IF(tls_handler_size > UINT64_MAX - program->base_size_aligned); EXIT_IF(tls_handler_size > UINT64_MAX - program->base_size_aligned);
program->mapped_size = program->base_size_aligned + tls_handler_size; program->mapped_size = program->base_size_aligned + tls_handler_size;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
const bool use_red_zone_protection = Config::RedZoneProtectionEnabled();
constexpr uint64_t RED_ZONE_TRAMPOLINE_SIZE = 8u * 1024u * 1024u;
if (use_red_zone_protection) {
EXIT_IF(RED_ZONE_TRAMPOLINE_SIZE > UINT64_MAX - program->mapped_size);
program->mapped_size += RED_ZONE_TRAMPOLINE_SIZE;
}
#endif
program->base_vaddr = Libs::LibKernel::Memory::AllocateProgramMemory( program->base_vaddr = Libs::LibKernel::Memory::AllocateProgramMemory(
g_desired_base_addr, program->mapped_size, Common::VirtualMemory::Mode::ExecuteReadWrite, g_desired_base_addr, program->mapped_size, Common::VirtualMemory::Mode::ExecuteReadWrite,
Common::PathToString(program->file_name.filename()).c_str()); Common::PathToString(program->file_name.filename()).c_str());
EXIT_IF(program->base_vaddr == 0);
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (use_red_zone_protection) {
program->red_zone_trampoline_vaddr = program->base_vaddr + program->base_size_aligned;
program->red_zone_trampoline_size = RED_ZONE_TRAMPOLINE_SIZE;
RegisterRedZonePatchModule(reinterpret_cast<void*>(program->base_vaddr),
program->base_size_aligned,
reinterpret_cast<void*>(program->red_zone_trampoline_vaddr),
program->red_zone_trampoline_size);
}
#endif
if (!is_shared) { if (!is_shared) {
program->tls.handler_vaddr = program->base_vaddr + program->base_size_aligned; program->tls.handler_vaddr = program->base_vaddr + program->base_size_aligned;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
program->tls.handler_vaddr += program->red_zone_trampoline_size;
#endif
} }
g_desired_base_addr += CODE_BASE_INCR * (1 + program->mapped_size / CODE_BASE_INCR); g_desired_base_addr += CODE_BASE_INCR * (1 + program->mapped_size / CODE_BASE_INCR);
EXIT_IF(program->base_vaddr == 0);
EXIT_IF(program->base_size_aligned < program->base_size); EXIT_IF(program->base_size_aligned < program->base_size);
LOGF("base_vaddr = 0x%016" PRIx64 "\n" LOGF("base_vaddr = 0x%016" PRIx64 "\n"
"base_size = 0x%016" PRIx64 "\n" "base_size = 0x%016" PRIx64 "\n"
@@ -2228,6 +2251,11 @@ void RuntimeLinker::LoadProgramToMemory(Program* program) {
} }
// program->elf->SetBaseVAddr(program->base_vaddr); // program->elf->SetBaseVAddr(program->base_vaddr);
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
std::vector<std::pair<uint64_t, uint64_t>> executable_segments;
uint64_t eh_frame_header_addr = 0;
uint64_t eh_frame_header_size = 0;
#endif
for (Elf64_Half i = 0; i < ehdr->e_phnum; i++) { for (Elf64_Half i = 0; i < ehdr->e_phnum; i++) {
if (phdr[i].p_memsz != 0 && (phdr[i].p_type == PT_LOAD || phdr[i].p_type == PT_OS_RELRO)) { if (phdr[i].p_memsz != 0 && (phdr[i].p_type == PT_LOAD || phdr[i].p_type == PT_OS_RELRO)) {
@@ -2250,6 +2278,11 @@ void RuntimeLinker::LoadProgramToMemory(Program* program) {
if (Common::VirtualMemory::IsExecute(mode)) { if (Common::VirtualMemory::IsExecute(mode)) {
PatchProgram(program, segment_addr, segment_memory_size); PatchProgram(program, segment_addr, segment_memory_size);
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (use_red_zone_protection) {
executable_segments.emplace_back(segment_addr, segment_file_size);
}
#endif
} }
if (!skip_protect) { if (!skip_protect) {
@@ -2284,8 +2317,42 @@ void RuntimeLinker::LoadProgramToMemory(Program* program) {
program->proc_param_vaddr = phdr[i].p_vaddr + program->base_vaddr; program->proc_param_vaddr = phdr[i].p_vaddr + program->base_vaddr;
} }
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (use_red_zone_protection && phdr[i].p_type == PT_GNU_EH_FRAME) {
eh_frame_header_addr = phdr[i].p_vaddr + program->base_vaddr;
eh_frame_header_size = phdr[i].p_memsz;
}
#endif
} }
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (use_red_zone_protection) {
std::vector<uintptr_t> function_starts;
if (!DecodeEhFrameFunctionStarts(eh_frame_header_addr, eh_frame_header_size,
&function_starts)) {
LOGF("Windows guest red-zone patching could not decode function boundaries for %s\n",
Common::PathToString(program->file_name).c_str());
}
for (const auto& [segment_addr, segment_size]: executable_segments) {
const auto result =
PatchRedZoneMemoryInstructions(segment_addr, segment_size, function_starts);
LOGF("Windows guest red-zone patching: %s, functions=%" PRIu64 ", red_zone=%" PRIu64
", memory=%" PRIu64 ", patched=%" PRIu64 ", short=%" PRIu64 ", stack=%" PRIu64
", control=%" PRIu64 ", unrelocatable=%" PRIu64 "\n",
Common::PathToString(program->file_name.filename()).c_str(), result.function_count,
result.red_zone_function_count, result.memory_instruction_count,
result.patched_memory_instruction_count, result.short_memory_instruction_count,
result.stack_dependent_memory_instruction_count,
result.control_flow_memory_instruction_count,
result.unrelocatable_memory_instruction_count);
Common::VirtualMemory::FlushInstructionCache(segment_addr, segment_size);
}
Common::VirtualMemory::FlushInstructionCache(program->red_zone_trampoline_vaddr,
program->red_zone_trampoline_size);
}
#endif
if (!is_shared) { if (!is_shared) {
SetupTlsHandler(program); SetupTlsHandler(program);
} }
@@ -2311,6 +2378,11 @@ void RuntimeLinker::DeleteProgram(Program* p) {
if (program->base_vaddr != 0 || program->mapped_size != 0) { if (program->base_vaddr != 0 || program->mapped_size != 0) {
EXIT_IF(program->base_vaddr == 0 || program->mapped_size == 0); EXIT_IF(program->base_vaddr == 0 || program->mapped_size == 0);
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (program->red_zone_trampoline_size != 0) {
UnregisterRedZonePatchModule(reinterpret_cast<void*>(program->base_vaddr));
}
#endif
EXIT_IF( EXIT_IF(
!Libs::LibKernel::Memory::FreeGuestMemory(program->base_vaddr, program->mapped_size)); !Libs::LibKernel::Memory::FreeGuestMemory(program->base_vaddr, program->mapped_size));
} }
+13 -9
View File
@@ -114,15 +114,19 @@ struct Program {
Program(); Program();
~Program(); ~Program();
int32_t unique_id = -1; int32_t unique_id = -1;
RuntimeLinker* rt = nullptr; RuntimeLinker* rt = nullptr;
std::filesystem::path file_name; std::filesystem::path file_name;
std::unique_ptr<Elf64> elf; std::unique_ptr<Elf64> elf;
std::unique_ptr<DynamicInfo> dynamic_info; std::unique_ptr<DynamicInfo> dynamic_info;
uint64_t base_vaddr = 0; uint64_t base_vaddr = 0;
uint64_t base_size = 0; uint64_t base_size = 0;
uint64_t base_size_aligned = 0; uint64_t base_size_aligned = 0;
uint64_t mapped_size = 0; uint64_t mapped_size = 0;
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
uint64_t red_zone_trampoline_vaddr = 0;
uint64_t red_zone_trampoline_size = 0;
#endif
std::unique_ptr<SymbolDatabase> export_symbols; std::unique_ptr<SymbolDatabase> export_symbols;
std::unique_ptr<SymbolDatabase> import_symbols; std::unique_ptr<SymbolDatabase> import_symbols;
ThreadLocalStorage tls; ThreadLocalStorage tls;
+10
View File
@@ -64,6 +64,9 @@ static void PrintUsage() {
::printf(" --spirv-debug-printf <true|false> Enable SPIR-V debug printf.\n"); ::printf(" --spirv-debug-printf <true|false> Enable SPIR-V debug printf.\n");
::printf( ::printf(
" --readback-linear-images <true|false> Read back writable linear images on submit.\n"); " --readback-linear-images <true|false> Read back writable linear images on submit.\n");
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
::printf(" --redzone Protect the guest SysV red zone.\n");
#endif
::printf(" --keymap <Pad=Key> DualSense mapping; may be repeated.\n"); ::printf(" --keymap <Pad=Key> DualSense mapping; may be repeated.\n");
::printf(" --rd Enable RenderDoc capture.\n"); ::printf(" --rd Enable RenderDoc capture.\n");
} }
@@ -138,6 +141,13 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he
continue; continue;
} }
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (arg == "--redzone") {
options.config.red_zone_protection_enabled = true;
continue;
}
#endif
if (!Common::StartsWith(arg, "--")) { if (!Common::StartsWith(arg, "--")) {
::printf("game input must be provided with --game\n"); ::printf("game input must be provided with --game\n");
return false; return false;
+123 -4
View File
@@ -19,6 +19,8 @@ namespace AudioOut2 = Libs::Audio::AudioOut2;
std::mutex g_device_mutex; std::mutex g_device_mutex;
std::condition_variable g_device_cv; std::condition_variable g_device_cv;
std::vector<int> g_live_devices; std::vector<int> g_live_devices;
std::vector<int> g_device_backed_handles;
std::vector<bool> g_output_blocking;
int g_next_device = 1; int g_next_device = 1;
int g_open_waiters = 0; int g_open_waiters = 0;
bool g_block_opens = false; bool g_block_opens = false;
@@ -61,6 +63,17 @@ struct PortState {
uint64_t reserved[6]; uint64_t reserved[6];
}; };
struct Attribute {
uint32_t attribute_id;
int32_t reserved;
const void* value;
size_t value_size;
};
struct Pcm {
const void* data;
};
const auto* AsParam(const PortParam* param) { const auto* AsParam(const PortParam* param) {
return reinterpret_cast<const AudioOut2::AudioOut2PortParam*>(param); return reinterpret_cast<const AudioOut2::AudioOut2PortParam*>(param);
} }
@@ -73,6 +86,10 @@ auto* AsState(PortState* state) {
return reinterpret_cast<AudioOut2::AudioOut2PortState*>(state); return reinterpret_cast<AudioOut2::AudioOut2PortState*>(state);
} }
const auto* AsAttribute(const Attribute* attribute) {
return reinterpret_cast<const AudioOut2::AudioOut2Attribute*>(attribute);
}
PortParam MakeParam(uint32_t data_format = 0x200) { PortParam MakeParam(uint32_t data_format = 0x200) {
PortParam param {}; PortParam param {};
param.data_format = data_format; param.data_format = data_format;
@@ -80,9 +97,9 @@ PortParam MakeParam(uint32_t data_format = 0x200) {
return param; return param;
} }
AudioOut2::AudioOut2ContextHandle CreateContext() { AudioOut2::AudioOut2ContextHandle CreateContext(uint32_t queue_depth = 4) {
ContextParam param {}; ContextParam param {};
param.queue_depth = 4; param.queue_depth = queue_depth;
param.num_grains = 512; param.num_grains = 512;
AudioOut2::AudioOut2ContextHandle context = 0; AudioOut2::AudioOut2ContextHandle context = 0;
Check(AudioOut2::AudioOut2ContextCreate(AsParam(&param), nullptr, 0, &context) == OK, Check(AudioOut2::AudioOut2ContextCreate(AsParam(&param), nullptr, 0, &context) == OK,
@@ -112,6 +129,23 @@ int LiveDeviceCount() {
return static_cast<int>(g_live_devices.size()); return static_cast<int>(g_live_devices.size());
} }
void SetPcm(AudioOut2::AudioOut2PortHandle port, const void* data) {
const Pcm pcm {data};
const Attribute attribute {0, 0, &pcm, sizeof(pcm)};
Check(AudioOut2::AudioOut2PortSetAttributes(port, AsAttribute(&attribute), 1) == OK,
"setting PCM failed");
}
void ResetOutputCalls() {
std::lock_guard lock(g_device_mutex);
g_output_blocking.clear();
}
std::vector<bool> OutputCalls() {
std::lock_guard lock(g_device_mutex);
return g_output_blocking;
}
void TestSlotReuse() { void TestSlotReuse() {
const auto context = CreateContext(); const auto context = CreateContext();
const auto param = MakeParam(); const auto param = MakeParam();
@@ -202,14 +236,83 @@ void TestContextDestroyCancelsPendingCreate() {
Check(LiveDeviceCount() == 0, "cancelled port create leaked a device"); Check(LiveDeviceCount() == 0, "cancelled port create leaked a device");
} }
void TestSynchronousDevicePushBypassesModelledQueue() {
const auto context = CreateContext(1);
const auto param = MakeParam();
AudioOut2::AudioOut2PortHandle port = 0;
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(&param), &port) == OK,
"device port create failed");
uint32_t pcm[512] {};
SetPcm(port, pcm);
ResetOutputCalls();
Check(AudioOut2::AudioOut2ContextPush(context, 1) == OK, "first sync push failed");
Check(AudioOut2::AudioOut2ContextPush(context, 1) == OK,
"device-paced sync push was blocked by modelled queue");
const auto calls = OutputCalls();
Check(calls.size() == 2, "sync pushes did not reach the device backend");
Check(calls[0] && calls[1], "sync pushes lost their blocking mode");
AudioOut2::AudioOut2PortDestroy(port);
AudioOut2::AudioOut2ContextDestroy(context);
}
void TestAsynchronousDevicePushKeepsQueueBounded() {
const auto context = CreateContext(1);
const auto param = MakeParam();
AudioOut2::AudioOut2PortHandle port = 0;
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(&param), &port) == OK,
"device port create failed");
uint32_t pcm[512] {};
SetPcm(port, pcm);
ResetOutputCalls();
Check(AudioOut2::AudioOut2ContextPush(context, 0) == OK, "first async push failed");
Check(AudioOut2::AudioOut2ContextPush(context, 0) != OK,
"full async queue accepted another buffer");
const auto calls = OutputCalls();
Check(calls.size() == 1 && !calls[0], "rejected async push reached the device backend");
uint32_t queued = 0;
uint32_t available = 0;
Check(AudioOut2::AudioOut2ContextGetQueueLevel(context, &queued, &available) == OK,
"queue-level query failed");
Check(queued == 1 && available == 0, "async queue level does not match accepted pushes");
AudioOut2::AudioOut2PortDestroy(port);
AudioOut2::AudioOut2ContextDestroy(context);
}
void TestHandleWithoutPcmDoesNotBypassQueue() {
const auto context = CreateContext(1);
const auto param = MakeParam();
AudioOut2::AudioOut2PortHandle port = 0;
Check(AudioOut2::AudioOut2PortCreate(context, AsParam(&param), &port) == OK,
"device port create failed");
ResetOutputCalls();
Check(AudioOut2::AudioOut2ContextPush(context, 1) == OK, "empty sync push failed");
Check(AudioOut2::AudioOut2ContextPush(context, 0) != OK,
"handle without PCM bypassed queue backpressure");
Check(OutputCalls().empty(), "empty push reached the device backend");
AudioOut2::AudioOut2PortDestroy(port);
AudioOut2::AudioOut2ContextDestroy(context);
}
} // namespace } // namespace
namespace Libs::Audio::AudioInternal { namespace Libs::Audio::AudioInternal {
int AudioOutOpen(int /*type*/, uint32_t /*samples_num*/, uint32_t /*freq*/, Format /*format*/) { int AudioOutOpen(int type, uint32_t /*samples_num*/, uint32_t /*freq*/, Format /*format*/) {
std::unique_lock lock(g_device_mutex); std::unique_lock lock(g_device_mutex);
const int handle = g_next_device++; const int handle = g_next_device++;
g_live_devices.push_back(handle); g_live_devices.push_back(handle);
if (type != 10) {
g_device_backed_handles.push_back(handle);
}
g_open_waiters++; g_open_waiters++;
g_device_cv.notify_all(); g_device_cv.notify_all();
g_device_cv.wait(lock, []() { return !g_block_opens; }); g_device_cv.wait(lock, []() { return !g_block_opens; });
@@ -222,9 +325,22 @@ void AudioOutClose(int handle) {
if (it != g_live_devices.end()) { if (it != g_live_devices.end()) {
g_live_devices.erase(it); g_live_devices.erase(it);
} }
const auto device_it =
std::find(g_device_backed_handles.begin(), g_device_backed_handles.end(), handle);
if (device_it != g_device_backed_handles.end()) {
g_device_backed_handles.erase(device_it);
}
} }
uint32_t AudioOutOutputs(const OutputParam* /*params*/, uint32_t /*num*/, bool /*blocking*/) { bool AudioOutHasDevice(int handle) {
std::lock_guard lock(g_device_mutex);
return std::find(g_device_backed_handles.begin(), g_device_backed_handles.end(), handle) !=
g_device_backed_handles.end();
}
uint32_t AudioOutOutputs(const OutputParam* /*params*/, uint32_t /*num*/, bool blocking) {
std::lock_guard lock(g_device_mutex);
g_output_blocking.push_back(blocking);
return 0; return 0;
} }
@@ -244,6 +360,9 @@ int main() {
TestFullTableRecovers(); TestFullTableRecovers();
TestConcurrentCreates(); TestConcurrentCreates();
TestContextDestroyCancelsPendingCreate(); TestContextDestroyCancelsPendingCreate();
TestSynchronousDevicePushBypassesModelledQueue();
TestAsynchronousDevicePushKeepsQueueBounded();
TestHandleWithoutPcmDoesNotBypassQueue();
std::printf("AudioOut2PortTests: all cases passed\n"); std::printf("AudioOut2PortTests: all cases passed\n");
return 0; return 0;
} }
+320
View File
@@ -0,0 +1,320 @@
#include "libs/imeDialog.h"
#include <array>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <utility>
namespace Ime = Libs::Dialog::ImeDialog;
#define CHECK(condition) \
do { \
if (!(condition)) { \
std::abort(); \
} \
} while (false)
namespace {
constexpr int Error(uint32_t value) { return static_cast<int32_t>(value); }
int g_filter_calls = 0;
int g_keyboard_filter_calls = 0;
bool g_block_keyboard = false;
uint16_t g_remap_keyboard = 0;
int32_t KYTY_SYSV_ABI CopyFilter(char16_t *out_text, uint32_t *out_length,
const char16_t *source,
uint32_t source_length) {
g_filter_calls++;
CHECK(*out_length >= source_length);
std::memcpy(out_text, source, source_length * sizeof(char16_t));
*out_length = source_length;
return 0;
}
int32_t KYTY_SYSV_ABI SupplementaryFilter(char16_t *out_text,
uint32_t *out_length,
const char16_t *, uint32_t) {
CHECK(*out_length >= 2);
out_text[0] = 0xd83d;
out_text[1] = 0xde00;
*out_length = 2;
return 0;
}
int KYTY_SYSV_ABI KeyboardFilter(const Ime::Keycode *source,
uint16_t *out_keycode, uint32_t *out_status,
void *) {
g_keyboard_filter_calls++;
if (g_block_keyboard) {
*out_keycode = 0;
*out_status = 0;
return 0;
}
if (g_remap_keyboard != 0) {
*out_keycode = g_remap_keyboard;
*out_status = source->status;
return 0;
}
return -1;
}
Ime::Param MakeParam(char16_t *text) {
Ime::Param param{};
param.user_id = 1000;
param.type = Ime::Type::Default;
param.supported_languages = 1;
param.enter_label = Ime::EnterLabel::Default;
param.input_text_buffer = text;
param.max_text_length = 31;
return param;
}
void TestLayoutAndPanelSize() {
static_assert(sizeof(Ime::Param) == 0x60);
static_assert(sizeof(Ime::ExtendedParam) == 0x88);
static_assert(sizeof(Ime::Result) == 0x10);
static_assert(sizeof(Ime::PositionAndForm) == 0x1c);
std::array<char16_t, 32> text{};
auto param = MakeParam(text.data());
uint32_t width = 0;
uint32_t height = 0;
CHECK(Ime::ImeDialogGetPanelSize(&param, &width, &height) == 0);
CHECK(width == 793 && height == 528);
param.option = Ime::OPTION_MULTILINE;
CHECK(Ime::ImeDialogGetPanelSize(&param, &width, &height) == 0);
CHECK(width == 793 && height == 628);
param.option = 0;
param.type = Ime::Type::Number;
CHECK(Ime::ImeDialogGetPanelSize(&param, &width, &height) == 0);
CHECK(width == 370 && height == 522);
param = MakeParam(text.data());
param.option = Ime::OPTION_EXT_KEYBOARD;
Ime::ExtendedParam extended{};
extended.option = 0x00000400;
CHECK(Ime::ImeDialogGetPanelSizeExtended(&param, &extended, &width,
&height) == 0);
CHECK(width == 793 && height == 168);
param.type = Ime::Type::BasicLatin;
CHECK(Ime::ImeDialogGetPanelSizeExtended(&param, &extended, &width,
&height) == 0);
CHECK(width == 793 && height == 103);
}
void TestAcceptLifecycle() {
std::array<char16_t, 32> text{u'A', u'l', u'i', u'c', u'e'};
auto param = MakeParam(text.data());
CHECK(Ime::ImeDialogInit(&param, nullptr) == 0);
CHECK(Ime::ImeDialogGetStatus() == static_cast<int>(Ime::Status::Running));
CHECK(Ime::ImeDialogInit(nullptr, nullptr) == Error(0x80bc0001));
Ime::HostSnapshot snapshot;
CHECK(Ime::GetHostSnapshot(&snapshot));
CHECK(snapshot.text == u"Alice");
CHECK(Ime::HostInsertText(snapshot.generation, u" 2"));
CHECK(Ime::HostBackspace(snapshot.generation));
CHECK(Ime::HostAccept(snapshot.generation));
CHECK(Ime::ImeDialogGetStatus() == static_cast<int>(Ime::Status::Finished));
CHECK(std::u16string(text.data()) == u"Alice ");
Ime::Result result{};
CHECK(Ime::ImeDialogGetResult(&result) == 0);
CHECK(result.endstatus == Ime::EndStatus::Ok);
CHECK(Ime::ImeDialogTerm() == 0);
CHECK(Ime::ImeDialogGetStatus() == static_cast<int>(Ime::Status::None));
}
void TestCancelAndAbortRestoreText() {
for (const auto expected :
{Ime::EndStatus::UserCanceled, Ime::EndStatus::Aborted}) {
std::array<char16_t, 32> text{u'o', u'l', u'd'};
auto param = MakeParam(text.data());
CHECK(Ime::ImeDialogInit(&param, nullptr) == 0);
Ime::HostSnapshot snapshot;
CHECK(Ime::GetHostSnapshot(&snapshot));
CHECK(Ime::HostInsertText(snapshot.generation, u" value"));
if (expected == Ime::EndStatus::UserCanceled) {
CHECK(Ime::HostCancel(snapshot.generation));
} else {
CHECK(Ime::ImeDialogAbort() == 0);
}
Ime::Result result{};
CHECK(Ime::ImeDialogGetResult(&result) == 0);
CHECK(result.endstatus == expected);
CHECK(std::u16string(text.data()) == u"old");
CHECK(Ime::ImeDialogTerm() == 0);
}
}
void TestValidation() {
std::array<char16_t, 32> text{};
auto param = MakeParam(text.data());
param.reserved[0] = 1;
CHECK(Ime::ImeDialogInit(&param, nullptr) == Error(0x80bc0032));
param.reserved[0] = 0;
param.max_text_length = Ime::IME_DIALOG_MAX_TEXT_LENGTH + 1;
CHECK(Ime::ImeDialogInit(&param, nullptr) == Error(0x80bc0016));
CHECK(Ime::ImeDialogGetResult(nullptr) == Error(0x80bc0107));
param = MakeParam(text.data());
Ime::ExtendedParam extended{};
extended.ext_keyboard_mode = 0x00000004;
CHECK(Ime::ImeDialogInit(&param, &extended) == Error(0x80bc001c));
extended = {};
extended.option = 0x00000200;
extended.disable_device =
Ime::DISABLE_DEVICE_CONTROLLER | Ime::DISABLE_DEVICE_EXT_KEYBOARD;
CHECK(Ime::ImeDialogInit(&param, &extended) == 0);
Ime::HostSnapshot snapshot;
CHECK(Ime::GetHostSnapshot(&snapshot));
CHECK(snapshot.disable_device == 3);
CHECK(Ime::ImeDialogAbort() == 0);
Ime::Result result{};
CHECK(Ime::ImeDialogGetResult(&result) == 0);
CHECK(Ime::ImeDialogTerm() == 0);
}
void TestFilteringAndInputPolicy() {
std::array<char16_t, 32> text{u'1'};
auto param = MakeParam(text.data());
param.type = Ime::Type::Number;
param.filter = CopyFilter;
g_filter_calls = 0;
CHECK(Ime::ImeDialogInit(&param, nullptr) == 0);
CHECK(Ime::ImeDialogGetStatus() == static_cast<int>(Ime::Status::Running));
CHECK(g_filter_calls == 0);
CHECK(Ime::ImeDialogGetResult(nullptr) == Error(0x80bc0031));
CHECK(Ime::ImeDialogTerm() == Error(0x80bc0106));
CHECK(g_filter_calls == 0);
Ime::HostSnapshot snapshot;
CHECK(Ime::GetHostSnapshot(&snapshot));
CHECK(Ime::HostInsertText(snapshot.generation, u"a2.\n"));
CHECK(Ime::ImeDialogGetStatus() == static_cast<int>(Ime::Status::Running));
CHECK(g_filter_calls == 1);
CHECK(std::u16string(text.data()) == u"12.");
CHECK(Ime::HostCancel(snapshot.generation));
Ime::Result result{};
CHECK(Ime::ImeDialogGetResult(&result) == 0);
CHECK(Ime::ImeDialogTerm() == 0);
}
void TestExternalKeyboardFilter() {
std::array<char16_t, 32> text{};
auto param = MakeParam(text.data());
Ime::ExtendedParam extended{};
extended.ext_keyboard_filter = KeyboardFilter;
CHECK(Ime::ImeDialogInit(&param, &extended) == 0);
Ime::HostSnapshot snapshot;
CHECK(Ime::GetHostSnapshot(&snapshot));
Ime::ExternalInput input{};
input.key.status = 3;
input.key.keycode = 4;
input.action = Ime::ExternalAction::Text;
input.text = u"x";
g_keyboard_filter_calls = 0;
g_block_keyboard = true;
CHECK(Ime::HostQueueExternalInput(snapshot.generation, input));
CHECK(Ime::ImeDialogGetStatus() == static_cast<int>(Ime::Status::Running));
CHECK(g_keyboard_filter_calls == 1);
CHECK(text[0] == u'\0');
g_block_keyboard = false;
CHECK(Ime::HostQueueExternalInput(snapshot.generation, std::move(input)));
CHECK(Ime::ImeDialogGetStatus() == static_cast<int>(Ime::Status::Running));
CHECK(g_keyboard_filter_calls == 2);
CHECK(std::u16string(text.data()) == u"x");
input = {};
input.key.status = 3;
input.key.keycode = 4;
input.action = Ime::ExternalAction::Text;
input.text = u"x";
g_remap_keyboard = 5;
CHECK(Ime::HostQueueExternalInput(snapshot.generation, std::move(input)));
CHECK(Ime::ImeDialogGetStatus() == static_cast<int>(Ime::Status::Running));
CHECK(g_keyboard_filter_calls == 3);
CHECK(std::u16string(text.data()) == u"xb");
input = {};
input.key.status = 0x00000201;
input.key.keycode = 4;
input.action = Ime::ExternalAction::Text;
input.text = u"x";
g_remap_keyboard = 50;
CHECK(Ime::HostQueueExternalInput(snapshot.generation, std::move(input)));
CHECK(Ime::ImeDialogGetStatus() == static_cast<int>(Ime::Status::Running));
CHECK(g_keyboard_filter_calls == 4);
CHECK(std::u16string(text.data()) == u"xb~");
input = {};
input.key.status = 3;
input.key.keycode = 4;
input.action = Ime::ExternalAction::Text;
input.text = u"x";
g_remap_keyboard = 88;
CHECK(Ime::HostQueueExternalInput(snapshot.generation, std::move(input)));
CHECK(Ime::ImeDialogGetStatus() == static_cast<int>(Ime::Status::Finished));
CHECK(g_keyboard_filter_calls == 5);
Ime::Result result{};
CHECK(Ime::ImeDialogGetResult(&result) == 0);
CHECK(result.endstatus == Ime::EndStatus::Ok);
CHECK(Ime::ImeDialogTerm() == 0);
g_remap_keyboard = 0;
CHECK(Ime::ImeDialogInit(&param, &extended) == 0);
CHECK(Ime::GetHostSnapshot(&snapshot));
input = {};
input.key.status = 1;
input.key.keycode = 41;
input.action = Ime::ExternalAction::Cancel;
CHECK(Ime::HostQueueExternalInput(snapshot.generation, std::move(input)));
input = {};
input.key.status = 3;
input.key.keycode = 4;
input.action = Ime::ExternalAction::Text;
input.text = u"ignored";
CHECK(Ime::HostQueueExternalInput(snapshot.generation, std::move(input)));
CHECK(Ime::ImeDialogGetStatus() == static_cast<int>(Ime::Status::Finished));
CHECK(g_keyboard_filter_calls == 6);
result = {};
CHECK(Ime::ImeDialogGetResult(&result) == 0);
CHECK(Ime::ImeDialogTerm() == 0);
}
void TestFilteredCursorBoundary() {
std::array<char16_t, 8> text{};
auto param = MakeParam(text.data());
param.filter = SupplementaryFilter;
CHECK(Ime::ImeDialogInit(&param, nullptr) == 0);
Ime::HostSnapshot snapshot;
CHECK(Ime::GetHostSnapshot(&snapshot));
CHECK(Ime::HostInsertText(snapshot.generation, u"x"));
CHECK(Ime::ImeDialogGetStatus() == static_cast<int>(Ime::Status::Running));
CHECK(Ime::GetHostSnapshot(&snapshot));
CHECK(snapshot.cursor == 2);
CHECK(text[0] == 0xd83d && text[1] == 0xde00 && text[2] == 0);
CHECK(Ime::HostCancel(snapshot.generation));
Ime::Result result{};
CHECK(Ime::ImeDialogGetResult(&result) == 0);
CHECK(Ime::ImeDialogTerm() == 0);
}
} // namespace
int main() {
TestLayoutAndPanelSize();
TestAcceptLifecycle();
TestCancelAndAbortRestoreText();
TestValidation();
TestFilteringAndInputPolicy();
TestExternalKeyboardFilter();
TestFilteredCursorBoundary();
return 0;
}
+147
View File
@@ -2,6 +2,8 @@
#include "graphics/host_gpu/memoryTracker.h" #include "graphics/host_gpu/memoryTracker.h"
#include "graphics/host_gpu/rangeSet.h" #include "graphics/host_gpu/rangeSet.h"
#include <atomic>
#include <chrono>
#include <cstdint> #include <cstdint>
#include <cstdio> #include <cstdio>
#include <cstdlib> #include <cstdlib>
@@ -211,6 +213,40 @@ void TestQueriesDoNotRequireMappedOwnership() {
"unowned tracker range did not expose its initial CPU-dirty state"); "unowned tracker range did not expose its initial CPU-dirty state");
} }
void TestConcurrentRegionPublication() {
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);
std::binary_semaphore start_first{0};
std::binary_semaphore start_second{0};
std::atomic_uint32_t cpu_dirty_results{0};
std::jthread first([&] {
start_first.acquire();
if (tracker.IsRegionCpuModified(address, page_size)) {
cpu_dirty_results.fetch_add(1, std::memory_order_relaxed);
}
});
std::jthread second([&] {
start_second.acquire();
if (tracker.IsRegionCpuModified(address, page_size)) {
cpu_dirty_results.fetch_add(1, std::memory_order_relaxed);
}
});
start_first.release();
start_second.release();
first.join();
second.join();
tracker.UntrackMemory(address, page_size);
Release(page_manager, memory, page_size);
Check(cpu_dirty_results.load(std::memory_order_relaxed) == 2,
"concurrent region publication lost initial CPU ownership");
}
void TestCpuDirtyUpload() { void TestCpuDirtyUpload() {
TrackerHarness harness; TrackerHarness harness;
auto &tracker = harness.tracker; auto &tracker = harness.tracker;
@@ -497,6 +533,114 @@ void TestCrossRegionUpload() {
Release(page_manager, memory, region_size * 2); Release(page_manager, memory, region_size * 2);
} }
void TestUploadDoesNotSerializeDisjointRegion() {
constexpr auto region_size = Libs::Graphics::TRACKER_REGION_SIZE;
constexpr auto page_size = Libs::Graphics::TRACKER_PAGE_SIZE;
TrackerHarness harness;
auto &tracker = harness.tracker;
auto &page_manager = harness.page_manager;
auto *memory = Allocate(page_manager, region_size * 2 / page_size);
const auto allocation_base = reinterpret_cast<uint64_t>(memory);
const auto second_region =
(allocation_base & ~(region_size - 1)) + region_size;
Check(second_region + page_size <= allocation_base + region_size * 2,
"test allocation does not span two tracker regions");
// Publish both managers before the concurrent section so this test measures
// tracker access serialization rather than manager allocation.
Check(tracker.IsRegionCpuModified(allocation_base, page_size) &&
tracker.IsRegionCpuModified(second_region, page_size),
"disjoint upload setup did not initialize both regions");
std::binary_semaphore upload_entered{0};
std::binary_semaphore finish_upload{0};
std::binary_semaphore query_finished{0};
std::atomic_bool query_result{false};
std::jthread uploader([&] {
tracker.ForEachUploadRange(
allocation_base, page_size, true, [](uint64_t, uint64_t) noexcept {},
[&]() noexcept {
upload_entered.release();
finish_upload.acquire();
});
});
upload_entered.acquire();
std::jthread query([&] {
query_result.store(tracker.IsRegionCpuModified(second_region, page_size),
std::memory_order_relaxed);
query_finished.release();
});
const bool completed_while_upload_blocked =
query_finished.try_acquire_for(std::chrono::seconds(5));
finish_upload.release();
uploader.join();
query.join();
tracker.UnmarkRegionAsGpuModified(allocation_base, page_size);
tracker.MarkRegionAsCpuModified(allocation_base, page_size);
tracker.UntrackMemory(allocation_base, region_size * 2);
Release(page_manager, memory, region_size * 2);
Check(completed_while_upload_blocked &&
query_result.load(std::memory_order_relaxed),
"upload callback serialized an unrelated tracker region");
}
void TestDownloadDoesNotSerializeDisjointRegion() {
constexpr auto region_size = Libs::Graphics::TRACKER_REGION_SIZE;
constexpr auto page_size = Libs::Graphics::TRACKER_PAGE_SIZE;
TrackerHarness harness;
auto &tracker = harness.tracker;
auto &page_manager = harness.page_manager;
auto *memory = Allocate(page_manager, region_size * 2 / page_size);
const auto allocation_base = reinterpret_cast<uint64_t>(memory);
const auto second_region =
(allocation_base & ~(region_size - 1)) + region_size;
Check(second_region + page_size <= allocation_base + region_size * 2,
"test allocation does not span two tracker regions");
tracker.ForEachUploadRange(
allocation_base, page_size, true, [](uint64_t, uint64_t) noexcept {},
[]() noexcept {});
tracker.ForEachUploadRange(
second_region, page_size, false, [](uint64_t, uint64_t) noexcept {},
[]() noexcept {});
std::binary_semaphore download_entered{0};
std::binary_semaphore finish_download{0};
std::binary_semaphore mutation_finished{0};
std::jthread downloader([&] {
tracker.ForEachDownloadRange<false>(allocation_base, page_size,
[&](uint64_t, uint64_t) noexcept {
download_entered.release();
finish_download.acquire();
});
});
download_entered.acquire();
std::jthread mutation([&] {
tracker.MarkRegionAsGpuModified(second_region, page_size);
mutation_finished.release();
});
const bool completed_while_download_blocked =
mutation_finished.try_acquire_for(std::chrono::seconds(5));
finish_download.release();
downloader.join();
mutation.join();
const bool both_gpu_owned =
tracker.IsRegionGpuModified(allocation_base, page_size) &&
tracker.IsRegionGpuModified(second_region, page_size);
tracker.UnmarkRegionAsGpuModified(allocation_base, page_size);
tracker.UnmarkRegionAsGpuModified(second_region, page_size);
tracker.MarkRegionAsCpuModified(allocation_base, page_size);
tracker.MarkRegionAsCpuModified(second_region, page_size);
tracker.UntrackMemory(allocation_base, region_size * 2);
Release(page_manager, memory, region_size * 2);
Check(completed_while_download_blocked && both_gpu_owned,
"download callback serialized an unrelated tracker region");
}
void TestGpuUnmarkUsesRegionMask() { void TestGpuUnmarkUsesRegionMask() {
constexpr auto region_size = Libs::Graphics::TRACKER_REGION_SIZE; constexpr auto region_size = Libs::Graphics::TRACKER_REGION_SIZE;
constexpr auto page_size = Libs::Graphics::TRACKER_PAGE_SIZE; constexpr auto page_size = Libs::Graphics::TRACKER_PAGE_SIZE;
@@ -683,6 +827,7 @@ int main(int argc, char **argv) {
} }
TestRangeSet(); TestRangeSet();
TestQueriesDoNotRequireMappedOwnership(); TestQueriesDoNotRequireMappedOwnership();
TestConcurrentRegionPublication();
TestCpuDirtyUpload(); TestCpuDirtyUpload();
TestRangeInvalidation(); TestRangeInvalidation();
TestGpuReacquisitionAfterInvalidation(); TestGpuReacquisitionAfterInvalidation();
@@ -690,6 +835,8 @@ int main(int argc, char **argv) {
TestExactDirtyIntervalsSharingTrackerPage(); TestExactDirtyIntervalsSharingTrackerPage();
TestGpuDownloadProtectionMirrors(); TestGpuDownloadProtectionMirrors();
TestCrossRegionUpload(); TestCrossRegionUpload();
TestUploadDoesNotSerializeDisjointRegion();
TestDownloadDoesNotSerializeDisjointRegion();
TestGpuUnmarkUsesRegionMask(); TestGpuUnmarkUsesRegionMask();
TestFullRegionGpuUnmarkBatching(); TestFullRegionGpuUnmarkBatching();
TestFatalPaths(); TestFatalPaths();
Loaded 100 of 104 files, more files were not shown because too many files have changed in this diff. Show more