mirror of
https://github.com/azahar-emu/azahar.git
synced 2026-11-13 01:29:59 +01:00
core: gsp: Fix several GSP accuracy issues (#2678)
This commit is contained in:
1 parent
68202bc4ff
commit
3c3e54989e
4 files changed
+566
-149
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user