mirror of
https://github.com/KytyPS5/KytyPS5.git
synced 2026-10-21 19:36:55 +02:00
Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ca7c8f0074 | |||
| d7063d0b81 | |||
| 091e6b31dc | |||
| 4cd6132758 | |||
| 9c263a9886 | |||
| c11fc96019 | |||
| 4f2b5eb749 | |||
| d7013dc2b3 | |||
| 0bbe301120 | |||
| 37aa6f88b0 | |||
| e3890febaa | |||
| a4da2a9f00 | |||
| 3005a210c6 | |||
| 027433fcdc | |||
| 7ff9e4907f | |||
| 87428d7a4b | |||
| 822ffba313 | |||
| 3c850b65c8 | |||
| 698c9f802f | |||
| 1d5f3c8f66 | |||
| ec78afb966 | |||
| 3a7656336e |
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -24,6 +24,7 @@ target_link_libraries(common
|
||||
Tracy::TracyClient
|
||||
PRIVATE
|
||||
cpuinfo
|
||||
kyty_git_version
|
||||
spdlog::spdlog
|
||||
)
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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
@@ -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_
|
||||
+215
-31
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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_
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
+17
-4
@@ -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
File diff suppressed because it is too large
Load Diff
+245
@@ -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_
|
||||
@@ -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
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user