gpu.cpp
1 // Copyright 2023 Citra Emulator Project 2 // Licensed under GPLv2 or any later version 3 // Refer to the license.txt file included. 4 5 #include "common/archives.h" 6 #include "common/microprofile.h" 7 #include "core/core.h" 8 #include "core/core_timing.h" 9 #include "core/hle/service/gsp/gsp_gpu.h" 10 #include "core/hle/service/plgldr/plgldr.h" 11 #include "video_core/debug_utils/debug_utils.h" 12 #include "video_core/gpu.h" 13 #include "video_core/gpu_debugger.h" 14 #include "video_core/pica/pica_core.h" 15 #include "video_core/pica/regs_lcd.h" 16 #include "video_core/renderer_base.h" 17 #include "video_core/renderer_software/sw_blitter.h" 18 #include "video_core/video_core.h" 19 20 namespace VideoCore { 21 22 constexpr VAddr VADDR_LCD = 0x1ED02000; 23 constexpr VAddr VADDR_GPU = 0x1EF00000; 24 25 MICROPROFILE_DEFINE(GPU_DisplayTransfer, "GPU", "DisplayTransfer", MP_RGB(100, 100, 255)); 26 MICROPROFILE_DEFINE(GPU_CmdlistProcessing, "GPU", "Cmdlist Processing", MP_RGB(100, 255, 100)); 27 28 struct GPU::Impl { 29 Core::Timing& timing; 30 Core::System& system; 31 Memory::MemorySystem& memory; 32 std::shared_ptr<Pica::DebugContext> debug_context; 33 Pica::PicaCore pica; 34 GraphicsDebugger gpu_debugger; 35 std::unique_ptr<RendererBase> renderer; 36 RasterizerInterface* rasterizer; 37 std::unique_ptr<SwRenderer::SwBlitter> sw_blitter; 38 Core::TimingEventType* vblank_event; 39 Service::GSP::InterruptHandler signal_interrupt; 40 41 explicit Impl(Core::System& system, Frontend::EmuWindow& emu_window, 42 Frontend::EmuWindow* secondary_window) 43 : timing{system.CoreTiming()}, system{system}, memory{system.Memory()}, 44 debug_context{Pica::g_debug_context}, pica{memory, debug_context}, 45 renderer{VideoCore::CreateRenderer(emu_window, secondary_window, pica, system)}, 46 rasterizer{renderer->Rasterizer()}, sw_blitter{std::make_unique<SwRenderer::SwBlitter>( 47 memory, rasterizer)} {} 48 ~Impl() = default; 49 }; 50 51 GPU::GPU(Core::System& system, Frontend::EmuWindow& emu_window, 52 Frontend::EmuWindow* secondary_window) 53 : impl{std::make_unique<Impl>(system, emu_window, secondary_window)} { 54 impl->vblank_event = impl->timing.RegisterEvent( 55 "GPU::VBlankCallback", 56 [this](uintptr_t user_data, s64 cycles_late) { VBlankCallback(user_data, cycles_late); }); 57 impl->timing.ScheduleEvent(FRAME_TICKS, impl->vblank_event); 58 59 // Bind the rasterizer to the PICA GPU 60 impl->pica.BindRasterizer(impl->rasterizer); 61 } 62 63 GPU::~GPU() = default; 64 65 PAddr GPU::VirtualToPhysicalAddress(VAddr addr) { 66 if (addr == 0) { 67 return 0; 68 } 69 70 if (addr >= Memory::VRAM_VADDR && addr <= Memory::VRAM_VADDR_END) { 71 return addr - Memory::VRAM_VADDR + Memory::VRAM_PADDR; 72 } 73 if (addr >= Memory::LINEAR_HEAP_VADDR && addr <= Memory::LINEAR_HEAP_VADDR_END) { 74 return addr - Memory::LINEAR_HEAP_VADDR + Memory::FCRAM_PADDR; 75 } 76 if (addr >= Memory::NEW_LINEAR_HEAP_VADDR && addr <= Memory::NEW_LINEAR_HEAP_VADDR_END) { 77 return addr - Memory::NEW_LINEAR_HEAP_VADDR + Memory::FCRAM_PADDR; 78 } 79 if (addr >= Memory::PLUGIN_3GX_FB_VADDR && addr <= Memory::PLUGIN_3GX_FB_VADDR_END) { 80 auto plg_ldr = Service::PLGLDR::GetService(impl->system); 81 if (plg_ldr) { 82 return addr - Memory::PLUGIN_3GX_FB_VADDR + plg_ldr->GetPluginFBAddr(); 83 } 84 } 85 86 LOG_ERROR(HW_Memory, "Unknown virtual address @ 0x{:08X}", addr); 87 return addr; 88 } 89 90 void GPU::SetInterruptHandler(Service::GSP::InterruptHandler handler) { 91 impl->signal_interrupt = handler; 92 impl->pica.SetInterruptHandler(handler); 93 } 94 95 void GPU::FlushRegion(PAddr addr, u32 size) { 96 impl->rasterizer->FlushRegion(addr, size); 97 } 98 99 void GPU::InvalidateRegion(PAddr addr, u32 size) { 100 impl->rasterizer->InvalidateRegion(addr, size); 101 } 102 103 void GPU::ClearAll(bool flush) { 104 impl->rasterizer->ClearAll(flush); 105 } 106 107 void GPU::Execute(const Service::GSP::Command& command) { 108 using Service::GSP::CommandId; 109 auto& regs = impl->pica.regs; 110 111 switch (command.id) { 112 case CommandId::RequestDma: { 113 impl->system.Memory().RasterizerFlushVirtualRegion( 114 command.dma_request.source_address, command.dma_request.size, Memory::FlushMode::Flush); 115 impl->system.Memory().RasterizerFlushVirtualRegion(command.dma_request.dest_address, 116 command.dma_request.size, 117 Memory::FlushMode::Invalidate); 118 119 // TODO(Subv): These memory accesses should not go through the application's memory mapping. 120 // They should go through the GSP module's memory mapping. 121 const auto process = impl->system.Kernel().GetCurrentProcess(); 122 impl->memory.CopyBlock(*process, command.dma_request.dest_address, 123 command.dma_request.source_address, command.dma_request.size); 124 impl->signal_interrupt(Service::GSP::InterruptId::DMA); 125 break; 126 } 127 case CommandId::SubmitCmdList: { 128 auto& params = command.submit_gpu_cmdlist; 129 auto& cmdbuffer = regs.internal.pipeline.command_buffer; 130 131 // Write to the command buffer GPU registers 132 cmdbuffer.addr[0].Assign(VirtualToPhysicalAddress(params.address) >> 3); 133 cmdbuffer.size[0].Assign(params.size >> 3); 134 cmdbuffer.trigger[0] = 1; 135 136 // Trigger processing of the command list 137 SubmitCmdList(0); 138 break; 139 } 140 case CommandId::MemoryFill: { 141 auto& params = command.memory_fill; 142 auto& memfill = regs.memory_fill_config; 143 144 // Write to the memory fill GPU registers. 145 if (params.start1 != 0) { 146 memfill[0].address_start = VirtualToPhysicalAddress(params.start1) >> 3; 147 memfill[0].address_end = VirtualToPhysicalAddress(params.end1) >> 3; 148 memfill[0].value_32bit = params.value1; 149 memfill[0].control = params.control1; 150 MemoryFill(0); 151 } 152 if (params.start2 != 0) { 153 memfill[1].address_start = VirtualToPhysicalAddress(params.start2) >> 3; 154 memfill[1].address_end = VirtualToPhysicalAddress(params.end2) >> 3; 155 memfill[1].value_32bit = params.value2; 156 memfill[1].control = params.control2; 157 MemoryFill(1); 158 } 159 break; 160 } 161 case CommandId::DisplayTransfer: { 162 auto& params = command.display_transfer; 163 auto& display_transfer = regs.display_transfer_config; 164 165 // Write to the transfer engine GPU registers. 166 display_transfer.input_address = VirtualToPhysicalAddress(params.in_buffer_address) >> 3; 167 display_transfer.output_address = VirtualToPhysicalAddress(params.out_buffer_address) >> 3; 168 display_transfer.input_size = params.in_buffer_size; 169 display_transfer.output_size = params.out_buffer_size; 170 display_transfer.flags = params.flags; 171 display_transfer.trigger.Assign(1); 172 173 // Trigger the display transfer. 174 MemoryTransfer(); 175 break; 176 } 177 case CommandId::TextureCopy: { 178 auto& params = command.texture_copy; 179 auto& texture_copy = regs.display_transfer_config; 180 181 // Write to the transfer engine GPU registers. 182 texture_copy.input_address = VirtualToPhysicalAddress(params.in_buffer_address) >> 3; 183 texture_copy.output_address = VirtualToPhysicalAddress(params.out_buffer_address) >> 3; 184 texture_copy.texture_copy.size = params.size; 185 texture_copy.texture_copy.input_size = params.in_width_gap; 186 texture_copy.texture_copy.output_size = params.out_width_gap; 187 texture_copy.flags = params.flags; 188 texture_copy.trigger.Assign(1); 189 190 // Trigger the texture copy. 191 MemoryTransfer(); 192 break; 193 } 194 case CommandId::CacheFlush: { 195 // Rasterizer flushing handled elsewhere in CPU read/write and other GPU handlers 196 // Use command.cache_flush.regions to implement this handler 197 break; 198 } 199 default: 200 LOG_ERROR(HW_GPU, "Unknown command {:#08X}", command.id.Value()); 201 } 202 203 // Notify debugger that a GSP command was processed. 204 if (impl->debug_context) { 205 impl->debug_context->OnEvent(Pica::DebugContext::Event::GSPCommandProcessed, &command); 206 } 207 } 208 209 void GPU::SetBufferSwap(u32 screen_id, const Service::GSP::FrameBufferInfo& info) { 210 const PAddr phys_address_left = VirtualToPhysicalAddress(info.address_left); 211 const PAddr phys_address_right = VirtualToPhysicalAddress(info.address_right); 212 213 // Update framebuffer properties. 214 auto& framebuffer = impl->pica.regs.framebuffer_config[screen_id]; 215 if (info.active_fb == 0) { 216 framebuffer.address_left1 = phys_address_left; 217 framebuffer.address_right1 = phys_address_right; 218 } else { 219 framebuffer.address_left2 = phys_address_left; 220 framebuffer.address_right2 = phys_address_right; 221 } 222 223 framebuffer.stride = info.stride; 224 framebuffer.format = info.format; 225 framebuffer.active_fb = info.shown_fb; 226 227 // Notify debugger about the buffer swap. 228 if (impl->debug_context) { 229 impl->debug_context->OnEvent(Pica::DebugContext::Event::BufferSwapped, nullptr); 230 } 231 232 if (screen_id == 0) { 233 MicroProfileFlip(); 234 impl->system.perf_stats->EndGameFrame(); 235 } 236 } 237 238 void GPU::SetColorFill(const Pica::ColorFill& fill) { 239 impl->pica.regs_lcd.color_fill_top = fill; 240 impl->pica.regs_lcd.color_fill_bottom = fill; 241 } 242 243 u32 GPU::ReadReg(VAddr addr) { 244 switch (addr & 0xFFFFF000) { 245 case VADDR_LCD: { 246 const u32 offset = addr - VADDR_LCD; 247 const u32 index = offset / sizeof(u32); 248 ASSERT(addr % sizeof(u32) == 0); 249 ASSERT(index < Pica::RegsLcd::NumIds()); 250 return impl->pica.regs_lcd[index]; 251 } 252 case VADDR_GPU: 253 case VADDR_GPU + 0x1000: { 254 const u32 offset = addr - VADDR_GPU; 255 const u32 index = offset / sizeof(u32); 256 ASSERT(addr % sizeof(u32) == 0); 257 ASSERT(index < Pica::PicaCore::Regs::NUM_REGS); 258 return impl->pica.regs.reg_array[index]; 259 } 260 default: 261 UNREACHABLE_MSG("Read from unknown GPU address {:#08X}", addr); 262 } 263 } 264 265 void GPU::WriteReg(VAddr addr, u32 data) { 266 switch (addr & 0xFFFFF000) { 267 case VADDR_LCD: { 268 const u32 offset = addr - VADDR_LCD; 269 const u32 index = offset / sizeof(u32); 270 ASSERT(addr % sizeof(u32) == 0); 271 ASSERT(index < Pica::RegsLcd::NumIds()); 272 impl->pica.regs_lcd[index] = data; 273 break; 274 } 275 case VADDR_GPU: 276 case VADDR_GPU + 0x1000: { 277 const u32 offset = addr - VADDR_GPU; 278 const u32 index = offset / sizeof(u32); 279 280 ASSERT(addr % sizeof(u32) == 0); 281 ASSERT(index < Pica::PicaCore::Regs::NUM_REGS); 282 impl->pica.regs.reg_array[index] = data; 283 284 // Handle registers that trigger GPU actions 285 switch (index) { 286 case GPU_REG_INDEX(memory_fill_config[0].trigger): 287 MemoryFill(0); 288 break; 289 case GPU_REG_INDEX(memory_fill_config[1].trigger): 290 MemoryFill(1); 291 break; 292 case GPU_REG_INDEX(display_transfer_config.trigger): 293 MemoryTransfer(); 294 break; 295 case GPU_REG_INDEX(internal.pipeline.command_buffer.trigger[0]): 296 SubmitCmdList(0); 297 break; 298 case GPU_REG_INDEX(internal.pipeline.command_buffer.trigger[1]): 299 SubmitCmdList(1); 300 break; 301 default: 302 break; 303 } 304 break; 305 } 306 default: 307 UNREACHABLE_MSG("Write to unknown GPU address {:#08X}", addr); 308 } 309 } 310 311 void GPU::Sync() { 312 impl->renderer->Sync(); 313 } 314 315 VideoCore::RendererBase& GPU::Renderer() { 316 return *impl->renderer; 317 } 318 319 Pica::PicaCore& GPU::PicaCore() { 320 return impl->pica; 321 } 322 323 const Pica::PicaCore& GPU::PicaCore() const { 324 return impl->pica; 325 } 326 327 Pica::DebugContext& GPU::DebugContext() { 328 return *Pica::g_debug_context; 329 } 330 331 GraphicsDebugger& GPU::Debugger() { 332 return impl->gpu_debugger; 333 } 334 335 void GPU::SubmitCmdList(u32 index) { 336 // Check if a command list was triggered. 337 auto& config = impl->pica.regs.internal.pipeline.command_buffer; 338 if (!config.trigger[index]) { 339 return; 340 } 341 342 MICROPROFILE_SCOPE(GPU_CmdlistProcessing); 343 344 // Forward command list processing to the PICA core. 345 const PAddr addr = config.GetPhysicalAddress(index); 346 const u32 size = config.GetSize(index); 347 impl->pica.ProcessCmdList(addr, size); 348 config.trigger[index] = 0; 349 } 350 351 void GPU::MemoryFill(u32 index) { 352 // Check if a memory fill was triggered. 353 auto& config = impl->pica.regs.memory_fill_config[index]; 354 if (!config.trigger) { 355 return; 356 } 357 358 // Perform memory fill. 359 if (!impl->rasterizer->AccelerateFill(config)) { 360 impl->sw_blitter->MemoryFill(config); 361 } 362 363 // It seems that it won't signal interrupt if "address_start" is zero. 364 // TODO: hwtest this 365 if (config.GetStartAddress() != 0) { 366 if (!index) { 367 impl->signal_interrupt(Service::GSP::InterruptId::PSC0); 368 } else { 369 impl->signal_interrupt(Service::GSP::InterruptId::PSC1); 370 } 371 } 372 373 // Reset "trigger" flag and set the "finish" flag 374 // This was confirmed to happen on hardware even if "address_start" is zero. 375 config.trigger.Assign(0); 376 config.finished.Assign(1); 377 } 378 379 void GPU::MemoryTransfer() { 380 // Check if a transfer was triggered. 381 auto& config = impl->pica.regs.display_transfer_config; 382 if (!config.trigger.Value()) { 383 return; 384 } 385 386 MICROPROFILE_SCOPE(GPU_DisplayTransfer); 387 388 // Notify debugger about the display transfer. 389 if (impl->debug_context) { 390 impl->debug_context->OnEvent(Pica::DebugContext::Event::IncomingDisplayTransfer, nullptr); 391 } 392 393 // Perform memory transfer 394 if (config.is_texture_copy) { 395 if (!impl->rasterizer->AccelerateTextureCopy(config)) { 396 impl->sw_blitter->TextureCopy(config); 397 } 398 } else { 399 if (!impl->rasterizer->AccelerateDisplayTransfer(config)) { 400 impl->sw_blitter->DisplayTransfer(config); 401 } 402 } 403 404 // Complete transfer. 405 config.trigger.Assign(0); 406 impl->signal_interrupt(Service::GSP::InterruptId::PPF); 407 } 408 409 void GPU::VBlankCallback(std::uintptr_t user_data, s64 cycles_late) { 410 // Present renderered frame. 411 impl->renderer->SwapBuffers(); 412 413 // Signal to GSP that GPU interrupt has occurred 414 impl->signal_interrupt(Service::GSP::InterruptId::PDC0); 415 impl->signal_interrupt(Service::GSP::InterruptId::PDC1); 416 417 // Reschedule recurrent event 418 impl->timing.ScheduleEvent(FRAME_TICKS - cycles_late, impl->vblank_event); 419 } 420 421 template <class Archive> 422 void GPU::serialize(Archive& ar, const u32 file_version) { 423 ar & impl->pica; 424 } 425 426 SERIALIZE_IMPL(GPU) 427 428 } // namespace VideoCore