Compare commits

...

22 Commits

Author SHA1 Message Date
Ali Almohaya ca7c8f0074 agc: fix linux build of gs oversubscription clamps (#232) 2026-08-11 07:38:22 +02:00
Pouare514 d7063d0b81 vulkan: remove validation layer configuration from device creation (#221)
* vulkan: remove validation layer configuration from device creation

* Remove accidental dependency gitlinks

---------

Co-authored-by: nmzik <Nmzik@mail.ru>
2026-08-09 23:05:31 +02:00
nmzik 091e6b31dc avplayer: decode media on bounded worker queues 2026-08-09 06:49:25 +02:00
nmzik 4cd6132758 gpu: allow byte-granular memory DMA 2026-08-09 05:31:18 +02:00
nmzik 9c263a9886 gpu: accept disabled default clip planes 2026-08-09 05:23:43 +02:00
nmzik c11fc96019 pad: accept system remote-control port 2026-08-09 05:15:40 +02:00
nmzik 4f2b5eb749 libSystemService fixes 2026-08-09 04:44:43 +02:00
nmzik d7013dc2b3 add left/right joystick remapping 2026-08-09 04:24:47 +02:00
nmzik 0bbe301120 add website/compatibility db 2026-08-09 04:08:32 +02:00
nmzik 37aa6f88b0 ime: implement libIme and shared host overlay 2026-08-09 02:58:49 +02:00
nmzik e3890febaa agc: new abi 2026-08-09 01:32:32 +02:00
nmzik a4da2a9f00 json2: implement typed Value constructor 2026-08-09 01:09:41 +02:00
nmzik 3005a210c6 renderer: ignore inactive HTile depth address 2026-08-08 23:55:45 +02:00
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
39 changed files with 2946 additions and 838 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
+3
View File
@@ -31,6 +31,9 @@ macOS support is experimental. The emulator is built for x86-64 and runs on Appl
Rosetta 2, with Vulkan provided by MoltenVK. A small number of titles have been verified in-game
on Apple Silicon hardware; see [Building on macOS](#building-on-macos).
Community game test results are available in the
[KytyPS5 Compatibility List](https://kytyps5.github.io/).
## Bugs and Issues
The project is in an early stage, so please be mindful when opening new issues. Expect crashes,
+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)));
@@ -183,6 +183,10 @@ static void HwCtxIgnoreAaMaskRegister([[maybe_unused]] uint32_t cmd_offset,
static void HwCtxIgnoreAlphaToMaskRegister([[maybe_unused]] uint32_t value) {}
static void HwCtxIgnoreDisabledUserClipPlane(CommandProcessor& cp, uint32_t value) {
EXIT_NOT_IMPLEMENTED(value != 0 || cp.GetCtx().GetClipControl().user_clip_planes != 0);
}
static void HwCtxIgnoreDrawPayloadControl([[maybe_unused]] uint32_t value) {}
static void HwCtxIgnoreObjprimIdControl([[maybe_unused]] uint32_t value) {}
@@ -2682,14 +2686,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) {
@@ -2697,10 +2708,6 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) {
cp.EmitGlobalBarrier();
}
if (gl2_writeback) {
cp.SynchronizeGpu();
}
trigger_interrupt();
return 7;
@@ -2711,10 +2718,6 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) {
cp.EmitGlobalBarrier();
}
if (gl2_writeback) {
cp.SynchronizeGpu();
}
trigger_interrupt();
return 7;
@@ -2728,17 +2731,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;
@@ -2753,7 +2745,6 @@ KYTY_CP_OP_PARSER(CpOpReleaseMem) {
if (data_sel == 5) {
if (gl2_writeback) {
cp.SynchronizeGpu();
cache_action = 0;
}
@@ -2764,7 +2755,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;
}
@@ -3354,6 +3347,12 @@ void GraphicsInitJmpTablesCxIndirect() {
};
}
for (auto cmd_offset = Pm4::PA_CL_UCP_0_X; cmd_offset <= Pm4::PA_CL_UCP_5_W; cmd_offset++) {
g_hw_ctx_indirect_func[cmd_offset] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
HwCtxIgnoreDisabledUserClipPlane(cp, value);
};
}
for (auto cmd_offset = Pm4::PA_SC_VPORT_SCISSOR_0_TL;
cmd_offset <= Pm4::PA_SC_VPORT_SCISSOR_15_TL; cmd_offset += 2) {
g_hw_ctx_indirect_func[cmd_offset] = [](KYTY_HW_CTX_INDIRECT_ARGS) {
@@ -4452,6 +4451,11 @@ void GraphicsInitJmpTablesUcIndirect() {
r.vertex_group_size = KYTY_PM4_GET(value, GE_CNTL, VERT_GRP_SIZE);
cp.GetUcfg().SetGeControl(r);
};
g_hw_uc_indirect_func[Pm4::GE_PC_ALLOC] = [](KYTY_HW_UC_INDIRECT_ARGS) {
(void)cp;
(void)cmd_offset;
(void)value;
};
g_hw_uc_indirect_func[Pm4::GE_USER_VGPR_EN] = [](KYTY_HW_UC_INDIRECT_ARGS) {
HW::GeUserVgprEn r;
+44 -34
View File
@@ -97,7 +97,7 @@ public:
void SubmitCompute(uint32_t queue, uint32_t* cmd_buffer, uint32_t num_dw,
bool trigger_agc_interrupt_on_done);
void SubmitFlipPreparation(uint64_t request_id);
void SuspendPoint();
void Done();
void Shutdown();
[[nodiscard]] bool IsStopping();
void SendCommand(Common::UniqueFunction<void>&& command);
@@ -106,7 +106,7 @@ public:
[[nodiscard]] static bool IsGpuThread() noexcept { return g_gpu_thread; }
private:
enum class SubmissionType { Graphics, Compute, FlipPreparation, SuspendPoint };
enum class SubmissionType { Graphics, Compute, FlipPreparation };
struct Submission {
SubmissionType type = SubmissionType::Graphics;
@@ -125,6 +125,7 @@ private:
};
void Enqueue(Submission submission);
void WaitForIdle();
void ProcessCommands();
bool Process(Submission& submission);
static void ThreadRun(void* data);
@@ -135,16 +136,17 @@ private:
Common::Mutex m_queue_mutex;
std::mutex m_shutdown_mutex;
Common::CondVar m_work_available;
Common::CondVar m_idle;
std::array<std::deque<Submission>, QueueCount> m_queues;
std::deque<Common::UniqueFunction<void>> m_commands;
std::atomic_uint32_t m_pending_commands {0};
uint32_t m_next_queue = 0;
uint32_t m_submission_count = 0;
bool m_reset_graphics = true;
bool m_processing = false;
bool m_graphics_done = true;
bool m_accepting = true;
bool m_stopping = false;
bool m_shutdown_complete = false;
std::binary_semaphore m_suspend_point_slot {1};
std::unique_ptr<CommandProcessor> m_gfx_cp;
std::array<std::unique_ptr<CommandProcessor>, ComputeQueueCount> m_compute_cp;
@@ -238,8 +240,8 @@ void GpuState::Submit(uint32_t* cmd_draw_buffer, uint32_t num_draw_dw, uint32_t*
submission.commands = OwnedCmdBuffer(cmd_draw_buffer, num_draw_dw);
submission.constant_commands = OwnedCmdBuffer(cmd_const_buffer, num_const_dw);
submission.trigger_agc_interrupt_on_done = trigger_agc_interrupt_on_done;
submission.reset_processor = m_reset_graphics;
m_reset_graphics = false;
submission.reset_processor = m_graphics_done;
m_graphics_done = false;
Enqueue(std::move(submission));
}
@@ -265,21 +267,19 @@ void GpuState::SubmitFlipPreparation(uint64_t request_id) {
Submission submission;
submission.type = SubmissionType::FlipPreparation;
submission.queue_id = 0;
submission.reset_processor = m_reset_graphics;
submission.reset_processor = m_graphics_done;
submission.flip_request_id = request_id;
m_reset_graphics = false;
m_graphics_done = false;
Enqueue(std::move(submission));
}
void GpuState::SuspendPoint() {
EXIT_IF(IsGpuThread());
m_suspend_point_slot.acquire();
void GpuState::Done() {
GpuMutexLock lock(m_submission_mutex);
Submission submission;
submission.type = SubmissionType::SuspendPoint;
submission.queue_id = 0;
m_reset_graphics = false;
Enqueue(std::move(submission));
if (!IsGpuThread()) {
WaitForIdle();
}
m_graphics_done = true;
m_done_num++;
}
int GpuState::GetFrameNum() {
@@ -299,8 +299,6 @@ CommandProcessor& GpuState::GetProcessor(uint32_t queue_id) {
}
void CommandProcessor::Reset() {
BufferWait();
m_sh_ctx.Reset();
m_ucfg.Reset();
m_ctx.Reset();
@@ -308,12 +306,9 @@ void CommandProcessor::Reset() {
m_context_state_pushed = false;
m_index_type_and_size = 0;
m_index_buffer_size = 0;
m_index_base_addr = 0;
m_user_data_marker = HW::UserSgprType::Unknown;
m_draw_indirect_args_base_addr = 0;
m_dispatch_indirect_args_base_addr = 0;
m_num_instances = 1;
m_predicate_skip = false;
std::memset(m_const_ram, 0, sizeof(m_const_ram));
}
@@ -380,7 +375,6 @@ void CommandProcessor::WaitDeDiff(uint32_t diff) {
}
void CommandProcessor::IncrementDe() {
BufferWait();
m_de_count++;
}
@@ -479,7 +473,6 @@ void CommandProcessor::DmaData(uint8_t engine, uint8_t dst_sel, uint8_t dst_cach
if (num_bytes == 0) {
return;
}
EXIT_NOT_IMPLEMENTED((num_bytes & 3u) != 0);
EXIT_NOT_IMPLEMENTED(dst_cache_policy > 3);
EXIT_NOT_IMPLEMENTED(src_cache_policy > 3);
EXIT_NOT_IMPLEMENTED(wait_for_previous > 1);
@@ -527,6 +520,13 @@ void GpuState::Enqueue(Submission submission) {
m_work_available.Signal();
}
void GpuState::WaitForIdle() {
Common::LockGuard lock(m_queue_mutex);
while (m_processing || !m_commands.empty() || m_submission_count != 0) {
m_idle.Wait(&m_queue_mutex);
}
}
void GpuState::ThreadRun(void* data) {
auto* gpu = static_cast<GpuState*>(data);
EXIT_IF(gpu == nullptr);
@@ -542,14 +542,19 @@ void GpuState::ThreadRun(void* data) {
{
Common::LockGuard lock(gpu->m_queue_mutex);
while (gpu->m_commands.empty() && gpu->m_submission_count == 0 && !gpu->m_stopping) {
gpu->m_processing = false;
gpu->m_idle.Signal();
gpu->m_work_available.Wait(&gpu->m_queue_mutex);
}
if (gpu->m_stopping && gpu->m_commands.empty() && gpu->m_submission_count == 0) {
gpu->m_processing = false;
gpu->m_idle.SignalAll();
should_stop = true;
} else if (!gpu->m_commands.empty()) {
command = std::move(gpu->m_commands.front());
gpu->m_commands.pop_front();
EXIT_IF(gpu->m_pending_commands.fetch_sub(1, std::memory_order_acq_rel) == 0);
gpu->m_processing = true;
} else {
int selected_queue = -1;
for (uint32_t offset = 0; offset < QueueCount; offset++) {
@@ -560,6 +565,7 @@ void GpuState::ThreadRun(void* data) {
}
}
if (selected_queue < 0) {
gpu->m_processing = false;
gpu->m_work_available.WaitFor(&gpu->m_queue_mutex, 100);
for (auto& queue: gpu->m_queues) {
if (!queue.empty()) {
@@ -573,6 +579,7 @@ void GpuState::ThreadRun(void* data) {
queue.pop_front();
gpu->m_submission_count--;
gpu->m_next_queue = (static_cast<uint32_t>(selected_queue) + 1) % QueueCount;
gpu->m_processing = true;
has_submission = true;
}
}
@@ -587,6 +594,11 @@ void GpuState::ThreadRun(void* data) {
EXIT_IF(g_current_processor != nullptr);
command();
Common::LockGuard lock(gpu->m_queue_mutex);
gpu->m_processing = false;
if (gpu->m_commands.empty() && gpu->m_submission_count == 0) {
gpu->m_idle.SignalAll();
}
continue;
}
@@ -605,19 +617,16 @@ void GpuState::ThreadRun(void* data) {
}
}
}
gpu->m_processing = false;
if (gpu->m_commands.empty() && gpu->m_submission_count == 0) {
gpu->m_idle.SignalAll();
}
}
}
bool GpuState::Process(Submission& submission) {
auto& cp = GetProcessor(submission.queue_id);
const bool first_slice = !submission.started;
if (submission.type == SubmissionType::SuspendPoint) {
EXIT_IF(!first_slice || submission.queue_id != 0);
cp.Reset();
m_done_num++;
m_suspend_point_slot.release();
return true;
}
if (first_slice && submission.reset_processor) {
cp.Reset();
@@ -705,7 +714,6 @@ bool GpuState::Process(Submission& submission) {
m_renderer.GetGpuResources().RunGarbageCollector();
cp.PrepareCpuFlip(submission.flip_request_id);
break;
case SubmissionType::SuspendPoint: EXIT("unreachable suspend point submission\n");
}
return complete;
@@ -1332,6 +1340,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) {
@@ -1534,7 +1545,6 @@ void CommandProcessor::TriggerEvent(uint32_t event_type, uint32_t event_index) {
event_index);
}
EmitGlobalBarrier();
SynchronizeGpu();
break;
// DbDataWritebackInvalidate, DbMetadataWritebackInvalidate, CbMetadataWritebackInvalidate.
case 0x0000002a:
@@ -1688,8 +1698,8 @@ void Gpu::SubmitFlipPreparation(uint64_t request_id) {
m_state->SubmitFlipPreparation(request_id);
}
void Gpu::SuspendPoint() {
m_state->SuspendPoint();
void Gpu::Done() {
m_state->Done();
}
int Gpu::GetFrameNum() const {
+1 -1
View File
@@ -28,7 +28,7 @@ public:
void SubmitCompute(uint32_t queue, uint32_t* commands, uint32_t size_dw,
bool trigger_agc_interrupt_on_done = false);
void SubmitFlipPreparation(uint64_t request_id);
void SuspendPoint();
void Done();
[[nodiscard]] int GetFrameNum() const;
[[nodiscard]] static bool IsGpuThread() noexcept;
+2
View File
@@ -372,6 +372,7 @@ constexpr uint32_t PA_CL_UCP_0_X = 0x16F;
constexpr uint32_t PA_CL_UCP_0_Y = 0x170;
constexpr uint32_t PA_CL_UCP_0_Z = 0x171;
constexpr uint32_t PA_CL_UCP_0_W = 0x172;
constexpr uint32_t PA_CL_UCP_5_W = 0x186;
constexpr uint32_t SPI_PS_INPUT_CNTL_0 = 0x191;
constexpr uint32_t SPI_PS_INPUT_CNTL_31 = 0x1B0;
constexpr uint32_t SPI_VS_OUT_CONFIG = 0x1B1;
@@ -1047,6 +1048,7 @@ constexpr uint32_t GE_USER_VGPR1 = 0x25C;
constexpr uint32_t GE_USER_VGPR2 = 0x25D;
constexpr uint32_t GE_USER_VGPR3 = 0x25E;
constexpr uint32_t GE_STEREO_CNTL = 0x25F;
constexpr uint32_t GE_PC_ALLOC = 0x260;
constexpr uint32_t GE_USER_VGPR_EN = 0x262;
constexpr uint32_t GE_USER_VGPR_EN_EN_USER_VGPR1_SHIFT = 0;
+135 -189
View File
@@ -22,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 {
@@ -191,21 +170,18 @@ 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);
});
}
@@ -276,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 (!IsRegionRegistered(vaddr, size)) {
return;
}
m_memory_tracker.InvalidateRegion(vaddr, size,
[this, vaddr, size] { ReadMemory(vaddr, size, true); });
}
@@ -298,60 +271,58 @@ void BufferCache::ReadMemory(uint64_t vaddr, uint64_t size, bool 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);
}
}
@@ -362,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);
@@ -409,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;
}
}
}
@@ -539,29 +504,22 @@ BufferBinding BufferCache::ObtainBuffer(CommandBuffer& command, uint64_t vaddr,
(void)m_texture_cache.InvalidateMemoryFromGPU(vaddr, size, true);
}
std::shared_ptr<Buffer> owner;
uint64_t offset = 0;
{
FaultSafeCacheLock lock(this, m_mutex);
auto& cached = GetOrCreateBuffer(command, vaddr, size);
std::vector<std::pair<uint64_t, uint64_t>> uploads;
m_memory_tracker.ForEachUploadRange(
vaddr, size, is_written,
[&](uint64_t address, uint64_t bytes) noexcept {
uploads.emplace_back(address, bytes);
},
[&]() noexcept {
for (const auto& [address, bytes]: uploads) {
Upload(command, *cached.buffer, cached.buffer->Offset(address),
reinterpret_cast<const void*>(address), bytes);
}
});
if (is_written) {
m_gpu_modified_ranges.Add(vaddr, size);
}
owner = cached.buffer;
offset = cached.buffer->Offset(vaddr);
auto& cached = GetOrCreateBuffer(command, vaddr, size);
std::vector<std::pair<uint64_t, uint64_t>> uploads;
m_memory_tracker.ForEachUploadRange(
vaddr, size, is_written,
[&](uint64_t address, uint64_t bytes) noexcept { uploads.emplace_back(address, bytes); },
[&]() noexcept {
for (const auto& [address, bytes]: uploads) {
Upload(command, *cached.buffer, cached.buffer->Offset(address),
reinterpret_cast<const void*>(address), bytes);
}
});
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(*owner, vaddr, size);
}
@@ -569,19 +527,14 @@ BufferBinding BufferCache::ObtainBuffer(CommandBuffer& command, uint64_t vaddr,
}
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) {
@@ -599,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");
@@ -674,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");
}
@@ -710,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);
@@ -772,8 +722,8 @@ void BufferCache::CopyBuffer(uint64_t dst_vaddr, uint64_t src_vaddr, uint64_t si
const bool dst_memory = !dst_gds;
const bool src_memory = !src_gds;
if ((dst_memory && dst_vaddr == 0) || (src_memory && src_vaddr == 0) || size == 0 ||
((dst_vaddr | src_vaddr | size) & 3u) != 0 || size > UINT64_MAX - dst_vaddr ||
size > UINT64_MAX - src_vaddr || (dst_gds && src_gds) ||
((dst_gds || src_gds) && ((dst_vaddr | src_vaddr | size) & 3u) != 0) ||
size > UINT64_MAX - dst_vaddr || size > UINT64_MAX - src_vaddr || (dst_gds && src_gds) ||
(dst_gds == src_gds && src_vaddr < dst_vaddr + size && dst_vaddr < src_vaddr + size) ||
(dst_gds && (dst_vaddr > m_gds_buffer.Size() || size > m_gds_buffer.Size() - dst_vaddr)) ||
(src_gds && (src_vaddr > m_gds_buffer.Size() || size > m_gds_buffer.Size() - src_vaddr))) {
@@ -836,7 +786,6 @@ bool BufferCache::IsRegionRegistered(uint64_t vaddr, uint64_t size) {
size > TRACKER_ADDRESS_SIZE - vaddr) {
EXIT("BufferCache: invalid registered-region query\n");
}
FaultSafeCacheLock lock(this, m_mutex);
// 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);
@@ -852,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();
}
@@ -876,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;
@@ -924,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 ||
+1 -2
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"
@@ -60,6 +59,7 @@ 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);
// 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);
@@ -99,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;
@@ -205,8 +205,6 @@ void RenderExecutor::ResolveRenderDepthTarget(uint64_t submit_id, RenderCommandB
if (z.depth_view.slice_max >= 32) {
DepthFatal("HTile clear tracking supports at most 32 slices");
}
} else if (z.htile_data_base_addr != 0) {
DepthFatal("HTile address without an enabled tile surface");
}
const bool size_xy_valid = z.size.valid;
const bool wh_valid = z.width_height_valid && z.width != 0 && z.height != 0;
@@ -953,11 +953,17 @@ void CreatePipelineInternal(
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;
@@ -1,36 +0,0 @@
#ifndef EMULATOR_SRC_GRAPHICS_PRESENTATION_IMEDIALOGOVERLAY_H_
#define EMULATOR_SRC_GRAPHICS_PRESENTATION_IMEDIALOGOVERLAY_H_
#include "common/common.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <memory>
union SDL_Event;
namespace Libs::Graphics {
struct GraphicContext;
void InitializeImeDialogInput();
void ShutdownImeDialogInput();
bool ProcessImeDialogInput(const SDL_Event& event);
class ImeDialogOverlay final {
public:
explicit ImeDialogOverlay(GraphicContext& graphics);
~ImeDialogOverlay();
KYTY_CLASS_NO_COPY(ImeDialogOverlay);
[[nodiscard]] bool PrepareFrame(vk::Extent2D extent, vk::Format format, uint32_t image_count);
void Record(vk::CommandBuffer command, vk::ImageView target);
void ReleaseVulkan();
private:
struct Impl;
std::unique_ptr<Impl> m_impl;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_PRESENTATION_IMEDIALOGOVERLAY_H_
@@ -1,4 +1,4 @@
#include "graphics/presentation/imeDialogOverlay.h"
#include "graphics/presentation/imeOverlay.h"
#include "SDL.h"
#include "common/assert.h"
@@ -7,6 +7,7 @@
#include "imgui.h"
#include "imgui_impl_vulkan.h"
#include "libs/controller.h"
#include "libs/ime.h"
#include "libs/imeDialog.h"
#include <algorithm>
@@ -26,7 +27,181 @@ namespace Libs::Graphics {
namespace {
namespace Ime = Libs::Dialog::ImeDialog;
namespace CoreIme = Libs::Ime;
namespace DialogIme = Libs::Dialog::ImeDialog;
namespace Ime {
enum class Type : uint32_t { Default = 0, BasicLatin = 1, Url = 2, Mail = 3, Number = 4 };
enum class EnterLabel : uint32_t { Default = 0, Send = 1, Search = 2, Go = 3 };
enum class Alignment : uint32_t { Start = 0, Center = 1, End = 2 };
enum class ExternalAction : uint8_t {
None,
Text,
Backspace,
MoveLeft,
MoveRight,
Cancel,
Accept,
Newline,
};
struct Keycode {
uint16_t keycode = 0;
char16_t character = u'\0';
uint32_t status = 0;
uint32_t type = 0;
int32_t user_id = 0;
uint32_t resource_id = 0;
uint64_t timestamp = 0;
};
struct ExternalInput {
Keycode key;
ExternalAction action = ExternalAction::None;
std::u16string text;
};
struct HostSnapshot {
uint64_t generation = 0;
Type type = Type::Default;
EnterLabel enter_label = EnterLabel::Default;
uint32_t option = 0;
uint32_t max_text_length = 0;
uint32_t cursor = 0;
uint32_t disable_device = 0;
bool key_panel_visible = true;
float posx = 0.0f;
float posy = 0.0f;
Alignment horizontal_alignment = Alignment::Start;
Alignment vertical_alignment = Alignment::Start;
uint32_t panel_width = 0;
uint32_t panel_height = 0;
std::u16string text;
std::u16string title;
std::u16string placeholder;
};
constexpr uint32_t OPTION_MULTILINE = 0x00000001;
constexpr uint32_t OPTION_NO_AUTO_CAPITALIZE = 0x00000002;
constexpr uint32_t OPTION_PASSWORD = 0x00000004;
constexpr uint32_t OPTION_FIXED_POSITION = 0x00000040;
constexpr uint32_t OPTION_DISABLE_POSITION_ADJ = 0x00000800;
constexpr uint32_t OPTION_USE_OVER_2K = 0x00004000;
constexpr uint32_t DISABLE_DEVICE_CONTROLLER = 0x00000001;
constexpr uint32_t DISABLE_DEVICE_EXT_KEYBOARD = 0x00000002;
constexpr uint64_t CORE_GENERATION_BIT = uint64_t {1} << 63;
uint64_t PackCoreGeneration(uint64_t generation) {
return generation | CORE_GENERATION_BIT;
}
bool IsCoreGeneration(uint64_t generation) {
return (generation & CORE_GENERATION_BIT) != 0;
}
uint64_t UnpackGeneration(uint64_t generation) {
return generation & ~CORE_GENERATION_BIT;
}
bool GetHostSnapshot(HostSnapshot* snapshot) {
if (snapshot == nullptr) {
return false;
}
CoreIme::HostSnapshot core;
if (CoreIme::GetHostSnapshot(&core)) {
snapshot->generation = PackCoreGeneration(core.generation);
snapshot->type = static_cast<Type>(core.type);
snapshot->enter_label = static_cast<EnterLabel>(core.enter_label);
snapshot->option = core.option;
snapshot->max_text_length = core.max_text_length;
snapshot->cursor = core.cursor;
snapshot->disable_device = core.disable_device;
snapshot->key_panel_visible = core.key_panel_visible;
snapshot->posx = core.posx;
snapshot->posy = core.posy;
snapshot->horizontal_alignment = static_cast<Alignment>(core.horizontal_alignment);
snapshot->vertical_alignment = static_cast<Alignment>(core.vertical_alignment);
snapshot->panel_width = core.panel_width;
snapshot->panel_height = core.panel_height;
snapshot->text = std::move(core.text);
snapshot->title.clear();
snapshot->placeholder.clear();
return true;
}
DialogIme::HostSnapshot dialog;
if (!DialogIme::GetHostSnapshot(&dialog)) {
return false;
}
snapshot->generation = dialog.generation;
snapshot->type = static_cast<Type>(dialog.type);
snapshot->enter_label = static_cast<EnterLabel>(dialog.enter_label);
snapshot->option = dialog.option;
snapshot->max_text_length = dialog.max_text_length;
snapshot->cursor = dialog.cursor;
snapshot->disable_device = dialog.disable_device;
snapshot->key_panel_visible = dialog.key_panel_visible;
snapshot->posx = dialog.posx;
snapshot->posy = dialog.posy;
snapshot->horizontal_alignment = static_cast<Alignment>(dialog.horizontal_alignment);
snapshot->vertical_alignment = static_cast<Alignment>(dialog.vertical_alignment);
snapshot->panel_width = dialog.panel_width;
snapshot->panel_height = dialog.panel_height;
snapshot->text = std::move(dialog.text);
snapshot->title = std::move(dialog.title);
snapshot->placeholder = std::move(dialog.placeholder);
return true;
}
bool HostInsertText(uint64_t generation, std::u16string_view text) {
return IsCoreGeneration(generation)
? CoreIme::HostInsertText(UnpackGeneration(generation), text)
: DialogIme::HostInsertText(generation, text);
}
bool HostBackspace(uint64_t generation) {
return IsCoreGeneration(generation) ? CoreIme::HostBackspace(UnpackGeneration(generation))
: DialogIme::HostBackspace(generation);
}
bool HostAccept(uint64_t generation) {
return IsCoreGeneration(generation) ? CoreIme::HostAccept(UnpackGeneration(generation))
: DialogIme::HostAccept(generation);
}
bool HostCancel(uint64_t generation) {
return IsCoreGeneration(generation) ? CoreIme::HostCancel(UnpackGeneration(generation))
: DialogIme::HostCancel(generation);
}
bool HostQueueExternalInput(uint64_t generation, ExternalInput input) {
if (!IsCoreGeneration(generation)) {
DialogIme::ExternalInput dialog;
dialog.key.keycode = input.key.keycode;
dialog.key.character = input.key.character;
dialog.key.status = input.key.status;
dialog.key.type = input.key.type;
dialog.key.user_id = input.key.user_id;
dialog.key.resource_id = input.key.resource_id;
dialog.key.timestamp = input.key.timestamp;
dialog.action = static_cast<DialogIme::ExternalAction>(input.action);
dialog.text = std::move(input.text);
return DialogIme::HostQueueExternalInput(generation, std::move(dialog));
}
CoreIme::ExternalInput core;
core.key.keycode = input.key.keycode;
core.key.character = input.key.character;
core.key.status = input.key.status;
core.key.type = input.key.type;
core.key.user_id = input.key.user_id;
core.key.resource_id = input.key.resource_id;
core.key.timestamp = input.key.timestamp;
core.action = static_cast<CoreIme::ExternalAction>(input.action);
core.text = std::move(input.text);
return CoreIme::HostQueueExternalInput(UnpackGeneration(generation), std::move(core));
}
} // namespace Ime
constexpr size_t INPUT_QUEUE_CAPACITY = 128;
@@ -114,28 +289,25 @@ void RetryVisibilityWakeup() {
}
}
void OnVisibilityChanged(bool visible, uint64_t generation) {
void RefreshVisibility() {
std::scoped_lock visibility_lock(g_visibility_mutex);
if (!g_input_lifecycle_active) {
return;
}
if (generation < g_generation || (generation == g_generation && visible == g_active)) {
Ime::HostSnapshot snapshot;
const bool visible = Ime::GetHostSnapshot(&snapshot);
const uint64_t generation = visible ? snapshot.generation : g_generation;
if (visible == g_active && (!visible || generation == g_generation)) {
return;
}
g_generation = generation;
bool capture_controller = false;
bool capture_keyboard = false;
bool multiline = false;
if (visible) {
Ime::HostSnapshot snapshot;
if (Ime::GetHostSnapshot(&snapshot) && snapshot.generation == generation) {
capture_controller = (snapshot.disable_device & Ime::DISABLE_DEVICE_CONTROLLER) == 0;
capture_keyboard = (snapshot.disable_device & Ime::DISABLE_DEVICE_EXT_KEYBOARD) == 0;
multiline = (snapshot.option & Ime::OPTION_MULTILINE) != 0;
} else {
visible = false;
}
capture_controller = (snapshot.disable_device & Ime::DISABLE_DEVICE_CONTROLLER) == 0;
capture_keyboard = (snapshot.disable_device & Ime::DISABLE_DEVICE_EXT_KEYBOARD) == 0;
multiline = (snapshot.option & Ime::OPTION_MULTILINE) != 0;
}
const bool was_controller = std::exchange(g_controller_captured, capture_controller);
if (capture_controller || was_controller) {
@@ -156,6 +328,14 @@ void OnVisibilityChanged(bool visible, uint64_t generation) {
}
}
void OnCoreVisibilityChanged(bool, uint64_t) {
RefreshVisibility();
}
void OnDialogVisibilityChanged(bool, uint64_t) {
RefreshVisibility();
}
uint32_t ExternalKeyStatus(SDL_Keymod modifiers, bool character_valid) {
uint32_t status = 0x00000001 | (character_valid ? 0x00000002 : 0);
if ((modifiers & KMOD_LCTRL) != 0) status |= 0x00000100;
@@ -305,7 +485,7 @@ void CheckVulkanResult(VkResult result) {
} // namespace
void InitializeImeDialogInput() {
void InitializeImeInput() {
const Uint32 type = SDL_RegisterEvents(1);
EXIT_IF(type == static_cast<Uint32>(-1));
{
@@ -313,15 +493,14 @@ void InitializeImeDialogInput() {
g_input_lifecycle_active = true;
g_visibility_event.store(type, std::memory_order_release);
}
Ime::SetVisibilityCallback(OnVisibilityChanged);
Ime::HostSnapshot snapshot;
if (Ime::GetHostSnapshot(&snapshot)) {
OnVisibilityChanged(true, snapshot.generation);
}
CoreIme::SetVisibilityCallback(OnCoreVisibilityChanged);
DialogIme::SetVisibilityCallback(OnDialogVisibilityChanged);
RefreshVisibility();
}
void ShutdownImeDialogInput() {
Ime::SetVisibilityCallback(nullptr);
void ShutdownImeInput() {
CoreIme::SetVisibilityCallback(nullptr);
DialogIme::SetVisibilityCallback(nullptr);
{
std::scoped_lock lock(g_visibility_mutex);
g_input_lifecycle_active = false;
@@ -346,7 +525,13 @@ void ShutdownImeDialogInput() {
}
}
bool ProcessImeDialogInput(const SDL_Event& event) {
ImeVisualState GetImeVisualState() noexcept {
const auto core = CoreIme::GetVisualState();
const auto dialog = DialogIme::GetVisualState();
return {core.active || dialog.active, core.revision + dialog.revision};
}
bool ProcessImeInput(const SDL_Event& event) {
RetryVisibilityWakeup();
if (event.type == g_visibility_event.load(std::memory_order_acquire)) {
VisibilityUpdate update {};
@@ -465,7 +650,7 @@ bool ProcessImeDialogInput(const SDL_Event& event) {
}
}
struct ImeDialogOverlay::Impl {
struct ImeOverlay::Impl {
explicit Impl(GraphicContext& context): graphics(context) {}
~Impl() {
@@ -488,7 +673,7 @@ struct ImeDialogOverlay::Impl {
io.LogFilename = nullptr;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
io.BackendPlatformName = "Kyty ImeDialog input";
io.BackendPlatformName = "Kyty IME input";
ImGui::StyleColorsDark();
auto& style = ImGui::GetStyle();
style.WindowRounding = 10.0f;
@@ -680,7 +865,7 @@ struct ImeDialogOverlay::Impl {
ImGui::SetNextWindowSize({width, height}, ImGuiCond_Always);
constexpr ImGuiWindowFlags flags = ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoSavedSettings;
ImGui::Begin("##ImeDialog", nullptr, flags);
ImGui::Begin("##Ime", nullptr, flags);
const std::u16string title = snapshot.title.empty() ? u"Enter text" : snapshot.title;
if (!snapshot.key_panel_visible) {
@@ -856,20 +1041,19 @@ struct ImeDialogOverlay::Impl {
std::chrono::steady_clock::time_point last_frame;
};
ImeDialogOverlay::ImeDialogOverlay(GraphicContext& graphics)
: m_impl(std::make_unique<Impl>(graphics)) {}
ImeOverlay::ImeOverlay(GraphicContext& graphics): m_impl(std::make_unique<Impl>(graphics)) {}
ImeDialogOverlay::~ImeDialogOverlay() = default;
ImeOverlay::~ImeOverlay() = default;
bool ImeDialogOverlay::PrepareFrame(vk::Extent2D extent, vk::Format format, uint32_t image_count) {
bool ImeOverlay::PrepareFrame(vk::Extent2D extent, vk::Format format, uint32_t image_count) {
return m_impl->PrepareFrame(extent, format, image_count);
}
void ImeDialogOverlay::Record(vk::CommandBuffer command, vk::ImageView target) {
void ImeOverlay::Record(vk::CommandBuffer command, vk::ImageView target) {
m_impl->Record(command, target);
}
void ImeDialogOverlay::ReleaseVulkan() {
void ImeOverlay::ReleaseVulkan() {
m_impl->ReleaseVulkan();
}
+42
View File
@@ -0,0 +1,42 @@
#ifndef EMULATOR_SRC_GRAPHICS_PRESENTATION_IMEOVERLAY_H_
#define EMULATOR_SRC_GRAPHICS_PRESENTATION_IMEOVERLAY_H_
#include "common/common.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include <memory>
union SDL_Event;
namespace Libs::Graphics {
struct GraphicContext;
struct ImeVisualState {
bool active;
uint64_t revision;
};
void InitializeImeInput();
void ShutdownImeInput();
bool ProcessImeInput(const SDL_Event& event);
ImeVisualState GetImeVisualState() noexcept;
class ImeOverlay final {
public:
explicit ImeOverlay(GraphicContext& graphics);
~ImeOverlay();
KYTY_CLASS_NO_COPY(ImeOverlay);
[[nodiscard]] bool PrepareFrame(vk::Extent2D extent, vk::Format format, uint32_t image_count);
void Record(vk::CommandBuffer command, vk::ImageView target);
void ReleaseVulkan();
private:
struct Impl;
std::unique_ptr<Impl> m_impl;
};
} // namespace Libs::Graphics
#endif // EMULATOR_SRC_GRAPHICS_PRESENTATION_IMEOVERLAY_H_
+84 -35
View File
@@ -18,35 +18,57 @@ namespace Libs::Graphics {
namespace {
struct ControlInfo {
std::string_view name;
uint32_t button = 0;
Controller::Axis axis = Controller::Axis::AxisMax;
bool positive = false;
};
static constexpr std::array CONTROL_INFO = {
ControlInfo {"L3", Controller::PAD_BUTTON_L3},
ControlInfo {"R3", Controller::PAD_BUTTON_R3},
ControlInfo {"Options", Controller::PAD_BUTTON_OPTIONS},
ControlInfo {"Up", Controller::PAD_BUTTON_UP},
ControlInfo {"Right", Controller::PAD_BUTTON_RIGHT},
ControlInfo {"Down", Controller::PAD_BUTTON_DOWN},
ControlInfo {"Left", Controller::PAD_BUTTON_LEFT},
ControlInfo {"L2", Controller::PAD_BUTTON_L2},
ControlInfo {"R2", Controller::PAD_BUTTON_R2},
ControlInfo {"L1", Controller::PAD_BUTTON_L1},
ControlInfo {"R1", Controller::PAD_BUTTON_R1},
ControlInfo {"Triangle", Controller::PAD_BUTTON_TRIANGLE},
ControlInfo {"Circle", Controller::PAD_BUTTON_CIRCLE},
ControlInfo {"Cross", Controller::PAD_BUTTON_CROSS},
ControlInfo {"Square", Controller::PAD_BUTTON_SQUARE},
ControlInfo {"TouchPad", Controller::PAD_BUTTON_TOUCH_PAD},
ControlInfo {"LeftStickLeft", 0, Controller::Axis::LeftX, false},
ControlInfo {"LeftStickRight", 0, Controller::Axis::LeftX, true},
ControlInfo {"LeftStickUp", 0, Controller::Axis::LeftY, false},
ControlInfo {"LeftStickDown", 0, Controller::Axis::LeftY, true},
ControlInfo {"RightStickLeft", 0, Controller::Axis::RightX, false},
ControlInfo {"RightStickRight", 0, Controller::Axis::RightX, true},
ControlInfo {"RightStickUp", 0, Controller::Axis::RightY, false},
ControlInfo {"RightStickDown", 0, Controller::Axis::RightY, true},
};
constexpr std::size_t INVALID_CONTROL = CONTROL_INFO.size();
struct Binding {
SDL_Keycode key = SDLK_UNKNOWN;
uint8_t mouse_button = 0;
uint32_t pad_button = 0;
std::size_t control = INVALID_CONTROL;
};
static constexpr std::array PAD_BUTTONS = {
std::pair {std::string_view("L3"), Controller::PAD_BUTTON_L3},
std::pair {std::string_view("R3"), Controller::PAD_BUTTON_R3},
std::pair {std::string_view("Options"), Controller::PAD_BUTTON_OPTIONS},
std::pair {std::string_view("Up"), Controller::PAD_BUTTON_UP},
std::pair {std::string_view("Right"), Controller::PAD_BUTTON_RIGHT},
std::pair {std::string_view("Down"), Controller::PAD_BUTTON_DOWN},
std::pair {std::string_view("Left"), Controller::PAD_BUTTON_LEFT},
std::pair {std::string_view("L2"), Controller::PAD_BUTTON_L2},
std::pair {std::string_view("R2"), Controller::PAD_BUTTON_R2},
std::pair {std::string_view("L1"), Controller::PAD_BUTTON_L1},
std::pair {std::string_view("R1"), Controller::PAD_BUTTON_R1},
std::pair {std::string_view("Triangle"), Controller::PAD_BUTTON_TRIANGLE},
std::pair {std::string_view("Circle"), Controller::PAD_BUTTON_CIRCLE},
std::pair {std::string_view("Cross"), Controller::PAD_BUTTON_CROSS},
std::pair {std::string_view("Square"), Controller::PAD_BUTTON_SQUARE},
std::pair {std::string_view("TouchPad"), Controller::PAD_BUTTON_TOUCH_PAD},
};
std::size_t ControlFromName(std::string_view name) {
const auto info = std::find_if(CONTROL_INFO.begin(), CONTROL_INFO.end(),
[name](const auto& item) { return item.name == name; });
return static_cast<std::size_t>(std::distance(CONTROL_INFO.begin(), info));
}
uint32_t PadButtonFromName(std::string_view name) {
const auto button = std::find_if(PAD_BUTTONS.begin(), PAD_BUTTONS.end(),
[name](const auto& item) { return item.first == name; });
return button != PAD_BUTTONS.end() ? button->second : 0;
const ControlInfo& Info(std::size_t control) {
EXIT_IF(control >= CONTROL_INFO.size());
return CONTROL_INFO[control];
}
SDL_Keycode NormalizeKey(SDL_Keycode key) {
@@ -74,7 +96,7 @@ uint8_t MouseButtonFromName(std::string_view name) {
}
bool Conflicts(const Binding& first, const Binding& second) {
return first.pad_button == second.pad_button ||
return first.control == second.control ||
(first.key != SDLK_UNKNOWN && first.key == second.key) ||
(first.mouse_button != 0 && first.mouse_button == second.mouse_button);
}
@@ -88,7 +110,7 @@ public:
Binding binding;
if (split != std::string_view::npos) {
binding.pad_button = PadButtonFromName(entry.substr(0, split));
binding.control = ControlFromName(entry.substr(0, split));
const auto host_input = entry.substr(split + 1);
binding.mouse_button = MouseButtonFromName(host_input);
if (binding.mouse_button == 0) {
@@ -98,7 +120,7 @@ public:
const bool reserved =
binding.key == SDLK_ESCAPE || binding.key == SDLK_SPACE || binding.key == SDLK_F1;
if (binding.pad_button == 0 || reserved ||
if (binding.control == INVALID_CONTROL || reserved ||
(binding.key == SDLK_UNKNOWN && binding.mouse_button == 0)) {
EXIT("Invalid input mapping: %s\n", value.c_str());
}
@@ -107,19 +129,19 @@ public:
}
[[nodiscard]] bool Custom() const { return m_custom; }
[[nodiscard]] uint32_t FindKey(int key_code) const {
[[nodiscard]] std::size_t FindKey(int key_code) const {
key_code = NormalizeKey(static_cast<SDL_Keycode>(key_code));
const auto binding =
std::find_if(m_bindings.begin(), m_bindings.begin() + m_size,
[key_code](const auto& item) { return item.key == key_code; });
return binding != m_bindings.begin() + m_size ? binding->pad_button : 0;
return binding != m_bindings.begin() + m_size ? binding->control : INVALID_CONTROL;
}
[[nodiscard]] uint32_t FindMouseButton(uint8_t mouse_button) const {
[[nodiscard]] std::size_t FindMouseButton(uint8_t mouse_button) const {
const auto binding = std::find_if(
m_bindings.begin(), m_bindings.begin() + m_size,
[mouse_button](const auto& item) { return item.mouse_button == mouse_button; });
return binding != m_bindings.begin() + m_size ? binding->pad_button : 0;
return binding != m_bindings.begin() + m_size ? binding->control : INVALID_CONTROL;
}
private:
@@ -135,9 +157,9 @@ private:
m_bindings[m_size++] = binding;
}
std::array<Binding, PAD_BUTTONS.size()> m_bindings {};
std::size_t m_size = 0;
bool m_custom;
std::array<Binding, CONTROL_INFO.size()> m_bindings {};
std::size_t m_size = 0;
bool m_custom;
};
const InputMap& GetInputMap() {
@@ -191,6 +213,33 @@ void SetStickAxis(Controller::Axis axis, bool negative, bool positive) {
Controller::ControllerAxis(Controller::HOST_INPUT_CONTROLLER_ID, axis, value);
}
void SetControl(std::size_t control, bool down) {
if (control == INVALID_CONTROL) {
return;
}
const auto& info = Info(control);
if (info.button != 0) {
SetButton(info.button, down);
return;
}
struct AxisKeys {
bool negative = false;
bool positive = false;
};
static std::array<AxisKeys, 4> axes;
const auto axis = static_cast<std::size_t>(info.axis);
EXIT_IF(axis >= axes.size());
if (info.positive) {
axes[axis].positive = down;
} else {
axes[axis].negative = down;
}
SetStickAxis(info.axis, axes[axis].negative, axes[axis].positive);
}
void DefaultKeyboardInput(int key_code, bool down) {
static StickKeys left;
static StickKeys right;
@@ -240,7 +289,7 @@ void HostInputInit() {
void HostInputKey(int key_code, bool down) {
const auto& map = GetInputMap();
if (map.Custom()) {
SetButton(map.FindKey(key_code), down);
SetControl(map.FindKey(key_code), down);
} else {
DefaultKeyboardInput(key_code, down);
}
@@ -249,7 +298,7 @@ void HostInputKey(int key_code, bool down) {
void HostInputMouseButton(uint8_t mouse_button, bool down) {
const auto& map = GetInputMap();
if (map.Custom() && mouse_button != 0) {
SetButton(map.FindMouseButton(mouse_button), down);
SetControl(map.FindMouseButton(mouse_button), down);
}
}
+15 -16
View File
@@ -31,13 +31,12 @@
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vma.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/presentation/imeDialogOverlay.h"
#include "graphics/presentation/imeOverlay.h"
#include "graphics/presentation/presenter.h"
#include "graphics/presentation/renderDoc.h"
#include "graphics/presentation/videoOut.h"
#include "graphics/presentation/window/windowInternal.h"
#include "libs/controller.h"
#include "libs/imeDialog.h"
#include "loader/systemContent.h"
#include <algorithm>
@@ -345,17 +344,17 @@ private:
void Destroy();
void RefreshSurfaceSize();
WindowContext& m_window;
vk::SwapchainKHR m_handle = nullptr;
vk::Format m_format = vk::Format::eUndefined;
vk::Extent2D m_extent {};
std::vector<vk::Image> m_images;
std::vector<vk::ImageView> m_image_views;
std::vector<vk::Semaphore> m_image_acquired;
std::vector<vk::Semaphore> m_render_complete;
std::unique_ptr<ImeDialogOverlay> m_ime_overlay;
uint32_t m_image_index = static_cast<uint32_t>(-1);
uint32_t m_frame_index = 0;
WindowContext& m_window;
vk::SwapchainKHR m_handle = nullptr;
vk::Format m_format = vk::Format::eUndefined;
vk::Extent2D m_extent {};
std::vector<vk::Image> m_images;
std::vector<vk::ImageView> m_image_views;
std::vector<vk::Semaphore> m_image_acquired;
std::vector<vk::Semaphore> m_render_complete;
std::unique_ptr<ImeOverlay> m_ime_overlay;
uint32_t m_image_index = static_cast<uint32_t>(-1);
uint32_t m_frame_index = 0;
};
struct Presenter::Impl {
@@ -618,7 +617,7 @@ Swapchain::Status Swapchain::AcquireNextImage() {
bool Swapchain::PrepareImeOverlay() {
if (m_ime_overlay == nullptr) {
m_ime_overlay = std::make_unique<ImeDialogOverlay>(m_window.graphic_ctx);
m_ime_overlay = std::make_unique<ImeOverlay>(m_window.graphic_ctx);
}
return m_ime_overlay->PrepareFrame(m_extent, m_format, ImageCount());
}
@@ -807,7 +806,7 @@ bool Presenter::IsGuestPaused() const noexcept {
}
bool Presenter::NeedsImeRefresh() const noexcept {
const auto visual = Dialog::ImeDialog::GetVisualState();
const auto visual = GetImeVisualState();
return visual.active ||
visual.revision != m_impl->presented_ime_revision.load(std::memory_order_acquire);
}
@@ -843,7 +842,7 @@ void Presenter::Present(Frame& frame, bool reuse) {
m_impl->RecoverSwapchain(Swapchain::Status::Recreate);
}
const auto ime_visual = Dialog::ImeDialog::GetVisualState();
const auto ime_visual = GetImeVisualState();
auto& swapchain = m_impl->swapchain;
for (uint32_t attempt = 0; attempt < 2; attempt++) {
auto status = swapchain.AcquireNextImage();
@@ -31,7 +31,7 @@
#include "graphics/host_gpu/renderer/renderContext.h"
#include "graphics/host_gpu/vma.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/presentation/imeDialogOverlay.h"
#include "graphics/presentation/imeOverlay.h"
#include "graphics/presentation/presenter.h"
#include "graphics/presentation/renderDoc.h"
#include "graphics/presentation/videoOut.h"
@@ -607,10 +607,6 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V
create_info.flags = {};
create_info.pQueueCreateInfos = &queue_create_info;
create_info.queueCreateInfoCount = 1;
create_info.enabledLayerCount =
(r.enable_validation_layers ? static_cast<uint32_t>(r.required_layers.size()) : 0);
create_info.ppEnabledLayerNames =
(r.enable_validation_layers ? r.required_layers.data() : nullptr);
create_info.enabledExtensionCount = static_cast<uint32_t>(device_extensions.size());
create_info.ppEnabledExtensionNames = device_extensions.data();
create_info.pEnabledFeatures = &device_features;
@@ -1015,7 +1011,7 @@ void WindowContext::RecreateSurface() {
}
WindowContext::~WindowContext() {
ShutdownImeDialogInput();
ShutdownImeInput();
presenter.reset();
LibKernel::Memory::InstallGpuResources(nullptr);
render_context.reset();
+8 -16
View File
@@ -31,7 +31,7 @@
#include "graphics/host_gpu/renderer/render.h"
#include "graphics/host_gpu/vma.h"
#include "graphics/host_gpu/vulkanCommon.h"
#include "graphics/presentation/imeDialogOverlay.h"
#include "graphics/presentation/imeOverlay.h"
#include "graphics/presentation/renderDoc.h"
#include "graphics/presentation/window/hostInput.h"
#include "graphics/presentation/window/windowInternal.h"
@@ -477,7 +477,7 @@ void WindowContext::ProcessEvent(double time_s) {
auto& game = loop;
auto* event = &game.event;
EXIT_IF(SDL_GetEventState(SDL_DISPLAYEVENT) != SDL_ENABLE);
if (ProcessImeDialogInput(*event)) {
if (ProcessImeInput(*event)) {
return;
}
@@ -783,7 +783,7 @@ static void WindowCreate(WindowContext& context) {
EXIT("%s\n", SDL_GetError());
}
HostInputInit();
InitializeImeDialogInput();
InitializeImeInput();
LOGF("WindowCreate(): width = %d, height = %d\n", width, height);
@@ -938,14 +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();
frame_num++;
@@ -957,11 +949,11 @@ void WindowContext::UpdateTitle() {
fps_frames = 0;
}
auto fps = fmt::format("[{} | {}] {}{}{}{}{}{}[{}] [{}], frame: {}, fps: {:f}", build_label,
build_type, (has_title ? title : ""), (has_title ? ", " : ""),
(has_title_id ? title_id : ""), (has_title_id ? ", " : ""),
(has_app_ver ? app_ver : ""), (has_app_ver ? " " : ""), device_name,
processor_name, frame_num, current_fps);
auto fps = fmt::format(
"[{} | {}] {}{}{}{}{}{}[{}] [{}], frame: {}, fps: {:f}", 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, processor_name, frame_num, current_fps);
#if defined(__APPLE__)
// AppKit traps on title changes off the main thread; fire-and-forget keeps present pacing.
+14
View File
@@ -4420,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();
@@ -4528,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();
+2
View File
@@ -269,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
+4 -5
View File
@@ -21,11 +21,10 @@
namespace {
constexpr char FILE_NAME[] = "compatibility_db.json";
constexpr char URL[] =
"https://github.com/Nmzik/KytyPS5/releases/download/compat-db/compatibility_db.json";
constexpr int RETRY_COUNT = 3;
constexpr int RETRY_DELAY_MS = 750;
constexpr char FILE_NAME[] = "compatibility_db.json";
constexpr char URL[] = "https://kytyps5.github.io/data/compatibility.json";
constexpr int RETRY_COUNT = 3;
constexpr int RETRY_DELAY_MS = 750;
struct LoadResult {
QMap<QString, CompatibilityEntry> entries;
+19 -13
View File
@@ -18,17 +18,25 @@ namespace {
constexpr int BINDING_COLUMN = 1;
struct PadButton {
struct PadControl {
const char* id;
const char* label;
const char* default_binding;
};
constexpr PadButton PAD_BUTTONS[] = {
constexpr PadControl PAD_CONTROLS[] = {
{"Up", "D-pad Up", "Up"},
{"Down", "D-pad Down", "Down"},
{"Left", "D-pad Left", "Left"},
{"Right", "D-pad Right", "Right"},
{"LeftStickUp", "Left stick Up", "W"},
{"LeftStickDown", "Left stick Down", "S"},
{"LeftStickLeft", "Left stick Left", "A"},
{"LeftStickRight", "Left stick Right", "D"},
{"RightStickUp", "Right stick Up", "T"},
{"RightStickDown", "Right stick Down", "G"},
{"RightStickLeft", "Right stick Left", "F"},
{"RightStickRight", "Right stick Right", "H"},
{"Triangle", "Triangle", "I"},
{"Circle", "Circle", "L"},
{"Cross", "Cross", "J"},
@@ -190,16 +198,14 @@ QHash<QString, QString> ParseMapping(const QStringList& mapping) {
InputMappingDialog::InputMappingDialog(const QStringList& mapping, QWidget* parent)
: QDialog(parent), m_customized(!mapping.isEmpty()) {
setWindowTitle(tr("Input Mapping"));
resize(440, 520);
resize(460, 650);
auto* layout = new QVBoxLayout(this);
layout->addWidget(new QLabel(tr("Map keyboard or mouse input to DualSense buttons.\n"
"Defaults: left stick W/A/S/D, right stick T/F/G/H."),
this));
layout->addWidget(new QLabel(tr("Map keyboard or mouse input to DualSense controls."), this));
m_bindings = new QTreeWidget(this);
m_bindings->setColumnCount(2);
m_bindings->setHeaderLabels({tr("DualSense button"), tr("Host input")});
m_bindings->setHeaderLabels({tr("DualSense control"), tr("Host input")});
m_bindings->setRootIsDecorated(false);
m_bindings->setSelectionMode(QAbstractItemView::SingleSelection);
m_bindings->header()->setSectionResizeMode(0, QHeaderView::Stretch);
@@ -207,12 +213,12 @@ InputMappingDialog::InputMappingDialog(const QStringList& mapping, QWidget* pare
layout->addWidget(m_bindings);
const auto parsed = ParseMapping(mapping);
for (const auto& button: PAD_BUTTONS) {
for (const auto& control: PAD_CONTROLS) {
auto* item = new QTreeWidgetItem(m_bindings);
item->setText(0, tr(button.label));
item->setData(0, Qt::UserRole, QString::fromLatin1(button.id));
SetBinding(item, m_customized ? parsed.value(QString::fromLatin1(button.id))
: QString::fromLatin1(button.default_binding));
item->setText(0, tr(control.label));
item->setData(0, Qt::UserRole, QString::fromLatin1(control.id));
SetBinding(item, m_customized ? parsed.value(QString::fromLatin1(control.id))
: QString::fromLatin1(control.default_binding));
}
m_bindings->setCurrentItem(m_bindings->topLevelItem(0));
@@ -286,7 +292,7 @@ void InputMappingDialog::ClearBinding() {
void InputMappingDialog::RestoreDefaults() {
for (int index = 0; index < m_bindings->topLevelItemCount(); index++) {
SetBinding(m_bindings->topLevelItem(index),
QString::fromLatin1(PAD_BUTTONS[index].default_binding));
QString::fromLatin1(PAD_CONTROLS[index].default_binding));
}
m_customized = false;
}
+91 -1
View File
@@ -26,6 +26,7 @@
#include <algorithm>
#include <atomic>
#include <cmath>
#include <cstddef>
#include <cstring>
#include <mutex>
@@ -1180,6 +1181,95 @@ int KYTY_SYSV_ABI GraphicsUpdatePrimState(ShaderRegister* cx_regs, ShaderRegiste
return OK;
}
struct GsOccupancyLimits {
uint32_t vertex;
uint32_t export_count;
};
static GsOccupancyLimits get_gs_occupancy_limits(const Shader* gs, uint32_t vertex_capacity,
uint32_t export_capacity) {
const auto* onchip =
find_shader_register(gs->cx_registers, gs->num_cx_registers, Pm4::VGT_GS_ONCHIP_CNTL);
const auto* subgroup =
find_shader_register(gs->cx_registers, gs->num_cx_registers, Pm4::GE_NGG_SUBGRP_CNTL);
const auto* vs_out =
find_shader_register(gs->cx_registers, gs->num_cx_registers, Pm4::SPI_VS_OUT_CONFIG);
const auto* cl_out =
find_shader_register(gs->cx_registers, gs->num_cx_registers, Pm4::PA_CL_VS_OUT_CNTL);
const auto* max_output = find_shader_register(gs->cx_registers, gs->num_cx_registers,
Pm4::GE_MAX_OUTPUT_PER_SUBGROUP);
const uint32_t output_words = ((max_output->value & 0x3ffu) + 31u) >> 5u;
const uint32_t subgroup_work =
((((onchip->value >> 11u) & 0x7ffu) * (subgroup->value & 0x1ffu)) + 31u) >> 5u;
uint32_t waves = std::max(subgroup_work, output_words);
if ((gs->specials->vgt_shader_stages_en.value & 0x00400000u) == 0) {
waves >>= 1u;
}
waves = std::max(waves, 1u);
const uint32_t exports = 1u + ((cl_out->value >> 21u) & 1u) + ((cl_out->value >> 22u) & 1u) +
((cl_out->value >> 23u) & 1u);
const uint32_t export_limit = (export_capacity / exports) * 4u;
const uint32_t vertex_limit = (vs_out->value & 0x80u) != 0
? 2048u
: vertex_capacity / (((vs_out->value >> 2u) & 0xfu) + 1u);
return {waves * (vertex_limit / output_words), waves * (export_limit / output_words)};
}
int KYTY_SYSV_ABI GraphicsGetGsOversubscription(ShaderRegister* regs, const Shader* gs,
uint32_t budget, float factor) {
PRINT_NAME();
constexpr uint32_t FULL_PC_OVERSUBSCRIPTION = 0x7ffu;
constexpr uint32_t FULL_SH_OVERSUBSCRIPTION = 0x007f0000u;
regs[0] = {Pm4::GE_PC_ALLOC, 0u};
regs[1] = {Pm4::SPI_SHADER_PGM_RSRC4_GS, 0u};
if (budget == 0u) {
return OK;
}
if (budget == UINT32_MAX) {
regs[0].value |= FULL_PC_OVERSUBSCRIPTION;
regs[1].value |= FULL_SH_OVERSUBSCRIPTION;
return OK;
}
const auto base = get_gs_occupancy_limits(gs, 1024u, 128u);
const auto expanded = get_gs_occupancy_limits(gs, 2048u, 382u);
const auto base_min = std::min(base.vertex, base.export_count);
const auto limit_shift =
(gs->specials->vgt_shader_stages_en.value & 0x00400000u) != 0 ? 5u : 6u;
const auto expanded_limit =
std::min({expanded.vertex, expanded.export_count, budget >> limit_shift, 1024u});
const auto headroom = expanded_limit > base_min ? expanded_limit - base_min : 0u;
const auto target = static_cast<uint32_t>(
std::fma(factor, static_cast<float>(headroom), static_cast<float>(base_min)));
if (target <= base_min) {
return OK;
}
if (target < base.export_count) {
const auto range = std::max(expanded.vertex - base.vertex, 1u);
auto value = static_cast<uint32_t>(
std::min<uint64_t>((static_cast<uint64_t>(target - base.vertex) << 10u) / range, 1024));
value = std::max(value, 1u);
regs[0].value = (regs[0].value & ~FULL_PC_OVERSUBSCRIPTION) |
(((value << 1u) - 1u) & FULL_PC_OVERSUBSCRIPTION);
regs[1].value |= FULL_SH_OVERSUBSCRIPTION;
} else {
const auto range = std::max(expanded.export_count - base.export_count, 1u);
const auto value = static_cast<uint32_t>(
std::min<uint64_t>((static_cast<uint64_t>(target - base.export_count) * 127u) / range, 127));
regs[0].value |= FULL_PC_OVERSUBSCRIPTION;
regs[1].value = (regs[1].value & ~FULL_SH_OVERSUBSCRIPTION) | (value << 16u);
}
return OK;
}
static uint32_t shader_semantic_word(const ShaderSemantic& semantic) {
return ((semantic.semantic & 0xffu) << 0u) | ((semantic.hardware_mapping & 0xffu) << 8u) |
((semantic.size_in_elements & 0xfu) << 16u) | ((semantic.is_f16 & 0x3u) << 20u) |
@@ -1440,7 +1530,7 @@ int KYTY_SYSV_ABI GraphicsSuspendPoint() {
PRINT_NAME();
EXIT_IF(g_renderer == nullptr);
g_renderer->GetGpu().SuspendPoint();
g_renderer->GetGpu().Done();
return OK;
}
+2
View File
@@ -56,6 +56,8 @@ int KYTY_SYSV_ABI GraphicsCreatePrimState(ShaderRegister* cx_regs, ShaderRegis
const Shader* hs, const Shader* gs, uint32_t prim_type);
int KYTY_SYSV_ABI GraphicsUpdatePrimState(ShaderRegister* cx_regs, ShaderRegister* uc_regs,
uint32_t prim_type);
int KYTY_SYSV_ABI GraphicsGetGsOversubscription(ShaderRegister* regs, const Shader* gs,
uint32_t budget, float factor);
int KYTY_SYSV_ABI GraphicsCreateInterpolantMapping(ShaderRegister* regs, const Shader* gs,
const Shader* ps);
int KYTY_SYSV_ABI GraphicsGetDataPacketPayloadAddress(uint32_t** addr, uint32_t* cmd, int type);
+659 -240
View File
File diff suppressed because it is too large Load Diff
+17 -4
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();
@@ -355,6 +356,18 @@ int KYTY_SYSV_ABI PadInit() {
return OK;
}
static bool PadOpenArgsAreValid(int user_id, int type, int index) {
constexpr int user_id_initial = 1000;
constexpr int user_id_system = 0xff;
constexpr int port_type_standard = 0;
constexpr int port_type_special = 2;
constexpr int port_type_remote = 16;
const bool personal_port =
user_id == user_id_initial && (type == port_type_standard || type == port_type_special);
const bool system_remote_control = user_id == user_id_system && type == port_type_remote;
return index == 0 && (personal_port || system_remote_control);
}
int KYTY_SYSV_ABI PadOpen(int user_id, int type, int index, const void* param) {
PRINT_NAME();
@@ -366,7 +379,7 @@ int KYTY_SYSV_ABI PadOpen(int user_id, int type, int index, const void* param) {
constexpr int pad_error_invalid_arg = -2137915391; /* 0x80920001 */
if (user_id != 1000 || (type != 0 && type != 2 && type != 16) || index != 0) {
if (!PadOpenArgsAreValid(user_id, type, index)) {
return pad_error_invalid_arg;
}
@@ -385,7 +398,7 @@ int KYTY_SYSV_ABI PadGetHandle(int user_id, int type, int index) {
constexpr int pad_error_device_no_handle = -2137915384; /* 0x80920008 */
if (user_id != 1000 || (type != 0 && type != 2 && type != 16) || index != 0) {
if (!PadOpenArgsAreValid(user_id, type, index)) {
return pad_error_device_no_handle;
}
+1020
View File
File diff suppressed because it is too large Load Diff
+245
View File
@@ -0,0 +1,245 @@
#ifndef EMULATOR_INCLUDE_EMULATOR_LIBS_IME_H_
#define EMULATOR_INCLUDE_EMULATOR_LIBS_IME_H_
#include "common/abi.h"
#include <cstddef>
#include <cstdint>
#include <string>
#include <string_view>
namespace Libs::Ime {
constexpr uint32_t WORK_BUFFER_SIZE = 20 * 1024;
constexpr uint32_t MAX_TEXT_LENGTH = 2048;
enum class Type : uint32_t { Default = 0, BasicLatin = 1, Url = 2, Mail = 3, Number = 4 };
enum class EnterLabel : uint32_t { Default = 0, Send = 1, Search = 2, Go = 3 };
enum class Alignment : uint32_t { Start = 0, Center = 1, End = 2 };
struct Event;
struct Keycode;
using TextFilter = int32_t(KYTY_SYSV_ABI*)(char16_t* out_text, uint32_t* out_text_length,
const char16_t* source_text,
uint32_t source_text_length);
using EventHandler = void(KYTY_SYSV_ABI*)(void* arg, const Event* event);
using ExtKeyboardFilter = int(KYTY_SYSV_ABI*)(const Keycode* source_keycode, uint16_t* out_keycode,
uint32_t* out_status, void* reserved);
struct Color {
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
};
struct Caret {
float x;
float y;
uint32_t height;
uint32_t index;
};
struct TextGeometry {
float x;
float y;
uint32_t width;
uint32_t height;
};
struct Rect {
float x;
float y;
uint32_t width;
uint32_t height;
};
enum class TextAreaMode : uint32_t { Disable = 0, Edit = 1, Preedit = 2, Select = 3 };
struct TextAreaProperty {
TextAreaMode mode;
uint32_t index;
int32_t length;
};
struct EditText {
char16_t* str;
uint32_t caret_index;
uint32_t area_num;
TextAreaProperty text_area[4];
};
struct KeyboardResourceIdArray {
int32_t user_id;
uint32_t resource_id[5];
};
struct Keycode {
uint16_t keycode;
char16_t character;
uint32_t status;
uint32_t type;
int32_t user_id;
uint32_t resource_id;
uint64_t timestamp;
};
union EventParam {
Rect rect;
EditText text;
uint32_t caret_move;
Keycode keycode;
KeyboardResourceIdArray resource_id_array;
uint64_t reserved[8];
};
struct Event {
uint32_t id;
EventParam param;
};
struct Param {
int32_t user_id;
Type type;
uint64_t supported_languages;
EnterLabel enter_label;
uint32_t input_method;
TextFilter filter;
uint32_t option;
uint32_t max_text_length;
char16_t* input_text_buffer;
float posx;
float posy;
Alignment horizontal_alignment;
Alignment vertical_alignment;
void* work;
void* arg;
EventHandler handler;
int8_t reserved[8];
};
struct ExtendedParam {
uint32_t option;
Color color_base;
Color color_line;
Color color_text_field;
Color color_preedit;
Color color_button_default;
Color color_button_function;
Color color_button_symbol;
Color color_text;
Color color_special;
uint32_t priority;
const char* additional_dictionary_path;
ExtKeyboardFilter ext_keyboard_filter;
uint32_t disable_device;
uint32_t ext_keyboard_mode;
int8_t reserved[60];
};
struct KeyboardParam {
uint32_t option;
int8_t reserved1[4];
void* arg;
EventHandler handler;
int8_t reserved2[8];
};
struct KeyboardInfo {
int32_t user_id;
uint32_t device;
uint32_t type;
uint32_t repeat_delay;
uint32_t repeat_rate;
uint32_t status;
int8_t reserved[12];
};
enum class ExternalAction : uint8_t {
None,
Text,
Backspace,
MoveLeft,
MoveRight,
Cancel,
Accept,
Newline,
};
struct ExternalInput {
Keycode key;
ExternalAction action;
std::u16string text;
};
static_assert(sizeof(Caret) == 0x10);
static_assert(sizeof(TextGeometry) == 0x10);
static_assert(sizeof(TextAreaProperty) == 0x0c);
static_assert(sizeof(EditText) == 0x40);
static_assert(sizeof(EventParam) == 0x40);
static_assert(alignof(EventParam) == 0x08);
static_assert(sizeof(Event) == 0x48);
static_assert(offsetof(Event, param) == 0x08);
static_assert(sizeof(Param) == 0x60);
static_assert(offsetof(Param, input_text_buffer) == 0x28);
static_assert(offsetof(Param, work) == 0x40);
static_assert(offsetof(Param, handler) == 0x50);
static_assert(sizeof(ExtendedParam) == 0x88);
static_assert(offsetof(ExtendedParam, additional_dictionary_path) == 0x30);
static_assert(sizeof(KeyboardParam) == 0x20);
static_assert(sizeof(KeyboardResourceIdArray) == 0x18);
static_assert(sizeof(KeyboardInfo) == 0x24);
static_assert(sizeof(Keycode) == 0x20);
struct VisualState {
bool active;
uint64_t revision;
};
struct HostSnapshot {
uint64_t generation;
Type type;
EnterLabel enter_label;
uint32_t option;
uint32_t max_text_length;
uint32_t cursor;
uint32_t disable_device;
bool key_panel_visible;
float posx;
float posy;
Alignment horizontal_alignment;
Alignment vertical_alignment;
uint32_t panel_width;
uint32_t panel_height;
std::u16string text;
};
using VisibilityCallback = void (*)(bool visible, uint64_t generation);
void KYTY_SYSV_ABI ImeParamInit(Param* param);
int KYTY_SYSV_ABI ImeGetPanelSize(const Param* param, uint32_t* width, uint32_t* height);
int KYTY_SYSV_ABI ImeOpen(const Param* param, const ExtendedParam* extended);
int KYTY_SYSV_ABI ImeUpdate(EventHandler handler);
int KYTY_SYSV_ABI ImeClose();
int KYTY_SYSV_ABI ImeSetText(const char16_t* text, uint32_t length);
int KYTY_SYSV_ABI ImeSetCaret(const Caret* caret);
int KYTY_SYSV_ABI ImeSetTextGeometry(TextAreaMode mode, const TextGeometry* geometry);
int KYTY_SYSV_ABI ImeKeyboardOpen(int32_t user_id, const KeyboardParam* param);
int KYTY_SYSV_ABI ImeKeyboardClose(int32_t user_id);
int KYTY_SYSV_ABI ImeKeyboardGetResourceId(int32_t user_id, KeyboardResourceIdArray* resource_ids);
int KYTY_SYSV_ABI ImeKeyboardGetInfo(uint32_t resource_id, KeyboardInfo* info);
int KYTY_SYSV_ABI ImeKeyboardSetMode(int32_t user_id, uint32_t mode);
VisualState GetVisualState() noexcept;
void SetVisibilityCallback(VisibilityCallback callback) noexcept;
bool GetHostSnapshot(HostSnapshot* snapshot);
bool HostInsertText(uint64_t generation, std::u16string_view text);
bool HostBackspace(uint64_t generation);
bool HostAccept(uint64_t generation);
bool HostCancel(uint64_t generation);
bool HostQueueExternalInput(uint64_t generation, ExternalInput input);
} // namespace Libs::Ime
#endif // EMULATOR_INCLUDE_EMULATOR_LIBS_IME_H_
+1
View File
@@ -171,6 +171,7 @@ LIB_DEFINE(InitGraphicsDriver_1) {
LIB_FUNC("vRoArM9zaIk", Gen5::GraphicsSetUcRegIndirectPatchAddRegisters);
LIB_FUNC("D9sr1xGUriE", Gen5::GraphicsCreatePrimState);
LIB_FUNC("Y3ymLfZ1384", Gen5::GraphicsUpdatePrimState);
LIB_FUNC("NKIzURsgV7I", Gen5::GraphicsGetGsOversubscription);
LIB_FUNC("HV4j+E0MBHE", Gen5::GraphicsCreateInterpolantMapping);
LIB_FUNC("V++UgBtQhn0", Gen5::GraphicsGetDataPacketPayloadAddress);
LIB_FUNC("s+VGAMDQ0AQ", Gen5::GraphicsGetDataPacketPayloadRange);
+23 -10
View File
@@ -167,6 +167,19 @@ static JsonObject* JsonObjectNew() {
return object;
}
static void JsonValueSetEmptyType(JsonValue* self, uint32_t type) {
if (self == nullptr) {
return;
}
self->type = type;
switch (type) {
case JsonValueTypeString: self->string = JsonStringNew(""); break;
case JsonValueTypeArray: self->array = JsonArrayNew(); break;
case JsonValueTypeObject: self->object = JsonObjectNew(); break;
default: self->uinteger = 0; break;
}
}
static void JsonValueDelete(JsonValue* self) {
if (self != nullptr) {
JsonValueClear(self);
@@ -497,6 +510,14 @@ static void* KYTY_SYSV_ABI JsonValueCtor(void* self) {
return self;
}
static JsonValue* KYTY_SYSV_ABI JsonValueTypeCtor(JsonValue* self, uint32_t type) {
PRINT_NAME();
JsonValueInit(self);
JsonValueSetEmptyType(self, type);
return self;
}
static JsonValue* KYTY_SYSV_ABI JsonValueBoolCtor(JsonValue* self, bool value) {
PRINT_NAME();
@@ -637,16 +658,7 @@ static void KYTY_SYSV_ABI JsonValueSetType(JsonValue* self, uint32_t type) {
PRINT_NAME();
JsonValueClear(self);
if (self == nullptr) {
return;
}
self->type = type;
switch (type) {
case JsonValueTypeString: self->string = JsonStringNew(""); break;
case JsonValueTypeArray: self->array = JsonArrayNew(); break;
case JsonValueTypeObject: self->object = JsonObjectNew(); break;
default: self->uinteger = 0; break;
}
JsonValueSetEmptyType(self, type);
}
static uint32_t KYTY_SYSV_ABI JsonValueGetType(const JsonValue* self) {
@@ -939,6 +951,7 @@ LIB_DEFINE(InitNet_1_Json2) {
LIB_FUNC("OcAgPxcq5Vk", LibJson2::JsonMemAllocatorDtor);
LIB_FUNC("qBMjqyBn3OM", LibJson2::JsonValueCtor);
LIB_FUNC("-wa17B7TGnw", LibJson2::JsonValueCtor);
LIB_FUNC("CbrT3dwDILo", LibJson2::JsonValueTypeCtor);
LIB_FUNC("WTtYf+cNnXI", LibJson2::JsonValueDtor);
LIB_FUNC("0eUrW9JAxM0", LibJson2::JsonValueDtor);
LIB_FUNC("S5JxQnoGF3E", LibJson2::JsonParserParse);
+2 -2
View File
@@ -2172,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);
@@ -3211,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);
@@ -3227,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);
-72
View File
@@ -3804,77 +3804,6 @@ LIB_DEFINE(InitPlatform_1_Random) {
} // namespace LibRandom
namespace LibIme {
LIB_VERSION("Ime", 1, "Ime", 1, 1);
static int KYTY_SYSV_ABI ImeKeyboardOpen(int user_id, const void* param) {
PRINT_NAME();
LOGF("\t user_id = %d\n"
"\t param = 0x%016" PRIx64 "\n",
user_id, reinterpret_cast<uint64_t>(param));
return 0;
}
static int KYTY_SYSV_ABI ImeKeyboardClose(int user_id) {
PRINT_NAME();
LOGF("\t user_id = %d\n", user_id);
return 0;
}
static int KYTY_SYSV_ABI ImeKeyboardGetResourceId(int user_id, void* resource_id_array) {
PRINT_NAME();
LOGF("\t user_id = %d\n"
"\t resource_id_array = 0x%016" PRIx64 "\n",
user_id, reinterpret_cast<uint64_t>(resource_id_array));
return 0;
}
static int KYTY_SYSV_ABI ImeKeyboardGetInfo(uint32_t resource_id, void* info) {
PRINT_NAME();
LOGF("\t resource_id = 0x%08" PRIx32 "\n"
"\t info = 0x%016" PRIx64 "\n",
resource_id, reinterpret_cast<uint64_t>(info));
return 0;
}
static int KYTY_SYSV_ABI ImeKeyboardSetMode(int user_id, uint32_t mode) {
PRINT_NAME();
LOGF("\t user_id = %d\n"
"\t mode = 0x%08" PRIx32 "\n",
user_id, mode);
return 0;
}
static int KYTY_SYSV_ABI ImeUpdate(void* handler) {
PRINT_NAME();
// LOGF("\t handler = 0x%016" PRIx64 "\n", reinterpret_cast<uint64_t>(handler));
return 0;
}
LIB_DEFINE(InitPlatform_1_Ime) {
LIB_FUNC("eaFXjfJv3xs", LibIme::ImeKeyboardOpen);
LIB_FUNC("PMVehSlfZ94", LibIme::ImeKeyboardClose);
LIB_FUNC("dKadqZFgKKQ", LibIme::ImeKeyboardGetResourceId);
LIB_FUNC("VkqLPArfFdc", LibIme::ImeKeyboardGetInfo);
LIB_FUNC("ua+13Hk9kKs", LibIme::ImeKeyboardSetMode);
LIB_FUNC("-4GCfYdNF1s", LibIme::ImeUpdate);
}
} // namespace LibIme
namespace LibRemoteplay {
LIB_VERSION("Remoteplay", 1, "Remoteplay", 1, 1);
@@ -3994,7 +3923,6 @@ LIB_DEFINE(InitPlatform_1_SharePlay) {
LIB_DEFINE(InitPlatform_1) {
LibRandom::InitPlatform_1_Random(s);
LibIme::InitPlatform_1_Ime(s);
LibRemoteplay::InitPlatform_1_Remoteplay(s);
LibWebBrowserDialog::InitPlatform_1_WebBrowserDialog(s);
LibGameLiveStreaming::InitPlatform_1_GameLiveStreaming(s);
+8 -1
View File
@@ -23,6 +23,7 @@ namespace SystemService {
[[maybe_unused]] constexpr int PARAM_ID_SUMMERTIME = 5;
[[maybe_unused]] constexpr int PARAM_ID_SYSTEM_NAME = 6;
[[maybe_unused]] constexpr int PARAM_ID_GAME_PARENTAL_LEVEL = 7;
[[maybe_unused]] constexpr int PARAM_ID_CC_ENABLE = 100;
[[maybe_unused]] constexpr int PARAM_ID_SCREEN_READER = 208;
[[maybe_unused]] constexpr int PARAM_ID_ENTER_BUTTON_ASSIGN = 1000;
@@ -33,6 +34,9 @@ namespace SystemService {
[[maybe_unused]] constexpr int PARAM_TIME_FORMAT_12HOUR = 0;
[[maybe_unused]] constexpr int PARAM_TIME_FORMAT_24HOUR = 1;
[[maybe_unused]] constexpr int PARAM_CC_DISABLED = 0;
[[maybe_unused]] constexpr int PARAM_CC_ENABLED = 1;
[[maybe_unused]] constexpr int MAX_SYSTEM_NAME_LENGTH = 65;
[[maybe_unused]] constexpr int PARAM_GAME_PARENTAL_OFF = 0;
@@ -97,9 +101,12 @@ static int KYTY_SYSV_ABI SystemServiceParamGetInt(int param_id, int* value) {
case PARAM_ID_TIME_ZONE: v = +180; break;
case PARAM_ID_SUMMERTIME: v = 0; break;
case PARAM_ID_GAME_PARENTAL_LEVEL: v = PARAM_GAME_PARENTAL_OFF; break;
case PARAM_ID_CC_ENABLE: v = PARAM_CC_DISABLED; break;
case PARAM_ID_SCREEN_READER: v = 0; break;
case PARAM_ID_ENTER_BUTTON_ASSIGN: v = PARAM_ENTER_BUTTON_ASSIGN_CROSS; break;
default: EXIT("unknown param_id: %d\n", param_id);
default:
LOGF_COLOR(Log::Color::Yellow, " unsupported param_id %d, using 0\n", param_id);
break;
}
LOGF(" %d = %d\n", param_id, v);
+5
View File
@@ -34,6 +34,10 @@ namespace LibKeyboard {
LIB_DEFINE(InitKeyboard_1);
} // namespace LibKeyboard
namespace Ime {
LIB_DEFINE(InitPlatform_1_Ime);
} // namespace Ime
namespace LibUlt {
LIB_DEFINE(InitUlt_1);
} // namespace LibUlt
@@ -89,6 +93,7 @@ void InitAll(Loader::SymbolDatabase* s) {
LIB_LOAD(InitLibKernel_1);
LIB_LOAD(LibMouse::InitMouse_1);
LIB_LOAD(LibKeyboard::InitKeyboard_1);
LIB_LOAD(Ime::InitPlatform_1_Ime);
LIB_LOAD(InitNet_1);
LIB_LOAD(InitPad_1);
LIB_LOAD(InitPlayGo_1);
+1 -1
View File
@@ -67,7 +67,7 @@ static void PrintUsage() {
#if KYTY_PLATFORM == KYTY_PLATFORM_WINDOWS
::printf(" --redzone Protect the guest SysV red zone.\n");
#endif
::printf(" --keymap <Pad=Key> DualSense mapping; may be repeated.\n");
::printf(" --keymap <Control=Input> DualSense mapping; may be repeated.\n");
::printf(" --rd Enable RenderDoc capture.\n");
}
+196 -87
View File
@@ -729,14 +729,6 @@ void Require(const char* shader_name, const char* stage, bool value, const std::
}
}
void WaitForSuspendPoint(Gpu& gpu) {
const int completion = gpu.GetFrameNum() + 1;
gpu.SuspendPoint();
while (gpu.GetFrameNum() < completion) {
std::this_thread::yield();
}
}
void CheckLeastRecentlyUsedCacheOrdering() {
Common::LeastRecentlyUsedCache<uint32_t, uint64_t> cache;
const auto first = cache.Insert(1, 1);
@@ -1615,6 +1607,146 @@ public:
order == 2 && gpu_thread != caller_thread && nested_thread == gpu_thread,
"host command did not run on the GPU thread or nested sync deadlocked");
{
const auto make_release_mem = [](uint32_t data_sel, uint32_t interrupt_selector,
void* destination, uint64_t value) {
std::array<uint32_t, 8> packet {};
const auto address = reinterpret_cast<uint64_t>(destination);
constexpr uint32_t eop_none = 0x28u;
constexpr uint32_t gcr_gl2_writeback = 1u << 9u;
packet[0] = KYTY_PM4(8, Pm4::IT_NOP, Pm4::R_RELEASE_MEM);
packet[1] = eop_none | (gcr_gl2_writeback << 12u);
packet[2] = (interrupt_selector << 24u) | (data_sel << 29u);
packet[3] = static_cast<uint32_t>(address);
packet[4] = static_cast<uint32_t>(address >> 32u);
packet[5] = static_cast<uint32_t>(value);
packet[6] = static_cast<uint32_t>(value >> 32u);
packet[7] = 0x123u;
return packet;
};
vk::SemaphoreTypeCreateInfo timeline_type {};
timeline_type.sType = vk::StructureType::eSemaphoreTypeCreateInfo;
timeline_type.semaphoreType = vk::SemaphoreType::eTimeline;
vk::SemaphoreCreateInfo timeline_create {};
timeline_create.sType = vk::StructureType::eSemaphoreCreateInfo;
timeline_create.pNext = &timeline_type;
vk::Semaphore blocked_timeline = nullptr;
Require("GpuCommandLane", "blocked timeline create",
m_runtime_context.device.createSemaphore(
&timeline_create, nullptr, &blocked_timeline) == vk::Result::eSuccess &&
blocked_timeline != nullptr,
"failed to create the blocked GPU timeline");
constexpr uint64_t release_tick = 1;
auto& gpu_scheduler = context.GetCommandScheduler();
auto processor = std::make_unique<CommandProcessor>(context);
gpu.SendCommandSync([&] {
processor->BufferInit();
SubmitInfo blocked_submit;
blocked_submit.AddWait(blocked_timeline, release_tick);
gpu_scheduler.Flush(blocked_submit);
});
std::binary_semaphore parser_complete {0};
alignas(uint64_t) uint64_t interrupt_only_gds_label = UINT64_MAX;
bool parser_kept_nonblocking_boundaries = false;
std::jthread no_gpu_wait([&] {
gpu.SendCommandSync([&] {
processor->BufferInit();
const auto tick_before = gpu_scheduler.CurrentTick();
processor->TriggerEvent(0x16u, 0u);
auto packet = make_release_mem(0, 0, nullptr, 0);
Pm4Execution execution;
const auto result = processor->Process(execution, packet.data(), packet.size());
processor->IncrementDe();
const bool cache_and_counter_did_not_submit =
result == Pm4ProcessResult::Complete &&
gpu_scheduler.CurrentTick() == tick_before;
auto gds_interrupt_only =
make_release_mem(5, 1, &interrupt_only_gds_label, 1ull << 16u);
Pm4Execution gds_interrupt_execution;
const auto interrupt_tick = gpu_scheduler.CurrentTick();
const auto interrupt_result = processor->Process(
gds_interrupt_execution, gds_interrupt_only.data(), gds_interrupt_only.size());
parser_kept_nonblocking_boundaries =
cache_and_counter_did_not_submit &&
interrupt_result == Pm4ProcessResult::Complete &&
gpu_scheduler.CurrentTick() == interrupt_tick + 1 &&
interrupt_only_gds_label == UINT64_MAX;
});
parser_complete.release();
});
const bool parser_did_not_wait_gpu =
parser_complete.try_acquire_for(std::chrono::seconds(2));
vk::SemaphoreSignalInfo signal_info {};
signal_info.sType = vk::StructureType::eSemaphoreSignalInfo;
signal_info.semaphore = blocked_timeline;
signal_info.value = release_tick;
Require("GpuCommandLane", "blocked timeline signal",
m_runtime_context.device.signalSemaphore(&signal_info) == vk::Result::eSuccess,
"failed to release the blocked GPU timeline");
no_gpu_wait.join();
gpu.SendCommandSync([&] { gpu_scheduler.Finish(); });
m_runtime_context.device.destroySemaphore(blocked_timeline, nullptr);
Require("GpuCommandLane", "nonblocking GPU packets",
parser_did_not_wait_gpu && parser_kept_nonblocking_boundaries,
"a cache event, GL2 writeback, DE counter, or interrupt boundary used the wrong "
"submission behavior");
alignas(uint64_t) uint64_t release_label = 0;
alignas(uint64_t) uint64_t gds_label = UINT64_MAX;
bool release_mem_submission_counts = false;
gpu.SendCommandSync([&] {
processor->BufferInit();
auto immediate = make_release_mem(1, 0, &release_label, 0x11223344u);
Pm4Execution immediate_execution;
const auto immediate_tick = gpu_scheduler.CurrentTick();
const auto immediate_result =
processor->Process(immediate_execution, immediate.data(), immediate.size());
const bool immediate_split_once =
gpu_scheduler.CurrentTick() == immediate_tick + 1;
auto gds = make_release_mem(5, 0, &gds_label, 1ull << 16u);
Pm4Execution gds_execution;
const auto gds_tick = gpu_scheduler.CurrentTick();
const auto gds_result = processor->Process(gds_execution, gds.data(), gds.size());
const bool gds_waited_once = gpu_scheduler.CurrentTick() == gds_tick + 1;
auto interrupt_only = make_release_mem(0, 4, nullptr, 0);
Pm4Execution interrupt_execution;
const auto interrupt_tick = gpu_scheduler.CurrentTick();
const auto interrupt_result = processor->Process(
interrupt_execution, interrupt_only.data(), interrupt_only.size());
const bool interrupt_split_once =
gpu_scheduler.CurrentTick() == interrupt_tick + 1;
auto gds_interrupt = make_release_mem(5, 2, &gds_label, 1ull << 16u);
Pm4Execution gds_interrupt_execution;
const auto gds_interrupt_tick = gpu_scheduler.CurrentTick();
const auto gds_interrupt_result = processor->Process(
gds_interrupt_execution, gds_interrupt.data(), gds_interrupt.size());
const bool gds_interrupt_waited_once =
gpu_scheduler.CurrentTick() == gds_interrupt_tick + 1;
release_mem_submission_counts =
immediate_result == Pm4ProcessResult::Complete && immediate_split_once &&
gds_result == Pm4ProcessResult::Complete && gds_waited_once &&
interrupt_result == Pm4ProcessResult::Complete && interrupt_split_once &&
gds_interrupt_result == Pm4ProcessResult::Complete &&
gds_interrupt_waited_once;
});
gpu.SendCommandSync([&] { gpu_scheduler.Finish(); });
Require("GpuCommandLane", "RELEASE_MEM submission counts",
release_mem_submission_counts &&
static_cast<uint32_t>(release_label) == 0x11223344u &&
static_cast<uint32_t>(gds_label) == 0,
"RELEASE_MEM lost its required split/readback or retained a redundant GPU wait");
}
alignas(uint32_t) uint32_t packet_marker_a = 0;
alignas(uint32_t) uint32_t packet_marker_b = 0;
const auto write_packet = [](uint32_t* packet, uint32_t* destination, uint32_t value) {
@@ -1667,7 +1799,7 @@ public:
});
std::this_thread::yield();
}
WaitForSuspendPoint(gpu);
gpu.Done();
Require("GpuCommandLane", "packet-boundary command polling",
packet_marker_a_at_callback == 11 && packet_marker_b_at_callback == 0 &&
packet_marker_a == 11 && packet_marker_b == 22,
@@ -1696,47 +1828,25 @@ public:
commands[16] = 22;
gpu.Submit(commands.data(), static_cast<uint32_t>(commands.size()), nullptr, 0);
std::binary_semaphore first_suspend_inserted {0};
std::binary_semaphore second_suspend_started {0};
std::atomic<bool> second_suspend_finished {false};
uint32_t suffix_after_first_suspend = UINT32_MAX;
const int ordered_completion = gpu.GetFrameNum() + 2;
std::binary_semaphore ordered_started {0};
std::atomic<bool> ordered_finished {false};
uint32_t ordered_suffix = 0;
std::jthread ordered([&] {
gpu.SuspendPoint();
suffix_after_first_suspend = suffix;
first_suspend_inserted.release();
second_suspend_started.release();
gpu.SuspendPoint();
second_suspend_finished = true;
ordered_started.release();
gpu.Done();
ordered_suffix = suffix;
ordered_finished = true;
});
const bool first_returned = first_suspend_inserted.try_acquire_for(std::chrono::seconds(2));
const bool second_started =
first_returned && second_suspend_started.try_acquire_for(std::chrono::seconds(2));
if (second_started) {
const auto ordered_deadline =
std::chrono::steady_clock::now() + std::chrono::seconds(2);
while (!second_suspend_finished.load() &&
std::chrono::steady_clock::now() < ordered_deadline) {
std::this_thread::yield();
}
}
const bool second_blocked_before_release = second_started && !second_suspend_finished.load();
ordered_started.acquire();
gpu.SendCommandSync([&] {
Require("GpuCommandLane", "submit done barrier", !ordered_finished.load(),
"submit done returned before a queued submission");
label = 1;
});
ordered.join();
while (gpu.GetFrameNum() < ordered_completion) {
std::this_thread::yield();
}
Require("GpuCommandLane", "asynchronous suspend insertion",
first_returned && suffix_after_first_suspend == 0,
"first suspend point blocked on earlier graphics work");
Require("GpuCommandLane", "single in-flight suspend point",
second_blocked_before_release && second_suspend_finished.load(),
"second suspend point did not wait for the first marker");
Require("GpuCommandLane", "ordered completion",
prefix == 11 && suffix == 22,
"queued suspend point completed before earlier graphics work");
prefix == 11 && suffix == 22 && ordered_suffix == 22 && ordered_finished.load(),
"submit done did not drain prior PM4 work");
auto& resources = context.GetGpuResources();
constexpr uint64_t empty_unmap_base = 0x0000000200400000ull;
@@ -1758,7 +1868,7 @@ public:
unmap_complete.acquire();
}
unmap_thread.join();
WaitForSuspendPoint(gpu);
gpu.Done();
Require("GpuCommandLane", "unmap queue progress",
unmap_returned && !resources.IsMapped(empty_unmap_base, empty_unmap_size) &&
prefix == 11 && suffix == 22,
@@ -1871,7 +1981,7 @@ public:
Require("GpuCommandLane", "DMA_DATA packet assembly", dma_cursor == dma_commands.size(),
"DMA_DATA GDS packet stream has the wrong size");
gpu.Submit(dma_commands.data(), static_cast<uint32_t>(dma_commands.size()), nullptr, 0);
WaitForSuspendPoint(gpu);
gpu.Done();
constexpr uint32_t clean_fill_value = 0xdecafbad;
gpu.SendCommandSync([&] {
auto& buffer_cache = resources.GetBufferCache();
@@ -2075,11 +2185,12 @@ public:
std::binary_semaphore command_entered {0};
std::binary_semaphore release_command {0};
std::atomic<bool> shutdown_complete {false};
std::atomic<bool> command_completed {false};
std::atomic<bool> submission_lane_entered {false};
gpu.SendCommand([&] {
command_entered.release();
release_command.acquire();
command_completed = true;
gpu.Done();
submission_lane_entered = true;
});
command_entered.acquire();
std::jthread shutdown_thread([&] {
@@ -2094,8 +2205,8 @@ public:
release_command.release();
shutdown_thread.join();
Require("GpuCommandLane", "owned shutdown completion",
shutdown_complete.load() && command_completed.load(),
"GPU owner did not drain an in-flight command");
shutdown_complete.load() && submission_lane_entered.load(),
"GPU owner did not drain a command that entered the submission lane");
context.ShutdownGpu();
std::printf("[host] %-32s ok\n", "GpuCommandLane");
}
@@ -3118,40 +3229,17 @@ public:
"prefix, or transferred ownership");
auto& buffer_cache = resources.GetBufferCache();
Require(name, "formatted lock-handoff fixture",
!buffer_cache.IsRegionRegistered(mip_desc.info.data.address,
mip_desc.info.data.size),
"formatted image already had a cached Buffer owner");
BufferBinding mip_formatted;
std::binary_semaphore obtain_entered {0};
std::binary_semaphore obtain_complete {0};
std::binary_semaphore buffer_visible {0};
auto texture_lock = TextureCacheTestAccess::Lock(texture_cache);
gpu.SendCommand([&] {
obtain_entered.release();
BufferBinding mip_formatted;
bool formatted_owner_was_absent = false;
gpu.SendCommandSync([&] {
formatted_owner_was_absent =
!buffer_cache.IsRegionRegistered(mip_desc.info.data.address,
mip_desc.info.data.size);
mip_formatted = buffer_cache.ObtainBuffer(command, mip_desc.info.data.address,
mip_desc.info.data.size, false, true, true);
obtain_complete.release();
});
obtain_entered.acquire();
std::jthread buffer_probe([&] {
while (!buffer_cache.IsRegionRegistered(mip_desc.info.data.address,
mip_desc.info.data.size)) {
std::this_thread::yield();
}
buffer_visible.release();
});
const bool buffer_released_before_texture =
buffer_visible.try_acquire_for(std::chrono::seconds(2));
texture_lock.unlock();
if (!buffer_released_before_texture) {
buffer_visible.acquire();
}
obtain_complete.acquire();
buffer_probe.join();
Require(name, "formatted cache lock handoff", buffer_released_before_texture,
"formatted Buffer acquisition retained the BufferCache lock while waiting for "
"TextureCache");
Require(name, "formatted cache fixture", formatted_owner_was_absent,
"formatted image already had a cached Buffer owner");
Require(name, "formatted Buffer path",
mip_formatted.owner != nullptr && mip_formatted.buffer != nullptr &&
texture_cache.GetImage(mip_image).IsGpuModified(),
@@ -18449,14 +18537,16 @@ void CheckPm4CeCompletion(RenderContext& renderer) {
uint32_t suffix = 0;
const auto address = reinterpret_cast<uint64_t>(&suffix);
std::array<uint32_t, 7> commands {};
std::array<uint32_t, 9> commands {};
commands[0] = 0xc0008600u;
commands[1] = 1;
commands[2] = KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0);
commands[2] = 0xc0008500u;
commands[3] = 0;
commands[4] = static_cast<uint32_t>(address);
commands[5] = static_cast<uint32_t>(address >> 32u);
commands[6] = 33;
commands[4] = KYTY_PM4(5, Pm4::IT_WRITE_DATA, 0);
commands[5] = 0;
commands[6] = static_cast<uint32_t>(address);
commands[7] = static_cast<uint32_t>(address >> 32u);
commands[8] = 33;
processor.ResetDeCe();
Pm4Execution execution;
@@ -18466,11 +18556,30 @@ void CheckPm4CeCompletion(RenderContext& renderer) {
suffix == 0,
"DE did not wait for an active CE stream");
processor.SetCeComplete(true);
Require("Pm4CeCompletion", "complete",
std::array<uint32_t, 2> ce_increment {0xc0008400u, 1};
Pm4Execution ce_execution;
Require("Pm4CeCompletion", "CE increment",
processor.Process(ce_execution, ce_increment.data(), ce_increment.size()) ==
Pm4ProcessResult::Complete,
"CE counter increment did not complete");
Require("Pm4CeCompletion", "counter gate",
processor.Process(execution, commands.data(), commands.size()) ==
Pm4ProcessResult::Complete &&
suffix == 33,
"DE did not resume after CE advanced or failed to increment its counter");
suffix = 0;
Pm4Execution balanced_execution;
Require("Pm4CeCompletion", "balanced wait",
processor.Process(balanced_execution, commands.data(), commands.size()) ==
Pm4ProcessResult::Blocked &&
suffix == 0,
"balanced CE/DE counters did not gate the next DE packet");
processor.SetCeComplete(true);
Require("Pm4CeCompletion", "stream complete",
processor.Process(balanced_execution, commands.data(), commands.size()) ==
Pm4ProcessResult::Complete &&
suffix == 33,
"DE remained blocked after the CE stream completed");
std::printf("[host] %-32s ok\n", "Pm4CeCompletion");
}