aboutsummaryrefslogtreecommitdiff
path: root/src/core
diff options
context:
space:
mode:
authorTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-27 22:28:10 -0400
committerTakoroka <137927095+Takoroka@users.noreply.github.com>2026-10-27 22:28:10 -0400
commit81eaf7ccbf07bfc73553d8e752a256aa3719bcb9 (patch)
treef29f826c747c092d29b33d25cfe26e0b1d576929 /src/core
parent51207c73c889dbdc4a586a5da23314f6d33e4eb9 (diff)
Switch 2: back GPU-only regions, pace presents, interleaved split methods
- gpu 214 creates GPU-only memory the graphics server backs itself (title 4 renders everything into six 300 MB regions at 0x81_0000_0000). Back each with application-pool pages mapped into the alias code region and map that for the GPU. This is what made title 4's frames appear. - vi2 229 waits (bounded) for the presented frame's GPU fence: the reply reports every buffer free, so without this the game laps Eden's GPU thread and rewrites command memory it has not read yet. - Split methods: keep several parked methods keyed by resume address (NVN interleaves command buffers) and drop them when the submission ends. - hid 214 (per-frame vibration send) answered quietly. - Debug aids: EDEN_NX2_WINDOW_TRACE / EDEN_NX2_RT_TRACE (render targets, clears, textures), EDEN_NX2_AUDIO_LEVEL (output peak), EDEN_NX2_PB_DUMP (command list for offline replay), parsed-entry change check.
Diffstat (limited to 'src/core')
-rw-r--r--src/core/hle/service/hid/hid_server.cpp6
-rw-r--r--src/core/hle/service/hid/hid_server.h3
-rw-r--r--src/core/hle/service/nx2_gsv.h106
3 files changed, 110 insertions, 5 deletions
diff --git a/src/core/hle/service/hid/hid_server.cpp b/src/core/hle/service/hid/hid_server.cpp
index 9689bce8dd..04b4663bc2 100644
--- a/src/core/hle/service/hid/hid_server.cpp
+++ b/src/core/hle/service/hid/hid_server.cpp
@@ -132,6 +132,7 @@ IHidServer::IHidServer(Core::System& system_, std::shared_ptr<ResourceManager> r
{210, C<&IHidServer::EndPermitVibrationSession>, "EndPermitVibrationSession"},
{211, C<&IHidServer::IsVibrationDeviceMounted>, "IsVibrationDeviceMounted"},
{212, C<&IHidServer::SendVibrationValueInBool>, "SendVibrationValueInBool"},
+ {214, C<&IHidServer::SendNx2VibrationValues>, "SendNx2VibrationValues (guessed)"},
{300, C<&IHidServer::ActivateConsoleSixAxisSensor>, "ActivateConsoleSixAxisSensor"},
{301, C<&IHidServer::StartConsoleSixAxisSensor>, "StartConsoleSixAxisSensor"},
{302, C<&IHidServer::StopConsoleSixAxisSensor>, "StopConsoleSixAxisSensor"},
@@ -1069,6 +1070,11 @@ Result IHidServer::IsVibrationDeviceMounted(
R_SUCCEED();
}
+Result IHidServer::SendNx2VibrationValues() {
+ LOG_DEBUG(Service_HID, "(STUBBED) called");
+ R_SUCCEED();
+}
+
Result IHidServer::SendVibrationValueInBool(
bool is_vibrating, Core::HID::VibrationDeviceHandle vibration_device_handle,
ClientAppletResourceUserId aruid) {
diff --git a/src/core/hle/service/hid/hid_server.h b/src/core/hle/service/hid/hid_server.h
index 57a1a04b9c..574d54c415 100644
--- a/src/core/hle/service/hid/hid_server.h
+++ b/src/core/hle/service/hid/hid_server.h
@@ -189,6 +189,9 @@ private:
Result SendVibrationValueInBool(bool is_vibrating,
Core::HID::VibrationDeviceHandle vibration_device_handle,
ClientAppletResourceUserId aruid);
+ // Switch 2 sends vibration every frame with 214 (pointer buffers of device handles and
+ // values, like 206); Eden doesn't emulate vibration, so accept and drop it.
+ Result SendNx2VibrationValues();
Result ActivateConsoleSixAxisSensor(ClientAppletResourceUserId aruid);
Result StartConsoleSixAxisSensor(Core::HID::ConsoleSixAxisSensorHandle console_sixaxis_handle,
ClientAppletResourceUserId aruid);
diff --git a/src/core/hle/service/nx2_gsv.h b/src/core/hle/service/nx2_gsv.h
index 7e64bca72d..1e78cd39d3 100644
--- a/src/core/hle/service/nx2_gsv.h
+++ b/src/core/hle/service/nx2_gsv.h
@@ -5,12 +5,16 @@
#include <algorithm>
#include <array>
+#include <thread>
+#include <chrono>
#include <string>
#include <cstdlib>
#include <cstdio>
#include <initializer_list>
#include <atomic>
#include <map>
+#include <optional>
+#include <memory>
#include <vector>
#include "common/alignment.h"
@@ -23,6 +27,10 @@
#include "core/core_timing.h"
#include "core/hle/kernel/k_client_session.h"
#include "core/hle/kernel/k_process.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/k_system_resource.h"
+#include "core/hle/kernel/k_memory_manager.h"
+#include "core/hle/kernel/k_page_group.h"
#include "core/hle/kernel/k_resource_limit.h"
#include "core/hle/kernel/k_server_session.h"
#include "core/hle/kernel/k_event.h"
@@ -249,7 +257,7 @@ public:
{102, &INx2GpuSession::SubSlotJob<3>, "Sub102 (job, slot in word 3)"},
{211, &INx2GpuSession::SubSubmitJob, "NormalMapGpuMemory4K"},
{213, &INx2GpuSession::SubSlotJob<8>, "NormalUnmapGpuMemory4K"},
- {214, &INx2GpuSession::SubSlotJob<11>, "CreateSparseRegion"},
+ {214, &INx2GpuSession::SubCreateGpuOnlyRegion, "CreateGpuOnlyRegion (guessed)"},
});
}
@@ -362,8 +370,8 @@ private:
template <size_t Slot>
void SubSlotJob(HLERequestContext& ctx) {
const auto* w = ctx.CommandBuffer();
- LOG_DEBUG(Service, "(STUBBED) nx2:gpu {} [2]={:#x} [3]={:#x} [4]={:#x} [5]={:#x} [6]={:#x} slot={}",
- ctx.GetCommand(), w[2], w[3], w[4], w[5], w[6], w[Slot]);
+ LOG_INFO(Service, "(STUBBED) nx2:gpu {} words {:08x} slot={}", ctx.GetCommand(),
+ fmt::join(std::span<const u32>{w + 2, 18}, " "), w[Slot]);
EventFor(w[Slot])->Signal(system.Kernel());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(ResultSuccess);
@@ -380,8 +388,8 @@ private:
const u64 c = u64{cmd_buf[8]} | (u64{cmd_buf[9]} << 32);
// Words 3-4: first GPU address, 5-6: last, 7: memory handle, 8-9: offset into it.
const u32 handle = cmd_buf[7];
- LOG_DEBUG(Service, "nx2:gpu 211 map [{:#x}, {:#x}] handle={} offset={:#x} slot={}", a, b,
- handle, c, slot);
+ LOG_INFO(Service, "nx2:gpu 211 map [{:#x}, {:#x}] handle={} offset={:#x} slot={}", a, b,
+ handle, c, slot);
if (const auto it = m_memory.find(handle); it != m_memory.end() && b > a) {
const u64 cpu = it->second.address + c;
if (cpu != a) {
@@ -395,6 +403,68 @@ private:
rb.Push(ResultSuccess);
}
+ // Sub 214: GPU-only memory [start, last] (words 3-6, job slot in word 11). The graphics
+ // server backs it itself: the client never maps anything there but renders into it (title 4
+ // keeps its render targets in six 300 MB regions at 0x81_0000_0000). Back each region with
+ // kernel memory placed in the process's alias code region and map that for the GPU.
+ void SubCreateGpuOnlyRegion(HLERequestContext& ctx) {
+ const auto* w = ctx.CommandBuffer();
+ const u64 start = u64{w[3]} | (u64{w[4]} << 32);
+ const u64 last = u64{w[5]} | (u64{w[6]} << 32);
+ const u32 slot = w[11];
+ if (last > start && ((last + 1 - start) % Core::Memory::YUZU_PAGESIZE) == 0) {
+ const u64 size = last + 1 - start;
+ if (const auto address = BackGpuOnlyRegion(size)) {
+ Nvidia::Devices::Nx2GpuMemoryRegistry::Get().MapAt(start, *address, size);
+ LOG_INFO(Service, "nx2:gpu 214 GPU-only region [{:#x}, {:#x}] backed at {:#x}",
+ start, last, *address);
+ }
+ } else {
+ LOG_WARNING(Service, "nx2:gpu 214 unexpected region [{:#x}, {:#x}]", start, last);
+ }
+ EventFor(slot)->Signal(system.Kernel());
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ }
+
+ std::optional<u64> BackGpuOnlyRegion(u64 size) {
+ using Core::Memory::YUZU_PAGESIZE;
+ auto& kernel_core = system.Kernel();
+ auto* const process = system.ApplicationProcess();
+ if (process == nullptr) {
+ return std::nullopt;
+ }
+ auto pg = std::make_unique<Kernel::KPageGroup>(
+ kernel_core, std::addressof(kernel_core.GetSystemSystemResource().GetBlockInfoManager()));
+ // Not cleared: the GPU writes these before reading them, and touching 300 MB would commit
+ // it on the host.
+ if (R_FAILED(kernel_core.MemoryManager().AllocateAndOpen(
+ pg.get(), size / YUZU_PAGESIZE,
+ Kernel::KMemoryManager::EncodeOption(Kernel::KMemoryManager::Pool::Application,
+ Kernel::KMemoryManager::Direction::FromBack)))) {
+ LOG_ERROR(Service, "nx2:gpu 214 could not allocate {:#x} bytes", size);
+ return std::nullopt;
+ }
+ auto& page_table = process->GetPageTable();
+ const u64 region_start = GetInteger(page_table.GetAliasCodeRegionStart());
+ const u64 region_size = page_table.GetAliasCodeRegionSize();
+ constexpr u64 Alignment = 0x200000; // 2 MiB
+ for (u64 offset = 0; offset + size <= region_size; offset += Alignment) {
+ const Common::ProcessAddress address{region_start + offset};
+ if (R_SUCCEEDED(page_table.MapPageGroup(address, *pg, Kernel::KMemoryState::IoMemory,
+ Kernel::KMemoryPermission::UserReadWrite))) {
+ m_gpu_only_memory.push_back(std::move(pg));
+ return GetInteger(address);
+ }
+ }
+ LOG_ERROR(Service, "nx2:gpu 214 no room for {:#x} bytes in the alias code region "
+ "[{:#x}, +{:#x})", size, region_start, region_size);
+ pg->Close(kernel_core);
+ return std::nullopt;
+ }
+
+ std::vector<std::unique_ptr<Kernel::KPageGroup>> m_gpu_only_memory;
+
struct GpuMemory {
u64 address;
u64 size;
@@ -498,12 +568,38 @@ private:
LOG_INFO(Service, "nx2:vi2 229 present #{} token={:#x} slot={} image={}", m_presents,
u64{w[2]} | (u64{w[3]} << 32), w[4], w[5]);
}
+ WaitForFrame(~w[22], w[24]);
Composite(w[5], ~w[22], w[24]);
constexpr u64 AvailableSlots = 0x7fff;
TipcReplyWords(ctx, {static_cast<u32>(AvailableSlots),
static_cast<u32>(AvailableSlots >> 32), 0, 0, 0, 0});
}
+ // The console's compositor frees a buffer only once its frame's GPU fence has passed, which
+ // is what keeps a game from running ahead of the GPU. This reply always reports every buffer
+ // free, so pace the game here instead: wait (bounded, in case the GPU waits on the CPU) until
+ // Eden's GPU thread has processed the frame. Otherwise the game laps the GPU thread and
+ // rewrites command memory it hasn't read yet.
+ void WaitForFrame(u32 fence_id, u32 fence_value) {
+ if (fence_id >= 192) {
+ return;
+ }
+ auto& syncpoints = system.Host1x().GetSyncpointManager();
+ const auto start = std::chrono::steady_clock::now();
+ const auto deadline = start + std::chrono::milliseconds{250};
+ while (!syncpoints.IsReadyGuest(fence_id, fence_value) &&
+ std::chrono::steady_clock::now() < deadline && system.IsPoweredOn()) {
+ std::this_thread::sleep_for(std::chrono::microseconds{200});
+ }
+ if (m_presents <= 8 || (m_presents % 600) == 0) {
+ LOG_INFO(Service, "nx2:vi2 present #{} fence ({}, {}) now {}, waited {} us",
+ m_presents, fence_id, fence_value, syncpoints.GetGuestSyncpointValue(fence_id),
+ std::chrono::duration_cast<std::chrono::microseconds>(
+ std::chrono::steady_clock::now() - start)
+ .count());
+ }
+ }
+
// Shows a presented native image: word 5 is the image id, ~word 22 / word 24 the GPU fence
// (syncpoint, value) to wait for (layout from changes.patch). The image is a block-linear
// RGBA8 surface in the console's native GOB layout at GPU address == CPU address.