Compare commits

...

32 Commits

Author SHA1 Message Date
nmzik 027433fcdc Update game status report form 2026-08-08 21:19:22 +02:00
DeathWhispers 7ff9e4907f Don't declare colorWriteEnable dynamic state on depth-only pipelines (#160)
fix(gpu): drop colorWriteEnable dynamic state on depth-only pipelines

Pipelines were created with VK_DYNAMIC_STATE_COLOR_WRITE_ENABLE_EXT
unconditionally on non-Apple platforms, but the draw path only calls
vkCmdSetColorWriteEnableEXT when there is at least one color attachment.
A depth-only draw therefore reached vkCmdDrawIndexed with the dynamic
state declared and never set, which violates
VUID-vkCmdDrawIndexed-None-07749.

With validation enabled the error is fatal, so any title that issues a
depth-only pass aborts. The launcher enables validation by default
(vulkan_validation_enabled = true), so this is the default configuration:
PAC-MAN WORLD Re-PAC crashes from the launcher while the same build runs
normally with --vulkan-validation false.

Gate the declaration on the same color_count the draw path uses. Both
sides read state.color_count from the same draw, so the two conditions
are now identical by construction.
2026-08-08 08:21:18 +02:00
Stefanos Costa 87428d7a4b kernel: do not release a waiter on a zero-count syncOnAddress wake (linux only) (#168)
* kernel: do not release a waiter on a zero-count syncOnAddress wake

sceKernelSyncOnAddressWake with a count of 0 must release no waiters. On
Linux it released exactly one.

WakeLinux passes the count straight to FUTEX_WAKE, which does not treat 0
as "wake nobody": the kernel's wake loop tests `if (++ret >= nr_wake) break`
after waking a waiter, so any waiter present is released before the loop can
break. A standalone futex program confirms it -- FUTEX_WAKE with val = 0
returns 1, and the waiter observes its wait return 0 with the word unchanged.

WakePortable already short-circuits count == 0, so the two backends of the
same ABI disagreed, with only the Linux one wrong.

TestWakeZeroIsNoOp in SyncOnAddressTests covers exactly this and fails on
Linux without the fix (15 consecutive failures on the assertion "zero-count
wake releases no waiters"); with the fix the suite passes 15 consecutive runs.

* kernel: shorten zero-count futex comment

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-08-08 08:17:56 +02:00
Ali Almohaya 822ffba313 feat: identify build provenance in the crash log report (#196)
* feat: identify build provenance in the crash log report

* cmake: reuse git version dependency target

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-08-08 08:16:49 +02:00
Ali Almohaya 3c850b65c8 fix: stop a connected controller from disabling keyboard input (#194)
fix(input): stop a connected controller from disabling keyboard input
2026-08-08 08:05:52 +02:00
nmzik 698c9f802f renderer: remove global BufferCache locking 2026-08-08 08:02:35 +02:00
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
nmzik 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
70 changed files with 4261 additions and 2229 deletions
@@ -1,6 +1,6 @@
name: Game Emulation Bug Report
description: Report KytyPS5 game boot, crash, freeze, rendering, audio, input, or gameplay behavior problems.
title: "[GAME BUG]: "
name: Game Emulation Status Report
description: Submit a KytyPS5 game compatibility result, including boot, rendering, audio, input, or gameplay behavior.
title: "[GAME STATUS]: "
body:
- type: markdown
attributes:
@@ -10,6 +10,7 @@ body:
Before submitting, please:
- Search existing issues first.
- Re-test with a clean game dump.
- Use the latest KytyPS5 build available to you.
- Attach the complete log file instead of edited snippets.
- type: markdown
@@ -44,6 +45,28 @@ body:
validations:
required: true
- type: dropdown
id: compatibility-status
attributes:
label: Compatibility status
description: Select the furthest point the game consistently reaches on this build.
options:
- "Doesn't boot"
- Logo
- Main menu
- In game
validations:
required: true
- type: markdown
attributes:
value: |
**Status guide**
- **Doesn't boot:** The game does not show its first logo or startup screen.
- **Logo:** The game shows a logo or startup screen but does not reach the main menu.
- **Main menu:** The game reaches its menus but does not enter gameplay.
- **In game:** The game reaches controllable gameplay, even if bugs prevent completion.
- type: input
id: regression-builds
attributes:
@@ -54,22 +77,22 @@ body:
- type: markdown
attributes:
value: |
## Problem details
## Test result
- type: textarea
id: what-happened
attributes:
label: What happened?
description: Describe the failure or incorrect behavior as directly as possible.
placeholder: "Example: The game reaches the main menu, then freezes after selecting Continue."
label: Result details
description: Describe what worked, what failed, and any visible or audible problems.
placeholder: "Example: The game reaches the main menu, then freezes after selecting Continue. Audio plays correctly until the freeze."
validations:
required: true
- type: textarea
id: steps-to-reproduce
attributes:
label: Steps to reproduce
description: List the shortest path that triggers the problem from a fresh KytyPS5 start.
label: Steps to reproduce the result
description: List the shortest path to the reported status or failure from a fresh KytyPS5 start.
placeholder: |
1. Open KytyPS5.
2. Boot the game.
@@ -81,7 +104,7 @@ body:
id: expected-behavior
attributes:
label: Expected behavior
description: Explain what should happen at the same point in the game.
description: Explain what should happen at the same point in the game. If there is no known problem, write "No known issue".
placeholder: "Example: The save should load and gameplay should continue normally."
validations:
required: true
+58 -19
View File
@@ -373,7 +373,13 @@ jobs:
release:
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]
runs-on: ubuntu-latest
permissions:
@@ -382,34 +388,62 @@ jobs:
RELEASE_NAME: ${{ needs.metadata.outputs.release_tag }}
steps:
- name: Download builds
- name: Download Windows build
if: needs.windows.result == 'success'
uses: actions/download-artifact@v4
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
run: |
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 "$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"
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"
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 \
"$GITHUB_WORKSPACE/$RELEASE_NAME-Linux-x86_64.tar.gz" .
@@ -418,11 +452,16 @@ jobs:
env:
GH_TOKEN: ${{ github.token }}
run: |
assets=(
"$RELEASE_NAME-Windows-x64.zip"
"$RELEASE_NAME-macOS-x86_64.zip"
"$RELEASE_NAME-Linux-x86_64.tar.gz"
)
assets=()
for artifact in \
"$RELEASE_NAME-Windows-x64.zip" \
"$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
gh release upload "$RELEASE_NAME" "${assets[@]}" \
--clobber \
+26
View File
@@ -2,6 +2,32 @@ set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE)
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)
add_subdirectory(SDL2)
+3 -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")
endif()
set(kyty_emulator_link_libraries common Vulkan::Headers spirv-tools-opt spirv-tools SDL2-static imgui xxhash FFmpeg::ffmpeg fmt::fmt nlohmann_json::nlohmann_json LibAtrac9 kyty_git_version)
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.
if(LINUX)
@@ -433,13 +433,6 @@ add_executable(resource_tracking_tests EXCLUDE_FROM_ALL
target_link_libraries(resource_tracking_tests fmt::fmt)
target_include_directories(resource_tracking_tests PRIVATE ${inc_headers})
add_executable(resource_mutex_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/ResourceMutexTests.cpp"
"${KYTY_SOURCE_DIR}/graphics/host_gpu/renderer/cache/resourceMutex.cpp"
)
target_link_libraries(resource_mutex_tests common)
target_include_directories(resource_mutex_tests PRIVATE ${inc_headers})
add_executable(audio_out2_port_tests EXCLUDE_FROM_ALL
"${KYTY_TESTS_DIR}/AudioOut2PortTests.cpp"
"${KYTY_SOURCE_DIR}/libs/libAudio2.cpp"
@@ -515,13 +508,14 @@ if(BUILD_TESTING)
add_test(NAME shader_vertex_metadata COMMAND $<TARGET_FILE:shader_vertex_metadata_tests>)
add_test(NAME shader_stage_runtime COMMAND $<TARGET_FILE:shader_stage_runtime_tests>)
add_test(NAME resource_tracking COMMAND $<TARGET_FILE:resource_tracking_tests>)
add_test(NAME resource_mutex COMMAND $<TARGET_FILE:resource_mutex_tests>)
add_test(NAME event_queue_lifetime COMMAND $<TARGET_FILE:event_queue_lifetime_tests>)
add_test(NAME sync_on_address COMMAND $<TARGET_FILE:sync_on_address_tests>)
add_test(NAME audio_out2_port COMMAND $<TARGET_FILE:audio_out2_port_tests>)
add_test(NAME shader_recompiler_compute COMMAND $<TARGET_FILE:shader_recompiler_compute_tests>)
add_test(NAME virtual_memory_allocation
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
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --scheduler-only)
add_test(NAME stream_buffer_ring
@@ -565,7 +559,6 @@ if(BUILD_TESTING)
shader_vertex_metadata_tests
shader_stage_runtime_tests
resource_tracking_tests
resource_mutex_tests
event_queue_lifetime_tests
sync_on_address_tests
shader_recompiler_compute_tests
+1
View File
@@ -24,6 +24,7 @@ target_link_libraries(common
Tracy::TracyClient
PRIVATE
cpuinfo
kyty_git_version
spdlog::spdlog
)
+2 -1
View File
@@ -3,6 +3,7 @@
#include "common/debug.h"
#include "common/logging/log.h"
#include "common/subsystems.h"
#include "kytyGitVersion.h"
#include <cstdio>
#include <cstdlib>
@@ -23,7 +24,7 @@ static std::string BuildFatalReport(const char* title, std::string_view text, co
DebugStack stack;
DebugStack::Trace(&stack);
std::string report = "--- Stack Trace ---\n";
std::string report = "--- Build ---\n" KYTY_BUILD_LABEL "\n--- Stack Trace ---\n";
for (int i = PRINT_STACK_FROM; i < stack.depth; i++) {
report += fmt::format("[{}] {:016x}\n", i - PRINT_STACK_FROM,
static_cast<uint64_t>(stack.GetAddr(i)));
+6
View File
@@ -101,6 +101,12 @@ bool ReadbackLinearImagesEnabled() {
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() {
return g_config->keymap;
}
+7 -1
View File
@@ -45,7 +45,10 @@ struct ConfigOptions {
bool spirv_debug_printf_enabled = false;
bool renderdoc_enabled = 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);
@@ -76,6 +79,9 @@ bool SpirvDebugPrintfEnabled();
bool RenderDocEnabled();
bool ReadbackLinearImagesEnabled();
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
bool RedZoneProtectionEnabled();
#endif
const Keymap& GetKeymap();
+6 -2
View File
@@ -28,7 +28,8 @@
#ifdef KYTY_WIN_CS
#include <windows.h> // IWYU pragma: keep
// 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
#define CREATE_WAITABLE_TIMER_HIGH_RESOLUTION 0x00000002
@@ -39,7 +40,10 @@ static void SleepHighResolution100ns(uint64_t units_100ns) {
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 start {};
if (QueryPerformanceFrequency(&frequency) != 0 && QueryPerformanceCounter(&start) != 0 &&
@@ -18,7 +18,9 @@
#include <algorithm>
#include <array>
#include <atomic>
#include <bit>
#include <cstdio>
#include <cstring>
#include <vector>
#define KYTY_HW_CTX_PARSER_ARGS \
@@ -895,23 +897,44 @@ KYTY_HW_CTX_PARSER(HwCtxSetModeControl) {
return 1;
}
static void HwCtxIgnorePolyOffsetRegister(uint32_t cmd_offset, uint32_t value) {
static std::atomic<uint32_t> log_count = 0;
static void HwCtxSetPolyOffsetRegister(CommandProcessor& cp, uint32_t cmd_offset, uint32_t value) {
HW::PolyOffset offset = cp.GetCtx().GetPolyOffset();
auto count = log_count.fetch_add(1);
if (count < 8) {
LOGF_COLOR(Log::Color::Red,
"\t temporary: ignoring polygon offset context register 0x%03" PRIx32
" = 0x%08" PRIx32 " (depth bias not implemented)\n",
cmd_offset, value);
switch (cmd_offset) {
case Pm4::PA_SU_POLY_OFFSET_DB_FMT_CNTL: {
const auto neg_bits =
KYTY_PM4_GET(value, PA_SU_POLY_OFFSET_DB_FMT_CNTL, NEG_NUM_DB_BITS);
offset.neg_num_db_bits = std::bit_cast<int8_t>(static_cast<uint8_t>(neg_bits));
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) {
auto num_values = KYTY_PM4_LEN(cmd_id) - 2u;
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;
@@ -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;
cs_regs.sgprs =
(value >> Pm4::COMPUTE_PGM_RSRC1_SGPRS_SHIFT) & Pm4::COMPUTE_PGM_RSRC1_SGPRS_MASK;
cs_regs.bulky =
(value >> Pm4::COMPUTE_PGM_RSRC1_BULKY_SHIFT) & Pm4::COMPUTE_PGM_RSRC1_BULKY_MASK;
cs_regs.wave_size = (((value >> Pm4::COMPUTE_PGM_RSRC1_W32_EN_SHIFT) &
Pm4::COMPUTE_PGM_RSRC1_W32_EN_MASK) != 0u)
? 32u
: 64u;
cs_regs.float_mode = (value >> Pm4::COMPUTE_PGM_RSRC1_FLOAT_MODE_SHIFT) &
Pm4::COMPUTE_PGM_RSRC1_FLOAT_MODE_MASK;
cs_regs.dx10_clamp = ((value >> Pm4::COMPUTE_PGM_RSRC1_DX10_CLAMP_SHIFT) &
Pm4::COMPUTE_PGM_RSRC1_DX10_CLAMP_MASK) != 0u;
cs_regs.ieee_mode = ((value >> Pm4::COMPUTE_PGM_RSRC1_IEEE_MODE_SHIFT) &
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;
case Pm4::COMPUTE_PGM_RSRC2:
cs_regs.scratch_en = (value >> Pm4::COMPUTE_PGM_RSRC2_SCRATCH_EN_SHIFT) &
Pm4::COMPUTE_PGM_RSRC2_SCRATCH_EN_MASK;
cs_regs.scratch_en = ((value >> Pm4::COMPUTE_PGM_RSRC2_SCRATCH_EN_SHIFT) &
Pm4::COMPUTE_PGM_RSRC2_SCRATCH_EN_MASK) != 0u;
cs_regs.user_sgpr = (value >> Pm4::COMPUTE_PGM_RSRC2_USER_SGPR_SHIFT) &
Pm4::COMPUTE_PGM_RSRC2_USER_SGPR_MASK;
cs_regs.tgid_x_en = (value >> Pm4::COMPUTE_PGM_RSRC2_TGID_X_EN_SHIFT) &
Pm4::COMPUTE_PGM_RSRC2_TGID_X_EN_MASK;
cs_regs.tgid_y_en = (value >> Pm4::COMPUTE_PGM_RSRC2_TGID_Y_EN_SHIFT) &
Pm4::COMPUTE_PGM_RSRC2_TGID_Y_EN_MASK;
cs_regs.tgid_z_en = (value >> Pm4::COMPUTE_PGM_RSRC2_TGID_Z_EN_SHIFT) &
Pm4::COMPUTE_PGM_RSRC2_TGID_Z_EN_MASK;
cs_regs.tg_size_en = (value >> Pm4::COMPUTE_PGM_RSRC2_TG_SIZE_EN_SHIFT) &
Pm4::COMPUTE_PGM_RSRC2_TG_SIZE_EN_MASK;
cs_regs.tgid_x_en = ((value >> Pm4::COMPUTE_PGM_RSRC2_TGID_X_EN_SHIFT) &
Pm4::COMPUTE_PGM_RSRC2_TGID_X_EN_MASK) != 0u;
cs_regs.tgid_y_en = ((value >> Pm4::COMPUTE_PGM_RSRC2_TGID_Y_EN_SHIFT) &
Pm4::COMPUTE_PGM_RSRC2_TGID_Y_EN_MASK) != 0u;
cs_regs.tgid_z_en = ((value >> Pm4::COMPUTE_PGM_RSRC2_TGID_Z_EN_SHIFT) &
Pm4::COMPUTE_PGM_RSRC2_TGID_Z_EN_MASK) != 0u;
cs_regs.tg_size_en = ((value >> Pm4::COMPUTE_PGM_RSRC2_TG_SIZE_EN_SHIFT) &
Pm4::COMPUTE_PGM_RSRC2_TG_SIZE_EN_MASK) != 0u;
cs_regs.tidig_comp_cnt = (value >> Pm4::COMPUTE_PGM_RSRC2_TIDIG_COMP_CNT_SHIFT) &
Pm4::COMPUTE_PGM_RSRC2_TIDIG_COMP_CNT_MASK;
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((buffer[0] & ~0xfu) != 0);
cp.Reset();
cp.ApplyContextStateOperation(ContextStateOperation::Clear);
return 1;
}
@@ -2649,14 +2682,21 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) {
const bool gl2_writeback = ((gcr_cntl & GcrGl2Writeback) != 0);
auto trigger_interrupt = [&]() {
bool queued = false;
switch (interrupt_selector) {
case 0x00:
case 0x03: break;
case 0x01:
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");
}
if (queued) {
cp.BufferFlush();
}
};
if (data_sel == 0 || interrupt_selector == 4) {
@@ -2664,10 +2704,6 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) {
cp.EmitGlobalBarrier();
}
if (gl2_writeback) {
cp.SynchronizeGpu();
}
trigger_interrupt();
return 7;
@@ -2678,10 +2714,6 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) {
cp.EmitGlobalBarrier();
}
if (gl2_writeback) {
cp.SynchronizeGpu();
}
trigger_interrupt();
return 7;
@@ -2695,17 +2727,6 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) {
cache_policy = 0;
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;
event_index = 6;
auto event_source = 2u;
@@ -2720,7 +2741,6 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) {
if (data_sel == 5) {
if (gl2_writeback) {
cp.SynchronizeGpu();
cache_action = 0;
}
@@ -2731,7 +2751,9 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) {
cp.WriteAtEndOfPipe32(cache_policy, event_write_dest, eop_event_type, cache_action,
event_index, event_source, dst_gpu_addr, static_cast<uint32_t>(value),
interrupt_selector, interrupt_context_id);
cp.BufferFlush();
if (interrupt_selector == 0x01) {
cp.BufferFlush();
}
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) {
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) {
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) {
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) {
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) {
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) {
HwCtxIgnorePolyOffsetRegister(cmd_offset, value);
HwCtxSetPolyOffsetRegister(cp, cmd_offset, value);
};
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) {
HW::VsShaderResource1 r1;
r1.vgprs = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_VS, VGPRS);
r1.sgprs = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_VS, SGPRS);
r1.priority = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_VS, PRIORITY);
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_VS, DX10_CLAMP) != 0;
r1.ieee_mode = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_VS, 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;
HW::HsShaderResource1 r1;
r1.vgprs = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_HS, VGPRS);
r1.priority = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_HS, PRIORITY);
r1.float_mode = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_HS, FLOAT_MODE);
r1.dx10_clamp = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_HS, 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_HS, IEEE_MODE) != 0;
r1.require_forward_progress =
KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_VS, FWD_PROGRESS) != 0;
r1.fp16_overflow = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_VS, FP16_OVFL) != 0;
KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_HS, FWD_PROGRESS) != 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);
};
g_hw_sh_indirect_func[Pm4::SPI_SHADER_PGM_RSRC2_HS] = [](KYTY_HW_SH_INDIRECT_ARGS) {
HW::VsShaderResource2 r2;
r2.scratch_en = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC2_VS, SCRATCH_EN) != 0;
r2.user_sgpr = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC2_VS, USER_SGPR) +
(KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC2_VS, USER_SGPR_MSB) << 5u);
r2.offchip_lds = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC2_VS, OC_LDS_EN) != 0;
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_VS, SHARED_VGPR_CNT);
HW::HsShaderResource2 r2;
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_HS, USER_SGPR) +
(KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC2_HS, USER_SGPR_MSB) << 5u);
r2.lds_size = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC2_HS, LDS_SIZE);
r2.shared_vgprs = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC2_HS, SHARED_VGPR_CNT);
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.require_forward_progress =
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.fp16_overflow = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC1_GS, FP16_OVFL) != 0;
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.extra_lds_size = KYTY_PM4_GET(value, SPI_SHADER_PGM_RSRC2_PS, EXTRA_LDS_SIZE);
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);
cp.GetShCtx().SetPsShaderResource2(r2);
};
+28 -18
View File
@@ -60,24 +60,34 @@ enum class GsOutputPrimitiveType : uint32_t {
};
enum class ChannelLayout : uint32_t {
kInvalid = 0,
k8 = 1,
k16 = 2,
k8_8 = 3,
k32 = 4,
k16_16 = 5,
k11_11_10 = 6,
k10_10_10_2 = 9,
k8_8_8_8 = 10,
k32_32 = 11,
k16_16_16_16 = 12,
k32_32_32_32 = 14,
k5_6_5 = 16,
k5_5_5_1 = 17,
k4_4_4_4 = 19,
kBc1 = 35,
kBc2 = 36,
kBc3 = 37,
kInvalid = 0,
k8 = 1,
k16 = 2,
k8_8 = 3,
k32 = 4,
k16_16 = 5,
k11_11_10 = 6,
k10_11_11 = 7,
k2_10_10_10 = 8,
k10_10_10_2 = 9,
k8_8_8_8 = 10,
k32_32 = 11,
k16_16_16_16 = 12,
k32_32_32 = 13,
k32_32_32_32 = 14,
k5_6_5 = 16,
k5_5_5_1 = 17,
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 {
+21 -30
View File
@@ -124,7 +124,6 @@ private:
uint64_t flip_request_id = 0;
};
void WaitLocked();
void Enqueue(Submission submission);
void WaitForIdle();
void ProcessCommands();
@@ -276,10 +275,8 @@ void GpuState::SubmitFlipPreparation(uint64_t request_id) {
void GpuState::Done() {
GpuMutexLock lock(m_submission_mutex);
if (IsGpuThread()) {
m_gfx_cp->BufferWait();
} else {
WaitLocked();
if (!IsGpuThread()) {
WaitForIdle();
}
m_graphics_done = true;
m_done_num++;
@@ -289,11 +286,6 @@ int GpuState::GetFrameNum() {
return m_done_num;
}
void GpuState::WaitLocked() {
WaitForIdle();
SendCommandSync([this] { m_gfx_cp->BufferWait(); });
}
CommandProcessor& GpuState::GetProcessor(uint32_t queue_id) {
EXIT_IF(queue_id >= QueueCount);
if (queue_id == 0) {
@@ -307,13 +299,11 @@ CommandProcessor& GpuState::GetProcessor(uint32_t queue_id) {
}
void CommandProcessor::Reset() {
BufferWait();
m_sh_ctx.Reset();
m_ucfg.Reset();
m_ctx.Reset();
m_saved_ctx.Reset();
m_context_state_pushed = false;
m_context_state_pushed = false;
m_index_type_and_size = 0;
m_index_buffer_size = 0;
m_user_data_marker = HW::UserSgprType::Unknown;
@@ -385,7 +375,6 @@ void CommandProcessor::WaitDeDiff(uint32_t diff) {
}
void CommandProcessor::IncrementDe() {
BufferWait();
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,
uint32_t thread_group_z, uint32_t mode) {
uint32_t frame_num = 0;
uint32_t local_x = 1;
uint32_t local_y = 1;
uint32_t local_z = 1;
// uint32_t local_x = 1;
// uint32_t local_y = 1;
// uint32_t local_z = 1;
{
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;
local_x = std::max(cs.num_thread_x, 1u);
local_y = std::max(cs.num_thread_y, 1u);
local_z = std::max(cs.num_thread_z, 1u);
// local_x = std::max(cs.num_thread_x, 1u);
// local_y = std::max(cs.num_thread_y, 1u);
// local_z = std::max(cs.num_thread_z, 1u);
if (cs.wave_size == 64u) {
static std::atomic_bool logged_wave64_shader {false};
if (!logged_wave64_shader.exchange(true, std::memory_order_relaxed)) {
@@ -1220,27 +1209,27 @@ void CommandProcessor::DispatchDirect(uint32_t thread_group_x, uint32_t thread_g
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) {
return (threads == 0
? 0u
: (threads + std::max(group_size, 1u) - 1u) / std::max(group_size, 1u));
return (threads == 0
? 0u
: (threads + std::max(group_size, 1u) - 1u) / std::max(group_size, 1u));
};
auto groups_x = thread_group_x;
auto groups_y = thread_group_y;
auto groups_z = thread_group_z;
if ((mode & DispatchInitiatorUseThreadDimensions) != 0) {
groups_x = group_count(thread_group_x, local_x);
groups_y = group_count(thread_group_y, local_y);
groups_z = group_count(thread_group_z, local_z);
groups_x = group_count(thread_group_x, local_x);
groups_y = group_count(thread_group_y, local_y);
groups_z = group_count(thread_group_z, local_z);
}
const uint64_t invocations =
static_cast<uint64_t>(groups_x) * groups_y * groups_z * local_x * local_y * local_z;
if (invocations != 0) {
BufferFlushAndWait();
}
BufferFlushAndWait();
}*/
}
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);
Sync::WriteAtEndOfPipeGds32(m_submit_id, CurrentBuffer(), dst, value & 0xffffu,
value >> 16u);
if (with_interrupt) {
m_renderer.TriggerEopEvent(interrupt_context_id);
}
return;
}
} 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);
}
EmitGlobalBarrier();
SynchronizeGpu();
break;
// DbDataWritebackInvalidate, DbMetadataWritebackInvalidate, CbMetadataWritebackInvalidate.
case 0x0000002a:
+71 -33
View File
@@ -348,6 +348,16 @@ struct ModeControl {
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 {
uint8_t color_srcblend = 1;
uint8_t color_comb_fcn = 0;
@@ -482,6 +492,26 @@ struct VsShaderResource2 {
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 {
uint8_t vgprs = 0;
uint8_t sgprs = 0;
@@ -500,7 +530,7 @@ struct PsShaderResource2 {
uint8_t user_sgpr = 0;
bool wave_cnt_en = false;
uint8_t extra_lds_size = 0;
bool raster_ordered_shading = false;
uint8_t raster_ordered_shading = 0;
uint8_t shared_vgprs = 0;
};
@@ -513,22 +543,27 @@ struct PsStageRegisters {
struct CsStageRegisters {
uint64_t data_addr = 0;
uint32_t num_thread_x = 0;
uint32_t num_thread_y = 0;
uint32_t num_thread_z = 0;
uint8_t vgprs = 0;
uint8_t sgprs = 0;
uint8_t bulky = 0;
uint8_t wave_size = 64;
uint8_t scratch_en = 0;
uint8_t user_sgpr = 0;
uint8_t tgid_x_en = 0;
uint8_t tgid_y_en = 0;
uint8_t tgid_z_en = 0;
uint8_t tg_size_en = 0;
uint8_t tidig_comp_cnt = 0;
uint8_t lds_size = 0;
uint64_t data_addr = 0;
uint32_t num_thread_x = 0;
uint32_t num_thread_y = 0;
uint32_t num_thread_z = 0;
uint8_t vgprs = 0;
uint8_t sgprs = 0;
uint8_t float_mode = 0;
bool dx10_clamp = false;
bool ieee_mode = false;
bool fp16_overflow = false;
bool bulky = false;
bool threadgroup_configuration = false;
uint8_t wave_size = 64;
bool scratch_en = false;
uint8_t user_sgpr = 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 {
@@ -543,24 +578,24 @@ struct LsStageRegisters {
struct HsStageRegisters {
uint64_t data_addr = 0;
VsShaderResource1 rsrc1;
VsShaderResource2 rsrc2;
HsShaderResource1 rsrc1;
HsShaderResource2 rsrc2;
uint64_t chksum = 0;
};
struct GsShaderResource1 {
uint8_t vgprs = 0;
uint8_t sgprs = 0;
uint8_t priority = 0;
uint8_t float_mode = 0;
bool dx10_clamp = false;
bool debug_mode = false;
bool ieee_mode = false;
bool cu_group_enable = false;
bool require_forward_progress = false;
bool lds_configuration = false;
uint8_t gs_vgpr_component_count = 0;
bool fp16_overflow = false;
uint8_t vgprs = 0;
uint8_t sgprs = 0;
uint8_t priority = 0;
uint8_t float_mode = 0;
bool dx10_clamp = false;
bool debug_mode = false;
bool ieee_mode = false;
bool cu_group_enable = false;
bool require_forward_progress = false;
bool threadgroup_configuration = false;
uint8_t gs_vgpr_component_count = 0;
bool fp16_overflow = false;
};
struct GsShaderResource2 {
@@ -880,6 +915,8 @@ public:
void SetDepthControl(const DepthControl& control) { m_depth_control = control; }
[[nodiscard]] const ModeControl& GetModeControl() const { return m_mode_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; }
void SetEqaaControl(const EqaaControl& control) { m_eqaa_control = control; }
[[nodiscard]] const StencilControl& GetStencilControl() const { return m_stencil_control; }
@@ -977,6 +1014,7 @@ private:
uint8_t m_stencil_clear_value = 0;
ModeControl m_mode_control;
PolyOffset m_poly_offset;
EqaaControl m_eqaa_control;
ShaderRegisters m_sh_regs;
@@ -1034,8 +1072,8 @@ public:
void SetLsShaderResource1(const VsShaderResource1& rsrc1) { m_vs.ls_regs.rsrc1 = rsrc1; }
void SetLsShaderResource2(const VsShaderResource2& rsrc2) { m_vs.ls_regs.rsrc2 = rsrc2; }
void SetHsShaderBase(uint64_t addr) { m_vs.hs_regs.data_addr = addr; }
void SetHsShaderResource1(const VsShaderResource1& rsrc1) { m_vs.hs_regs.rsrc1 = rsrc1; }
void SetHsShaderResource2(const VsShaderResource2& rsrc2) { m_vs.hs_regs.rsrc2 = rsrc2; }
void SetHsShaderResource1(const HsShaderResource1& rsrc1) { m_vs.hs_regs.rsrc1 = rsrc1; }
void SetHsShaderResource2(const HsShaderResource2& rsrc2) { m_vs.hs_regs.rsrc2 = rsrc2; }
void SetHsShaderChksum(uint32_t value) { m_vs.hs_regs.chksum = value; }
void SetGsShaderBase(uint64_t addr) { m_vs.gs_regs.data_addr = addr; }
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_MASK = 0x1;
constexpr uint32_t DB_SRESULTS_COMPARE_STATE0 = 0x2B0;
constexpr uint32_t DB_SRESULTS_COMPARE_STATE1 = 0x2B1;
constexpr uint32_t VGT_GS_MAX_VERT_OUT = 0x2CE;
constexpr uint32_t GE_NGG_SUBGRP_CNTL = 0x2D3;
constexpr uint32_t VGT_TESS_DISTRIBUTION = 0x2D4;
constexpr uint32_t VGT_SHADER_STAGES_EN = 0x2D5;
constexpr uint32_t VGT_LS_HS_CONFIG = 0x2D6;
constexpr uint32_t VGT_TF_PARAM = 0x2DB;
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_CLAMP = 0x2DF;
constexpr uint32_t PA_SU_POLY_OFFSET_FRONT_SCALE = 0x2E0;
constexpr uint32_t PA_SU_POLY_OFFSET_FRONT_OFFSET = 0x2E1;
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 DB_SRESULTS_COMPARE_STATE0 = 0x2B0;
constexpr uint32_t DB_SRESULTS_COMPARE_STATE1 = 0x2B1;
constexpr uint32_t VGT_GS_MAX_VERT_OUT = 0x2CE;
constexpr uint32_t GE_NGG_SUBGRP_CNTL = 0x2D3;
constexpr uint32_t VGT_TESS_DISTRIBUTION = 0x2D4;
constexpr uint32_t VGT_SHADER_STAGES_EN = 0x2D5;
constexpr uint32_t VGT_LS_HS_CONFIG = 0x2D6;
constexpr uint32_t VGT_TF_PARAM = 0x2DB;
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_NEG_NUM_DB_BITS_SHIFT = 0;
constexpr uint32_t PA_SU_POLY_OFFSET_DB_FMT_CNTL_NEG_NUM_DB_BITS_MASK = 0xFF;
constexpr uint32_t PA_SU_POLY_OFFSET_DB_FMT_CNTL_DB_IS_FLOAT_FMT_SHIFT = 8;
constexpr uint32_t PA_SU_POLY_OFFSET_DB_FMT_CNTL_DB_IS_FLOAT_FMT_MASK = 0x1;
constexpr uint32_t PA_SU_POLY_OFFSET_CLAMP = 0x2DF;
constexpr uint32_t PA_SU_POLY_OFFSET_FRONT_SCALE = 0x2E0;
constexpr uint32_t PA_SU_POLY_OFFSET_FRONT_OFFSET = 0x2E1;
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_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_MASK = 0x1;
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_MASK = 0x1;
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_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_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_LOAD_INTRAWAVE_COLLISION_SHIFT = 26;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_LOAD_INTRAWAVE_COLLISION_MASK = 0x1;
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_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 = 0xB;
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_USER_SGPR_SHIFT = 1;
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_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_MASK = 0xFF;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_PS_RASTER_ORDERED_SHADING_SHIFT = 25;
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_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_MASK = 0xF;
constexpr uint32_t SPI_SHADER_USER_DATA_PS_0 = 0xC;
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_MASK = 0xF;
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_31 = 0xAB;
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_3 = 0xB5;
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_HI_ES = 0xC9;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_ES = 0xCA;
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_15 = 0xDB;
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_RSRC4_HS = 0x101;
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_PGM_RSRC3_HS = 0x107;
constexpr uint32_t SPI_SHADER_PGM_LO_HS = 0x108;
constexpr uint32_t SPI_SHADER_PGM_HI_HS = 0x109;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS = 0x10A;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_HS = 0x10B;
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 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_31 = 0xAB;
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_3 = 0xB5;
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_HI_ES = 0xC9;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_ES = 0xCA;
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_15 = 0xDB;
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_RSRC4_HS = 0x101;
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_PGM_RSRC3_HS = 0x107;
constexpr uint32_t SPI_SHADER_PGM_LO_HS = 0x108;
constexpr uint32_t SPI_SHADER_PGM_HI_HS = 0x109;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS = 0x10A;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_HS = 0x10B;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_VGPRS_SHIFT = 0;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_VGPRS_MASK = 0x3F;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_PRIORITY_SHIFT = 10;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_PRIORITY_MASK = 0x3;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_FLOAT_MODE_SHIFT = 12;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_FLOAT_MODE_MASK = 0xFF;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_DX10_CLAMP_SHIFT = 21;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_DX10_CLAMP_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_DEBUG_MODE_SHIFT = 22;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_DEBUG_MODE_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_IEEE_MODE_SHIFT = 23;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_IEEE_MODE_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_FWD_PROGRESS_SHIFT = 25;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_FWD_PROGRESS_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_WGP_MODE_SHIFT = 26;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_WGP_MODE_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_LS_VGPR_COMP_CNT_SHIFT = 28;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_LS_VGPR_COMP_CNT_MASK = 0x3;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_FP16_OVFL_SHIFT = 30;
constexpr uint32_t SPI_SHADER_PGM_RSRC1_HS_FP16_OVFL_MASK = 0x1;
constexpr uint32_t SPI_SHADER_PGM_RSRC2_HS_SCRATCH_EN_SHIFT = 0;
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_VGPRS_SHIFT = 0;
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_MASK = 0xF;
constexpr uint32_t COMPUTE_PGM_RSRC1_BULKY_SHIFT = 24;
constexpr uint32_t COMPUTE_PGM_RSRC1_BULKY_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_RSRC1 = 0x212;
constexpr uint32_t COMPUTE_PGM_RSRC1_VGPRS_SHIFT = 0;
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_MASK = 0xF;
constexpr uint32_t COMPUTE_PGM_RSRC1_FLOAT_MODE_SHIFT = 12;
constexpr uint32_t COMPUTE_PGM_RSRC1_FLOAT_MODE_MASK = 0xFF;
constexpr uint32_t COMPUTE_PGM_RSRC1_DX10_CLAMP_SHIFT = 21;
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_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 {
TileBlockFamily family = TileBlockFamily::Standard256B;
TileBlockFamily family = TileBlockFamily::Count;
uint32_t bytes_per_element = 0;
uint32_t block_size = 0;
uint32_t block_width = 0;
@@ -47,38 +47,69 @@ struct TileBlockLayout {
uint32_t block_depth = 0;
};
struct TileVolumeLayout {
TileBlockFamily family = TileBlockFamily::Count;
uint32_t bytes_per_element = 0;
uint32_t texel_width = 1;
uint32_t texel_height = 1;
uint32_t first_tail_level = 0;
uint32_t block_depth = 1;
uint64_t block_slice_size = 0;
uint64_t total_size = 0;
uint64_t level_offsets[16] = {};
uint64_t level_sizes[16] = {};
uint32_t tail_x[16] = {};
uint32_t tail_y[16] = {};
uint32_t level_widths[16] = {};
uint32_t level_heights[16] = {};
struct TileTextureElementLayout {
uint32_t bytes = 0;
uint32_t texel_width = 1;
uint32_t texel_height = 1;
};
struct TileTextureBlockLayout {
TileBlockLayout block;
uint32_t texel_width = 1;
uint32_t texel_height = 1;
};
enum class TileSurfaceDimension : uint32_t {
Dim2D,
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,
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,
uint32_t& byte_offset);
bool TileGetBlockXor(const TileBlockLayout& layout, uint32_t block_x, uint32_t block_y,
uint32_t& byte_offset);
bool TileGetBlockXor(const TileBlockLayout& layout, uint32_t block_x, uint32_t block_y,
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,
uint32_t stencil_format, bool htile, TileSizeAlign& stencil_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,
TileSizeAlign& total_size, TileSizeOffset* level_sizes,
TilePaddedSize* padded_size);
void TileGetTextureSize(uint32_t format, uint32_t width, uint32_t height, uint32_t pitch,
uint32_t levels, uint32_t tile, TileSizeAlign* total_size,
TileSizeOffset* level_sizes, TilePaddedSize* padded_size);
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,
TileSizeAlign& total_size);
uint32_t TileGetTexturePitch(uint32_t format, uint32_t width, uint32_t levels, uint32_t tile);
void TileGetTextureSize(uint32_t format, uint32_t width, uint32_t height, uint32_t levels,
uint32_t tile, TileSizeAlign* total_size, TileSizeOffset* level_sizes,
TilePaddedSize* padded_size);
void TileGetTextureTotalSize(uint32_t format, uint32_t width, uint32_t height, uint32_t depth,
uint32_t levels, uint32_t tile, bool volume_texture,
TileSizeAlign& total_size);
uint32_t TileGetTexturePitch(uint32_t format, uint32_t width, uint32_t tile);
} // 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) {
CheckNotInUploadCallback();
std::lock_guard access(m_access_mutex);
return Iterate<true>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock);
return manager->IsModified<DirtySource::Cpu>(offset, bytes);
@@ -88,7 +87,6 @@ bool MemoryTracker::IsRegionCpuModified(uint64_t vaddr, uint64_t size) {
bool MemoryTracker::IsRegionGpuModified(uint64_t vaddr, uint64_t size) {
CheckNotInUploadCallback();
std::lock_guard access(m_access_mutex);
return Iterate<false>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock);
return manager->IsModified<DirtySource::Gpu>(offset, bytes);
@@ -97,7 +95,6 @@ bool MemoryTracker::IsRegionGpuModified(uint64_t vaddr, uint64_t size) {
void MemoryTracker::MarkRegionAsCpuModified(uint64_t vaddr, uint64_t size) {
CheckNotInUploadCallback();
std::lock_guard access(m_access_mutex);
Iterate<true>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock);
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) {
CheckNotInUploadCallback();
std::lock_guard access(m_access_mutex);
Iterate<true>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock);
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) {
CheckNotInUploadCallback();
std::lock_guard access(m_access_mutex);
Iterate<false>(vaddr, size, [](RegionManager* manager, uint64_t offset, uint64_t bytes) {
std::scoped_lock lock(manager->lock);
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;
managers.reserve((vaddr % TRACKER_REGION_SIZE + size + TRACKER_REGION_SIZE - 1) /
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) {
CheckNotInUploadCallback();
std::lock_guard access(m_access_mutex);
UntrackMemoryLocked(vaddr, size);
UntrackMemoryImpl(vaddr, size);
}
} // 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<Func&, uint64_t, uint64_t>);
CheckNotInUploadCallback();
std::lock_guard access(m_access_mutex);
std::vector<RegionManager*> managers;
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t, uint64_t) {
managers.push_back(manager);
@@ -109,7 +108,6 @@ public:
static_assert(std::is_nothrow_invocable_v<RangeFunc&, uint64_t, uint64_t>);
static_assert(std::is_nothrow_invocable_v<UploadFunc&>);
CheckNotInUploadCallback();
std::unique_lock access(m_access_mutex);
Iterate<true>(vaddr, size, [](RegionManager*, uint64_t, uint64_t) {});
const auto* previous_upload_owner = std::exchange(s_upload_owner, this);
Iterate<false>(vaddr, size, [&](RegionManager* manager, uint64_t offset, uint64_t bytes) {
@@ -173,13 +171,12 @@ private:
}
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);
std::unique_ptr<std::atomic<RegionManager*>[]> m_regions;
std::vector<std::unique_ptr<RegionManager>> m_region_storage;
std::mutex m_region_mutex;
std::mutex m_access_mutex;
PageManager& m_page_manager;
};
+136 -200
View File
@@ -5,7 +5,6 @@
#include "common/profiler.h"
#include "graphics/guest_gpu/graphicsRun.h"
#include "graphics/host_gpu/graphicContext.h"
#include "graphics/host_gpu/renderer/cache/resourceMutex.h"
#include "graphics/host_gpu/renderer/cache/textureCache.h"
#include "graphics/host_gpu/renderer/commandScheduler.h"
#include "graphics/host_gpu/renderer/render.h"
@@ -23,30 +22,9 @@ namespace Libs::Graphics {
namespace {
thread_local const void* g_cache_lock_owner = nullptr;
constexpr uint64_t MiB = 1024 * 1024;
constexpr uint64_t GdsBufferSize = 64 * 1024;
class FaultSafeCacheLock final {
public:
FaultSafeCacheLock(const void* owner, Common::Mutex& mutex): m_mutex(mutex) {
if (g_cache_lock_owner != nullptr) {
EXIT("BufferCache: recursive cache lock acquisition\n");
}
g_cache_lock_owner = owner;
m_mutex.Lock();
}
~FaultSafeCacheLock() {
m_mutex.Unlock();
g_cache_lock_owner = nullptr;
}
private:
Common::Mutex& m_mutex;
};
} // namespace
uint64_t BufferCache::AlignDown(uint64_t value) noexcept {
@@ -60,15 +38,6 @@ uint64_t BufferCache::AlignUp(uint64_t value) {
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,
const void* source, uint64_t size) {
auto* bytes = static_cast<const uint8_t*>(source);
@@ -201,27 +170,23 @@ void BufferCache::QueueGarbageDownload(std::span<const DownloadCopy> copies, Ret
m_scheduler.DeferOperation(
[this, downloads = std::move(downloads), retire = std::move(retire)]() mutable {
PublishDownloads(downloads);
{
FaultSafeCacheLock lock(this, m_mutex);
for (const auto& range: downloads) {
m_gpu_modified_ranges.Subtract(range.address, range.size);
}
// ForEachDownloadRange reports full tracker pages, and every exact GPU-owned
// interval on those pages was downloaded and removed. Clearing the original
// query therefore cannot orphan a dirty sibling on an edge page.
m_memory_tracker.UnmarkRegionAsGpuModified(retire.address, retire.size);
if (m_memory_tracker.IsRegionGpuModified(retire.address, retire.size) ||
!m_gpu_modified_ranges.Intersections(retire.address, retire.size).empty()) {
EXIT("BufferCache: asynchronous garbage collection retained GPU ownership\n");
}
m_memory_tracker.UntrackMemory(retire.address, retire.size);
for (const auto& range: downloads) {
m_gpu_modified_ranges.Subtract(range.address, range.size);
}
// ForEachDownloadRange reports full tracker pages, and every exact GPU-owned
// interval on those pages was downloaded and removed. Clearing the original
// query therefore cannot orphan a dirty sibling on an edge page.
m_memory_tracker.UnmarkRegionAsGpuModified(retire.address, retire.size);
if (m_memory_tracker.IsRegionGpuModified(retire.address, retire.size) ||
!m_gpu_modified_ranges.Intersections(retire.address, retire.size).empty()) {
EXIT("BufferCache: asynchronous garbage collection retained GPU ownership\n");
}
m_memory_tracker.UntrackMemory(retire.address, retire.size);
});
}
BufferCache::BufferCache(GraphicContext& graphics, CommandScheduler& scheduler,
PageManager& page_manager, TextureCache& texture_cache,
ResourceMutex& resource_mutex)
PageManager& page_manager, TextureCache& texture_cache)
: m_graphics(graphics), m_scheduler(scheduler),
m_gds_buffer(graphics, scheduler, MemoryUsage::Stream, 0, AllFlags, GdsBufferSize),
m_memory_tracker(page_manager),
@@ -229,7 +194,7 @@ BufferCache::BufferCache(GraphicContext& graphics, CommandScheduler& scheduler,
m_stream_buffer(graphics, scheduler, MemoryUsage::Stream, 64 * MiB),
m_download_buffer(graphics, scheduler, MemoryUsage::Download, 32 * MiB),
m_device_buffer(graphics, scheduler, MemoryUsage::DeviceLocal, 128 * MiB),
m_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()));
m_gds_buffer.Flush(0, m_gds_buffer.Size());
if (!m_graphics.CanReportMemoryUsage()) {
@@ -287,9 +252,6 @@ void BufferCache::InvalidateMemory(uint64_t vaddr, uint64_t size) {
size > TRACKER_ADDRESS_SIZE - vaddr) {
EXIT("BufferCache: invalid memory-invalidation range\n");
}
if (!HasPageOverlap(vaddr, size)) {
return;
}
m_memory_tracker.InvalidateRegion(vaddr, size,
[this, vaddr, size] { ReadMemory(vaddr, size, true); });
}
@@ -304,70 +266,63 @@ void BufferCache::ReadMemory(uint64_t vaddr, uint64_t size, bool is_write) {
"addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
vaddr, size);
}
if (m_resource_mutex.IsOwnedByCurrentThread()) {
EXIT("unsupported buffer readback from a pre-owned resource transaction, "
"addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
vaddr, size);
}
m_scheduler.Context().GetGpu().SendCommandSync(
[this, vaddr, size, is_write] { ReadMemoryOnGpu(vaddr, size, is_write); });
}
void BufferCache::ReadMemoryOnGpu(uint64_t vaddr, uint64_t size, bool is_write) {
// CPU invalidation reaches this point only for a GPU-owned tracker page. Resolve the exact
// Buffer owner on the GPU thread so the cache index remains single-thread-owned.
if (is_write && !IsRegionRegistered(vaddr, size)) {
return;
}
std::vector<DownloadCopy> copies;
{
FaultSafeCacheLock lock(this, m_mutex);
m_memory_tracker.ForEachDownloadRange<false>(
vaddr, size,
[&](uint64_t address, uint64_t bytes) noexcept {
m_memory_tracker.ValidateGpuDirtyPages(m_gpu_modified_ranges, address, bytes,
"memory invalidation");
},
[&](uint64_t address, uint64_t bytes) noexcept {
for (const auto range: m_gpu_modified_ranges.Intersections(address, bytes)) {
for (uint64_t copied = 0; copied < range.size;) {
const auto copy_address = range.address + copied;
auto owner = m_buffers.upper_bound(copy_address);
if (owner == m_buffers.begin()) {
EXIT("BufferCache: invalidation readback has no buffer owner\n");
}
auto& cached = *std::prev(owner)->second;
if (!cached.buffer->IsInBounds(copy_address, 1)) {
EXIT(
"BufferCache: invalidation readback is outside its buffer owner\n");
}
const auto copy_size = std::min(range.size - copied,
cached.vaddr + cached.size - copy_address);
copies.push_back({cached.buffer, cached.buffer->Offset(copy_address),
copy_address, copy_size});
copied += copy_size;
m_memory_tracker.ForEachDownloadRange<false>(
vaddr, size,
[&](uint64_t address, uint64_t bytes) noexcept {
m_memory_tracker.ValidateGpuDirtyPages(m_gpu_modified_ranges, address, bytes,
"memory invalidation");
},
[&](uint64_t address, uint64_t bytes) noexcept {
for (const auto range: m_gpu_modified_ranges.Intersections(address, bytes)) {
for (uint64_t copied = 0; copied < range.size;) {
const auto copy_address = range.address + copied;
auto owner = m_buffers.upper_bound(copy_address);
if (owner == m_buffers.begin()) {
EXIT("BufferCache: invalidation readback has no buffer owner\n");
}
auto& cached = *std::prev(owner)->second;
if (!cached.buffer->IsInBounds(copy_address, 1)) {
EXIT("BufferCache: invalidation readback is outside its buffer owner\n");
}
const auto copy_size =
std::min(range.size - copied, cached.vaddr + cached.size - copy_address);
copies.push_back({cached.buffer, cached.buffer->Offset(copy_address),
copy_address, copy_size});
copied += copy_size;
}
});
if (copies.empty()) {
if (!is_write) {
return;
}
// A preceding read fault can consume the last GPU-owned copy after this write
// invalidation has already chosen to flush. Complete the CPU ownership handoff even
// though this callback no longer has bytes to download.
m_memory_tracker.MarkRegionAsCpuModified(vaddr, size);
}
});
if (copies.empty()) {
if (!is_write) {
return;
}
// A preceding read fault can consume the last GPU-owned copy after this write invalidation
// has already chosen to flush. Complete the CPU ownership handoff even though this callback
// no longer has bytes to download.
m_memory_tracker.MarkRegionAsCpuModified(vaddr, size);
return;
}
auto downloads = RecordDownloads(copies);
m_scheduler.FinishCurrent();
PublishDownloads(downloads);
{
FaultSafeCacheLock lock(this, m_mutex);
for (const auto& range: downloads) {
m_gpu_modified_ranges.Subtract(range.address, range.size);
}
// The enumeration above covered whole dirty pages and every exact interval on them.
m_memory_tracker.UnmarkRegionAsGpuModified(vaddr, size);
if (is_write) {
m_memory_tracker.MarkRegionAsCpuModified(vaddr, size);
}
for (const auto& range: downloads) {
m_gpu_modified_ranges.Subtract(range.address, range.size);
}
// The enumeration above covered whole dirty pages and every exact interval on them.
m_memory_tracker.UnmarkRegionAsGpuModified(vaddr, size);
if (is_write) {
m_memory_tracker.MarkRegionAsCpuModified(vaddr, size);
}
}
@@ -378,43 +333,40 @@ void BufferCache::UnmapMemory(uint64_t vaddr, uint64_t size) {
std::vector<DownloadCopy> copies;
std::vector<std::pair<uint64_t, uint64_t>> modified_buffers;
std::vector<std::pair<uint64_t, uint64_t>> retired_buffers;
{
FaultSafeCacheLock lock(this, m_mutex);
for (const auto& [begin, cached]: m_buffers) {
if (vaddr < begin + cached->size && begin < vaddr + size) {
retired_buffers.emplace_back(begin, cached->size);
}
for (const auto& [begin, cached]: m_buffers) {
if (vaddr < begin + cached->size && begin < vaddr + size) {
retired_buffers.emplace_back(begin, cached->size);
}
for (const auto& [begin, cached]: m_buffers) {
if (vaddr >= begin + cached->size || begin >= vaddr + size ||
!m_memory_tracker.IsRegionGpuModified(begin, cached->size)) {
continue;
}
const auto dirty = m_gpu_modified_ranges.Intersections(begin, cached->size);
if (dirty.empty()) {
EXIT("BufferCache: GPU-modified buffer has no dirty ranges\n");
}
modified_buffers.emplace_back(begin, cached->size);
}
for (const auto& [begin, cached]: m_buffers) {
if (vaddr >= begin + cached->size || begin >= vaddr + size ||
!m_memory_tracker.IsRegionGpuModified(begin, cached->size)) {
continue;
}
for (const auto& [begin, bytes]: modified_buffers) {
auto owner = m_buffers.find(begin);
if (owner == m_buffers.end() || owner->second->size != bytes) {
EXIT("BufferCache: unmap owner changed during collection\n");
}
auto& cached = *owner->second;
m_memory_tracker.ForEachDownloadRange<false>(
begin, cached.size,
[&](uint64_t address, uint64_t bytes) noexcept {
m_memory_tracker.ValidateGpuDirtyPages(m_gpu_modified_ranges, address, bytes,
"unmap");
},
[&](uint64_t address, uint64_t bytes) noexcept {
for (const auto& range: m_gpu_modified_ranges.Intersections(address, bytes)) {
copies.push_back(
{cached.buffer, range.address - begin, range.address, range.size});
}
});
const auto dirty = m_gpu_modified_ranges.Intersections(begin, cached->size);
if (dirty.empty()) {
EXIT("BufferCache: GPU-modified buffer has no dirty ranges\n");
}
modified_buffers.emplace_back(begin, cached->size);
}
for (const auto& [begin, bytes]: modified_buffers) {
auto owner = m_buffers.find(begin);
if (owner == m_buffers.end() || owner->second->size != bytes) {
EXIT("BufferCache: unmap owner changed during collection\n");
}
auto& cached = *owner->second;
m_memory_tracker.ForEachDownloadRange<false>(
begin, cached.size,
[&](uint64_t address, uint64_t bytes) noexcept {
m_memory_tracker.ValidateGpuDirtyPages(m_gpu_modified_ranges, address, bytes,
"unmap");
},
[&](uint64_t address, uint64_t bytes) noexcept {
for (const auto& range: m_gpu_modified_ranges.Intersections(address, bytes)) {
copies.push_back(
{cached.buffer, range.address - begin, range.address, range.size});
}
});
}
if (!copies.empty()) {
auto downloads = RecordDownloads(copies);
@@ -425,29 +377,26 @@ void BufferCache::UnmapMemory(uint64_t vaddr, uint64_t size) {
// command stream before removing such backing.
m_scheduler.FinishCurrent();
}
{
FaultSafeCacheLock lock(this, m_mutex);
for (const auto& [begin, bytes]: modified_buffers) {
m_gpu_modified_ranges.Subtract(begin, bytes);
m_memory_tracker.UnmarkRegionAsGpuModified(begin, bytes);
}
for (const auto& [begin, bytes]: retired_buffers) {
m_memory_tracker.MarkRegionAsCpuModified(begin, bytes);
}
if (!m_gpu_modified_ranges.Intersections(vaddr, size).empty()) {
EXIT("BufferCache: unmap retained dirty byte ranges\n");
}
m_memory_tracker.UntrackMemory(vaddr, size);
for (auto it = m_buffers.begin(); it != m_buffers.end();) {
if (vaddr < it->first + it->second->size && it->first < vaddr + size) {
if (it->second->size > m_total_used_memory) {
EXIT("BufferCache: allocation accounting underflow\n");
}
m_total_used_memory -= it->second->size;
it = m_buffers.erase(it);
} else {
++it;
for (const auto& [begin, bytes]: modified_buffers) {
m_gpu_modified_ranges.Subtract(begin, bytes);
m_memory_tracker.UnmarkRegionAsGpuModified(begin, bytes);
}
for (const auto& [begin, bytes]: retired_buffers) {
m_memory_tracker.MarkRegionAsCpuModified(begin, bytes);
}
if (!m_gpu_modified_ranges.Intersections(vaddr, size).empty()) {
EXIT("BufferCache: unmap retained dirty byte ranges\n");
}
m_memory_tracker.UntrackMemory(vaddr, size);
for (auto it = m_buffers.begin(); it != m_buffers.end();) {
if (vaddr < it->first + it->second->size && it->first < vaddr + size) {
if (it->second->size > m_total_used_memory) {
EXIT("BufferCache: allocation accounting underflow\n");
}
m_total_used_memory -= it->second->size;
it = m_buffers.erase(it);
} else {
++it;
}
}
}
@@ -529,7 +478,6 @@ BufferBinding BufferCache::ObtainBuffer(CommandBuffer& command, uint64_t vaddr,
if (command.IsInvalid() || command.IsExecute()) {
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 &&
!m_memory_tracker.IsRegionGpuModified(vaddr, size) &&
@@ -556,7 +504,6 @@ BufferBinding BufferCache::ObtainBuffer(CommandBuffer& command, uint64_t vaddr,
(void)m_texture_cache.InvalidateMemoryFromGPU(vaddr, size, true);
}
FaultSafeCacheLock lock(this, m_mutex);
auto& cached = GetOrCreateBuffer(command, vaddr, size);
std::vector<std::pair<uint64_t, uint64_t>> uploads;
m_memory_tracker.ForEachUploadRange(
@@ -571,26 +518,23 @@ BufferBinding BufferCache::ObtainBuffer(CommandBuffer& command, uint64_t vaddr,
if (is_written) {
m_gpu_modified_ranges.Add(vaddr, size);
}
auto owner = cached.buffer;
uint64_t offset = cached.buffer->Offset(vaddr);
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> buffer;
{
FaultSafeCacheLock lock(this, m_mutex);
if (m_null_buffer != nullptr) {
return m_null_buffer;
}
m_null_buffer = std::make_shared<Buffer>(m_graphics, m_scheduler, MemoryUsage::DeviceLocal,
0, AllFlags, 16);
buffer = m_null_buffer;
if (m_null_buffer != nullptr) {
return m_null_buffer;
}
m_null_buffer = std::make_shared<Buffer>(m_graphics, m_scheduler, MemoryUsage::DeviceLocal, 0,
AllFlags, 16);
const std::array<uint8_t, 16> zeros {};
Upload(m_scheduler.Current(), *buffer, 0, zeros.data(), zeros.size());
return buffer;
Upload(m_scheduler.Current(), *m_null_buffer, 0, zeros.data(), zeros.size());
return m_null_buffer;
}
ImageBufferSource BufferCache::ObtainBufferForImage(uint64_t vaddr, uint64_t size) {
@@ -608,11 +552,10 @@ ImageBufferSource BufferCache::ObtainBufferForImage(uint64_t vaddr, uint64_t siz
};
{
FaultSafeCacheLock lock(this, m_mutex);
const bool cpu_modified = m_memory_tracker.IsRegionCpuModified(vaddr, size);
const bool gpu_modified = m_memory_tracker.IsRegionGpuModified(vaddr, size);
const auto dirty = m_gpu_modified_ranges.Intersections(vaddr, size);
const bool has_dirty_buffer_source = !dirty.empty();
const bool cpu_modified = m_memory_tracker.IsRegionCpuModified(vaddr, size);
const bool gpu_modified = m_memory_tracker.IsRegionGpuModified(vaddr, size);
const auto dirty = m_gpu_modified_ranges.Intersections(vaddr, size);
const bool has_dirty_buffer_source = !dirty.empty();
m_memory_tracker.ValidateGpuDirtyOwnership(m_gpu_modified_ranges, vaddr, size,
"image source");
@@ -683,10 +626,9 @@ ImageBufferSource BufferCache::ObtainBufferForImage(uint64_t vaddr, uint64_t siz
}
m_staging_buffer.Commit();
FaultSafeCacheLock lock(this, m_mutex);
const auto dirty = m_gpu_modified_ranges.Intersections(vaddr, size);
const bool has_dirty_buffer_source = !dirty.empty();
auto owner = find_owner();
const auto dirty = m_gpu_modified_ranges.Intersections(vaddr, size);
const bool has_dirty_buffer_source = !dirty.empty();
auto owner = find_owner();
if (has_dirty_buffer_source && owner == m_buffers.end()) {
EXIT("BufferCache: GPU-dirty image source lost its native owner\n");
}
@@ -719,8 +661,7 @@ void BufferCache::WriteHostMemory(uint64_t vaddr, std::span<const uint8_t> data)
}
Libs::LibKernel::Memory::WriteBacking(vaddr, data.data(), data.size());
FaultSafeCacheLock lock(this, m_mutex);
const auto end = vaddr + data.size();
const auto end = vaddr + data.size();
for (auto& [address, cached]: m_buffers) {
const auto cached_end = address + cached->size;
const auto begin = std::max(vaddr, address);
@@ -750,8 +691,7 @@ void BufferCache::FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool
}
(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 (region.image_bytes) {
m_texture_cache.InvalidateMemory(vaddr, size);
@@ -790,8 +730,7 @@ void BufferCache::CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t si
EXIT("BufferCache: invalid or overlapping copy range\n");
}
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 {};
const auto dst_region =
dst_memory ? m_texture_cache.QueryRegion(dst_vaddr, size) : TextureCache::RegionInfo {};
@@ -842,18 +781,19 @@ void BufferCache::CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t si
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 ||
size > TRACKER_ADDRESS_SIZE - vaddr) {
EXIT("BufferCache: invalid page-overlap query\n");
EXIT("BufferCache: invalid registered-region query\n");
}
FaultSafeCacheLock lock(this, m_mutex);
for (const auto& [address, cached]: m_buffers) {
if (PageOverlaps(vaddr, size, address, cached->size)) {
return true;
}
// Cached buffers are ordered and non-overlapping. The last buffer beginning before the query
// end is therefore the only possible intersection.
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) {
@@ -861,7 +801,6 @@ bool BufferCache::IsRegionGpuModified(uint64_t vaddr, uint64_t size) {
}
bool BufferCache::HasGpuDirtyBytes(uint64_t vaddr, uint64_t size) {
FaultSafeCacheLock lock(this, m_mutex);
return !m_gpu_modified_ranges.Intersections(vaddr, size).empty();
}
@@ -870,8 +809,7 @@ bool BufferCache::IsRegionCpuModified(uint64_t vaddr, uint64_t size) {
}
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()) {
m_total_used_memory = m_graphics.GetDeviceMemoryUsage();
}
@@ -886,7 +824,6 @@ void BufferCache::RunGarbageCollector() {
std::vector<RetiredBuffer> retires;
std::vector<std::pair<RetiredBuffer, std::vector<DownloadCopy>>> dirty_retires;
{
FaultSafeCacheLock lock(this, m_mutex);
std::vector<uint64_t> candidates;
for (const auto& [address, owner]: m_buffers) {
const auto& cached = *owner;
@@ -934,7 +871,6 @@ void BufferCache::RunGarbageCollector() {
QueueGarbageDownload(copies, std::move(retire));
}
FaultSafeCacheLock lock(this, m_mutex);
for (const auto& retire: retires) {
auto found = m_buffers.find(retire.address);
if (found == m_buffers.end() || found->second->size != retire.size ||
+3 -8
View File
@@ -3,7 +3,6 @@
#include "common/abi.h"
#include "common/common.h"
#include "common/threads.h"
#include "graphics/host_gpu/memoryTracker.h"
#include "graphics/host_gpu/rangeSet.h"
#include "graphics/host_gpu/renderer/cache/streamBuffer.h"
@@ -20,7 +19,6 @@ struct GraphicContext;
class CommandBuffer;
class CommandScheduler;
class TextureCache;
class ResourceMutex;
struct BufferBinding {
std::shared_ptr<void> owner;
@@ -41,7 +39,7 @@ public:
}
BufferCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
TextureCache& texture_cache, ResourceMutex& resource_mutex);
TextureCache& texture_cache);
~BufferCache();
KYTY_CLASS_NO_COPY(BufferCache);
@@ -61,7 +59,8 @@ public:
void FillBuffer(uint64_t vaddr, uint64_t size, uint32_t value, bool is_gds = false);
void CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t size, bool dst_gds = false,
bool src_gds = false);
[[nodiscard]] bool HasPageOverlap(uint64_t vaddr, uint64_t size);
// Cache-index and exact dirty-range queries require GPU-thread serialization.
[[nodiscard]] bool IsRegionRegistered(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 IsRegionGpuModified(uint64_t vaddr, uint64_t size);
@@ -84,8 +83,6 @@ private:
[[nodiscard]] static constexpr uint64_t AlignDownload(uint64_t size) noexcept {
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 bool ResolveOverlap(CacheRange& merged, CacheRange candidate) noexcept;
void Upload(CommandBuffer& command, Buffer& destination, uint64_t destination_offset,
@@ -102,7 +99,6 @@ private:
GraphicContext& m_graphics;
CommandScheduler& m_scheduler;
Buffer m_gds_buffer;
Common::Mutex m_mutex;
std::shared_ptr<Buffer> m_null_buffer;
std::map<uint64_t, std::unique_ptr<CachedBuffer>> m_buffers;
RangeSet m_gpu_modified_ranges;
@@ -112,7 +108,6 @@ private:
StreamBuffer m_download_buffer;
StreamBuffer m_device_buffer;
TextureCache& m_texture_cache;
ResourceMutex& m_resource_mutex;
uint64_t m_total_used_memory = 0;
uint64_t m_trigger_gc_memory = 1ull * 1024 * 1024 * 1024;
uint64_t m_critical_gc_memory = 2ull * 1024 * 1024 * 1024;
@@ -6,9 +6,8 @@
namespace Libs::Graphics {
GpuResourceManager::GpuResourceManager(GraphicContext& graphics, CommandScheduler& scheduler)
: m_scheduler(scheduler),
m_buffer_cache(graphics, scheduler, m_page_manager, m_texture_cache, m_resource_mutex),
m_texture_cache(graphics, scheduler, m_page_manager, m_buffer_cache, m_resource_mutex) {}
: m_scheduler(scheduler), m_buffer_cache(graphics, scheduler, m_page_manager, m_texture_cache),
m_texture_cache(graphics, scheduler, m_page_manager, m_buffer_cache) {}
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",
vaddr, size);
}
if (m_resource_mutex.IsOwnedByCurrentThread()) {
EXIT("unsupported memory unmap from a pre-owned resource transaction, "
"addr=0x%016" PRIx64 " size=0x%016" PRIx64 "\n",
vaddr, size);
}
const auto unmap = [this, vaddr, size] {
if (m_scheduler.Active()) {
const auto tick = m_scheduler.CurrentTick();
@@ -5,7 +5,6 @@
#include "common/common.h"
#include "graphics/host_gpu/pageManager.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 <cstdint>
@@ -35,7 +34,6 @@ public:
private:
PageManager m_page_manager;
ResourceMutex m_resource_mutex;
CommandScheduler& m_scheduler;
BufferCache m_buffer_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/host_gpu/graphicContext.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/image/imageView.h"
#include "graphics/host_gpu/renderer/image/textureCommon.h"
@@ -56,13 +55,12 @@ private:
} // namespace
TextureCache::TextureCache(GraphicContext& graphics, CommandScheduler& scheduler,
PageManager& page_manager, BufferCache& buffer_cache,
ResourceMutex& resource_mutex)
PageManager& page_manager, BufferCache& buffer_cache)
: m_graphics(graphics), m_scheduler(scheduler), m_page_manager(page_manager),
m_blit_helper(graphics, scheduler),
m_tiler(std::make_unique<TileManager>(graphics, scheduler,
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()) {
if (m_graphics.CanReportMemoryUsage()) {
constexpr int64_t GiB = 1024ll * 1024 * 1024;
@@ -434,9 +432,8 @@ void TextureCache::UntrackImageTail(ImageId id) {
}
void TextureCache::TrackImageDownload(ImageId id) {
std::lock_guard transaction(m_resource_mutex);
CacheLock lock(*this, m_lock);
auto& image = ResolveImage(id);
CacheLock lock(*this, m_lock);
auto& image = ResolveImage(id);
TrackImageDownloadLocked(id, image);
}
@@ -911,7 +908,6 @@ TextureCache::BuildColorTransfer(const Image& image, BindingType binding,
uint32_t format = info.guest_format;
uint32_t layers = info.TransferLayers();
bool volume = info.IsVolume();
bool layered = info.IsLayered();
bool allow_depth_tile = direction == TransferDirection::Upload;
const char* owner =
direction == TransferDirection::Upload ? "TextureCache" : "TextureCache readback";
@@ -940,7 +936,6 @@ TextureCache::BuildColorTransfer(const Image& image, BindingType binding,
format = info.guest_format;
layers = info.resources.layers;
volume = false;
layered = layers > 1;
allow_depth_tile = false;
plan.swap_bgra16 = info.bgra16;
owner = "VideoOut";
@@ -958,19 +953,14 @@ TextureCache::BuildColorTransfer(const Image& image, BindingType binding,
plan.swap_bgra16 = info.bgra16;
}
plan.layout = TextureCalcUploadLayout(format, info.extent.width, info.extent.height,
info.resources.levels, layers, info.pitch, info.tile_mode,
info.data.size, allow_depth_tile, volume, owner);
plan.regions = TextureBuildImageCopies(plan.layout, info.extent.width, info.extent.height,
layers, info.resources.levels, layered, volume);
plan.tiled = static_cast<Prospero::TileMode>(plan.layout.tile) != Prospero::TileMode::kLinear;
plan.layout = TextureCalcUploadLayout(format, info.extent.width, info.extent.height,
info.resources.levels, layers, info.tile_mode,
info.data.size, allow_depth_tile, volume, owner);
plan.regions = TextureBuildImageCopies(plan.layout);
plan.tiled = static_cast<Prospero::TileMode>(plan.layout.surface.description.tile_mode) !=
Prospero::TileMode::kLinear;
if (plan.tiled) {
// CMASK/FMASK decoding is intentionally deferred.
if (plan.layout.tile_family == TileBlockFamily::Depth64KB &&
Prospero::IsFmaskTextureFormat(format)) {
return plan;
}
if (!TextureBuildGpuTileInfos(info.data.size, plan.regions, plan.layout, format, layers,
if (!TextureBuildGpuTileInfos(info.data.size, plan.regions, plan.layout,
info.resources.levels, plan.tiles)) {
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 "
"pitch=%u levels=%u layers=%u samples=%u\n",
static_cast<uint32_t>(desc.type), info.data.address, info.data.size,
info.guest_format, info.tile_mode, static_cast<uint32_t>(plan.layout.tile_family),
info.extent.width, info.extent.height, info.extent.depth, info.pitch,
info.resources.levels, info.resources.layers, info.samples);
info.guest_format, info.tile_mode,
static_cast<uint32_t>(plan.layout.surface.texture.block.family), info.extent.width,
info.extent.height, info.extent.depth, info.pitch, info.resources.levels,
info.resources.layers, info.samples);
}
TileManager::Result linear {source.Handle(), source_offset, info.data.size};
if (plan.tiled) {
@@ -1152,8 +1143,7 @@ void TextureCache::RefreshImage(ImageId id, const ImageDesc& desc) {
}
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);
}
@@ -1196,11 +1186,9 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
}
ImageId result {};
bool inserted_new = false;
{
std::lock_guard transaction(m_resource_mutex);
CacheLock lock(*this, m_lock);
const auto candidates =
CacheLock lock(*this, m_lock);
const auto candidates =
FindImagesInRegion(desc.info.data.address, desc.info.data.size, false);
for (const auto id: candidates) {
@@ -1243,19 +1231,12 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
}
if (!result) {
result = InsertImage(desc.info);
inserted_new = true;
auto& inserted = ResolveImage(result);
if (m_buffer_cache.HasGpuDirtyBytes(inserted.info.data.address,
inserted.info.data.size)) {
inserted.MarkBufferModified();
}
}
if (inserted_new) {
InitializeImage(result, desc);
} else {
RefreshImage(result, desc);
}
auto& image = ResolveImage(result);
if (desc.type == BindingType::VideoOut &&
desc.info.metadata.compression != VideoOutCompression::Uncompressed) {
@@ -1280,6 +1261,13 @@ ImageId TextureCache::FindImage(ImageDesc& desc, bool exact_format) {
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) {
if (!GuestRange {address, size}.Valid()) {
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) {
std::lock_guard transaction(m_resource_mutex);
CacheLock lock(*this, m_lock);
auto& image = ResolveImage(id);
CacheLock lock(*this, m_lock);
auto& image = ResolveImage(id);
TouchImage(image);
if (!image.info.data.Empty()) {
if (!image.registered || image.depth_id || image.binding.needs_rebind) {
EXIT("TextureCache: texture requires rediscovery before final acquisition\n");
}
}
if (desc.type == BindingType::Storage) {
image.MarkGpuModified();
}
if (!image.info.data.Empty()) {
RefreshImage(id, desc);
}
switch (desc.type) {
case BindingType::Texture: break;
case BindingType::Storage:
if (image.info.data.Empty()) {
image.MarkGpuModified();
} else {
if (!image.info.data.Empty()) {
if (!image.registered || image.depth_id) {
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) {
EXIT("TextureCache: invalid color-target binding\n");
}
std::lock_guard transaction(m_resource_mutex);
CacheLock lock(*this, m_lock);
auto& image = ResolveImage(id);
CacheLock lock(*this, m_lock);
auto& image = ResolveImage(id);
if (!image.registered || image.depth_id || image.binding.needs_rebind) {
EXIT("TextureCache: color target requires rediscovery before final acquisition\n");
}
TouchImage(image);
image.MarkGpuModified();
image.usage.render_target = true;
RefreshImage(id, desc);
CommitGpuWrite(image);
image.usage.render_target = true;
TrackImageDownloadLocked(id, image);
const auto view = image.FindView(desc.view_info);
RetainImage(m_scheduler.Current(), id);
@@ -1378,13 +1368,14 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) {
if (desc.type != BindingType::DepthTarget) {
EXIT("TextureCache: invalid depth-target binding\n");
}
std::lock_guard transaction(m_resource_mutex);
CacheLock lock(*this, m_lock);
auto& image = ResolveImage(id);
CacheLock lock(*this, m_lock);
auto& image = ResolveImage(id);
if (!image.registered || image.depth_id || image.binding.needs_rebind) {
EXIT("TextureCache: depth target requires rediscovery before final acquisition\n");
}
TouchImage(image);
image.MarkGpuModified();
image.usage.depth_target = true;
RefreshImage(id, desc);
if (desc.info.HasMetadata()) {
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});
}
CommitGpuWrite(image);
image.usage.depth_target = true;
if (desc.info.HasStencil()) {
AssociateStencilLocked(id, desc.info.stencil);
}
@@ -1402,9 +1392,8 @@ vk::ImageView TextureCache::FindDepthTarget(ImageId id, const ImageDesc& desc) {
}
void TextureCache::MarkGpuWritten(ImageId id) {
std::lock_guard transaction(m_resource_mutex);
CacheLock lock(*this, m_lock);
auto& image = ResolveImage(id);
CacheLock lock(*this, m_lock);
auto& image = ResolveImage(id);
if (!image.registered || image.depth_id) {
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()) {
EXIT("TextureCache: invalid image clear\n");
}
std::lock_guard transaction(m_resource_mutex);
CacheLock lock(*this, m_lock);
ImageId selected {};
vk::ImageAspectFlags aspect {};
@@ -1713,14 +1701,9 @@ bool BufferCache::SynchronizeBufferFromImage(Buffer& buffer, uint64_t vaddr, uin
return false;
}
if (color.tiled) {
const auto binding = m_texture_cache.UploadBinding(image);
const auto format =
binding == TextureCache::BindingType::RenderTarget
? ImageOps::RenderTargetTransferFormat(image.info.bytes_per_block)
: image.info.guest_format;
color.tiles.clear();
if (!TextureBuildGpuTileInfos(copy_size, color.regions, color.layout, format,
image.info.TransferLayers(), levels, color.tiles)) {
if (!TextureBuildGpuTileInfos(copy_size, color.regions, color.layout, levels,
color.tiles)) {
return false;
}
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) {
std::lock_guard transaction(m_resource_mutex);
CacheLock lock(*this, m_lock);
const auto found = m_surface_metas.find(address);
CacheLock lock(*this, m_lock);
const auto found = m_surface_metas.find(address);
if (found == m_surface_metas.end()) {
return false;
}
@@ -1919,9 +1901,8 @@ void TextureCache::UnmapMemory(uint64_t address, uint64_t size) {
if (!GuestRange {address, size}.Valid()) {
EXIT("TextureCache: invalid unmap range\n");
}
std::lock_guard transaction(m_resource_mutex);
CacheLock lock(*this, m_lock);
auto images = FindImagesInRegion(address, size, false);
CacheLock lock(*this, m_lock);
auto images = FindImagesInRegion(address, size, false);
if (!images.empty()) {
m_scheduler.Finish();
}
@@ -1938,9 +1919,8 @@ void TextureCache::UnmapMemory(uint64_t address, uint64_t size) {
}
void TextureCache::RunGarbageCollector() {
std::lock_guard transaction(m_resource_mutex);
CacheLock lock(*this, m_lock);
const uint64_t tick = m_gc_tick++;
CacheLock lock(*this, m_lock);
const uint64_t tick = m_gc_tick++;
if (m_graphics.CanReportMemoryUsage()) {
m_total_used_memory = m_graphics.GetDeviceMemoryUsage();
}
@@ -2000,8 +1980,7 @@ void TextureCache::RunGarbageCollector() {
}
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) {
const auto owner = ResolveOwner(id);
if (owner != nullptr && owner->registered && owner->IsGpuModified()) {
+2 -3
View File
@@ -26,7 +26,6 @@ class Buffer;
class BufferCache;
class CommandBuffer;
class CommandScheduler;
class ResourceMutex;
class RenderExecutor;
class StreamBuffer;
class TileManager;
@@ -49,11 +48,12 @@ public:
};
TextureCache(GraphicContext& graphics, CommandScheduler& scheduler, PageManager& page_manager,
BufferCache& buffer_cache, ResourceMutex& resource_mutex);
BufferCache& buffer_cache);
~TextureCache();
KYTY_CLASS_NO_COPY(TextureCache);
[[nodiscard]] ImageId FindImage(ImageDesc& desc, bool exact_format = false);
void UpdateImage(ImageId id);
[[nodiscard]] ImageId FindImageFromRange(uint64_t address, uint64_t size,
bool ensure_valid = true);
[[nodiscard]] vk::ImageView FindTexture(ImageId id, const ImageDesc& desc);
@@ -168,7 +168,6 @@ private:
BlitHelper m_blit_helper;
std::unique_ptr<TileManager> m_tiler;
BufferCache& m_buffer_cache;
ResourceMutex& m_resource_mutex;
std::vector<Slot> m_slots;
std::vector<uint32_t> m_free_slots;
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;
} else if (tile) {
if (volume) {
pitch = TileGetTexturePitch(transfer_format, width, levels, rt.attrib3.tile_mode);
pitch = TileGetTexturePitch(transfer_format, width, rt.attrib3.tile_mode);
} else if (standard64) {
pitch = TileGetTexturePitch(Prospero::GpuEnumValue(Prospero::BufferFormat::k32Float),
width, levels, rt.attrib3.tile_mode);
width, rt.attrib3.tile_mode);
} else {
pitch = TileGetRenderTargetPitch(width, bytes_per_element, rt.attrib.num_fragments);
}
@@ -192,13 +192,20 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
pitch = width;
}
TileSizeOffset mip_sizes[16] {};
TilePaddedSize mip_padded[16] {};
TileVolumeLayout volume_layout {};
uint64_t backing_size = 0;
TileSizeOffset mip_sizes[16] {};
TilePaddedSize mip_padded[16] {};
TileSurfaceLayout volume_layout {};
uint64_t backing_size = 0;
if (volume) {
if (!tile || !TileGetTextureVolumeLayout(transfer_format, width, height, depth, levels,
rt.attrib3.tile_mode, volume_layout)) {
const TileSurfaceDescription description {transfer_format,
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,
depth, levels, rt.attrib3.tile_mode);
}
@@ -209,7 +216,7 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
bool valid_layout = false;
if (standard64) {
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);
valid_layout = layout.size != 0 && layout.align == 65536;
} else {
@@ -289,11 +296,12 @@ void RenderExecutor::ResolveRenderColorTarget(uint64_t submit_id, RenderCommandB
desc.info.tile_mode = rt.attrib3.tile_mode;
for (uint32_t level = 0; level < levels; level++) {
if (volume) {
const auto& mip = volume_layout.mips[level];
desc.info.mip_layout[level] = {
volume_layout.level_offsets[level],
volume_layout.level_sizes[level],
volume_layout.level_widths[level],
volume_layout.level_heights[level],
mip.offset,
mip.size,
mip.padded_width,
mip.padded_height,
};
continue;
}
@@ -99,7 +99,10 @@ bool CommandScheduler::InDeferredOperation() noexcept {
CommandScheduler::CommandScheduler(RenderContext& context, GraphicContext& 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() {
Shutdown();
@@ -154,10 +157,7 @@ void CommandScheduler::Begin(HW::Context& registers, HW::UserConfig& user_config
m_shaders = &shaders;
if (!Active()) {
for (auto& buffer: m_buffers) {
buffer = std::make_unique<RenderCommandBuffer>(*this);
}
m_current = 0;
m_current = static_cast<int>(GrowCommandBuffers());
}
BindCurrent();
@@ -235,7 +235,13 @@ void CommandScheduler::Wait(uint64_t tick) {
}
void CommandScheduler::PopPendingOperations() {
m_master.Refresh();
PopPendingOperations(true);
}
void CommandScheduler::PopPendingOperations(bool refresh_gpu_tick) {
if (refresh_gpu_tick) {
m_master.Refresh();
}
for (;;) {
Common::UniqueFunction<void> callback;
{
@@ -362,7 +368,7 @@ uint64_t CommandScheduler::NextSubmitSequence() noexcept {
}
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 {
@@ -384,15 +390,52 @@ CommandBuffer& CommandScheduler::SubmitCurrent(SubmitInfo& submit) {
const auto signal_tick = m_master.NextTick();
submit.AddSignal(m_master.Handle(), signal_tick);
submitted.Execute(submit);
m_recording = false;
m_buffer_ticks[static_cast<size_t>(m_current)] = signal_tick;
m_recording = false;
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() {
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();
PopPendingOperations();
PopPendingOperations(!refreshed);
BindCurrent();
Current().Begin();
m_recording = true;
@@ -16,6 +16,7 @@
#include <queue>
#include <thread>
#include <vector>
namespace Libs::Graphics {
@@ -31,8 +32,6 @@ struct CommandSlot {
class CommandScheduler {
public:
static constexpr int BufferCount = 8;
CommandScheduler(RenderContext& context, GraphicContext& graphics);
~CommandScheduler();
KYTY_CLASS_NO_COPY(CommandScheduler);
@@ -65,6 +64,8 @@ public:
[[nodiscard]] GraphicContext& Graphics() const noexcept { return m_graphics; }
private:
static constexpr size_t CommandBufferGrowStep = 4;
class CommandPool {
public:
CommandPool() = default;
@@ -94,30 +95,34 @@ private:
void BindCurrent() const;
CommandBuffer& SubmitCurrent(SubmitInfo& submit);
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 RunOperation(Common::UniqueFunction<void>&& operation);
[[nodiscard]] CommandSlot* AllocateCommandBuffer();
[[nodiscard]] uint64_t NextSubmitSequence() noexcept;
MasterSemaphore m_master;
RenderContext& m_context;
GraphicContext& m_graphics;
CommandPool m_command_pool;
std::array<std::unique_ptr<RenderCommandBuffer>, BufferCount> m_buffers;
std::queue<PendingOperation> m_pending_operations;
std::queue<PendingOperation> m_priority_operations;
std::mutex m_operation_mutex;
std::condition_variable m_operation_available;
std::jthread m_priority_thread;
bool m_priority_active = false;
uint64_t m_priority_active_tick = 0;
OperationState m_operation_state = OperationState::Open;
int m_current = -1;
bool m_recording = false;
HW::Context* m_registers = nullptr;
HW::UserConfig* m_user_config = nullptr;
HW::Shader* m_shaders = nullptr;
std::atomic<uint64_t> m_submit_sequence = 0;
MasterSemaphore m_master;
RenderContext& m_context;
GraphicContext& m_graphics;
CommandPool m_command_pool;
std::vector<std::unique_ptr<RenderCommandBuffer>> m_buffers;
std::vector<uint64_t> m_buffer_ticks;
std::queue<PendingOperation> m_pending_operations;
std::queue<PendingOperation> m_priority_operations;
std::mutex m_operation_mutex;
std::condition_variable m_operation_available;
std::jthread m_priority_thread;
bool m_priority_active = false;
uint64_t m_priority_active_tick = 0;
OperationState m_operation_state = OperationState::Open;
int m_current = -1;
bool m_recording = false;
HW::Context* m_registers = nullptr;
HW::UserConfig* m_user_config = nullptr;
HW::Shader* m_shaders = nullptr;
std::atomic<uint64_t> m_submit_sequence = 0;
friend class CommandBuffer;
};
+16 -17
View File
@@ -10,6 +10,7 @@
#include "graphics/host_gpu/renderer/render.h"
#include <algorithm>
#include <array>
#include <atomic>
#include <cmath>
#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_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) {
static bool logged = false;
if (!logged) {
@@ -1067,18 +1060,24 @@ static ScissorRect ScissorRectClamp(ScissorRect r, uint32_t width, uint32_t heig
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) {
EXIT_IF(mask == nullptr);
for (uint32_t candidate = 0; candidate < 16; candidate++) {
uint16_t expected = 0;
for (uint32_t combination = 0; combination < 16; combination++) {
if ((combination & candidate) == candidate) {
expected |= static_cast<uint16_t>(1u << combination);
}
}
if (expected == rule) {
static constexpr auto rules = MakeScissorIntersectionRules();
for (uint32_t candidate = 0; candidate < rules.size(); candidate++) {
if (rules[candidate] == rule) {
*mask = static_cast<uint8_t>(candidate);
return true;
}
@@ -38,7 +38,10 @@ namespace Libs::Graphics {
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)) {
case Prospero::StencilOp::kKeep: return vk::StencilOp::eKeep;
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::kAddWrap: return vk::StencilOp::eIncrementAndWrap;
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_test_enable = has_stencil && dc.stencil_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) ||
(dc.backface_enable &&
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");
}
r.stencil_static_front = {
ConvertStencilOp(sc.stencil_fail), ConvertStencilOp(sc.stencil_zpass),
ConvertStencilOp(sc.stencil_zfail), static_cast<vk::CompareOp>(dc.stencilfunc)};
r.stencil_dynamic_front = {sm.stencil_mask,
rc.stencil_clear_enable ? 0u : sm.stencil_writemask,
sm.stencil_testval};
ConvertStencilOp(sc.stencil_fail, front_write_mask, sm.stencil_opval),
ConvertStencilOp(sc.stencil_zpass, front_write_mask, sm.stencil_opval),
ConvertStencilOp(sc.stencil_zfail, front_write_mask, sm.stencil_opval),
static_cast<vk::CompareOp>(dc.stencilfunc)};
r.stencil_dynamic_front = {sm.stencil_mask, front_write_mask, sm.stencil_testval};
if (dc.backface_enable) {
r.stencil_static_back = {ConvertStencilOp(sc.stencil_fail_bf),
ConvertStencilOp(sc.stencil_zpass_bf),
ConvertStencilOp(sc.stencil_zfail_bf),
static_cast<vk::CompareOp>(dc.stencilfunc_bf)};
r.stencil_dynamic_back = {sm.stencil_mask_bf,
rc.stencil_clear_enable ? 0u : sm.stencil_writemask_bf,
sm.stencil_testval_bf};
r.stencil_static_back = {
ConvertStencilOp(sc.stencil_fail_bf, back_write_mask, sm.stencil_opval_bf),
ConvertStencilOp(sc.stencil_zpass_bf, back_write_mask, sm.stencil_opval_bf),
ConvertStencilOp(sc.stencil_zfail_bf, back_write_mask, sm.stencil_opval_bf),
static_cast<vk::CompareOp>(dc.stencilfunc_bf)};
r.stencil_dynamic_back = {sm.stencil_mask_bf, back_write_mask, sm.stencil_testval_bf};
} else {
r.stencil_static_back = r.stencil_static_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) {
KYTY_PROFILER_FUNCTION();
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) {
return;
}
@@ -168,14 +168,6 @@ RenderTargetFormatInfo TextureGetRenderTargetFormat(uint32_t raw_layout, uint32_
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) {
return volume_texture ? std::max(depth >> level, 1u) : depth;
}
@@ -188,20 +180,6 @@ static size_t GetTextureRegionCount(uint32_t depth, uint64_t levels, bool volume
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) {
switch (static_cast<Prospero::CompSwizzle>(s)) {
case Prospero::CompSwizzle::kZero: return vk::ComponentSwizzle::eZero;
@@ -241,12 +219,11 @@ vk::Format TextureGetFormat(uint32_t fmt) {
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 stride = 0;
for (uint32_t i = 0; i < levels; i++) {
stride =
std::max(stride, static_cast<uint64_t>(level_sizes[i].offset) + level_sizes[i].size);
stride = std::max(stride, mips[i].offset + mips[i].size);
}
if (depth > 1 && total_size != 0 && total_size % depth == 0) {
@@ -259,39 +236,26 @@ uint64_t CalcTextureSliceStride(const TileSizeOffset* level_sizes, uint64_t leve
return stride;
}
uint64_t CalcLinearUploadLevelSize(uint32_t fmt, uint32_t pitch, uint32_t height) {
if (const uint32_t bytes_per_element = Prospero::NumBytesPerElement(fmt);
bytes_per_element != 0) {
return static_cast<uint64_t>(pitch) * height * bytes_per_element;
}
if (const uint32_t bytes_per_block = Prospero::BlockCompressedBytesPerBlock(fmt);
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 CalcLinearUploadLevelSize(const TileTextureElementLayout& element, uint32_t pitch,
uint32_t height) {
const uint32_t elements_w =
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);
return static_cast<uint64_t>(elements_w) * elements_h * element.bytes;
}
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 offset = 0;
auto pitch = base_pitch;
auto h = static_cast<uint32_t>(height);
for (uint32_t i = 0; i < levels; i++) {
const auto size = CalcLinearUploadLevelSize(fmt, pitch, h);
EXIT_NOT_IMPLEMENTED(size > 0xffffffffull);
EXIT_NOT_IMPLEMENTED(offset > 0xffffffffull);
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;
const auto size = CalcLinearUploadLevelSize(element, pitch, h);
mips[i].size = size;
mips[i].offset = offset;
offset += size;
if (pitch > 1) {
@@ -307,141 +271,123 @@ uint64_t SetLinearUploadLevels(TileSizeOffset* level_sizes, uint32_t fmt, uint64
} // namespace
TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint64_t width, uint64_t height,
uint64_t levels, uint32_t depth, uint64_t pitch,
uint64_t tile, uint64_t upload_size,
bool allow_depth_tile, bool volume_texture,
const char* owner) {
TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint32_t width, uint32_t height,
uint32_t levels, uint32_t depth, uint32_t tile,
uint64_t upload_size, bool allow_depth_tile,
bool volume_texture, const char* owner) {
TextureUploadLayout layout {};
layout.tile = static_cast<uint32_t>(tile);
layout.pitch = static_cast<uint32_t>(pitch);
layout.texel_block = Prospero::BlockCompressedBytesPerBlock(fmt) != 0 ? 4u : 1u;
layout.volume_texture = volume_texture;
layout.surface.description = {
fmt,
tile,
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 (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 {
if (fmt == 0) {
EXIT("%s: legacy texture upload format unsupported: fmt=0 tile=%u size=%" PRIu64
" extent=%" PRIu64 "x%" PRIu64 " pitch=%" PRIu64 " levels=%" PRIu64 "\n",
owner, layout.tile, upload_size, width, height, pitch, levels);
" extent=%ux%u levels=%u\n",
owner, description.tile_mode, upload_size, width, height, levels);
}
if (!layout.volume_texture) {
layout.slice_stride =
CalcTextureSliceStride(layout.level_sizes, levels, upload_size, depth);
switch (tile_mode) {
case Prospero::TileMode::kLinear:
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;
}
std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayout& layout,
uint32_t width, uint32_t height,
uint32_t depth, uint64_t levels,
bool array_texture, bool volume_texture) {
uint32_t mip_width = width;
uint32_t mip_height = height;
uint32_t mip_pitch = volume_texture && static_cast<Prospero::TileMode>(layout.tile) !=
Prospero::TileMode::kLinear
? width
: layout.pitch;
std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayout& layout) {
const auto& description = layout.surface.description;
const bool volume_texture = description.dimension == TileSurfaceDimension::Dim3D;
const auto depth = volume_texture ? description.depth : description.layers;
uint32_t mip_width = description.width;
uint32_t mip_height = description.height;
uint32_t mip_pitch = volume_texture && static_cast<Prospero::TileMode>(description.tile_mode) !=
Prospero::TileMode::kLinear
? description.width
: layout.pitch;
std::vector<vk::BufferImageCopy> regions;
regions.reserve(GetTextureRegionCount(depth, levels, volume_texture));
for (uint32_t i = 0; i < levels; i++) {
EXIT_NOT_IMPLEMENTED(layout.level_sizes[i].size == 0);
regions.reserve(GetTextureRegionCount(depth, description.levels, volume_texture));
for (uint32_t i = 0; i < description.levels; i++) {
EXIT_NOT_IMPLEMENTED(layout.mips[i].size == 0);
const auto mip_depth = GetTextureLevelDepth(depth, i, volume_texture);
for (uint32_t z = 0; z < mip_depth; z++) {
const auto slice_offset = z * layout.slice_stride;
vk::BufferImageCopy region {};
region.bufferOffset = layout.level_sizes[i].offset + slice_offset;
region.imageSubresource = {vk::ImageAspectFlagBits::eColor, i, array_texture ? z : 0,
region.bufferOffset = layout.mips[i].offset + slice_offset;
region.imageSubresource = {vk::ImageAspectFlagBits::eColor, i, volume_texture ? 0u : z,
1};
region.imageOffset.z = volume_texture ? static_cast<int>(z) : 0;
region.imageExtent = {mip_width, mip_height, 1};
const bool linear =
static_cast<Prospero::TileMode>(layout.tile) == Prospero::TileMode::kLinear;
const bool linear = static_cast<Prospero::TileMode>(description.tile_mode) ==
Prospero::TileMode::kLinear;
if (linear) {
region.bufferRowLength = layout.padded_sizes[i].width;
region.bufferImageHeight = layout.padded_sizes[i].height;
region.bufferRowLength = layout.mips[i].row_length;
region.bufferImageHeight = layout.mips[i].image_height;
} 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;
};
const auto pitch = align(mip_pitch, layout.texel_block);
region.bufferRowLength = pitch > align(mip_width, layout.texel_block) ? pitch : 0;
const auto pitch = align(mip_pitch, texel_width);
region.bufferRowLength = pitch > align(mip_width, texel_width) ? pitch : 0;
}
regions.push_back(region);
}
@@ -460,24 +406,6 @@ std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayo
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) {
if (offset > capacity || length > capacity - offset) {
return false;
@@ -487,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,
const TextureUploadLayout& layout, uint32_t fmt, uint32_t depth,
uint64_t levels, std::vector<GpuTileInfo>& out_infos) {
const TextureUploadLayout& layout, uint32_t levels,
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 ||
regions.size() != GetTextureRegionCount(depth, levels, layout.volume_texture) ||
Prospero::IsFmaskTextureFormat(fmt)) {
regions.size() != GetTextureRegionCount(depth, levels, volume_texture) ||
Prospero::IsFmaskTextureFormat(description.format)) {
return false;
}
GpuTileElementLayout element {};
if (layout.tile_family == TileBlockFamily::RenderTarget64KB ||
layout.tile_family == TileBlockFamily::Depth64KB) {
element.bytes = Prospero::RenderTargetBytesPerElement(fmt);
} else if (!GetGpuTileElementLayout(fmt, element)) {
return false;
}
if (element.bytes == 0) {
const auto& surface = layout.surface;
const auto& texture = surface.texture;
const auto& block = texture.block;
if (surface.description.levels < levels || block.bytes_per_element == 0 ||
block.family == TileBlockFamily::Count) {
return false;
}
std::vector<GpuTileInfo> infos;
infos.reserve(regions.size());
if (layout.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;
if (volume_texture) {
size_t region_base = 0;
for (uint32_t level = 0; level < levels; ++level) {
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;
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);
@@ -537,50 +455,43 @@ bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferI
info.bytes_per_element = block.bytes_per_element;
info.linear_offset = region.bufferOffset;
info.tiled_offset =
static_cast<uint64_t>(z / block.block_depth) * volume.block_slice_size +
volume.level_offsets[level];
static_cast<uint64_t>(z / block.block_depth) * surface.block_slice_size +
surface.mips[level].offset;
const uint64_t linear_span =
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,
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)) {
return false;
}
info.linear_slice_stride = linear_stride;
info.width =
std::max((region.imageExtent.width + element.wide - 1u) / element.wide, 1u);
info.height = std::max((logical_height + element.tall - 1u) / element.tall, 1u);
info.depth = copy_depth;
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.depth = copy_depth;
info.surface_z =
block.block_depth == 1 ? static_cast<uint32_t>(region.imageOffset.z) : 0;
info.pitch = std::max((pitch + element.wide - 1u) / element.wide, 1u);
info.tail_x = tail ? volume.tail_x[level] : 0;
info.tail_y = tail ? volume.tail_y[level] : 0;
info.pitch = std::max((pitch + texture.texel_width - 1u) / texture.texel_width, 1u);
info.tail_x = tail ? surface.mips[level].tail_x : 0;
info.tail_y = tail ? surface.mips[level].tail_y : 0;
info.tail = tail;
info.tiled_width = volume.level_widths[level];
info.tiled_height = volume.level_heights[level];
info.tiled_width = surface.mips[level].padded_width;
info.tiled_height = surface.mips[level].padded_height;
infos.push_back(info);
}
region_base += mip_depth;
}
} else {
const auto base_family = layout.tile_family;
if (base_family == TileBlockFamily::Count) {
return false;
}
size_t region_index = 0;
for (uint32_t level = 0; level < levels; level++) {
const auto& level_size = layout.level_sizes[level];
const bool tail = level >= layout.first_tail_level;
const auto family = base_family;
TileBlockLayout block {};
if (!TileGetBlockLayout(family, element.bytes, block)) {
return false;
}
const auto level_depth = GetTextureLevelDepth(depth, level, layout.volume_texture);
const auto& level_size = layout.mips[level];
const auto& mip = surface.mips[level];
const bool tail = level >= surface.first_tail_level;
const auto level_depth = GetTextureLevelDepth(depth, level, false);
for (uint32_t z = 0; z < level_depth; z++) {
const auto& region = regions[region_index++];
const auto pitch =
@@ -589,39 +500,36 @@ bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferI
? region.bufferImageHeight
: region.imageExtent.height;
GpuTileInfo info {};
info.family = block.family;
info.bytes_per_element = block.bytes_per_element;
info.linear_offset = region.bufferOffset;
info.tiled_offset = TextureUploadSliceSourceOffset(layout, level, z);
if (!SetGpuTileSize(info.linear_offset, level_size.size, UINT64_MAX,
info.linear_size) ||
!SetGpuTileSize(info.tiled_offset, GetLevelSrcSize(level_size), tiled_size,
info.tiled_size)) {
info.family = block.family;
info.bytes_per_element = block.bytes_per_element;
info.linear_offset = region.bufferOffset;
const auto source_stride = layout.source_slice_stride != 0
? layout.source_slice_stride
: surface.block_slice_size;
if (z > (UINT64_MAX - mip.offset) / source_stride) {
return false;
}
info.width =
std::max((region.imageExtent.width + element.wide - 1u) / element.wide, 1u);
info.height = std::max((logical_height + element.tall - 1u) / element.tall, 1u);
info.surface_z = base_family == TileBlockFamily::RenderTarget64KB ||
base_family == TileBlockFamily::Depth64KB
info.tiled_offset = mip.offset + static_cast<uint64_t>(z) * source_stride;
if (!SetGpuTileSize(info.linear_offset, level_size.size, UINT64_MAX,
info.linear_size) ||
!SetGpuTileSize(info.tiled_offset, mip.size, tiled_size, info.tiled_size)) {
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
: 0;
info.pitch = std::max((pitch + element.wide - 1u) / element.wide, 1u);
info.tail = tail;
info.tail_x = tail ? level_size.x : 0;
info.tail_y = tail ? level_size.y : 0;
info.tiled_width =
layout.padded_sizes[level].width != 0
? 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;
info.pitch = std::max((pitch + texture.texel_width - 1u) / texture.texel_width, 1u);
info.tail = tail;
info.tail_x = tail ? mip.tail_x : 0;
info.tail_y = tail ? mip.tail_y : 0;
info.tiled_width = mip.padded_width;
info.tiled_height = mip.padded_height;
infos.push_back(info);
}
}
@@ -19,34 +19,32 @@ struct RenderTargetFormatInfo {
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 {
uint32_t tile = 0;
uint32_t pitch = 0;
uint32_t texel_block = 1;
uint64_t slice_stride = 0;
uint64_t source_slice_stride = 0;
uint32_t first_tail_level = 16;
TileBlockFamily tile_family = TileBlockFamily::Count;
bool volume_texture = false;
TileSizeOffset level_sizes[16] = {};
TilePaddedSize padded_sizes[16] = {};
uint32_t pitch = 0;
uint64_t slice_stride = 0;
uint64_t source_slice_stride = 0;
TileSurfaceLayout surface;
TextureUploadMipLayout mips[16] = {};
};
vk::ComponentMapping TextureGetComponentMapping(uint32_t swizzle);
vk::Format TextureGetFormat(uint32_t fmt);
RenderTargetFormatInfo TextureGetRenderTargetFormat(uint32_t layout, uint32_t type, uint32_t order);
TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint64_t width, uint64_t height,
uint64_t levels, uint32_t depth, uint64_t pitch,
uint64_t tile, uint64_t upload_size,
bool allow_depth_tile, bool volume_texture,
const char* owner);
std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayout& layout,
uint32_t width, uint32_t height,
uint32_t depth, uint64_t levels,
bool array_texture, bool volume_texture);
TextureUploadLayout TextureCalcUploadLayout(uint32_t fmt, uint32_t width, uint32_t height,
uint32_t levels, uint32_t depth, uint32_t tile,
uint64_t upload_size, bool allow_depth_tile,
bool volume_texture, const char* owner);
std::vector<vk::BufferImageCopy> TextureBuildImageCopies(const TextureUploadLayout& layout);
bool TextureBuildGpuTileInfos(uint64_t tiled_size, const std::vector<vk::BufferImageCopy>& regions,
const TextureUploadLayout& layout, uint32_t fmt, uint32_t depth,
uint64_t levels, std::vector<GpuTileInfo>& infos);
const TextureUploadLayout& layout, uint32_t levels,
std::vector<GpuTileInfo>& infos);
} // namespace Libs::Graphics
@@ -239,7 +239,7 @@ bool IsSupportedDepthTargetDescriptor(const ShaderTextureResource& descriptor, c
const auto samples = multisampled ? 1u << descriptor.LastLevel() : 1u;
const auto pitch =
multisampled ? TileGetDepthPitch(width, image.info.bytes_per_block, descriptor.LastLevel())
: TileGetTexturePitch(descriptor.Format(), width, 1, descriptor.TileMode());
: TileGetTexturePitch(descriptor.Format(), width, descriptor.TileMode());
const bool supported_2d = type == Prospero::ImageType::kColor2D &&
image.info.resources.layers == 1 && descriptor.Depth() == 0 &&
descriptor.BaseArray5() == 0;
@@ -321,7 +321,7 @@ static void ValidateDepthTargetBinding(const ShaderRecompiler::IR::ImageResource
return;
}
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());
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 "
@@ -373,26 +373,33 @@ static bool IsSupportedStorageTextureDescriptor(const ShaderRecompiler::IR::Imag
const bool is_3d = resource.dimension == ShaderRecompiler::Decoder::ImageDimension::Dim3D &&
descriptor.Type() == Prospero::GpuEnumValue(Prospero::ImageType::kColor3D) &&
descriptor.BaseArray5() == 0;
TileBlockLayout depth_block {};
const auto depth_bpe = Prospero::RenderTargetBytesPerElement(descriptor.Format());
const bool supported_depth_tile =
tile == Prospero::GpuEnumValue(Prospero::TileMode::kDepth) && !resource.read &&
!Prospero::IsFmaskTextureFormat(descriptor.Format()) && (is_2d || is_2d_array) &&
TileGetBlockLayout(TileBlockFamily::Depth64KB, depth_bpe, depth_block);
const bool supported_standard_256b =
(is_2d || is_2d_array) &&
tile == Prospero::GpuEnumValue(Prospero::TileMode::kStandard256B) &&
TileIsStandard256BTextureSupported(descriptor.Format());
const bool supported_standard_tile =
(tile == Prospero::GpuEnumValue(Prospero::TileMode::kStandard4KB) &&
TileIsStandard4KBTextureSupported(descriptor.Format())) ||
(tile == Prospero::GpuEnumValue(Prospero::TileMode::kStandard64KB) &&
TileIsStandard64KBTextureSupported(descriptor.Format()));
const bool supported_tile = tile == Prospero::GpuEnumValue(Prospero::TileMode::kLinear) ||
tile == Prospero::GpuEnumValue(Prospero::TileMode::kRenderTarget) ||
supported_depth_tile || supported_standard_256b ||
supported_standard_tile;
const auto swizzle = descriptor.DstSelXYZW();
TileTextureBlockLayout tile_layout {};
bool supported_tile = false;
switch (static_cast<Prospero::TileMode>(tile)) {
case Prospero::TileMode::kLinear: supported_tile = true; break;
case Prospero::TileMode::kDepth:
supported_tile =
!resource.read && !Prospero::IsFmaskTextureFormat(descriptor.Format()) &&
(is_2d || is_2d_array) &&
TileGetTextureBlockLayout(descriptor.Format(), tile, false, tile_layout);
break;
case Prospero::TileMode::kStandard256B:
supported_tile =
(is_2d || is_2d_array) &&
TileGetTextureBlockLayout(descriptor.Format(), tile, false, tile_layout);
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 =
IsValidImageSwizzle(swizzle) &&
(swizzle == DstSel(4, 5, 6, 7) || !resource.read || resource.atomic);
@@ -498,18 +505,20 @@ static TextureCache::ImageDesc NullTextureDesc(const ShaderRecompiler::IR::Image
static void PopulateTextureMipLayout(ImageInfo& info) {
if (info.IsVolume() && info.tile_mode != Prospero::GpuEnumValue(Prospero::TileMode::kLinear)) {
TileVolumeLayout volume {};
if (!TileGetTextureVolumeLayout(info.guest_format, info.extent.width, info.extent.height,
info.extent.depth, info.resources.levels, info.tile_mode,
volume)) {
TileSurfaceLayout surface {};
const TileSurfaceDescription description {
info.guest_format, info.tile_mode, TileSurfaceDimension::Dim3D, info.extent.width,
info.extent.height, info.extent.depth, info.resources.levels, 1};
if (!TileGetTiledTextureLayout(description, surface)) {
EXIT("unsupported normalized volume texture layout\n");
}
for (uint32_t level = 0; level < info.resources.levels; level++) {
const auto& mip = surface.mips[level];
info.mip_layout[level] = {
volume.level_offsets[level],
volume.level_sizes[level],
volume.level_widths[level],
volume.level_heights[level],
mip.offset,
mip.size,
mip.padded_width,
mip.padded_height,
};
}
return;
@@ -517,7 +526,7 @@ static void PopulateTextureMipLayout(ImageInfo& info) {
TileSizeOffset levels[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);
for (uint32_t level = 0; level < info.resources.levels; level++) {
const auto offset =
@@ -675,9 +684,9 @@ RenderExecutor::ResolveTexture(const ShaderRecompiler::IR::ImageResource& reso
}
size.size *= image_layers;
} else {
pitch = TileGetTexturePitch(format, width, levels, tile);
TileGetTextureTotalSize(format, width, height, volume ? depth : image_layers, pitch, levels,
tile, volume, size);
pitch = TileGetTexturePitch(format, width, tile);
TileGetTextureTotalSize(format, width, height, volume ? depth : image_layers, levels, tile,
volume, size);
}
EXIT_NOT_IMPLEMENTED(size.size == 0 || size.align == 0 ||
(address & (static_cast<uint64_t>(size.align) - 1u)) != 0);
@@ -924,22 +933,21 @@ RenderExecutor::PrepareGraphicsBindings(CommandBuffer& buffer, const ShaderStage
return bindings;
}
static void RetainBindings(CommandBuffer& buffer,
const DescriptorCache::NativeDescriptors& resources) {
static void RetainBindings(CommandBuffer& buffer, DescriptorCache::NativeDescriptors& resources) {
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) {
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) {
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) {
buffer.RetainResourceUntilFence(view.owner);
buffer.RetainResourceUntilFence(std::move(view.owner));
}
}
}
@@ -36,6 +36,22 @@ VulkanMemoryBarrier MakeShaderWriteDependency() {
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) {
EXIT_IF(buffer == nullptr);
@@ -84,6 +100,21 @@ bool HasShaderBufferWrites(const ShaderStageRuntime& runtime) {
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) {
EXIT_IF(vk_buffer == nullptr || !source_stages);
const auto barrier = MakeShaderWriteDependency();
@@ -18,12 +18,17 @@ struct ShaderBufferWriteRange {
};
vk::PipelineStageFlags ShaderPipelineStages(vk::ShaderStageFlags stages);
VulkanMemoryBarrier MakeShaderAccessDependency();
VulkanMemoryBarrier MakeShaderWriteHazardDependency();
VulkanMemoryBarrier MakeShaderWriteDependency();
vk::BufferMemoryBarrier MakeGdsDependency(vk::Buffer buffer);
std::vector<ShaderBufferWriteRange>
CollectShaderBufferWrites(const ShaderRecompiler::IR::Program& program,
const ShaderRecompiler::IR::ResourceSnapshot& resources);
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);
} // namespace Libs::Graphics
@@ -947,15 +947,23 @@ void CreatePipelineInternal(
vk::DynamicState::eViewport,
vk::DynamicState::eScissor,
vk::DynamicState::eLineWidth,
vk::DynamicState::eDepthBiasEnable,
vk::DynamicState::eDepthBias,
vk::DynamicState::eStencilCompareMask,
vk::DynamicState::eStencilReference,
vk::DynamicState::eStencilWriteMask,
#if !defined(__APPLE__)
// Keep last so depth-only pipelines can omit this dynamic state.
vk::DynamicState::eColorWriteEnableEXT, // unsupported by MoltenVK; static mask instead
#endif
};
const auto dynamic_states_count =
auto dynamic_states_count =
static_cast<uint32_t>(sizeof(dynamic_states) / sizeof(dynamic_states[0]));
#if !defined(__APPLE__)
if (static_params.color_count == 0) {
dynamic_states_count--;
}
#endif
vk::PipelineDynamicStateCreateInfo dynamic_state {};
dynamic_state.sType = vk::StructureType::ePipelineDynamicStateCreateInfo;
@@ -325,9 +325,6 @@ void RenderExecutor::DispatchDirect(uint64_t submit_id, RenderCommandBuffer& buf
auto vk_buffer = buffer.Handle();
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);
has_storage_writes =
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;
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();
}
+125 -45
View File
@@ -64,12 +64,32 @@ int32_t ResolveVertexOffset(uint32_t index_offset, const ShaderVertexInputInfo&
return 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_mrt_state_log_count = 0;
static std::atomic<uint32_t> g_shader_stage_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_mrt_state_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 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) {
switch (type) {
case RenderColorType::NoColorOutput: return "NoColorOutput";
@@ -335,6 +355,22 @@ static PipelineDynamicParameters BuildGraphicsDynamicParams(const RenderCommandB
ret.stencil_front = depth.stencil_dynamic_front;
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
// 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++) {
@@ -379,6 +415,13 @@ static void SetDynamicParams(const RenderCommandBuffer& buffer, vk::CommandBuffe
}
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) {
vk_buffer.setStencilCompareMask(vk::StencilFaceFlagBits::eFront,
dynamic_params.stencil_front.compareMask);
@@ -684,11 +727,24 @@ struct VertexBufferRange {
[[nodiscard]] uint64_t RequestedSize() const { return requested_end - base_address; }
};
static std::vector<BufferBinding> AcquireVertexBuffers(RenderCommandBuffer& buffer,
const ShaderVertexInputInfo& vs_input_info) {
struct PreparedVertexBuffers {
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.
std::vector<VertexBufferRange> ranges;
ranges.reserve(vs_input_info.buffers_num);
std::array<VertexBufferRange, ShaderVertexInputInfo::RES_MAX> ranges {};
uint32_t range_count = 0;
for (int i = 0; i < vs_input_info.buffers_num; i++) {
const auto& vertex = vs_input_info.buffers[i];
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",
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) {
return left.base_address < right.base_address;
});
std::sort(ranges.begin(), ranges.begin() + range_count,
[](const VertexBufferRange& left, const VertexBufferRange& right) {
return left.base_address < right.base_address;
});
// Merge overlapping or touching ranges before acquiring host buffers.
std::vector<VertexBufferRange> merged_ranges;
merged_ranges.reserve(ranges.size());
for (const auto& range: ranges) {
if (!merged_ranges.empty() && merged_ranges.back().requested_end >= range.base_address) {
merged_ranges.back().requested_end =
std::max(merged_ranges.back().requested_end, range.requested_end);
std::array<VertexBufferRange, ShaderVertexInputInfo::RES_MAX> merged_ranges {};
uint32_t merged_count = 0;
for (uint32_t i = 0; i < range_count; i++) {
const auto& range = ranges[i];
if (merged_count != 0 &&
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;
}
merged_ranges.push_back(range);
merged_ranges[merged_count++] = {range.base_address, range.requested_end};
}
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
const auto size =
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.
std::vector<BufferBinding> bindings;
bindings.reserve(vs_input_info.buffers_num);
PreparedVertexBuffers prepared;
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++) {
const auto& vertex = vs_input_info.buffers[i];
const auto size = VertexBufferDescriptorSize(vertex);
if (size == 0) {
auto owner = cache.ObtainNullBuffer();
bindings.push_back({owner, owner->Handle(), 0});
if (null_owner == nullptr) {
null_owner = cache.ObtainNullBuffer();
null_buffer = null_owner->Handle();
}
prepared.buffers[i] = null_buffer;
prepared.offsets[i] = 0;
continue;
}
const auto range = std::ranges::find_if(merged_ranges, [&](const VertexBufferRange& value) {
return vertex.addr >= value.base_address && vertex.addr < value.acquired_end;
});
if (range == merged_ranges.end()) {
const auto range = std::find_if(merged_ranges.begin(), merged_ranges.begin() + merged_count,
[&](const VertexBufferRange& value) {
return vertex.addr >= value.base_address &&
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",
vertex.addr);
}
auto binding = range->binding;
binding.offset += vertex.addr - range->base_address;
bindings.push_back(std::move(binding));
prepared.buffers[i] = range->binding.buffer;
prepared.offsets[i] = range->binding.offset + vertex.addr - range->base_address;
}
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,
@@ -883,10 +960,9 @@ static void RefreshShaders(RenderCommandBuffer& buffer, const DrawCallInfo& draw
}
}
static std::vector<BufferBinding> PrepareVertexBuffers(uint64_t submit_id,
RenderCommandBuffer& buffer,
const DrawCallInfo& draw,
const ShaderVertexInputInfo& vs_input_info) {
static PreparedVertexBuffers PrepareVertexBuffers(uint64_t submit_id, RenderCommandBuffer& buffer,
const DrawCallInfo& draw,
const ShaderVertexInputInfo& vs_input_info) {
EXIT_IF(draw.name == nullptr);
(void)submit_id;
@@ -920,24 +996,28 @@ static PreparedIndexBuffer PrepareIndexBuffer(RenderCommandBuffer& buffe
}
static void CommitVertexBuffers(RenderCommandBuffer& buffer, vk::CommandBuffer vk_buffer,
std::vector<BufferBinding>& bindings) {
for (uint32_t slot = 0; slot < bindings.size(); slot++) {
auto& binding = bindings[slot];
if (binding.owner != nullptr) {
buffer.RetainResourceUntilFence(binding.owner);
PreparedVertexBuffers& prepared) {
for (uint32_t i = 0; i < prepared.owner_count; i++) {
if (prepared.owners[i] != nullptr) {
buffer.RetainResourceUntilFence(std::move(prepared.owners[i]));
}
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,
const PreparedIndexBuffer& prepared) {
PreparedIndexBuffer& prepared) {
if (prepared.size == 0) {
return;
}
if (prepared.owner != nullptr) {
buffer.RetainResourceUntilFence(prepared.owner);
buffer.RetainResourceUntilFence(std::move(prepared.owner));
}
EXIT_IF(prepared.buffer == nullptr);
vk_buffer.bindIndexBuffer(prepared.buffer, prepared.offset, prepared.type);
+4 -4
View File
@@ -15,12 +15,16 @@ struct ShaderVertexInputInfo;
struct PipelineDynamicParameters {
bool stencil_test_enable = false;
bool depth_bias_enable = false;
float viewport_scale[3] = {};
float viewport_offset[3] = {};
int scissor_ltrb[4] = {0};
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;
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_standard_layout_v<PipelineDynamicParameters>);
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,
const ShaderVertexInputInfo& vs_input_info);
+2 -4
View File
@@ -128,8 +128,7 @@ static bool BindRenderDocApi(HMODULE module) {
g_api = static_cast<RenderDocApi*>(api);
g_api->SetCaptureFilePathTemplate("_RenderDoc/kyty");
static RenderDocInputButton capture_keys[] = {eRENDERDOC_Key_F1};
g_api->SetCaptureKeys(capture_keys, 1);
g_api->SetCaptureKeys(nullptr, 0);
g_api->UnloadCrashHandler();
char module_path[MAX_PATH] = {};
@@ -170,8 +169,7 @@ static bool BindRenderDocApi(HMODULE module) {
g_api = static_cast<RenderDocApi*>(api);
g_api->SetCaptureFilePathTemplate("_RenderDoc/kyty");
static RenderDocInputButton capture_keys[] = {eRENDERDOC_Key_F1};
g_api->SetCaptureKeys(capture_keys, 1);
g_api->SetCaptureKeys(nullptr, 0);
g_api->UnloadCrashHandler();
Dl_info info {};
+6 -10
View File
@@ -537,10 +537,10 @@ Graphics::ImageInfo BufferAttributeGroup::ImageInfo(const VideoOutBuffer& buffer
const auto tile_mode =
Graphics::Prospero::GpuEnumValue(Graphics::Prospero::TileMode::kRenderTarget);
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::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 ||
(buffer.data_address & (total.align - 1u)) != 0) {
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()) {
const auto sleep_begin = Common::Timer::QueryPerformanceCounter();
if (total_wait > 0) {
const auto sleep_end = sleep_begin + static_cast<uint64_t>(total_wait);
auto now = sleep_begin;
while (!token.stop_requested() && now < sleep_end) {
const auto remaining_us = (sleep_end - now) * 1000000u / frequency;
Common::Thread::SleepMicro(
static_cast<uint32_t>(std::clamp<uint64_t>(remaining_us, 1, 1000)));
now = Common::Timer::QueryPerformanceCounter();
}
const auto remaining_us =
(static_cast<uint64_t>(total_wait) * 1000000u + frequency - 1) / frequency;
Common::Thread::SleepMicro(static_cast<uint32_t>(
std::clamp<uint64_t>(remaining_us, 1, std::numeric_limits<uint32_t>::max())));
}
if (token.stop_requested()) {
break;
@@ -387,9 +387,11 @@ struct Presenter::Impl {
desc.view_info.usage = vk::ImageUsageFlagBits::eTransferSrc;
desc.type = TextureCache::BindingType::VideoOut;
auto& cache = renderer.GetTextureCache();
auto& image = cache.GetImage(cache.FindImage(desc));
auto& cache = renderer.GetTextureCache();
const auto image_id = cache.FindImage(desc);
auto& image = cache.GetImage(image_id);
image.usage.video_out = true;
cache.UpdateImage(image_id);
return image;
}
@@ -260,6 +260,10 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa
LOGF("sampleRateShading is not supported\n");
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) {
LOGF("fragmentStoresAndAtomics is not supported\n");
@@ -549,6 +553,7 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
EXIT_NOT_IMPLEMENTED(required_features13.synchronization2 == VK_TRUE &&
supported_features13.synchronization2 != VK_TRUE);
EXIT_NOT_IMPLEMENTED(supported_features2.features.sampleRateShading != VK_TRUE);
EXIT_NOT_IMPLEMENTED(supported_features2.features.depthBiasClamp != VK_TRUE);
features12.timelineSemaphore = VK_TRUE;
vk::PhysicalDeviceFeatures device_features {};
@@ -564,6 +569,7 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
device_features.independentBlend = VK_TRUE;
device_features.tessellationShader = VK_TRUE;
device_features.sampleRateShading = VK_TRUE;
device_features.depthBiasClamp = VK_TRUE;
graphics.sample_rate_shading_enabled = true;
device_features.vertexPipelineStoresAndAtomics =
supported_features2.features.vertexPipelineStoresAndAtomics;
+7 -22
View File
@@ -752,26 +752,18 @@ void WindowContext::Run() {
#if defined(__APPLE__)
DrainMainThreadTasks();
#endif
if (SDL_PollEvent(&loop.event) != 0) {
ProcessEvent(timer.GetTimeS());
continue;
}
if (loop.paused.load(std::memory_order_acquire)) {
if (!timer.IsPaused()) {
timer.Pause();
}
if (SDL_WaitEvent(&loop.event) == 0) {
EXIT("%s\n", SDL_GetError());
}
ProcessEvent(timer.GetTimeS());
continue;
}
if (timer.IsPaused()) {
} else if (timer.IsPaused()) {
timer.Resume();
}
Common::Thread::SleepMicro(1000);
if (SDL_WaitEvent(&loop.event) == 0) {
EXIT("%s\n", SDL_GetError());
}
ProcessEvent(timer.GetTimeS());
}
}
@@ -946,13 +938,6 @@ void WindowContext::UpdateTitle() {
static constexpr auto build_type = "Unknown";
#endif
#if defined(KYTY_OFFICIAL_BUILD)
static constexpr auto build_label = "Official build " KYTY_RELEASE_TAG;
#elif defined(KYTY_FORK_BUILD)
static constexpr auto build_label = "Fork build " KYTY_BUILD_REPOSITORY " " KYTY_GIT_HASH;
#else
static constexpr auto build_label = "Source build " KYTY_GIT_HASH;
#endif
const auto now = Common::Timer::QueryPerformanceCounter();
const auto frequency = Common::Timer::QueryPerformanceFrequency();
@@ -965,7 +950,7 @@ void WindowContext::UpdateTitle() {
fps_frames = 0;
}
auto fps = fmt::format("[{} | {}] {}{}{}{}{}{}[{}] [{}], frame: {}, fps: {:f}", build_label,
auto fps = fmt::format("[{} | {}] {}{}{}{}{}{}[{}] [{}], frame: {}, fps: {:f}", KYTY_BUILD_LABEL,
build_type, (has_title ? title : ""), (has_title ? ", " : ""),
(has_title_id ? title_id : ""), (has_title_id ? ", " : ""),
(has_app_ver ? app_ver : ""), (has_app_ver ? " " : ""), device_name,
@@ -562,8 +562,7 @@ static bool EvaluateRuntimeSourcesImpl(const Program&
const auto dynamic =
std::find(program.srt.dynamic_sources.begin(), program.srt.dynamic_sources.end(),
request.source) != program.srt.dynamic_sources.end();
if (descriptor == nullptr || dynamic ||
!DescriptorSourceResolved(program, request.source)) {
if (descriptor == nullptr || dynamic) {
if (error != nullptr) {
*error = Diagnostic(program, request.use_pc,
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.cu_group_enable != 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.fp16_overflow != 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);
}
}
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);
if (sh.shader_z_format != 0x00000000 && sh.shader_z_format != 0x00000001 &&
+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;
}
#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 {
public:
enum class FailureReason {
@@ -264,6 +271,19 @@ public:
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) {
*placeholder_preserved = true;
}
@@ -832,6 +852,8 @@ public:
EXIT_IF(VirtualFree(reinterpret_cast<void*>(region_vaddr), region_size,
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
for (const auto& [region_vaddr, region_size]: regions) {
RestorePlaceholder(region_vaddr, region_size);
@@ -1252,8 +1274,7 @@ private:
if (base != it->first || end != it->first + it->second) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (VirtualFree(reinterpret_cast<void*>(base), end - base,
MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS) != 0) {
if (CoalescePlaceholders(base, end - base)) {
m_free.erase(first, last);
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) {
return PthreadMutexInitNamed(mutex, attr, "");
int KYTY_SYSV_ABI PthreadMutexInit(PthreadMutex* mutex, const PthreadMutexattr* attr,
const char* name) {
return PthreadMutexInitNamed(mutex, attr, name);
}
int KYTY_SYSV_ABI PthreadMutexDestroy(PthreadMutex* mutex) {
@@ -2443,8 +2444,9 @@ static int PthreadRwlockInitNamed(PthreadRwlock* rwlock, const PthreadRwlockattr
return OK;
}
int KYTY_SYSV_ABI PthreadRwlockInit(PthreadRwlock* rwlock, const PthreadRwlockattr* attr) {
return PthreadRwlockInitNamed(rwlock, attr, "");
int KYTY_SYSV_ABI PthreadRwlockInit(PthreadRwlock* rwlock, const PthreadRwlockattr* attr,
const char* name) {
return PthreadRwlockInitNamed(rwlock, attr, name);
}
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) {
return PthreadCondInitNamed(cond, attr, "");
int KYTY_SYSV_ABI PthreadCondInit(PthreadCond* cond, const PthreadCondattr* attr,
const char* name) {
return PthreadCondInitNamed(cond, attr, name);
}
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));
}
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,
LibKernel::KernelClockid clock_id) {
PRINT_NAME();
@@ -4525,6 +4535,13 @@ int KYTY_SYSV_ABI pthread_rwlock_destroy(LibKernel::PthreadRwlock* 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,
LibKernel::pthread_key_destructor_func_t destructor) {
PRINT_NAME();
@@ -4560,7 +4577,7 @@ int KYTY_SYSV_ABI pthread_mutex_init(LibKernel::PthreadMutex* mutex,
const LibKernel::PthreadMutexattr* attr) {
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) {
+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 PthreadMutexattrSettype(PthreadMutexattr* attr, int type);
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 PthreadMutexLock(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 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 PthreadRwlockTimedrdlock(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 PthreadCondBroadcast(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 PthreadCondSignalto(PthreadCond* cond, Pthread thread);
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_wrlock(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,
LibKernel::pthread_key_destructor_func_t destructor);
int KYTY_SYSV_ABI pthread_key_delete(LibKernel::PthreadKey key);
+5
View File
@@ -119,6 +119,11 @@ int WaitLinux(volatile T* address, T expected, const uint32_t* timeout_micros,
}
int WakeLinux(volatile void* address, int32_t count) {
// The legacy FUTEX_WAKE path may wake one waiter when count is zero.
if (count == 0) {
return OK;
}
const auto result = syscall(SYS_futex, const_cast<void*>(address), FUTEX_WAKE_PRIVATE, count,
nullptr, nullptr, 0);
return result < 0 ? KERNEL_ERROR_EINVAL : OK;
+11
View File
@@ -1,2 +1,13 @@
#include "cmake_config.h"
#define KYTY_GIT_VERSION "@KYTY_GIT_VERSION@"
#define KYTY_GIT_HASH "@KYTY_GIT_HASH@"
/* Build provenance, shown in the window title and in crash reports. */
#if defined(KYTY_OFFICIAL_BUILD)
#define KYTY_BUILD_LABEL "Official build " KYTY_RELEASE_TAG
#elif defined(KYTY_FORK_BUILD)
#define KYTY_BUILD_LABEL "Fork build " KYTY_BUILD_REPOSITORY " " KYTY_GIT_HASH
#else
#define KYTY_BUILD_LABEL "Source build " KYTY_GIT_HASH
#endif
@@ -88,6 +88,16 @@
</property>
</widget>
</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>
</widget>
</item>
+15 -2
View File
@@ -99,7 +99,10 @@ public:
QString printf_output_file = "_kyty.txt";
ProfilerDirection profiler_direction = ProfilerDirection::None;
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");
@@ -119,7 +122,10 @@ public:
printf_output_file = other.printf_output_file;
profiler_direction = other.profiler_direction;
renderdoc_enabled = other.renderdoc_enabled;
host_input_mapping = other.host_input_mapping;
#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) {
@@ -156,6 +162,9 @@ public:
KYTY_CFG_SET(printf_output_file);
KYTY_CFG_SET(profiler_direction);
KYTY_CFG_SET(renderdoc_enabled);
#if defined(_WIN32)
KYTY_CFG_SET(red_zone_protection_enabled);
#endif
s->setValue("host_input_mapping", host_input_mapping);
KYTY_CFG_SET(elf);
}
@@ -183,6 +192,10 @@ public:
KYTY_CFG_GET(printf_output_file);
KYTY_CFG_GET(profiler_direction);
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();
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_vulkan_validation->setChecked(info.vulkan_validation_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_log_direction, info.shader_log_direction);
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.shader_validation_enabled = ui.checkBox_shader_validation->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());
info.shader_log_direction = TextToEnum<Configuration::ShaderLogDirection>(
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 << "--profiler-direction" << EnumToText(info.profiler_direction);
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) {
args << "--keymap" << binding;
}
+3 -2
View File
@@ -208,7 +208,8 @@ void GameController::AddState(const ControllerState& state) {
void GameController::Button(int id, uint32_t button, bool down) {
Common::LockGuard lock(m_mutex);
if (m_active_id == id) {
// The keyboard shares the player-1 pad with the active gamepad.
if (m_active_id == id || id == HOST_INPUT_CONTROLLER_ID) {
auto state = GetLastState();
state.time = LibKernel::KernelGetProcessTime();
@@ -226,7 +227,7 @@ void GameController::Button(int id, uint32_t button, bool down) {
void GameController::Axis(int id, Controller::Axis axis, int value) {
Common::LockGuard lock(m_mutex);
if (m_active_id == id) {
if (m_active_id == id || id == HOST_INPUT_CONTROLLER_ID) {
auto state = GetLastState();
state.time = LibKernel::KernelGetProcessTime();
+4 -4
View File
@@ -481,13 +481,13 @@ static KYTY_SYSV_ABI int MtxInit(LibKernel::PthreadMutex* mtx, int type) {
constexpr int mtx_recursive = 0x100;
if ((type & mtx_recursive) == 0) {
return mtx_result(LibKernel::PthreadMutexInit(mtx, nullptr));
return mtx_result(LibKernel::PthreadMutexInit(mtx, nullptr, nullptr));
}
LibKernel::PthreadMutexattr attr = nullptr;
int result = LibKernel::PthreadMutexattrInit(&attr);
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);
return mtx_result(result);
@@ -659,8 +659,8 @@ void KYTY_SYSV_ABI LibcHeapGetTraceInfo(Info* info) {
info->mstate_table = g_mstate_table;
}
int KYTY_SYSV_ABI LibcInternalExtCxaThreadAtexit(thread_atexit_destructor_t destructor, void* object,
void* /*module_id*/) {
int KYTY_SYSV_ABI LibcInternalExtCxaThreadAtexit(thread_atexit_destructor_t destructor,
void* object, void* /*module_id*/) {
PRINT_NAME();
g_thread_atexit_destructors.push_back({destructor, object});
+5 -62
View File
@@ -516,6 +516,7 @@ static void QueuePendingSignal(Pthread thread, int signum) {
PthreadQueuePendingSignal(thread, signum);
}
#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS
static void WaitForSignalDispatch(Pthread thread, int signum) {
constexpr auto DISPATCH_WAIT_STEP = std::chrono::microseconds(1000);
constexpr auto DISPATCH_WAIT_MAX = std::chrono::milliseconds(2);
@@ -526,6 +527,7 @@ static void WaitForSignalDispatch(Pthread thread, int signum) {
waited += DISPATCH_WAIT_STEP;
}
}
#endif
static bool TakePendingSignal(Pthread thread, int 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
void KernelDispatchPendingSignalForCurrentThread() {
@@ -1206,8 +1154,7 @@ static int KYTY_SYSV_ABI KernelRaiseException(Pthread thread, int signum) {
}
HANDLE target_thread =
OpenThread(THREAD_SET_CONTEXT | THREAD_SUSPEND_RESUME | THREAD_GET_CONTEXT, FALSE,
static_cast<DWORD>(target_thread_id));
OpenThread(THREAD_SET_CONTEXT, FALSE, static_cast<DWORD>(target_thread_id));
if (target_thread == nullptr) {
return KERNEL_ERROR_EINVAL;
}
@@ -1231,10 +1178,6 @@ static int KYTY_SYSV_ABI KernelRaiseException(Pthread thread, int signum) {
Common::CondVar::SignalThread(PthreadGetUniqueId(thread));
PthreadWakeForSignal(thread);
WaitForSignalDispatch(thread, signum);
if (PthreadHasPendingSignal(thread, signum)) {
DispatchSignalWithSuspendedThreadContext(target_thread, thread, signum, handler);
}
CloseHandle(target_thread);
return OK;
#elif defined(__x86_64__)
@@ -2229,9 +2172,11 @@ LIB_DEFINE(InitLibKernel_1_Posix) {
LIB_FUNC("ltCfaGr2JGE", Posix::pthread_mutex_destroy);
LIB_FUNC("mkx2fVhNMsg", Posix::pthread_cond_broadcast);
LIB_FUNC("2MOy+rUfuhQ", Posix::pthread_cond_signal);
LIB_FUNC("0TyVk4MSLt0", Posix::pthread_cond_init);
LIB_FUNC("Op8TBGY5KHg", Posix::pthread_cond_wait);
LIB_FUNC("Z4QosVuAsA0", Posix::pthread_once);
LIB_FUNC("1471ajPzxh0", Posix::pthread_rwlock_destroy);
LIB_FUNC("ytQULN-nhL4", Posix::pthread_rwlock_init);
LIB_FUNC("sIlRvQqsN2Y", Posix::pthread_rwlock_wrlock);
LIB_FUNC("SFxTMOfuCkE", LibKernel::PthreadRwlockTryrdlock);
LIB_FUNC("XhWHn6P5R7U", LibKernel::PthreadRwlockTrywrlock);
@@ -3268,7 +3213,6 @@ LIB_DEFINE(InitLibKernel_1_Pthread) {
LIB_FUNC("FXPWHNk8Of0", LibKernel::PthreadAttrGetschedparam);
LIB_FUNC("6ULAa0fq4jA", LibKernel::PthreadRwlockInit);
LIB_FUNC("ytQULN-nhL4", LibKernel::PthreadRwlockInit);
LIB_FUNC("BB+kb08Tl9A", LibKernel::PthreadRwlockDestroy);
LIB_FUNC("Ox9i0c7L5w0", LibKernel::PthreadRwlockRdlock);
LIB_FUNC("iGjsr1WAtI0", LibKernel::PthreadRwlockRdlock);
@@ -3284,7 +3228,6 @@ LIB_DEFINE(InitLibKernel_1_Pthread) {
LIB_FUNC("h-OifiouBd8", LibKernel::PthreadRwlockattrSettype);
LIB_FUNC("2Tb92quprl0", LibKernel::PthreadCondInit);
LIB_FUNC("0TyVk4MSLt0", LibKernel::PthreadCondInit);
LIB_FUNC("g+PZd2hiacg", LibKernel::PthreadCondDestroy);
LIB_FUNC("RXXqi4CtF8w", LibKernel::PthreadCondDestroy);
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_DYNAMIC = 2;
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_PROCPARAM = 0x61000001;
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/gamePatch.h"
#include "loader/jit.h"
#include "loader/redZonePatcher.h"
#include "loader/symbolDatabase.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);
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(
g_desired_base_addr, program->mapped_size, Common::VirtualMemory::Mode::ExecuteReadWrite,
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) {
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);
EXIT_IF(program->base_vaddr == 0);
EXIT_IF(program->base_size_aligned < program->base_size);
LOGF("base_vaddr = 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);
#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++) {
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)) {
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) {
@@ -2284,8 +2317,42 @@ void RuntimeLinker::LoadProgramToMemory(Program* program) {
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) {
SetupTlsHandler(program);
}
@@ -2311,6 +2378,11 @@ void RuntimeLinker::DeleteProgram(Program* p) {
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(
!Libs::LibKernel::Memory::FreeGuestMemory(program->base_vaddr, program->mapped_size));
}
+13 -9
View File
@@ -114,15 +114,19 @@ struct Program {
Program();
~Program();
int32_t unique_id = -1;
RuntimeLinker* rt = nullptr;
std::filesystem::path file_name;
std::unique_ptr<Elf64> elf;
std::unique_ptr<DynamicInfo> dynamic_info;
uint64_t base_vaddr = 0;
uint64_t base_size = 0;
uint64_t base_size_aligned = 0;
uint64_t mapped_size = 0;
int32_t unique_id = -1;
RuntimeLinker* rt = nullptr;
std::filesystem::path file_name;
std::unique_ptr<Elf64> elf;
std::unique_ptr<DynamicInfo> dynamic_info;
uint64_t base_vaddr = 0;
uint64_t base_size = 0;
uint64_t base_size_aligned = 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> import_symbols;
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(
" --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(" --rd Enable RenderDoc capture.\n");
}
@@ -138,6 +141,13 @@ static bool ParseArgs(int argc, char* argv[], RunOptions& options, bool& show_he
continue;
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (arg == "--redzone") {
options.config.red_zone_protection_enabled = true;
continue;
}
#endif
if (!Common::StartsWith(arg, "--")) {
::printf("game input must be provided with --game\n");
return false;
+147
View File
@@ -2,6 +2,8 @@
#include "graphics/host_gpu/memoryTracker.h"
#include "graphics/host_gpu/rangeSet.h"
#include <atomic>
#include <chrono>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
@@ -211,6 +213,40 @@ void TestQueriesDoNotRequireMappedOwnership() {
"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() {
TrackerHarness harness;
auto &tracker = harness.tracker;
@@ -497,6 +533,114 @@ void TestCrossRegionUpload() {
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() {
constexpr auto region_size = Libs::Graphics::TRACKER_REGION_SIZE;
constexpr auto page_size = Libs::Graphics::TRACKER_PAGE_SIZE;
@@ -683,6 +827,7 @@ int main(int argc, char **argv) {
}
TestRangeSet();
TestQueriesDoNotRequireMappedOwnership();
TestConcurrentRegionPublication();
TestCpuDirtyUpload();
TestRangeInvalidation();
TestGpuReacquisitionAfterInvalidation();
@@ -690,6 +835,8 @@ int main(int argc, char **argv) {
TestExactDirtyIntervalsSharingTrackerPage();
TestGpuDownloadProtectionMirrors();
TestCrossRegionUpload();
TestUploadDoesNotSerializeDisjointRegion();
TestDownloadDoesNotSerializeDisjointRegion();
TestGpuUnmarkUsesRegionMask();
TestFullRegionGpuUnmarkBatching();
TestFatalPaths();
-120
View File
@@ -1,120 +0,0 @@
#include "graphics/host_gpu/renderer/cache/resourceMutex.h"
#include <atomic>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <mutex>
#include <string>
#include <thread>
#include <vector>
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#undef min
#undef max
#endif
namespace {
using Libs::Graphics::ResourceMutex;
void Check(bool value, const char* text) {
if (!value) {
std::fprintf(stderr, "ResourceMutexTests: failed: %s\n", text);
std::abort();
}
}
void YieldMany() {
for (uint32_t i = 0; i < 4096; i++) {
std::this_thread::yield();
}
}
void TestOwnership() {
ResourceMutex mutex;
Check(!mutex.IsOwnedByCurrentThread(), "fresh mutex reported an owner");
{
std::lock_guard lock(mutex);
Check(mutex.IsOwnedByCurrentThread(), "owner tracking was not installed");
}
Check(!mutex.IsOwnedByCurrentThread(), "owner tracking survived unlock");
}
void TestSerializesTransactions() {
ResourceMutex mutex;
std::unique_lock owner(mutex);
std::atomic_bool contender_started {false};
std::atomic_bool contender_entered {false};
std::thread contender([&] {
contender_started.store(true, std::memory_order_release);
std::lock_guard lock(mutex);
contender_entered.store(true, std::memory_order_release);
});
while (!contender_started.load(std::memory_order_acquire)) {
std::this_thread::yield();
}
YieldMany();
Check(!contender_entered.load(std::memory_order_acquire),
"contender entered an active resource transaction");
owner.unlock();
contender.join();
Check(contender_entered.load(std::memory_order_acquire),
"contender did not resume after resource transaction");
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
[[noreturn]] void RunDeathCase() {
ResourceMutex mutex;
std::lock_guard first(mutex);
std::lock_guard second(mutex);
std::_Exit(0x7f);
}
void CheckDeathCase() {
char path[MAX_PATH] {};
Check(GetModuleFileNameA(nullptr, path, MAX_PATH) != 0, "GetModuleFileName failed");
std::string command = std::string("\"") + path + "\" --death";
std::vector<char> mutable_command(command.begin(), command.end());
mutable_command.push_back('\0');
STARTUPINFOA startup {sizeof(startup)};
PROCESS_INFORMATION process {};
Check(CreateProcessA(nullptr, mutable_command.data(), nullptr, nullptr, FALSE, CREATE_NO_WINDOW,
nullptr, nullptr, &startup, &process) != 0,
"CreateProcess failed");
const auto wait = WaitForSingleObject(process.hProcess, 10000);
if (wait != WAIT_OBJECT_0) {
TerminateProcess(process.hProcess, 0x7e);
}
Check(wait == WAIT_OBJECT_0, "ResourceMutex death test timed out");
DWORD exit_code = 0;
Check(GetExitCodeProcess(process.hProcess, &exit_code) != 0 && exit_code == 321,
"ResourceMutex death path used the wrong exit");
CloseHandle(process.hThread);
CloseHandle(process.hProcess);
}
#endif
} // namespace
int main(int argc, char** argv) {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
if (argc == 2 && std::strcmp(argv[1], "--death") == 0) {
RunDeathCase();
}
#else
(void)argc;
(void)argv;
#endif
TestOwnership();
TestSerializesTransactions();
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
CheckDeathCase();
#endif
std::puts("ResourceMutexTests: all cases passed");
return 0;
}
File diff suppressed because it is too large Load Diff
+205 -1
View File
@@ -8,15 +8,28 @@
#include "kernel/memory.h"
#include "kernel/pthread.h"
#include "libs/errno.h"
#include "loader/redZonePatcher.h"
#include "loader/runtimeLinker.h"
#include "loader/systemContent.h"
#include <array>
#include <cinttypes>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <string>
#include <vector>
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
#ifdef DeleteFile
#undef DeleteFile
#endif
#endif
namespace {
@@ -133,6 +146,105 @@ void InitSubsystems() {
initialized = true;
}
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
void* g_red_zone_fault_page = nullptr;
LONG CALLBACK RedZoneFaultHandler(EXCEPTION_POINTERS* exception) {
if (exception == nullptr || exception->ExceptionRecord == nullptr ||
exception->ContextRecord == nullptr ||
exception->ExceptionRecord->ExceptionCode != EXCEPTION_ACCESS_VIOLATION ||
reinterpret_cast<void*>(exception->ExceptionRecord->ExceptionInformation[1]) !=
g_red_zone_fault_page) {
return EXCEPTION_CONTINUE_SEARCH;
}
// Model the Windows exception stack footprint that prompted the static patch:
// data below the interrupted RSP is not part of the Windows ABI contract.
*reinterpret_cast<uint64_t*>(exception->ContextRecord->Rsp - 0x18) = 0;
DWORD old_protection = 0;
if (VirtualProtect(g_red_zone_fault_page, 0x1000, PAGE_READWRITE, &old_protection) == FALSE) {
return EXCEPTION_CONTINUE_SEARCH;
}
return EXCEPTION_CONTINUE_EXECUTION;
}
void TestWindowsGuestRedZoneStaticPatcher() {
const char* test = "WindowsGuestRedZoneStaticPatcher";
constexpr uint64_t SENTINEL = 0x1122334455667788ull;
constexpr uint64_t CODE_SIZE = 0x4000;
constexpr uint64_t TRAMPOLINE_SIZE = 0x4000;
const auto mapping = Libs::LibKernel::Memory::AllocateProgramMemory(
0x0000000902000000ull, CODE_SIZE + TRAMPOLINE_SIZE,
Common::VirtualMemory::Mode::ExecuteReadWrite, "red_zone_patcher_test");
Check(test, mapping != 0, "failed to allocate patch test code");
std::vector<uint8_t> code;
const auto emit = [&code](std::initializer_list<uint8_t> bytes) {
code.insert(code.end(), bytes.begin(), bytes.end());
};
const auto emit64 = [&code](uint64_t value) {
const auto offset = code.size();
code.resize(offset + sizeof(value));
std::memcpy(code.data() + offset, &value, sizeof(value));
};
emit({0x48, 0xb8});
emit64(SENTINEL); // movabs rax, sentinel
emit({0x48, 0x89, 0x44, 0x24, 0xe8}); // mov [rsp-0x18], rax
emit({0x48, 0x8b, 0x07}); // mov rax, [rdi] (faultable, 3 bytes)
emit({0x48, 0x8b, 0x44, 0x24, 0xe8}); // mov rax, [rsp-0x18]
emit({0x48, 0xb9});
emit64(SENTINEL); // movabs rcx, sentinel
emit({0x48, 0x39, 0xc8}); // cmp rax, rcx
emit({0x0f, 0x94, 0xc0}); // sete al
emit({0x0f, 0xb6, 0xc0, 0xc3}); // movzx eax, al; ret
Check(test, code.size() < CODE_SIZE, "generated patch test code is too large");
std::memcpy(reinterpret_cast<void*>(mapping), code.data(), code.size());
Check(test, Common::VirtualMemory::FlushInstructionCache(mapping, code.size()),
"failed to flush generated test code");
g_red_zone_fault_page = VirtualAlloc(nullptr, 0x1000, MEM_RESERVE | MEM_COMMIT, PAGE_NOACCESS);
Check(test, g_red_zone_fault_page != nullptr, "failed to allocate fault page");
auto* handler = AddVectoredExceptionHandler(1, RedZoneFaultHandler);
Check(test, handler != nullptr, "failed to install test exception handler");
using GuestFunction = uint64_t(KYTY_SYSV_ABI*)(const uint64_t*);
const auto function = reinterpret_cast<GuestFunction>(mapping);
const bool unpatched_was_corrupted = function(static_cast<const uint64_t*>(g_red_zone_fault_page)) == 0;
DWORD old_protection = 0;
Check(test, VirtualProtect(g_red_zone_fault_page, 0x1000, PAGE_NOACCESS, &old_protection) != FALSE,
"failed to reset fault page protection");
Loader::RegisterRedZonePatchModule(reinterpret_cast<void*>(mapping), CODE_SIZE,
reinterpret_cast<void*>(mapping + CODE_SIZE),
TRAMPOLINE_SIZE);
const std::array<uintptr_t, 1> function_starts = {static_cast<uintptr_t>(mapping)};
const auto result = Loader::PatchRedZoneMemoryInstructions(
mapping, code.size(), function_starts);
Check(test, Common::VirtualMemory::FlushInstructionCache(mapping, CODE_SIZE + TRAMPOLINE_SIZE),
"failed to flush patched test code");
const bool patched_preserved = function(static_cast<const uint64_t*>(g_red_zone_fault_page)) == 1;
Loader::UnregisterRedZonePatchModule(reinterpret_cast<void*>(mapping));
RemoveVectoredExceptionHandler(handler);
VirtualFree(g_red_zone_fault_page, 0, MEM_RELEASE);
g_red_zone_fault_page = nullptr;
const bool freed = Libs::LibKernel::Memory::FreeGuestMemory(mapping, CODE_SIZE + TRAMPOLINE_SIZE);
Check(test, unpatched_was_corrupted, "test harness did not reproduce red-zone corruption");
Check(test, result.red_zone_function_count == 1 && result.memory_instruction_count >= 1 &&
result.patched_memory_instruction_count >= 1 &&
result.unrelocatable_memory_instruction_count == 0,
"static patcher did not cover the faultable instruction");
Check(test, patched_preserved, "patched fault still corrupted the guest red zone");
Check(test, freed, "failed to free patch test code");
std::printf("[host] %-48s ok\n", test);
}
#else
void TestWindowsGuestRedZoneStaticPatcher() {
std::printf("[host] %-48s skipped\n", "WindowsGuestRedZoneStaticPatcher");
}
#endif
void RunTest(void (*test_func)()) {
if (g_failed_tests != 0) {
return;
@@ -495,6 +607,85 @@ void TestFlexibleMemoryReuseIsZeroFilled() {
std::printf("[host] %-48s ok\n", test);
}
void TestSmallerFlexibleMapReusesReleasedHole() {
const char* test = "SmallerFlexibleMapReusesReleasedHole";
const auto baseline = AvailableFlexibleMemory(test);
constexpr uint64_t SmallSize = SceKernelPageSize * 12;
constexpr uint64_t LargeSize = SceKernelPageSize * 16;
constexpr uint64_t BlockSize = SceKernelPageSize * 4;
constexpr int RuntimeMap = 0x8000;
// Leave a 0x30000 hole before an allocated backing block. The large mapping must then be
// assembled from two flexible-backing extents, matching the allocation that exposed the
// PPSA30528 regression.
void* seed_hole = nullptr;
CheckOk(test,
Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory(
&seed_hole, SmallSize, SceKernelProtCpuRw, RuntimeMap, "backing_seed_hole"),
"KernelMapNamedFlexibleMemory(seed hole)");
void* seed_blocker = nullptr;
CheckOk(test,
Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory(
&seed_blocker, BlockSize, SceKernelProtCpuRw, RuntimeMap, "backing_seed_blocker"),
"KernelMapNamedFlexibleMemory(seed blocker)");
CheckOk(test,
Libs::LibKernel::Memory::KernelMunmap(reinterpret_cast<uint64_t>(seed_hole), SmallSize),
"KernelMunmap(seed hole)");
void* virtual_blocker = seed_hole;
CheckOk(test,
Libs::LibKernel::Memory::KernelReserveVirtualRange(&virtual_blocker, SmallSize, 0,
SceKernelPageSize),
"KernelReserveVirtualRange(seed virtual blocker)");
Check(test, virtual_blocker == seed_hole, "seed virtual hole was not reserved in place");
void* large = nullptr;
CheckOk(test,
Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory(
&large, LargeSize, SceKernelProtCpuRw, RuntimeMap, "released_large_hole"),
"KernelMapNamedFlexibleMemory(large)");
void* blocker = nullptr;
CheckOk(test,
Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory(
&blocker, SmallSize, SceKernelProtCpuRw, RuntimeMap, "adjacent_blocker"),
"KernelMapNamedFlexibleMemory(blocker)");
const auto large_base = reinterpret_cast<uint64_t>(large);
const auto blocker_base = reinterpret_cast<uint64_t>(blocker);
Check(test, blocker_base == large_base + LargeSize,
"hint-less flexible maps were not adjacent");
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(large_base, LargeSize),
"KernelMunmap(large)");
void* reused = nullptr;
CheckOk(test,
Libs::LibKernel::Memory::KernelMapNamedFlexibleMemory(
&reused, SmallSize, SceKernelProtCpuRw, RuntimeMap, "smaller_hole_reuse"),
"KernelMapNamedFlexibleMemory(smaller reuse)");
Check(test, reinterpret_cast<uint64_t>(reused) == large_base,
"smaller flexible map did not reuse the released hole");
*reinterpret_cast<uint64_t*>(reused) = 0x4b59545952455553ull; // "KYTYREUS"
Check(test, *reinterpret_cast<const uint64_t*>(reused) == 0x4b59545952455553ull,
"reused flexible hole is not writable");
CheckOk(test,
Libs::LibKernel::Memory::KernelMunmap(reinterpret_cast<uint64_t>(reused), SmallSize),
"KernelMunmap(reused)");
CheckOk(test, Libs::LibKernel::Memory::KernelMunmap(blocker_base, SmallSize),
"KernelMunmap(blocker)");
CheckOk(
test,
Libs::LibKernel::Memory::KernelMunmap(reinterpret_cast<uint64_t>(seed_blocker), BlockSize),
"KernelMunmap(seed blocker)");
CheckOk(test,
Libs::LibKernel::Memory::KernelMunmap(reinterpret_cast<uint64_t>(virtual_blocker),
SmallSize),
"KernelMunmap(seed virtual blocker)");
Check(test, AvailableFlexibleMemory(test) == baseline,
"released-hole reuse leaked flexible memory capacity");
std::printf("[host] %-48s ok\n", test);
}
void TestGuestStackUsesPrivateOwnerMemoryAndCache() {
const char* test = "GuestStackUsesPrivateOwnerMemoryAndCache";
const auto baseline = AvailableFlexibleMemory(test);
@@ -605,6 +796,13 @@ void TestRuntimeMemoryOwnerLifecycle() {
test,
Libs::LibKernel::Memory::TestPlaceholderRangeIsFree(adjacent_first, SceKernelPageSize * 2),
"combined adjacent runtime free did not restore one owner placeholder");
const auto adjacent_reused = Libs::LibKernel::Memory::AllocateRuntimeMemory(
adjacent_first, SceKernelPageSize, Common::VirtualMemory::Mode::ReadWrite,
"runtime_adjacent_reuse", true);
Check(test, adjacent_reused == adjacent_first,
"combined adjacent runtime placeholder could not be split for reuse");
Check(test, Libs::LibKernel::Memory::FreeGuestMemory(adjacent_reused, SceKernelPageSize),
"adjacent runtime reuse cleanup failed");
std::printf("[host] %-48s ok\n", test);
}
@@ -2246,9 +2444,14 @@ void TestModuleRelocationUsesWritableHostMapping() {
} // namespace
int main() {
int main(int argc, char** argv) {
InitSubsystems();
if (argc == 2 && std::strcmp(argv[1], "--red-zone-patcher-only") == 0) {
RunTest(TestWindowsGuestRedZoneStaticPatcher);
return g_failed_tests == 0 ? 0 : 1;
}
RunTest(TestWindowsGuestRedZoneStaticPatcher);
RunTest(TestProsperoArgumentAndInfoSizeContracts);
RunTest(TestGuestAddressSpaceOwnsReservationsBeforeBacking);
RunTest(TestGuestAddressSpaceHasNoFixedFallback);
@@ -2258,6 +2461,7 @@ int main() {
RunTest(TestFlexibleDmemCompatAndAlignmentFlags);
RunTest(TestFlexibleNoCoalescePreservesBoundaries);
RunTest(TestFlexibleMemoryReuseIsZeroFilled);
RunTest(TestSmallerFlexibleMapReusesReleasedHole);
RunTest(TestGuestStackUsesPrivateOwnerMemoryAndCache);
RunTest(TestMainEntryUsesGuestStackAndDisablesHostChecks);
RunTest(TestFragmentedBackingUnmapRollback);