core: gsp: Fix several GSP accuracy issues (#2678)

This commit is contained in:
PabloMK7 authored and GitHub committed 2026-10-15 15:21:32 +02:00
1 parent 68202bc4ff
commit 3c3e54989e
4 files changed
+566 -149

No files matched your search

+382 -105
View File
@@ -1,10 +1,11 @@
// Copyright 2014-2026 Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the misc/licenses/gplv2.txt file included.
// Licensed under GPLv3 or any later version
// Refer to the LICENSE.txt file included.
#include <span>
#include <vector>
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/deque.hpp>
#include <boost/serialization/optional.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include "common/archives.h"
@@ -39,13 +40,29 @@ namespace ErrCodes {
enum {
// TODO(purpasmart): Check if this name fits its actual usage
OutofRangeOrMisalignedAddress = 513,
NoSaveVRAMSysAreaPerm = 518,
NoPermission = 518,
FirstInitialization = 519,
ForceModeCanceled = 520,
};
}
constexpr Result ResultNoSaveVRAMSysAreaPerm(ErrCodes::NoSaveVRAMSysAreaPerm, ErrorModule::GX,
constexpr Result ResultNoSaveVRAMSysAreaPerm(ErrCodes::NoPermission, ErrorModule::GX,
ErrorSummary::NothingHappened, ErrorLevel::Permanent);
constexpr Result ResultNoGpuRight(ErrCodes::NoPermission, ErrorModule::GX,
ErrorSummary::NothingHappened,
ErrorLevel::Permanent); // 0xD8202A06
constexpr Result ResultInvalidBuffer(ErrorDescription::InvalidAddress, ErrorModule::GX,
ErrorSummary::InvalidArgument,
ErrorLevel::Usage); // 0xE0E02BF5
constexpr Result ResultInvalidPriority(ErrorDescription::InvalidSection, ErrorModule::GX,
ErrorSummary::InvalidArgument,
ErrorLevel::Usage); // 0xE0E02BE8
constexpr Result ResultNotSupported(ErrorDescription::NotImplemented, ErrorModule::GX,
ErrorSummary::NotSupported, ErrorLevel::Usage); // 0xE0C02BF4
constexpr Result ResultForceModeCanceled(ErrCodes::ForceModeCanceled, ErrorModule::GX,
ErrorSummary::Canceled, ErrorLevel::Success); // 0x01202A08
constexpr Result ResultGpuRightBusy(ErrorDescription::Busy, ErrorModule::GX,
ErrorSummary::WouldBlock, ErrorLevel::Status); // 0xC8402BF0
constexpr Result ResultFirstInitialization(ErrCodes::FirstInitialization, ErrorModule::GX,
ErrorSummary::Success, ErrorLevel::Success);
constexpr Result ResultRegsOutOfRangeOrMisaligned(ErrCodes::OutofRangeOrMisalignedAddress,
@@ -88,15 +105,11 @@ std::pair<bool, FrameBufferInfo*> GSP_GPU::GetFrameBufferInfo(u32 thread_id, u32
// If the thread ID is invalid, the last cached
// data is used.
if (thread_id != std::numeric_limits<u32>::max()) {
// For each thread there are two FrameBufferUpdate fields
const u32 offset = 0x200 + (2 * thread_id + screen_index) * sizeof(FrameBufferUpdate);
FrameBufferUpdate* screen =
reinterpret_cast<FrameBufferUpdate*>(shared_memory->GetPointer(offset));
FrameBufferUpdate* screen = GetFrameBufferUpdate(thread_id, screen_index);
if (screen->is_dirty || force_update) {
cached_framebuffer_infos[screen_index] = screen->framebuffer_info[screen->index];
screen->is_dirty.Assign(false);
cached_framebuffer_infos[screen_index] = screen->framebuffer_info[screen->index & 1];
screen->is_dirty = 0;
is_dirty = true;
}
}
@@ -104,18 +117,39 @@ std::pair<bool, FrameBufferInfo*> GSP_GPU::GetFrameBufferInfo(u32 thread_id, u32
return {is_dirty, &cached_framebuffer_infos[screen_index]};
}
FrameBufferUpdate* GSP_GPU::GetFrameBufferUpdate(u32 thread_id, u32 screen_index) {
const u32 offset = 0x200 + (2 * thread_id + screen_index) * sizeof(FrameBufferUpdate);
return reinterpret_cast<FrameBufferUpdate*>(shared_memory->GetPointer(offset));
}
InterruptRelayQueue* GSP_GPU::GetInterruptRelayQueue(u32 thread_id) {
u8* ptr = shared_memory->GetPointer(sizeof(InterruptRelayQueue) * thread_id);
return reinterpret_cast<InterruptRelayQueue*>(ptr);
}
bool GSP_GPU::HasGpuRight(Kernel::HLERequestContext& ctx) {
return GetSessionData(ctx.Session())->thread_id == thread_id_with_rights;
}
void GSP_GPU::ClientDisconnected(std::shared_ptr<Kernel::ServerSession> server_session) {
SessionData* session_data = GetSessionData(server_session);
session_data->interrupt_event = nullptr;
session_data->registered = false;
std::erase_if(right_waiters, [session_data](const RightWaitContext& waiter) {
return waiter.thread_id == session_data->thread_id;
});
if (thread_id_with_rights == session_data->thread_id) {
ReleaseRight(session_data);
}
// Clear the framebuffer update slots of the thread.
for (u32 screen_id = 0; screen_id < 2; ++screen_id) {
auto* update = GetFrameBufferUpdate(session_data->thread_id, screen_id);
update->index = 0;
update->is_dirty = 0;
update->pad1 = 0;
update->framebuffer_info[0].address_left = 0;
update->framebuffer_info[1].address_left = 0;
}
if (True(session_data->relay_queue_flags & RelayEventQueueFlags::AllowSaveVramSysArea)) {
vram_backup_handler.ResetState();
}
@@ -225,6 +259,7 @@ public:
enum class Type : u32 {
SaveVRAMSysArea,
RestoreVRAMSysArea,
AcquireRight,
};
explicit ThreadCallback(Core::System& system_, std::string name_, Type type_)
@@ -232,16 +267,19 @@ public:
void WakeUp(std::shared_ptr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx,
Kernel::ThreadWakeupReason reason) {
if (type == Type::SaveVRAMSysArea || type == Type::RestoreVRAMSysArea) {
IPC::RequestBuilder rb = IPC::RequestBuilder(ctx, 1, 0);
rb.Push(ResultSuccess);
}
IPC::RequestBuilder rb = IPC::RequestBuilder(ctx, 1, 0);
rb.Push(result);
}
void SetResult(Result result_) {
result = result_;
}
private:
Core::System& system;
std::string name;
Type type;
Result result = ResultSuccess;
ThreadCallback() : system(Core::Global<Core::System>()) {}
@@ -250,6 +288,7 @@ private:
ar& boost::serialization::base_object<Kernel::HLERequestContext::WakeupCallback>(*this);
ar & name;
ar & type;
ar & result.raw;
}
friend class boost::serialization::access;
};
@@ -257,22 +296,82 @@ private:
void GSP_GPU::WriteHWRegs(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u32 reg_addr = rp.Pop<u32>();
const u32 size = rp.Pop<u32>();
// Real HW ignores this.
[[maybe_unused]] const u32 size = rp.Pop<u32>();
const auto src_data = rp.PopStaticBuffer();
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(GSP::WriteHWRegs(reg_addr, size, src_data, system.GPU()));
if (!HasGpuRight(ctx)) {
rb.Push(ResultNoGpuRight);
return;
}
rb.Push(GSP::WriteHWRegs(reg_addr, static_cast<u32>(src_data.size()), src_data, system.GPU()));
}
void GSP_GPU::WriteHWRegsWithMask(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u32 reg_addr = rp.Pop<u32>();
const u32 size = rp.Pop<u32>();
// Real HW ignores this.
[[maybe_unused]] const u32 size = rp.Pop<u32>();
const auto src_data = rp.PopStaticBuffer();
const auto mask_data = rp.PopStaticBuffer();
auto mask_data = rp.PopStaticBuffer();
// If the mask data is smaller, it is treated as zeroes.
if (mask_data.size() < src_data.size()) {
mask_data.resize(src_data.size(), 0);
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(GSP::WriteHWRegsWithMask(reg_addr, size, src_data, mask_data, system.GPU()));
if (!HasGpuRight(ctx)) {
rb.Push(ResultNoGpuRight);
return;
}
// Real hardware explicitly checks this combination to reset the LCD mode state, and writes are
// skipped.
// TODO(PabloMK7): Handle LCD state here properly.
if (reg_addr == 0x400424 && src_data.size() == 0x50) {
rb.Push(ResultSuccess);
return;
}
rb.Push(GSP::WriteHWRegsWithMask(reg_addr, static_cast<u32>(src_data.size()), src_data,
mask_data, system.GPU()));
}
void GSP_GPU::WriteHWRegRepeat(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const u32 reg_addr = rp.Pop<u32>();
// Real HW ignores this.
[[maybe_unused]] const u32 size = rp.Pop<u32>();
const auto src_data = rp.PopStaticBuffer();
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
if (!HasGpuRight(ctx)) {
rb.Push(ResultNoGpuRight);
return;
}
const u32 size_bytes = static_cast<u32>(src_data.size());
if (reg_addr & 3 || reg_addr >= 0x420000) {
rb.Push(ResultRegsOutOfRangeOrMisaligned);
return;
}
if (size_bytes > 0x80) {
rb.Push(ResultRegsInvalidSize);
return;
}
if (size_bytes & 3) {
rb.Push(ResultRegsMisaligned);
return;
}
for (u32 offset = 0; offset < size_bytes; offset += sizeof(u32)) {
u32 value;
std::memcpy(&value, &src_data[offset], sizeof(u32));
system.GPU().WriteReg(REGS_BEGIN + reg_addr, value);
}
rb.Push(ResultSuccess);
}
void GSP_GPU::ReadHWRegs(Kernel::HLERequestContext& ctx) {
@@ -283,6 +382,12 @@ void GSP_GPU::ReadHWRegs(Kernel::HLERequestContext& ctx) {
static constexpr u32 MaxReadSize = 0x80;
u32 size = std::min(input_size, MaxReadSize);
if (!HasGpuRight(ctx)) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultNoGpuRight);
return;
}
if ((reg_addr % 4) != 0 || reg_addr >= 0x420000) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultRegsOutOfRangeOrMisaligned);
@@ -314,40 +419,101 @@ void GSP_GPU::SetBufferSwap(Kernel::HLERequestContext& ctx) {
u32 screen_id = rp.Pop<u32>();
auto fb_info = rp.PopRaw<FrameBufferInfo>();
system.GPU().SetBufferSwap(screen_id, fb_info);
if (!HasGpuRight(ctx)) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultNoGpuRight);
return;
}
// Real hardware ignores invalid screens and still returns success.
if (screen_id < 2) {
system.GPU().SetBufferSwap(screen_id, fb_info);
} else {
LOG_ERROR(Service_GSP, "Invalid screen id {}", screen_id);
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
}
void GSP_GPU::SetCommandList(Kernel::HLERequestContext& ctx) {
// Stubbed command, which was once implemented in older GSP versions.
// Only returns success.
IPC::RequestParser rp(ctx);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
}
void GSP_GPU::RequestDma(Kernel::HLERequestContext& ctx) {
// Stubbed command, which was once implemented in older GSP versions.
// Only returns success.
IPC::RequestParser rp(ctx);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
}
/// Checks if the given address is valid for cache operations.
static bool IsValidCacheRange(VAddr address, u32 size, bool allow_vram) {
const VAddr end = address + size;
const auto contains = [address, end](VAddr range_start, VAddr range_end) {
return address >= range_start && address <= range_end && end >= range_start &&
end <= range_end;
};
return contains(Memory::LINEAR_HEAP_VADDR, Memory::LINEAR_HEAP_VADDR_END) ||
contains(Memory::NEW_LINEAR_HEAP_VADDR, Memory::NEW_LINEAR_HEAP_VADDR_END) ||
contains(Memory::N3DS_EXTRA_RAM_VADDR, Memory::N3DS_EXTRA_RAM_VADDR_END) ||
(allow_vram && address >= Memory::VRAM_VADDR && address <= Memory::VRAM_VADDR_END);
}
void GSP_GPU::DoCacheOperation(Kernel::HLERequestContext& ctx, bool allow_vram) {
IPC::RequestParser rp(ctx);
const u32 address = rp.Pop<u32>();
const u32 size = rp.Pop<u32>();
[[maybe_unused]] auto process = rp.PopObject<Kernel::Process>();
// The CPU cache is not emulated, only the address check.
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
if (!IsValidCacheRange(address, size, allow_vram)) {
LOG_ERROR(Service_GSP, "Invalid cache operation range address=0x{:08X}, size=0x{:08X}",
address, size);
rb.Push(ResultInvalidBuffer);
return;
}
rb.Push(ResultSuccess);
}
void GSP_GPU::FlushDataCache(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
[[maybe_unused]] u32 address = rp.Pop<u32>();
[[maybe_unused]] u32 size = rp.Pop<u32>();
[[maybe_unused]] auto process = rp.PopObject<Kernel::Process>();
// TODO(purpasmart96): Verify return header on HW
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
LOG_TRACE(Service_GSP, "(STUBBED) called address=0x{:08X}, size=0x{:08X}, process={}", address,
size, process->process_id);
DoCacheOperation(ctx, false);
}
void GSP_GPU::InvalidateDataCache(Kernel::HLERequestContext& ctx) {
DoCacheOperation(ctx, true);
}
void GSP_GPU::RegisterInterruptEvents(Kernel::HLERequestContext& ctx) {
Unsupported(ctx);
}
void GSP_GPU::StoreDataCache(Kernel::HLERequestContext& ctx) {
DoCacheOperation(ctx, false);
}
void GSP_GPU::ResetGpuCore(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
[[maybe_unused]] u32 address = rp.Pop<u32>();
[[maybe_unused]] u32 size = rp.Pop<u32>();
[[maybe_unused]] auto process = rp.PopObject<Kernel::Process>();
// TODO(purpasmart96): Verify return header on HW
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
if (!HasGpuRight(ctx)) {
rb.Push(ResultNoGpuRight);
return;
}
DoResetGpuCore();
rb.Push(ResultSuccess);
}
LOG_TRACE(Service_GSP, "(STUBBED) called address=0x{:08X}, size=0x{:08X}, process={}", address,
size, process->process_id);
void GSP_GPU::Unsupported(Kernel::HLERequestContext& ctx) {
// Stubbed commands, which were once implemented in older GSP versions.
IPC::RequestParser rp(ctx);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultNotSupported);
}
void GSP_GPU::SetAxiConfigQoSMode(Kernel::HLERequestContext& ctx) {
@@ -357,6 +523,8 @@ void GSP_GPU::SetAxiConfigQoSMode(Kernel::HLERequestContext& ctx) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
// TODO(PabloMK7): Implement the AXI registers even if they do not
// do anything. For now this function remains stubbed.
LOG_DEBUG(Service_GSP, "(STUBBED) called mode=0x{:08X}", mode);
}
@@ -383,15 +551,28 @@ void GSP_GPU::RegisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) {
auto relay_queue_flags = static_cast<RelayEventQueueFlags>(rp.Pop<u32>());
auto interrupt_event = rp.PopObject<Kernel::Event>();
ASSERT_MSG(interrupt_event, "handle is not valid!");
interrupt_event->SetName("GSP_GPU::interrupt_event");
// Real hardware stores the handle without checking it.
if (interrupt_event) {
interrupt_event->SetName("GSP_GPU::interrupt_event");
} else {
LOG_ERROR(Service_GSP, "Invalid interrupt event handle");
}
SessionData* session_data = GetSessionData(ctx.Session());
session_data->interrupt_event = std::move(interrupt_event);
session_data->registered = true;
session_data->relay_queue_flags = relay_queue_flags;
// Clear the relevant queue information, as real hardware does.
auto* interrupt_relay_queue = GetInterruptRelayQueue(session_data->thread_id);
interrupt_relay_queue->index = 0;
interrupt_relay_queue->number_interrupts = 0;
interrupt_relay_queue->error_code = 0;
interrupt_relay_queue->config = 0;
interrupt_relay_queue->missed_PDC0 = 0;
interrupt_relay_queue->missed_PDC1 = 0;
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
if (first_initialization) {
@@ -584,15 +765,6 @@ void Service::GSP::GSP_GPU::ProcessPendingInterruptImpl(InterruptId interrupt_id
}
}
// Update framebuffer information if requested
const s32 screen_id = (interrupt_id == InterruptId::PDC0) ? 0 : 1;
auto info = GetFrameBufferInfo(thread_id, screen_id);
if (info.first || force_buffer_swap[screen_id]) {
force_buffer_swap[screen_id] = false;
system.GPU().SetBufferSwap(screen_id, *info.second);
}
// If it's the bottom screen interrupt and a HLE event is pending,
// signal it and reset.
if (interrupt_id == InterruptId::PDC1 && session_data->on_vblank_event) {
@@ -615,6 +787,15 @@ void GSP_GPU::SignalInterrupt(InterruptId interrupt_id, u64 wait_delay_ns) {
// Normal interrupts are only signaled for the active thread (ie, the thread that has the GPU
// right), but the PDC0/1 interrupts are signaled for every registered thread.
if (interrupt_id == InterruptId::PDC0 || interrupt_id == InterruptId::PDC1) {
// Update the framebuffer if requested. Only the framebuffer info of the thread with the
// GPU right is used.
const u32 screen_id = (interrupt_id == InterruptId::PDC0) ? 0 : 1;
auto info = GetFrameBufferInfo(thread_id_with_rights, screen_id);
if (info.first || force_buffer_swap[screen_id]) {
force_buffer_swap[screen_id] = false;
system.GPU().SetBufferSwap(screen_id, *info.second);
}
for (u32 thread_id = 0; thread_id < MaxGSPThreads; ++thread_id) {
SignalInterruptForThread(interrupt_id, thread_id, wait_delay_ns);
}
@@ -633,6 +814,14 @@ void GSP_GPU::SetLcdForceBlack(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const bool enable_black = rp.Pop<bool>();
// After the GPU core reset of a forced AcquireRight, only the right holder can turn the
// screens back on.
if (gpu_core_reset_by_force && !enable_black && !HasGpuRight(ctx)) {
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultNoGpuRight);
return;
}
Pica::ColorFill data{};
data.is_enabled.Assign(enable_black);
system.GPU().SetColorFill(data);
@@ -649,6 +838,7 @@ void GSP_GPU::TriggerCmdReqQueue(Kernel::HLERequestContext& ctx) {
// the function always succeeds.
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
return;
}
auto* command_buffer = GetCommandBuffer(thread_id_with_rights);
@@ -679,9 +869,15 @@ void GSP_GPU::TriggerCmdReqQueue(Kernel::HLERequestContext& ctx) {
// Decode and execute command
system.perf_stats->BeginGPUProcessing();
gpu.Execute(command);
const Result result = gpu.Execute(command);
system.perf_stats->EndGPUProcessing();
// The GSP module stores the error of a failed command and keeps going.
if (result.IsError()) {
command_buffer->unk[0] = result.raw;
command_buffer->status.Assign(CommandBuffer::STATUS_CMD_FAILED);
}
if (command.stop) {
command_buffer->status.Assign(CommandBuffer::STATUS_STOPPED);
}
@@ -703,6 +899,18 @@ void GSP_GPU::TriggerCmdReqQueue(Kernel::HLERequestContext& ctx) {
}
}
void GSP_GPU::SetDisplayTransfer(Kernel::HLERequestContext& ctx) {
Unsupported(ctx);
}
void GSP_GPU::SetTextureCopy(Kernel::HLERequestContext& ctx) {
Unsupported(ctx);
}
void GSP_GPU::SetMemoryFill(Kernel::HLERequestContext& ctx) {
Unsupported(ctx);
}
void GSP_GPU::ImportDisplayCaptureInfo(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
@@ -908,61 +1116,96 @@ void GSP_GPU::RestoreVramSysArea(Kernel::HLERequestContext& ctx) {
"GSP_GPU::RestoreVramSysArea", std::chrono::nanoseconds(-1), vblank_callback);
}
Result GSP_GPU::AcquireGpuRight(const Kernel::HLERequestContext& ctx,
const std::shared_ptr<Kernel::Process>& process, u32 flag,
bool blocking) {
const auto session_data = GetSessionData(ctx.Session());
LOG_DEBUG(Service_GSP, "called flag={:08X} process={} thread_id={}", flag, process->process_id,
session_data->thread_id);
void GSP_GPU::ApplyPerProgramSettings(const Kernel::Process& process) {
bool right_eye_disable_allow =
Common::Hacks::hack_manager.GetHackAllowMode(Common::Hacks::HackType::RIGHT_EYE_DISABLE,
process->codeset->program_id) !=
process.codeset->program_id) !=
Common::Hacks::HackAllowMode::DISALLOW;
bool requires_shader_fixup =
Common::Hacks::hack_manager.GetHackAllowMode(
Common::Hacks::HackType::REQUIRES_SHADER_FIXUP, process->codeset->program_id,
Common::Hacks::HackType::REQUIRES_SHADER_FIXUP, process.codeset->program_id,
Common::Hacks::HackAllowMode::DISALLOW) != Common::Hacks::HackAllowMode::DISALLOW;
delay_texture_copy_completion =
Common::Hacks::hack_manager.GetHackAllowMode(
Common::Hacks::HackType::DELAY_TEXTURE_COPY_COMPLETION, process->codeset->program_id,
Common::Hacks::HackType::DELAY_TEXTURE_COPY_COMPLETION, process.codeset->program_id,
Common::Hacks::HackAllowMode::DISALLOW) != Common::Hacks::HackAllowMode::DISALLOW;
auto& gpu = system.GPU();
gpu.ApplyPerProgramSettings(process->codeset->program_id);
gpu.ApplyPerProgramSettings(process.codeset->program_id);
gpu.GetRightEyeDisabler().SetEnabled(right_eye_disable_allow);
gpu.PicaCore().vs_setup.SetRequiresShaderFixup(requires_shader_fixup);
gpu.PicaCore().gs_setup.SetRequiresShaderFixup(requires_shader_fixup);
}
void GSP_GPU::GrantGpuRight(u32 thread_id, u32 client_thread_id, const Kernel::Process& process) {
ApplyPerProgramSettings(process);
system.GPU().Renderer().Rasterizer()->SwitchDiskResources(process.codeset->program_id);
thread_id_with_rights = thread_id;
active_client_thread_id = client_thread_id;
for (u32 screen_id = 0; screen_id < 2; ++screen_id) {
GetFrameBufferUpdate(thread_id_with_rights, screen_id)->is_dirty = 0;
}
}
Result GSP_GPU::AcquireGpuRight(const Kernel::HLERequestContext& ctx,
const std::shared_ptr<Kernel::Process>& process, bool force,
bool try_only) {
const auto session_data = GetSessionData(ctx.Session());
LOG_DEBUG(Service_GSP, "called force={} try={} process={} thread_id={}", force, try_only,
process->process_id, session_data->thread_id);
if (thread_id_with_rights == session_data->thread_id) {
return {ErrorDescription::AlreadyDone, ErrorModule::GX, ErrorSummary::Success,
ErrorLevel::Success};
}
gpu.Renderer().Rasterizer()->SwitchDiskResources(process->codeset->program_id);
if (blocking) {
// TODO: The thread should be put to sleep until acquired.
ASSERT_MSG(thread_id_with_rights == std::numeric_limits<u32>::max(),
"Sleeping for GPU right is not yet supported.");
} else if (thread_id_with_rights != std::numeric_limits<u32>::max()) {
return {ErrorDescription::Busy, ErrorModule::GX, ErrorSummary::WouldBlock,
ErrorLevel::Status};
if (force && !force_mode) {
// The first forced AcquireRight steals the right and switches the GSP
// module to force mode until the system restarts.
if (thread_id_with_rights != std::numeric_limits<u32>::max()) {
LOG_WARNING(Service_GSP, "GPU right taken from thread {} by force",
thread_id_with_rights);
}
force_mode = true;
GrantGpuRight(session_data->thread_id, ctx.ClientThread()->thread_id, *process);
return ResultSuccess;
}
thread_id_with_rights = session_data->thread_id;
active_client_thread_id = ctx.ClientThread()->thread_id;
if (thread_id_with_rights != std::numeric_limits<u32>::max()) {
return ResultGpuRightBusy;
}
// In force mode, if AcquireRight is used without force, then it returns a
// special success error code.
if (force_mode && !force && !try_only) {
return ResultForceModeCanceled;
}
GrantGpuRight(session_data->thread_id, ctx.ClientThread()->thread_id, *process);
return ResultSuccess;
}
void GSP_GPU::DoResetGpuCore() {
// The GSP module turns on the LCD force black of both screens, resets and reinitialises the
// GPU and applies the framebuffers again. The screens stay black until SetLcdForceBlack(0)
// is called. TODO(PabloMK7): Emulate the low level details of a GPU reset.
Pica::ColorFill data{};
data.is_enabled.Assign(1);
system.GPU().SetColorFill(data);
force_buffer_swap[0] = true;
force_buffer_swap[1] = true;
}
void GSP_GPU::TryAcquireRight(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const auto process = rp.PopObject<Kernel::Process>();
const auto result = AcquireGpuRight(ctx, process, 0, false);
const auto result = AcquireGpuRight(ctx, process, false, true);
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(result);
@@ -970,19 +1213,55 @@ void GSP_GPU::TryAcquireRight(Kernel::HLERequestContext& ctx) {
void GSP_GPU::AcquireRight(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const auto flag = rp.Pop<u32>();
const bool force = rp.Pop<u8>() != 0;
const auto process = rp.PopObject<Kernel::Process>();
const auto result = AcquireGpuRight(ctx, process, flag, true);
const auto result = AcquireGpuRight(ctx, process, force, false);
if (result == ResultGpuRightBusy) {
// The GSP module waits until the holder releases the right, then gives it to the thread.
// Sleep the caller thread until the right is available.
const auto session_data = GetSessionData(ctx.Session());
auto callback = std::make_shared<ThreadCallback>(system, "GSP_GPU::AcquireRight",
ThreadCallback::Type::AcquireRight);
auto event =
ctx.SleepClientThread("GSP_GPU::AcquireRight", std::chrono::nanoseconds(-1), callback);
right_waiters.push_back({session_data->thread_id, ctx.ClientThread()->thread_id, force,
process, std::move(event), std::move(callback)});
return;
}
// The first forced AcquireRight resets the GPU core and
// makes SetLcdForceBlack(0) require the right.
if (force && !gpu_core_reset_by_force) {
gpu_core_reset_by_force = true;
DoResetGpuCore();
}
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(result);
}
void GSP_GPU::ReleaseRight(const SessionData* session_data) {
if (thread_id_with_rights == session_data->thread_id) {
thread_id_with_rights = std::numeric_limits<u32>::max();
active_client_thread_id = std::numeric_limits<u32>::max();
if (thread_id_with_rights != session_data->thread_id) {
return;
}
thread_id_with_rights = std::numeric_limits<u32>::max();
active_client_thread_id = std::numeric_limits<u32>::max();
// Wake up the first thread waiting in AcquireRight. In force mode, threads waiting without
// the flag are woken with ResultForceModeCanceled and do not get the right.
while (!right_waiters.empty()) {
auto wait_context = std::move(right_waiters.front());
right_waiters.pop_front();
if (force_mode && !wait_context.force) {
wait_context.callback->SetResult(ResultForceModeCanceled);
wait_context.event->Signal();
continue;
}
GrantGpuRight(wait_context.thread_id, wait_context.client_thread_id, *wait_context.process);
wait_context.event->Signal();
break;
}
}
@@ -999,20 +1278,6 @@ void GSP_GPU::ReleaseRight(Kernel::HLERequestContext& ctx) {
LOG_WARNING(Service_GSP, "called");
}
void GSP_GPU::StoreDataCache(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
[[maybe_unused]] u32 address = rp.Pop<u32>();
[[maybe_unused]] u32 size = rp.Pop<u32>();
auto process = rp.PopObject<Kernel::Process>();
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
rb.Push(ResultSuccess);
LOG_TRACE(Service_GSP, "(STUBBED) called address=0x{:08X}, size=0x{:08X}, process={}", address,
size, process->process_id);
}
void GSP_GPU::SetLedForceOff(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
@@ -1025,12 +1290,21 @@ void GSP_GPU::SetLedForceOff(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_GSP, "(STUBBED) called");
}
void GSP_GPU::SetTestCommand(Kernel::HLERequestContext& ctx) {
Unsupported(ctx);
}
void GSP_GPU::SetInternalPriorities(Kernel::HLERequestContext& ctx) {
IPC::RequestParser rp(ctx);
const auto priority = rp.Pop<u32>();
const auto priority_with_rights = rp.Pop<u32>();
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
if (priority < 0x10 || priority > 0x3F || priority_with_rights < 0x11 ||
priority_with_rights > 0x3F) {
rb.Push(ResultInvalidPriority);
return;
}
rb.Push(ResultSuccess);
LOG_DEBUG(Service_GSP, "(STUBBED) called priority={:#02X}, priority_with_rights={:#02X}",
@@ -1063,6 +1337,9 @@ void GSP_GPU::serialize(Archive& ar, const unsigned int) {
ar & cached_framebuffer_infos;
ar & force_buffer_swap;
ar & vram_backup_handler;
ar & right_waiters;
ar & force_mode;
ar & gpu_core_reset_by_force;
}
SERIALIZE_IMPL(GSP_GPU)
@@ -1071,19 +1348,19 @@ GSP_GPU::GSP_GPU(Core::System& system) : ServiceFramework("gsp::Gpu", 4), system
// clang-format off
{0x0001, &GSP_GPU::WriteHWRegs, "WriteHWRegs"},
{0x0002, &GSP_GPU::WriteHWRegsWithMask, "WriteHWRegsWithMask"},
{0x0003, nullptr, "WriteHWRegRepeat"},
{0x0003, &GSP_GPU::WriteHWRegRepeat, "WriteHWRegRepeat"},
{0x0004, &GSP_GPU::ReadHWRegs, "ReadHWRegs"},
{0x0005, &GSP_GPU::SetBufferSwap, "SetBufferSwap"},
{0x0006, nullptr, "SetCommandList"},
{0x0007, nullptr, "RequestDma"},
{0x0006, &GSP_GPU::SetCommandList, "SetCommandList"},
{0x0007, &GSP_GPU::RequestDma, "RequestDma"},
{0x0008, &GSP_GPU::FlushDataCache, "FlushDataCache"},
{0x0009, &GSP_GPU::InvalidateDataCache, "InvalidateDataCache"},
{0x000A, nullptr, "RegisterInterruptEvents"},
{0x000A, &GSP_GPU::RegisterInterruptEvents, "RegisterInterruptEvents"},
{0x000B, &GSP_GPU::SetLcdForceBlack, "SetLcdForceBlack"},
{0x000C, &GSP_GPU::TriggerCmdReqQueue, "TriggerCmdReqQueue"},
{0x000D, nullptr, "SetDisplayTransfer"},
{0x000E, nullptr, "SetTextureCopy"},
{0x000F, nullptr, "SetMemoryFill"},
{0x000D, &GSP_GPU::SetDisplayTransfer, "SetDisplayTransfer"},
{0x000E, &GSP_GPU::SetTextureCopy, "SetTextureCopy"},
{0x000F, &GSP_GPU::SetMemoryFill, "SetMemoryFill"},
{0x0010, &GSP_GPU::SetAxiConfigQoSMode, "SetAxiConfigQoSMode"},
{0x0011, &GSP_GPU::SetPerfLogMode, "SetPerfLogMode"},
{0x0012, &GSP_GPU::GetPerfLog, "GetPerfLog"},
@@ -1095,9 +1372,9 @@ GSP_GPU::GSP_GPU(Core::System& system) : ServiceFramework("gsp::Gpu", 4), system
{0x0018, &GSP_GPU::ImportDisplayCaptureInfo, "ImportDisplayCaptureInfo"},
{0x0019, &GSP_GPU::SaveVramSysArea, "SaveVramSysArea"},
{0x001A, &GSP_GPU::RestoreVramSysArea, "RestoreVramSysArea"},
{0x001B, nullptr, "ResetGpuCore"},
{0x001B, &GSP_GPU::ResetGpuCore, "ResetGpuCore"},
{0x001C, &GSP_GPU::SetLedForceOff, "SetLedForceOff"},
{0x001D, nullptr, "SetTestCommand"},
{0x001D, &GSP_GPU::SetTestCommand, "SetTestCommand"},
{0x001E, &GSP_GPU::SetInternalPriorities, "SetInternalPriorities"},
{0x001F, &GSP_GPU::StoreDataCache, "StoreDataCache"},
// clang-format on
+71 -7
View File
@@ -1,11 +1,12 @@
// Copyright 2014-2026 Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the misc/licenses/gplv2.txt file included.
// Licensed under GPLv3 or any later version
// Refer to the LICENSE.txt file included.
#pragma once
#include <array>
#include <cstddef>
#include <deque>
#include <memory>
#include <optional>
#include <string>
@@ -61,8 +62,8 @@ struct FrameBufferInfo {
static_assert(sizeof(FrameBufferInfo) == 0x1c, "Struct has incorrect size");
struct FrameBufferUpdate {
BitField<0, 1, u8> index; // Index used for GSP::SetBufferSwap
BitField<0, 1, u8> is_dirty; // true if GSP should update GPU framebuffer registers
u8 index; // Index of the framebuffer info to use
u8 is_dirty; // Non-zero if GSP should update GPU framebuffer registers
u16 pad1;
FrameBufferInfo framebuffer_info[2];
@@ -204,7 +205,7 @@ public:
/**
* Retrieves the framebuffer info stored in the GSP shared memory for the
* specified screen index and thread id.
* specified screen index and thread id, and clears its update flag.
* @param thread_id GSP thread id of the process that accesses the structure that we are
* requesting.
* @param screen_index Index of the screen we are requesting (Top = 0, Bottom = 1).
@@ -216,6 +217,9 @@ public:
/// Gets a pointer to a thread command buffer in GSP shared memory
CommandBuffer* GetCommandBuffer(u32 thread_id);
/// Gets the pointer to the thread's framebuffer update slot for a screen in GSP shared memory
FrameBufferUpdate* GetFrameBufferUpdate(u32 thread_id, u32 screen_index);
/// Gets a pointer to the interrupt relay queue for a given thread index
InterruptRelayQueue* GetInterruptRelayQueue(u32 thread_id);
@@ -246,6 +250,8 @@ private:
void ProcessPendingInterrupt(size_t pending_interrupt_id);
bool HasGpuRight(Kernel::HLERequestContext& ctx);
void ProcessPendingInterruptImpl(InterruptId interrupt_id, u32 thread_id);
/**
@@ -273,9 +279,19 @@ private:
*/
void WriteHWRegsWithMask(Kernel::HLERequestContext& ctx);
/// Write the data to the same register repeatedly.
void WriteHWRegRepeat(Kernel::HLERequestContext& ctx);
/// Read a GSP GPU hardware register
void ReadHWRegs(Kernel::HLERequestContext& ctx);
/// Helper for stubbed commands.
void Unsupported(Kernel::HLERequestContext& ctx);
void DoCacheOperation(Kernel::HLERequestContext& ctx, bool allow_vram);
void DoResetGpuCore();
/**
* GSP_GPU::SetBufferSwap service function
*
@@ -289,6 +305,10 @@ private:
*/
void SetBufferSwap(Kernel::HLERequestContext& ctx);
void SetCommandList(Kernel::HLERequestContext& ctx);
void RequestDma(Kernel::HLERequestContext& ctx);
/**
* GSP_GPU::FlushDataCache service function
*
@@ -319,6 +339,8 @@ private:
*/
void InvalidateDataCache(Kernel::HLERequestContext& ctx);
void RegisterInterruptEvents(Kernel::HLERequestContext& ctx);
/**
* GSP_GPU::SetLcdForceBlack service function
*
@@ -334,6 +356,12 @@ private:
/// This triggers handling of the GX command written to the command buffer in shared memory.
void TriggerCmdReqQueue(Kernel::HLERequestContext& ctx);
void SetDisplayTransfer(Kernel::HLERequestContext& ctx);
void SetTextureCopy(Kernel::HLERequestContext& ctx);
void SetMemoryFill(Kernel::HLERequestContext& ctx);
/**
* GSP_GPU::SetAxiConfigQoSMode service function
* Inputs:
@@ -468,6 +496,8 @@ private:
/// Force the 3D LED State (0 = On, Non-Zero = Off)
void SetLedForceOff(Kernel::HLERequestContext& ctx);
void SetTestCommand(Kernel::HLERequestContext& ctx);
/**
* GSP_GPU::SetInternalPriorities service function
* Inputs:
@@ -479,6 +509,8 @@ private:
*/
void SetInternalPriorities(Kernel::HLERequestContext& ctx);
void ResetGpuCore(Kernel::HLERequestContext& ctx);
/// Returns the session data for the specified registered thread id, or nullptr if not found.
SessionData* FindRegisteredThreadData(u32 thread_id);
@@ -487,8 +519,40 @@ private:
std::unique_ptr<Kernel::SessionRequestHandler::SessionDataBase> MakeSessionData() override;
Result AcquireGpuRight(const Kernel::HLERequestContext& ctx,
const std::shared_ptr<Kernel::Process>& process, u32 flag,
bool blocking);
const std::shared_ptr<Kernel::Process>& process, bool force,
bool try_only);
/// Set by forced AcquireRight, from then onwards AcquireRight without the force flag does
/// not give the right (until the system restarts).
bool force_mode = false;
bool gpu_core_reset_by_force = false;
void ApplyPerProgramSettings(const Kernel::Process& process);
/// Gives the GPU right to a GSP thread.
void GrantGpuRight(u32 thread_id, u32 client_thread_id, const Kernel::Process& process);
struct RightWaitContext {
u32 thread_id;
u32 client_thread_id;
bool force;
std::shared_ptr<Kernel::Process> process;
std::shared_ptr<Kernel::Event> event;
std::shared_ptr<ThreadCallback> callback;
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar & thread_id;
ar & client_thread_id;
ar & force;
ar & process;
ar & event;
ar & callback;
}
};
/// Threads waiting for the GPU right in request order.
std::deque<RightWaitContext> right_waiters;
Core::System& system;
+105 -33
View File
@@ -1,6 +1,6 @@
// Copyright 2023-2026 Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the misc/licenses/gplv2.txt file included.
// Licensed under GPLv3 or any later version
// Refer to the LICENSE.txt file included.
#include "common/archives.h"
#include "common/hacks/hack_manager.h"
@@ -101,28 +101,45 @@ GPU::GPU(Core::System& system, Frontend::EmuWindow& emu_window,
GPU::~GPU() = default;
PAddr GPU::VirtualToPhysicalAddress(VAddr addr) {
if (addr == 0) {
return 0;
}
constexpr Result ResultInvalidGpuBuffer(ErrorDescription::InvalidAddress, ErrorModule::GX,
ErrorSummary::InvalidArgument,
ErrorLevel::Usage); // 0xE0E02BF5
constexpr Result ResultUnknownGxCommand(ErrorDescription::NotImplemented, ErrorModule::GX,
ErrorSummary::NotSupported,
ErrorLevel::Usage); // 0xE0C02BF4
std::optional<PAddr> GPU::VirtualToPhysicalAddress(VAddr addr, u32 size) {
// Address translation of the GSP module.
// The linear heaps and the New 3DS extra memory must contain both the start,
// and the end of the buffer, VRAM only the start. The ends of the
// ranges are inclusive.
const VAddr end = addr + size;
const auto contains = [addr, end](VAddr range_start, VAddr range_end) {
return addr >= range_start && addr <= range_end && end >= range_start && end <= range_end;
};
if (contains(Memory::LINEAR_HEAP_VADDR, Memory::LINEAR_HEAP_VADDR_END)) {
return addr - Memory::LINEAR_HEAP_VADDR + Memory::FCRAM_PADDR;
}
if (contains(Memory::NEW_LINEAR_HEAP_VADDR, Memory::NEW_LINEAR_HEAP_VADDR_END)) {
return addr - Memory::NEW_LINEAR_HEAP_VADDR + Memory::FCRAM_PADDR;
}
if (addr >= Memory::VRAM_VADDR && addr <= Memory::VRAM_VADDR_END) {
return addr - Memory::VRAM_VADDR + Memory::VRAM_PADDR;
}
if (addr >= Memory::LINEAR_HEAP_VADDR && addr <= Memory::LINEAR_HEAP_VADDR_END) {
return addr - Memory::LINEAR_HEAP_VADDR + Memory::FCRAM_PADDR;
}
if (addr >= Memory::NEW_LINEAR_HEAP_VADDR && addr <= Memory::NEW_LINEAR_HEAP_VADDR_END) {
return addr - Memory::NEW_LINEAR_HEAP_VADDR + Memory::FCRAM_PADDR;
if (contains(Memory::N3DS_EXTRA_RAM_VADDR, Memory::N3DS_EXTRA_RAM_VADDR_END)) {
return addr - Memory::N3DS_EXTRA_RAM_VADDR + Memory::N3DS_EXTRA_RAM_PADDR;
}
// TODO(PabloMK7): Find a better way to handle this.
PAddr plg_fb_addr;
if (addr >= Memory::PLUGIN_3GX_FB_VADDR && addr <= Memory::PLUGIN_3GX_FB_VADDR_END &&
if (contains(Memory::PLUGIN_3GX_FB_VADDR, Memory::PLUGIN_3GX_FB_VADDR_END) &&
(plg_fb_addr = impl->system.Memory().Plugin3GXFramebufferAddress())) {
return addr - Memory::PLUGIN_3GX_FB_VADDR + plg_fb_addr;
}
LOG_ERROR(HW_Memory, "Unknown virtual address @ 0x{:08X}", addr);
return addr;
LOG_ERROR(HW_GPU, "Invalid GPU buffer address 0x{:08X} (size 0x{:X})", addr, size);
return std::nullopt;
}
void GPU::SetInterruptHandler(Service::GSP::InterruptHandler handler) {
@@ -142,9 +159,10 @@ void GPU::ClearAll(bool flush) {
impl->rasterizer->ClearAll(flush);
}
void GPU::Execute(const Service::GSP::Command& command) {
Result GPU::Execute(const Service::GSP::Command& command) {
using Service::GSP::CommandId;
auto& regs = impl->pica.regs;
Result result = ResultSuccess;
switch (command.id) {
case CommandId::RequestDma: {
@@ -176,8 +194,10 @@ void GPU::Execute(const Service::GSP::Command& command) {
auto& params = command.submit_gpu_cmdlist;
auto& cmdbuffer = regs.internal.pipeline.command_buffer;
// Write to the command buffer GPU registers
cmdbuffer.addr[0].Assign(VirtualToPhysicalAddress(params.address) >> 3);
// Write to the command buffer GPU registers.
// The GSP module uses address 0 when the buffer is not in GPU visible memory.
const PAddr address = VirtualToPhysicalAddress(params.address, params.size).value_or(0);
cmdbuffer.addr[0].Assign(address >> 3);
cmdbuffer.size[0].Assign(params.size >> 3);
cmdbuffer.trigger[0] = 1;
@@ -189,19 +209,36 @@ void GPU::Execute(const Service::GSP::Command& command) {
auto& params = command.memory_fill;
auto& memfill = regs.memory_fill_config;
const auto translate = [this](u32 start, u32 end) -> std::optional<PAddr> {
if (start >= end) {
return std::nullopt;
}
return VirtualToPhysicalAddress(start, end - start);
};
// Write to the memory fill GPU registers.
// If both buffers are set GSP dispatches PSC0 only.
const bool has_both_bufs = params.start1 != 0 && params.start2 != 0;
if (params.start1 != 0) {
memfill[0].address_start = VirtualToPhysicalAddress(params.start1) >> 3;
memfill[0].address_end = VirtualToPhysicalAddress(params.end1) >> 3;
const auto start = translate(params.start1, params.end1);
if (!start) {
result = ResultInvalidGpuBuffer;
break;
}
memfill[0].address_start = *start >> 3;
memfill[0].address_end = (*start + params.end1 - params.start1) >> 3;
memfill[0].value_32bit = params.value1;
memfill[0].control = params.control1;
MemoryFill(0, has_both_bufs ? std::numeric_limits<u32>::max() : 0);
}
if (params.start2 != 0) {
memfill[1].address_start = VirtualToPhysicalAddress(params.start2) >> 3;
memfill[1].address_end = VirtualToPhysicalAddress(params.end2) >> 3;
const auto start = translate(params.start2, params.end2);
if (!start) {
result = ResultInvalidGpuBuffer;
break;
}
memfill[1].address_start = *start >> 3;
memfill[1].address_end = (*start + params.end2 - params.start2) >> 3;
memfill[1].value_32bit = params.value2;
memfill[1].control = params.control2;
MemoryFill(1, has_both_bufs ? 0 : 1);
@@ -213,8 +250,12 @@ void GPU::Execute(const Service::GSP::Command& command) {
auto& display_transfer = regs.display_transfer_config;
// Write to the transfer engine GPU registers.
display_transfer.input_address = VirtualToPhysicalAddress(params.in_buffer_address) >> 3;
display_transfer.output_address = VirtualToPhysicalAddress(params.out_buffer_address) >> 3;
// The GSP module uses address 0 when the buffer is not in GPU visible memory (only the
// first byte is checked).
display_transfer.input_address =
VirtualToPhysicalAddress(params.in_buffer_address, 1).value_or(0) >> 3;
display_transfer.output_address =
VirtualToPhysicalAddress(params.out_buffer_address, 1).value_or(0) >> 3;
display_transfer.input_size = params.in_buffer_size;
display_transfer.output_size = params.out_buffer_size;
display_transfer.flags = params.flags;
@@ -229,8 +270,11 @@ void GPU::Execute(const Service::GSP::Command& command) {
auto& texture_copy = regs.display_transfer_config;
// Write to the transfer engine GPU registers.
texture_copy.input_address = VirtualToPhysicalAddress(params.in_buffer_address) >> 3;
texture_copy.output_address = VirtualToPhysicalAddress(params.out_buffer_address) >> 3;
// The GSP module uses address 0 when the buffer is not in GPU visible memory.
texture_copy.input_address =
VirtualToPhysicalAddress(params.in_buffer_address, params.size).value_or(0) >> 3;
texture_copy.output_address =
VirtualToPhysicalAddress(params.out_buffer_address, params.size).value_or(0) >> 3;
texture_copy.texture_copy.size = params.size;
texture_copy.texture_copy.input_size = params.in_width_gap;
texture_copy.texture_copy.output_size = params.out_width_gap;
@@ -248,31 +292,48 @@ void GPU::Execute(const Service::GSP::Command& command) {
}
default:
LOG_ERROR(HW_GPU, "Unknown command {:#08X}", command.id.Value());
result = ResultUnknownGxCommand;
}
// Notify debugger that a GSP command was processed.
if (impl->debug_context) {
impl->debug_context->OnEvent(Pica::DebugContext::Event::GSPCommandProcessed, &command);
}
return result;
}
void GPU::SetBufferSwap(u32 screen_id, const Service::GSP::FrameBufferInfo& info) {
const PAddr phys_address_left = VirtualToPhysicalAddress(info.address_left);
const PAddr phys_address_right = VirtualToPhysicalAddress(info.address_right);
// The GSP module does not apply a framebuffer outside GPU visible memory. The right address
// is used only for the top screen in stereoscopic mode and when it is not zero.
// Otherwise the left address is used for both eyes.
const auto phys_address_left = VirtualToPhysicalAddress(info.address_left, info.stride);
if (!phys_address_left) {
return;
}
// Update framebuffer properties.
auto& framebuffer = impl->pica.regs.framebuffer_config[screen_id];
if (screen_id == 0) {
const bool stereoscopic = (info.format & 0x70) == 0x20;
const PAddr phys_address_right =
(!stereoscopic || info.address_right == 0)
? *phys_address_left
: VirtualToPhysicalAddress(info.address_right, info.stride).value_or(0);
if (info.active_fb == 0) {
framebuffer.address_right1 = phys_address_right;
} else {
framebuffer.address_right2 = phys_address_right;
}
}
if (info.active_fb == 0) {
framebuffer.address_left1 = phys_address_left;
framebuffer.address_right1 = phys_address_right;
framebuffer.address_left1 = *phys_address_left;
} else {
framebuffer.address_left2 = phys_address_left;
framebuffer.address_right2 = phys_address_right;
framebuffer.address_left2 = *phys_address_left;
}
framebuffer.stride = info.stride;
framebuffer.format = info.format;
framebuffer.active_fb = info.shown_fb;
framebuffer.format = (framebuffer.format & 0xFFFF0000) | (info.format & 0xFFFF);
framebuffer.active_fb = info.shown_fb & 0xFF;
// Notify debugger about the buffer swap.
if (impl->debug_context) {
@@ -407,6 +468,17 @@ void GPU::SubmitCmdList(u32 index) {
// Forward command list processing to the PICA core.
const PAddr addr = config.GetPhysicalAddress(index);
const u32 size = config.GetSize(index);
// Do not process the list if the memory range is not sane.
auto& memory = impl->system.Memory();
const u8* head = memory.GetPhysicalPointer(addr);
if (!head || (size != 0 && memory.GetPhysicalPointer(addr + size - 1) != head + size - 1)) {
LOG_ERROR(HW_GPU, "Invalid command list address 0x{:08X} (size 0x{:X})", addr, size);
impl->signal_interrupt(Service::GSP::InterruptId::P3D, 0);
config.trigger[index] = 0;
return;
}
impl->pica.ProcessCmdList(addr, size,
!right_eye_disabler->ShouldAllowCmdQueueTrigger(addr, size));
config.trigger[index] = 0;
+8 -4
View File
@@ -1,13 +1,15 @@
// Copyright 2023-2026 Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the misc/licenses/gplv2.txt file included.
// Licensed under GPLv3 or any later version
// Refer to the LICENSE.txt file included.
#pragma once
#include <functional>
#include <memory>
#include <optional>
#include <boost/serialization/access.hpp>
#include "core/hle/result.h"
#include "core/hle/service/gsp/gsp_interrupt.h"
namespace Service::GSP {
@@ -61,7 +63,7 @@ public:
void ClearAll(bool flush);
/// Executes the provided GSP command.
void Execute(const Service::GSP::Command& command);
Result Execute(const Service::GSP::Command& command);
/// Updates GPU display framebuffer configuration using the specified parameters.
void SetBufferSwap(u32 screen_id, const Service::GSP::FrameBufferInfo& info);
@@ -120,7 +122,9 @@ private:
struct Impl;
std::unique_ptr<Impl> impl;
PAddr VirtualToPhysicalAddress(VAddr addr);
/// Translates the address of a GPU buffer as the GSP module does, or returns std::nullopt when
/// the buffer is not in GPU visible memory.
std::optional<PAddr> VirtualToPhysicalAddress(VAddr addr, u32 size);
};
} // namespace VideoCore