diff options
| author | 2014-07-22 22:59:26 -0400 | |
|---|---|---|
| committer | 2014-08-05 23:57:53 -0400 | |
| commit | ec14ffe1cda04cd098ce07f3d3ad96c253e91eed (patch) | |
| tree | fe459fc75a4ba62ed1a730e8a4ccbdffa2846dca /src | |
| parent | MemMap: Fixed typo with GetPointer to VRAM address. (diff) | |
| download | yuzu-ec14ffe1cda04cd098ce07f3d3ad96c253e91eed.tar.gz yuzu-ec14ffe1cda04cd098ce07f3d3ad96c253e91eed.tar.xz yuzu-ec14ffe1cda04cd098ce07f3d3ad96c253e91eed.zip | |
GSP: Implements preliminary command synchronization via GPU interrupts.
Core: Added a comment to explain the logic for the RunLoop iterations.
Diffstat (limited to 'src')
| -rw-r--r-- | src/core/core.cpp | 20 | ||||
| -rw-r--r-- | src/core/hle/service/gsp.cpp | 111 | ||||
| -rw-r--r-- | src/core/hle/service/gsp.h | 16 | ||||
| -rw-r--r-- | src/core/hw/gpu.cpp | 22 |
4 files changed, 140 insertions, 29 deletions
diff --git a/src/core/core.cpp b/src/core/core.cpp index 7dc0809d0..fc9909377 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp | |||
| @@ -26,21 +26,25 @@ ARM_Interface* g_sys_core = nullptr; ///< ARM11 system (OS) core | |||
| 26 | /// Run the core CPU loop | 26 | /// Run the core CPU loop |
| 27 | void RunLoop() { | 27 | void RunLoop() { |
| 28 | for (;;){ | 28 | for (;;){ |
| 29 | g_app_core->Run(GPU::kFrameTicks); | 29 | // This function loops for 100 instructions in the CPU before trying to update hardware. |
| 30 | // This is a little bit faster than SingleStep, and should be pretty much equivalent. The | ||
| 31 | // number of instructions chosen is fairly arbitrary, however a large number will more | ||
| 32 | // drastically affect the frequency of GSP interrupts and likely break things. The point of | ||
| 33 | // this is to just loop in the CPU for more than 1 instruction to reduce overhead and make | ||
| 34 | // it a little bit faster... | ||
| 35 | g_app_core->Run(100); | ||
| 30 | HW::Update(); | 36 | HW::Update(); |
| 31 | Kernel::Reschedule(); | 37 | if (HLE::g_reschedule) { |
| 38 | Kernel::Reschedule(); | ||
| 39 | } | ||
| 32 | } | 40 | } |
| 33 | } | 41 | } |
| 34 | 42 | ||
| 35 | /// Step the CPU one instruction | 43 | /// Step the CPU one instruction |
| 36 | void SingleStep() { | 44 | void SingleStep() { |
| 37 | g_app_core->Step(); | 45 | g_app_core->Step(); |
| 38 | 46 | HW::Update(); | |
| 39 | // Update and reschedule after approx. 1 frame | 47 | if (HLE::g_reschedule) { |
| 40 | u64 current_ticks = Core::g_app_core->GetTicks(); | ||
| 41 | if ((current_ticks - g_last_ticks) >= GPU::kFrameTicks || HLE::g_reschedule) { | ||
| 42 | g_last_ticks = current_ticks; | ||
| 43 | HW::Update(); | ||
| 44 | Kernel::Reschedule(); | 48 | Kernel::Reschedule(); |
| 45 | } | 49 | } |
| 46 | } | 50 | } |
diff --git a/src/core/hle/service/gsp.cpp b/src/core/hle/service/gsp.cpp index b20203e27..f3d9fd26d 100644 --- a/src/core/hle/service/gsp.cpp +++ b/src/core/hle/service/gsp.cpp | |||
| @@ -21,6 +21,27 @@ | |||
| 21 | // Main graphics debugger object - TODO: Here is probably not the best place for this | 21 | // Main graphics debugger object - TODO: Here is probably not the best place for this |
| 22 | GraphicsDebugger g_debugger; | 22 | GraphicsDebugger g_debugger; |
| 23 | 23 | ||
| 24 | /// GSP thread interrupt queue header | ||
| 25 | struct GX_InterruptQueue { | ||
| 26 | union { | ||
| 27 | u32 hex; | ||
| 28 | |||
| 29 | // Index of last interrupt in the queue | ||
| 30 | BitField<0,8,u32> index; | ||
| 31 | |||
| 32 | // Number of interrupts remaining to be processed by the userland code | ||
| 33 | BitField<8,8,u32> number_interrupts; | ||
| 34 | |||
| 35 | // Error code - zero on success, otherwise an error has occurred | ||
| 36 | BitField<16,8,u32> error_code; | ||
| 37 | }; | ||
| 38 | |||
| 39 | u32 unk0; | ||
| 40 | u32 unk1; | ||
| 41 | |||
| 42 | GSP_GPU::GXInterruptId slot[0x34]; ///< Interrupt ID slots | ||
| 43 | }; | ||
| 44 | |||
| 24 | /// GSP shared memory GX command buffer header | 45 | /// GSP shared memory GX command buffer header |
| 25 | union GX_CmdBufferHeader { | 46 | union GX_CmdBufferHeader { |
| 26 | u32 hex; | 47 | u32 hex; |
| @@ -45,20 +66,28 @@ namespace GSP_GPU { | |||
| 45 | Handle g_event = 0; | 66 | Handle g_event = 0; |
| 46 | Handle g_shared_memory = 0; | 67 | Handle g_shared_memory = 0; |
| 47 | 68 | ||
| 48 | u32 g_thread_id = 0; | 69 | u32 g_thread_id = 1; |
| 49 | 70 | ||
| 50 | /// Gets a pointer to the start (header) of a command buffer in GSP shared memory | 71 | /// Gets a pointer to the start (header) of a command buffer in GSP shared memory |
| 51 | static inline u8* GX_GetCmdBufferPointer(u32 thread_id, u32 offset=0) { | 72 | static inline u8* GX_GetCmdBufferPointer(u32 thread_id, u32 offset=0) { |
| 73 | if (0 == g_shared_memory) return nullptr; | ||
| 74 | |||
| 52 | return Kernel::GetSharedMemoryPointer(g_shared_memory, 0x800 + (thread_id * 0x200) + offset); | 75 | return Kernel::GetSharedMemoryPointer(g_shared_memory, 0x800 + (thread_id * 0x200) + offset); |
| 53 | } | 76 | } |
| 54 | 77 | ||
| 78 | /// Gets a pointer to the start (header) of a command buffer in GSP shared memory | ||
| 79 | static inline GX_InterruptQueue* GetInterruptQueue(u32 thread_id) { | ||
| 80 | return (GX_InterruptQueue*)Kernel::GetSharedMemoryPointer(g_shared_memory, sizeof(GX_InterruptQueue) * thread_id); | ||
| 81 | } | ||
| 82 | |||
| 55 | /// Finishes execution of a GSP command | 83 | /// Finishes execution of a GSP command |
| 56 | void GX_FinishCommand(u32 thread_id) { | 84 | void GX_FinishCommand(u32 thread_id) { |
| 57 | GX_CmdBufferHeader* header = (GX_CmdBufferHeader*)GX_GetCmdBufferPointer(thread_id); | 85 | GX_CmdBufferHeader* header = (GX_CmdBufferHeader*)GX_GetCmdBufferPointer(thread_id); |
| 58 | 86 | ||
| 59 | g_debugger.GXCommandProcessed(GX_GetCmdBufferPointer(thread_id, 0x20 + (header->index * 0x20))); | 87 | g_debugger.GXCommandProcessed(GX_GetCmdBufferPointer(thread_id, 0x20 + (header->index * 0x20))); |
| 60 | 88 | ||
| 61 | header->number_commands = header->number_commands - 1; | 89 | header->number_commands = 0; |
| 90 | |||
| 62 | // TODO: Increment header->index? | 91 | // TODO: Increment header->index? |
| 63 | } | 92 | } |
| 64 | 93 | ||
| @@ -134,33 +163,55 @@ void RegisterInterruptRelayQueue(Service::Interface* self) { | |||
| 134 | u32* cmd_buff = Service::GetCommandBuffer(); | 163 | u32* cmd_buff = Service::GetCommandBuffer(); |
| 135 | u32 flags = cmd_buff[1]; | 164 | u32 flags = cmd_buff[1]; |
| 136 | g_event = cmd_buff[3]; | 165 | g_event = cmd_buff[3]; |
| 166 | g_shared_memory = Kernel::CreateSharedMemory("GSPSharedMem"); | ||
| 137 | 167 | ||
| 138 | _assert_msg_(GSP, (g_event != 0), "handle is not valid!"); | 168 | _assert_msg_(GSP, (g_event != 0), "handle is not valid!"); |
| 139 | 169 | ||
| 140 | Kernel::SetEventLocked(g_event, false); | 170 | cmd_buff[2] = g_thread_id++; // ThreadID |
| 171 | cmd_buff[4] = g_shared_memory; // GSP shared memory | ||
| 141 | 172 | ||
| 142 | // Hack - This function will permanently set the state of the GSP event such that GPU command | 173 | Kernel::SignalEvent(GSP_GPU::g_event); // TODO(bunnei): Is this correct? |
| 143 | // synchronization barriers always passthrough. Correct solution would be to set this after the | ||
| 144 | // GPU as processed all queued up commands, but due to the emulator being single-threaded they | ||
| 145 | // will always be ready. | ||
| 146 | Kernel::SetPermanentLock(g_event, true); | ||
| 147 | |||
| 148 | cmd_buff[0] = 0; // Result - no error | ||
| 149 | cmd_buff[2] = g_thread_id; // ThreadID | ||
| 150 | cmd_buff[4] = g_shared_memory; // GSP shared memory | ||
| 151 | } | 174 | } |
| 152 | 175 | ||
| 176 | /** | ||
| 177 | * Signals that the specified interrupt type has occurred to userland code | ||
| 178 | * @param interrupt_id ID of interrupt that is being signalled | ||
| 179 | */ | ||
| 180 | void SignalInterrupt(GXInterruptId interrupt_id) { | ||
| 181 | if (0 == GSP_GPU::g_event) { | ||
| 182 | WARN_LOG(GSP, "cannot synchronize until GSP event has been created!"); | ||
| 183 | return; | ||
| 184 | } | ||
| 185 | if (0 == g_shared_memory) { | ||
| 186 | WARN_LOG(GSP, "cannot synchronize until GSP shared memory has been created!"); | ||
| 187 | return; | ||
| 188 | } | ||
| 189 | for (int thread_id = 0; thread_id < 0x4; ++thread_id) { | ||
| 190 | GX_InterruptQueue* interrupt_queue = GetInterruptQueue(thread_id); | ||
| 191 | interrupt_queue->number_interrupts = interrupt_queue->number_interrupts + 1; | ||
| 192 | |||
| 193 | u8 next = interrupt_queue->index; | ||
| 194 | next += interrupt_queue->number_interrupts; | ||
| 195 | next = next % 0x34; | ||
| 196 | |||
| 197 | interrupt_queue->slot[next] = interrupt_id; | ||
| 198 | interrupt_queue->error_code = 0x0; // No error | ||
| 199 | } | ||
| 200 | Kernel::SignalEvent(GSP_GPU::g_event); | ||
| 201 | } | ||
| 153 | 202 | ||
| 154 | /// This triggers handling of the GX command written to the command buffer in shared memory. | 203 | /// Executes the next GSP command |
| 155 | void TriggerCmdReqQueue(Service::Interface* self) { | 204 | void ExecuteCommand(int thread_id, int command_index) { |
| 156 | 205 | ||
| 157 | // Utility function to convert register ID to address | 206 | // Utility function to convert register ID to address |
| 158 | auto WriteGPURegister = [](u32 id, u32 data) { | 207 | auto WriteGPURegister = [](u32 id, u32 data) { |
| 159 | GPU::Write<u32>(0x1EF00000 + 4 * id, data); | 208 | GPU::Write<u32>(0x1EF00000 + 4 * id, data); |
| 160 | }; | 209 | }; |
| 161 | 210 | ||
| 162 | GX_CmdBufferHeader* header = (GX_CmdBufferHeader*)GX_GetCmdBufferPointer(g_thread_id); | 211 | GX_CmdBufferHeader* header = (GX_CmdBufferHeader*)GX_GetCmdBufferPointer(thread_id); |
| 163 | auto& command = *(const GXCommand*)GX_GetCmdBufferPointer(g_thread_id, 0x20 + (header->index * 0x20)); | 212 | auto& command = *(const GXCommand*)GX_GetCmdBufferPointer(thread_id, (command_index + 1) * 0x20); |
| 213 | |||
| 214 | NOTICE_LOG(GSP, "decoding command 0x%08X", (int)command.id.Value()); | ||
| 164 | 215 | ||
| 165 | switch (command.id) { | 216 | switch (command.id) { |
| 166 | 217 | ||
| @@ -186,6 +237,7 @@ void TriggerCmdReqQueue(Service::Interface* self) { | |||
| 186 | g_debugger.CommandListCalled(params.address, | 237 | g_debugger.CommandListCalled(params.address, |
| 187 | (u32*)Memory::GetPointer(params.address), | 238 | (u32*)Memory::GetPointer(params.address), |
| 188 | params.size); | 239 | params.size); |
| 240 | SignalInterrupt(GXInterruptId::P3D); | ||
| 189 | break; | 241 | break; |
| 190 | } | 242 | } |
| 191 | 243 | ||
| @@ -208,6 +260,16 @@ void TriggerCmdReqQueue(Service::Interface* self) { | |||
| 208 | 260 | ||
| 209 | // TODO: Check if texture copies are implemented correctly.. | 261 | // TODO: Check if texture copies are implemented correctly.. |
| 210 | case GXCommandId::SET_DISPLAY_TRANSFER: | 262 | case GXCommandId::SET_DISPLAY_TRANSFER: |
| 263 | // TODO(bunnei): Signalling all of these interrupts here is totally wrong, but it seems to | ||
| 264 | // work well enough for running demos. Need to figure out how these all work and trigger | ||
| 265 | // them correctly. | ||
| 266 | SignalInterrupt(GXInterruptId::PSC0); | ||
| 267 | SignalInterrupt(GXInterruptId::PSC1); | ||
| 268 | SignalInterrupt(GXInterruptId::PPF); | ||
| 269 | SignalInterrupt(GXInterruptId::P3D); | ||
| 270 | SignalInterrupt(GXInterruptId::DMA); | ||
| 271 | break; | ||
| 272 | |||
| 211 | case GXCommandId::SET_TEXTURE_COPY: | 273 | case GXCommandId::SET_TEXTURE_COPY: |
| 212 | { | 274 | { |
| 213 | auto& params = command.image_copy; | 275 | auto& params = command.image_copy; |
| @@ -233,8 +295,21 @@ void TriggerCmdReqQueue(Service::Interface* self) { | |||
| 233 | default: | 295 | default: |
| 234 | ERROR_LOG(GSP, "unknown command 0x%08X", (int)command.id.Value()); | 296 | ERROR_LOG(GSP, "unknown command 0x%08X", (int)command.id.Value()); |
| 235 | } | 297 | } |
| 298 | } | ||
| 236 | 299 | ||
| 237 | GX_FinishCommand(g_thread_id); | 300 | /// This triggers handling of the GX command written to the command buffer in shared memory. |
| 301 | void TriggerCmdReqQueue(Service::Interface* self) { | ||
| 302 | // Iterate through each thread's command queue... | ||
| 303 | for (int thread_id = 0; thread_id < 0x4; ++thread_id) { | ||
| 304 | GX_CmdBufferHeader* header = (GX_CmdBufferHeader*)GX_GetCmdBufferPointer(thread_id); | ||
| 305 | |||
| 306 | // Iterate through each command... | ||
| 307 | for (int command_index = 0; command_index < header->number_commands; ++command_index) { | ||
| 308 | ExecuteCommand(thread_id, command_index); | ||
| 309 | } | ||
| 310 | |||
| 311 | GX_FinishCommand(thread_id); | ||
| 312 | } | ||
| 238 | } | 313 | } |
| 239 | 314 | ||
| 240 | const Interface::FunctionInfo FunctionTable[] = { | 315 | const Interface::FunctionInfo FunctionTable[] = { |
| @@ -275,7 +350,7 @@ const Interface::FunctionInfo FunctionTable[] = { | |||
| 275 | 350 | ||
| 276 | Interface::Interface() { | 351 | Interface::Interface() { |
| 277 | Register(FunctionTable, ARRAY_SIZE(FunctionTable)); | 352 | Register(FunctionTable, ARRAY_SIZE(FunctionTable)); |
| 278 | g_shared_memory = Kernel::CreateSharedMemory("GSPSharedMem"); | 353 | g_shared_memory = 0; |
| 279 | } | 354 | } |
| 280 | 355 | ||
| 281 | Interface::~Interface() { | 356 | Interface::~Interface() { |
diff --git a/src/core/hle/service/gsp.h b/src/core/hle/service/gsp.h index a83cb4846..5a649d2df 100644 --- a/src/core/hle/service/gsp.h +++ b/src/core/hle/service/gsp.h | |||
| @@ -29,6 +29,16 @@ enum class GXCommandId : u32 { | |||
| 29 | SET_COMMAND_LIST_FIRST = 0x05, | 29 | SET_COMMAND_LIST_FIRST = 0x05, |
| 30 | }; | 30 | }; |
| 31 | 31 | ||
| 32 | enum class GXInterruptId : u8 { | ||
| 33 | PSC0 = 0x00, | ||
| 34 | PSC1 = 0x01, | ||
| 35 | PDC0 = 0x02, // Seems called every vertical screen line | ||
| 36 | PDC1 = 0x03, // Seems called every frame | ||
| 37 | PPF = 0x04, | ||
| 38 | P3D = 0x05, | ||
| 39 | DMA = 0x06, | ||
| 40 | }; | ||
| 41 | |||
| 32 | struct GXCommand { | 42 | struct GXCommand { |
| 33 | BitField<0, 8, GXCommandId> id; | 43 | BitField<0, 8, GXCommandId> id; |
| 34 | 44 | ||
| @@ -84,4 +94,10 @@ public: | |||
| 84 | 94 | ||
| 85 | }; | 95 | }; |
| 86 | 96 | ||
| 97 | /** | ||
| 98 | * Signals that the specified interrupt type has occurred to userland code | ||
| 99 | * @param interrupt_id ID of interrupt that is being signalled | ||
| 100 | */ | ||
| 101 | void SignalInterrupt(GXInterruptId interrupt_id); | ||
| 102 | |||
| 87 | } // namespace | 103 | } // namespace |
diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp index c00be2a83..41976d989 100644 --- a/src/core/hw/gpu.cpp +++ b/src/core/hw/gpu.cpp | |||
| @@ -7,7 +7,11 @@ | |||
| 7 | 7 | ||
| 8 | #include "core/core.h" | 8 | #include "core/core.h" |
| 9 | #include "core/mem_map.h" | 9 | #include "core/mem_map.h" |
| 10 | |||
| 11 | #include "core/hle/hle.h" | ||
| 10 | #include "core/hle/kernel/thread.h" | 12 | #include "core/hle/kernel/thread.h" |
| 13 | #include "core/hle/service/gsp.h" | ||
| 14 | |||
| 11 | #include "core/hw/gpu.h" | 15 | #include "core/hw/gpu.h" |
| 12 | 16 | ||
| 13 | #include "video_core/video_core.h" | 17 | #include "video_core/video_core.h" |
| @@ -17,7 +21,8 @@ namespace GPU { | |||
| 17 | 21 | ||
| 18 | RegisterSet<u32, Regs> g_regs; | 22 | RegisterSet<u32, Regs> g_regs; |
| 19 | 23 | ||
| 20 | u64 g_last_ticks = 0; ///< Last CPU ticks | 24 | u32 g_cur_line = 0; ///< Current vertical screen line |
| 25 | u64 g_last_ticks = 0; ///< Last CPU ticks | ||
| 21 | 26 | ||
| 22 | /** | 27 | /** |
| 23 | * Sets whether the framebuffers are in the GSP heap (FCRAM) or VRAM | 28 | * Sets whether the framebuffers are in the GSP heap (FCRAM) or VRAM |
| @@ -249,17 +254,28 @@ template void Write<u8>(u32 addr, const u8 data); | |||
| 249 | void Update() { | 254 | void Update() { |
| 250 | u64 current_ticks = Core::g_app_core->GetTicks(); | 255 | u64 current_ticks = Core::g_app_core->GetTicks(); |
| 251 | 256 | ||
| 252 | // Fake a vertical blank | 257 | // Synchronize line... |
| 253 | if ((current_ticks - g_last_ticks) >= kFrameTicks) { | 258 | if ((current_ticks - g_last_ticks) >= GPU::kFrameTicks / 400) { |
| 259 | GSP_GPU::SignalInterrupt(GSP_GPU::GXInterruptId::PDC0); | ||
| 260 | g_cur_line++; | ||
| 254 | g_last_ticks = current_ticks; | 261 | g_last_ticks = current_ticks; |
| 262 | } | ||
| 263 | |||
| 264 | // Synchronize frame... | ||
| 265 | if (g_cur_line >= 400) { | ||
| 266 | g_cur_line = 0; | ||
| 267 | GSP_GPU::SignalInterrupt(GSP_GPU::GXInterruptId::PDC1); | ||
| 255 | VideoCore::g_renderer->SwapBuffers(); | 268 | VideoCore::g_renderer->SwapBuffers(); |
| 256 | Kernel::WaitCurrentThread(WAITTYPE_VBLANK); | 269 | Kernel::WaitCurrentThread(WAITTYPE_VBLANK); |
| 270 | HLE::Reschedule(__func__); | ||
| 257 | } | 271 | } |
| 258 | } | 272 | } |
| 259 | 273 | ||
| 260 | /// Initialize hardware | 274 | /// Initialize hardware |
| 261 | void Init() { | 275 | void Init() { |
| 276 | g_cur_line = 0; | ||
| 262 | g_last_ticks = Core::g_app_core->GetTicks(); | 277 | g_last_ticks = Core::g_app_core->GetTicks(); |
| 278 | |||
| 263 | // SetFramebufferLocation(FRAMEBUFFER_LOCATION_FCRAM); | 279 | // SetFramebufferLocation(FRAMEBUFFER_LOCATION_FCRAM); |
| 264 | SetFramebufferLocation(FRAMEBUFFER_LOCATION_VRAM); | 280 | SetFramebufferLocation(FRAMEBUFFER_LOCATION_VRAM); |
| 265 | 281 | ||