mirror of
				https://github.com/PabloMK7/citra.git
				synced 2025-10-31 05:40:04 +00:00 
			
		
		
		
	Merge pull request #3324 from Subv/gspgpu
Services: Keep track of per-session data in GSPGPU
This commit is contained in:
		
						commit
						9b647d459b
					
				
					 3 changed files with 152 additions and 58 deletions
				
			
		|  | @ -59,7 +59,9 @@ public: | |||
| 
 | ||||
|     /// Empty placeholder structure for services with no per-session data. The session data classes
 | ||||
|     /// in each service must inherit from this.
 | ||||
|     struct SessionDataBase {}; | ||||
|     struct SessionDataBase { | ||||
|         virtual ~SessionDataBase() = default; | ||||
|     }; | ||||
| 
 | ||||
| protected: | ||||
|     /// Creates the storage for the session data of the service.
 | ||||
|  |  | |||
|  | @ -9,7 +9,6 @@ | |||
| #include "core/core.h" | ||||
| #include "core/hle/ipc.h" | ||||
| #include "core/hle/ipc_helpers.h" | ||||
| #include "core/hle/kernel/event.h" | ||||
| #include "core/hle/kernel/handle_table.h" | ||||
| #include "core/hle/kernel/shared_memory.h" | ||||
| #include "core/hle/result.h" | ||||
|  | @ -51,6 +50,20 @@ constexpr ResultCode ERR_REGS_INVALID_SIZE(ErrorDescription::InvalidSize, ErrorM | |||
|                                            ErrorSummary::InvalidArgument, | ||||
|                                            ErrorLevel::Usage); // 0xE0E02BEC
 | ||||
| 
 | ||||
| /// Maximum number of threads that can be registered at the same time in the GSP module.
 | ||||
| constexpr u32 MaxGSPThreads = 4; | ||||
| 
 | ||||
| /// Thread ids currently in use by the sessions connected to the GSPGPU service.
 | ||||
| static std::array<bool, MaxGSPThreads> used_thread_ids = {false, false, false, false}; | ||||
| 
 | ||||
| static u32 GetUnusedThreadId() { | ||||
|     for (u32 id = 0; id < MaxGSPThreads; ++id) { | ||||
|         if (!used_thread_ids[id]) | ||||
|             return id; | ||||
|     } | ||||
|     ASSERT_MSG(false, "All GSP threads are in use"); | ||||
| } | ||||
| 
 | ||||
| /// Gets a pointer to a thread command buffer in GSP shared memory
 | ||||
| static inline u8* GetCommandBuffer(Kernel::SharedPtr<Kernel::SharedMemory> shared_memory, | ||||
|                                    u32 thread_id) { | ||||
|  | @ -319,12 +332,16 @@ void GSP_GPU::RegisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) { | |||
|     IPC::RequestParser rp(ctx, 0x13, 1, 2); | ||||
|     u32 flags = rp.Pop<u32>(); | ||||
| 
 | ||||
|     interrupt_event = rp.PopObject<Kernel::Event>(); | ||||
|     auto interrupt_event = rp.PopObject<Kernel::Event>(); | ||||
|     // TODO(mailwl): return right error code instead assert
 | ||||
|     ASSERT_MSG((interrupt_event != nullptr), "handle is not valid!"); | ||||
| 
 | ||||
|     interrupt_event->name = "GSP_GSP_GPU::interrupt_event"; | ||||
| 
 | ||||
|     SessionData* session_data = GetSessionData(ctx.Session()); | ||||
|     session_data->interrupt_event = std::move(interrupt_event); | ||||
|     session_data->registered = true; | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); | ||||
| 
 | ||||
|     if (first_initialization) { | ||||
|  | @ -335,25 +352,60 @@ void GSP_GPU::RegisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) { | |||
|         rb.Push(RESULT_SUCCESS); | ||||
|     } | ||||
| 
 | ||||
|     rb.Push(thread_id); | ||||
|     rb.Push(session_data->thread_id); | ||||
|     rb.PushCopyObjects(shared_memory); | ||||
| 
 | ||||
|     thread_id++; | ||||
|     interrupt_event->Signal(); // TODO(bunnei): Is this correct?
 | ||||
| 
 | ||||
|     LOG_WARNING(Service_GSP, "called, flags=0x%08X", flags); | ||||
|     LOG_DEBUG(Service_GSP, "called, flags=0x%08X", flags); | ||||
| } | ||||
| 
 | ||||
| void GSP_GPU::UnregisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x14, 0, 0); | ||||
| 
 | ||||
|     thread_id = 0; | ||||
|     interrupt_event = nullptr; | ||||
|     SessionData* session_data = GetSessionData(ctx.Session()); | ||||
|     session_data->interrupt_event = nullptr; | ||||
|     session_data->registered = false; | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
| 
 | ||||
|     LOG_WARNING(Service_GSP, "(STUBBED) called"); | ||||
|     LOG_DEBUG(Service_GSP, "called"); | ||||
| } | ||||
| 
 | ||||
| void GSP_GPU::SignalInterruptForThread(InterruptId interrupt_id, u32 thread_id) { | ||||
|     SessionData* session_data = FindRegisteredThreadData(thread_id); | ||||
|     if (session_data == nullptr) | ||||
|         return; | ||||
| 
 | ||||
|     auto interrupt_event = session_data->interrupt_event; | ||||
|     if (interrupt_event == nullptr) { | ||||
|         LOG_WARNING(Service_GSP, "cannot synchronize until GSP event has been created!"); | ||||
|         return; | ||||
|     } | ||||
|     InterruptRelayQueue* interrupt_relay_queue = GetInterruptRelayQueue(shared_memory, thread_id); | ||||
|     u8 next = interrupt_relay_queue->index; | ||||
|     next += interrupt_relay_queue->number_interrupts; | ||||
|     next = next % 0x34; // 0x34 is the number of interrupt slots
 | ||||
| 
 | ||||
|     interrupt_relay_queue->number_interrupts += 1; | ||||
| 
 | ||||
|     interrupt_relay_queue->slot[next] = interrupt_id; | ||||
|     interrupt_relay_queue->error_code = 0x0; // No error
 | ||||
| 
 | ||||
|     // Update framebuffer information if requested
 | ||||
|     // TODO(yuriks): Confirm where this code should be called. It is definitely updated without
 | ||||
|     //               executing any GSP commands, only waiting on the event.
 | ||||
|     // TODO(Subv): The real GSP module triggers PDC0 after updating both the top and bottom
 | ||||
|     // screen, it is currently unknown what PDC1 does.
 | ||||
|     int screen_id = | ||||
|         (interrupt_id == InterruptId::PDC0) ? 0 : (interrupt_id == InterruptId::PDC1) ? 1 : -1; | ||||
|     if (screen_id != -1) { | ||||
|         FrameBufferUpdate* info = GetFrameBufferInfo(thread_id, screen_id); | ||||
|         if (info->is_dirty) { | ||||
|             GSP::SetBufferSwap(screen_id, info->framebuffer_info[info->index]); | ||||
|             info->is_dirty.Assign(false); | ||||
|         } | ||||
|     } | ||||
|     interrupt_event->Signal(); | ||||
| } | ||||
| 
 | ||||
| /**
 | ||||
|  | @ -363,43 +415,26 @@ void GSP_GPU::UnregisterInterruptRelayQueue(Kernel::HLERequestContext& ctx) { | |||
|  * @todo This probably does not belong in the GSP module, instead move to video_core | ||||
|  */ | ||||
| void GSP_GPU::SignalInterrupt(InterruptId interrupt_id) { | ||||
|     if (!gpu_right_acquired) { | ||||
|         return; | ||||
|     } | ||||
|     if (nullptr == interrupt_event) { | ||||
|         LOG_WARNING(Service_GSP, "cannot synchronize until GSP event has been created!"); | ||||
|         return; | ||||
|     } | ||||
|     if (nullptr == shared_memory) { | ||||
|         LOG_WARNING(Service_GSP, "cannot synchronize until GSP shared memory has been created!"); | ||||
|         return; | ||||
|     } | ||||
|     for (int thread_id = 0; thread_id < 0x4; ++thread_id) { | ||||
|         InterruptRelayQueue* interrupt_relay_queue = | ||||
|             GetInterruptRelayQueue(shared_memory, thread_id); | ||||
|         u8 next = interrupt_relay_queue->index; | ||||
|         next += interrupt_relay_queue->number_interrupts; | ||||
|         next = next % 0x34; // 0x34 is the number of interrupt slots
 | ||||
| 
 | ||||
|         interrupt_relay_queue->number_interrupts += 1; | ||||
| 
 | ||||
|         interrupt_relay_queue->slot[next] = interrupt_id; | ||||
|         interrupt_relay_queue->error_code = 0x0; // No error
 | ||||
| 
 | ||||
|         // Update framebuffer information if requested
 | ||||
|         // TODO(yuriks): Confirm where this code should be called. It is definitely updated without
 | ||||
|         //               executing any GSP commands, only waiting on the event.
 | ||||
|         int screen_id = | ||||
|             (interrupt_id == InterruptId::PDC0) ? 0 : (interrupt_id == InterruptId::PDC1) ? 1 : -1; | ||||
|         if (screen_id != -1) { | ||||
|             FrameBufferUpdate* info = GetFrameBufferInfo(thread_id, screen_id); | ||||
|             if (info->is_dirty) { | ||||
|                 GSP::SetBufferSwap(screen_id, info->framebuffer_info[info->index]); | ||||
|                 info->is_dirty.Assign(false); | ||||
|             } | ||||
|     // The PDC0 and PDC1 interrupts are fired even if the GPU right hasn't been acquired.
 | ||||
|     // 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) { | ||||
|         for (u32 thread_id = 0; thread_id < MaxGSPThreads; ++thread_id) { | ||||
|             SignalInterruptForThread(interrupt_id, thread_id); | ||||
|         } | ||||
|         return; | ||||
|     } | ||||
|     interrupt_event->Signal(); | ||||
| 
 | ||||
|     // For normal interrupts, don't do anything if no process has acquired the GPU right.
 | ||||
|     if (active_thread_id == -1) | ||||
|         return; | ||||
| 
 | ||||
|     SignalInterruptForThread(interrupt_id, active_thread_id); | ||||
| } | ||||
| 
 | ||||
| MICROPROFILE_DEFINE(GPU_GSP_DMA, "GPU", "GSP DMA", MP_RGB(100, 0, 255)); | ||||
|  | @ -622,18 +657,34 @@ void GSP_GPU::AcquireRight(Kernel::HLERequestContext& ctx) { | |||
|     u32 flag = rp.Pop<u32>(); | ||||
|     auto process = rp.PopObject<Kernel::Process>(); | ||||
| 
 | ||||
|     gpu_right_acquired = true; | ||||
|     SessionData* session_data = GetSessionData(ctx.Session()); | ||||
| 
 | ||||
|     LOG_WARNING(Service_GSP, "called flag=%08X process=%u thread_id=%u", flag, process->process_id, | ||||
|                 session_data->thread_id); | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
| 
 | ||||
|     LOG_WARNING(Service_GSP, "called flag=%08X process=%u", flag, process->process_id); | ||||
|     if (active_thread_id == session_data->thread_id) { | ||||
|         rb.Push(ResultCode(ErrorDescription::AlreadyDone, ErrorModule::GX, ErrorSummary::Success, | ||||
|                            ErrorLevel::Success)); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     // TODO(Subv): This case should put the caller thread to sleep until the right is released.
 | ||||
|     ASSERT_MSG(active_thread_id == -1, "GPU right has already been acquired"); | ||||
| 
 | ||||
|     active_thread_id = session_data->thread_id; | ||||
| 
 | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
| } | ||||
| 
 | ||||
| void GSP_GPU::ReleaseRight(Kernel::HLERequestContext& ctx) { | ||||
|     IPC::RequestParser rp(ctx, 0x17, 0, 0); | ||||
| 
 | ||||
|     gpu_right_acquired = false; | ||||
|     SessionData* session_data = GetSessionData(ctx.Session()); | ||||
|     ASSERT_MSG(active_thread_id == session_data->thread_id, | ||||
|                "Wrong thread tried to release GPU right"); | ||||
|     active_thread_id = -1; | ||||
| 
 | ||||
|     IPC::RequestBuilder rb = rp.MakeBuilder(1, 0); | ||||
|     rb.Push(RESULT_SUCCESS); | ||||
|  | @ -655,6 +706,17 @@ void GSP_GPU::StoreDataCache(Kernel::HLERequestContext& ctx) { | |||
|               size, process->process_id); | ||||
| } | ||||
| 
 | ||||
| SessionData* GSP_GPU::FindRegisteredThreadData(u32 thread_id) { | ||||
|     for (auto& session_info : connected_sessions) { | ||||
|         SessionData* data = static_cast<SessionData*>(session_info.data.get()); | ||||
|         if (!data->registered) | ||||
|             continue; | ||||
|         if (data->thread_id == thread_id) | ||||
|             return data; | ||||
|     } | ||||
|     return nullptr; | ||||
| } | ||||
| 
 | ||||
| GSP_GPU::GSP_GPU() : ServiceFramework("gsp::Gpu", 2) { | ||||
|     static const FunctionInfo functions[] = { | ||||
|         {0x00010082, &GSP_GPU::WriteHWRegs, "WriteHWRegs"}, | ||||
|  | @ -691,17 +753,26 @@ GSP_GPU::GSP_GPU() : ServiceFramework("gsp::Gpu", 2) { | |||
|     }; | ||||
|     RegisterHandlers(functions); | ||||
| 
 | ||||
|     interrupt_event = nullptr; | ||||
| 
 | ||||
|     using Kernel::MemoryPermission; | ||||
|     shared_memory = Kernel::SharedMemory::Create(nullptr, 0x1000, MemoryPermission::ReadWrite, | ||||
|                                                  MemoryPermission::ReadWrite, 0, | ||||
|                                                  Kernel::MemoryRegion::BASE, "GSP:SharedMemory"); | ||||
| 
 | ||||
|     thread_id = 0; | ||||
|     gpu_right_acquired = false; | ||||
|     first_initialization = true; | ||||
| }; | ||||
| 
 | ||||
| SessionData::SessionData() { | ||||
|     // Assign a new thread id to this session when it connects. Note: In the real GSP service this
 | ||||
|     // is done through a real thread (svcCreateThread) but we have to simulate it since our HLE
 | ||||
|     // services don't have threads.
 | ||||
|     thread_id = GetUnusedThreadId(); | ||||
|     used_thread_ids[thread_id] = true; | ||||
| } | ||||
| 
 | ||||
| SessionData::~SessionData() { | ||||
|     // Free the thread id slot so that other sessions can use it.
 | ||||
|     used_thread_ids[thread_id] = false; | ||||
| } | ||||
| 
 | ||||
| } // namespace GSP
 | ||||
| } // namespace Service
 | ||||
|  |  | |||
|  | @ -8,12 +8,12 @@ | |||
| #include <string> | ||||
| #include "common/bit_field.h" | ||||
| #include "common/common_types.h" | ||||
| #include "core/hle/kernel/event.h" | ||||
| #include "core/hle/kernel/hle_ipc.h" | ||||
| #include "core/hle/result.h" | ||||
| #include "core/hle/service/service.h" | ||||
| 
 | ||||
| namespace Kernel { | ||||
| class Event; | ||||
| class SharedMemory; | ||||
| } // namespace Kernel
 | ||||
| 
 | ||||
|  | @ -179,7 +179,19 @@ struct CommandBuffer { | |||
| }; | ||||
| static_assert(sizeof(CommandBuffer) == 0x200, "CommandBuffer struct has incorrect size"); | ||||
| 
 | ||||
| class GSP_GPU final : public ServiceFramework<GSP_GPU> { | ||||
| struct SessionData : public Kernel::SessionRequestHandler::SessionDataBase { | ||||
|     SessionData(); | ||||
|     ~SessionData(); | ||||
| 
 | ||||
|     /// Event triggered when GSP interrupt has been signalled
 | ||||
|     Kernel::SharedPtr<Kernel::Event> interrupt_event; | ||||
|     /// Thread index into interrupt relay queue
 | ||||
|     u32 thread_id; | ||||
|     /// Whether RegisterInterruptRelayQueue was called for this session
 | ||||
|     bool registered = false; | ||||
| }; | ||||
| 
 | ||||
| class GSP_GPU final : public ServiceFramework<GSP_GPU, SessionData> { | ||||
| public: | ||||
|     GSP_GPU(); | ||||
|     ~GSP_GPU() = default; | ||||
|  | @ -201,6 +213,14 @@ public: | |||
|     FrameBufferUpdate* GetFrameBufferInfo(u32 thread_id, u32 screen_index); | ||||
| 
 | ||||
| private: | ||||
|     /**
 | ||||
|      * Signals that the specified interrupt type has occurred to userland code for the specified GSP | ||||
|      * thread id. | ||||
|      * @param interrupt_id ID of interrupt that is being signalled. | ||||
|      * @param thread_id GSP thread that will receive the interrupt. | ||||
|      */ | ||||
|     void SignalInterruptForThread(InterruptId interrupt_id, u32 thread_id); | ||||
| 
 | ||||
|     /**
 | ||||
|      * GSP_GPU::WriteHWRegs service function | ||||
|      * | ||||
|  | @ -351,14 +371,15 @@ private: | |||
|      */ | ||||
|     void StoreDataCache(Kernel::HLERequestContext& ctx); | ||||
| 
 | ||||
|     /// Event triggered when GSP interrupt has been signalled
 | ||||
|     Kernel::SharedPtr<Kernel::Event> interrupt_event; | ||||
|     /// GSP shared memoryings
 | ||||
|     Kernel::SharedPtr<Kernel::SharedMemory> shared_memory; | ||||
|     /// Thread index into interrupt relay queue
 | ||||
|     u32 thread_id = 0; | ||||
|     /// Returns the session data for the specified registered thread id, or nullptr if not found.
 | ||||
|     SessionData* FindRegisteredThreadData(u32 thread_id); | ||||
| 
 | ||||
|     /// GSP shared memory
 | ||||
|     Kernel::SharedPtr<Kernel::SharedMemory> shared_memory; | ||||
| 
 | ||||
|     /// Thread id that currently has GPU rights or -1 if none.
 | ||||
|     int active_thread_id = -1; | ||||
| 
 | ||||
|     bool gpu_right_acquired = false; | ||||
|     bool first_initialization = true; | ||||
| }; | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue