/ src / video_core / gpu.cpp
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