duckstation

duckstation, but archived from the revision just before upstream changed it to a proprietary software project, this version is the libre one
git clone https://git.neptards.moe/u3shit/duckstation.git
Log | Files | Refs | README | LICENSE

gpu_backend.cpp (10048B)


      1 // SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin <stenzek@gmail.com>
      2 // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0)
      3 
      4 #include "gpu_backend.h"
      5 #include "common/align.h"
      6 #include "common/log.h"
      7 #include "common/timer.h"
      8 #include "settings.h"
      9 #include "util/state_wrapper.h"
     10 Log_SetChannel(GPUBackend);
     11 
     12 std::unique_ptr<GPUBackend> g_gpu_backend;
     13 
     14 GPUBackend::GPUBackend() = default;
     15 
     16 GPUBackend::~GPUBackend() = default;
     17 
     18 bool GPUBackend::Initialize(bool force_thread)
     19 {
     20   if (force_thread || g_settings.gpu_use_thread)
     21     StartGPUThread();
     22 
     23   return true;
     24 }
     25 
     26 void GPUBackend::Reset()
     27 {
     28   Sync(true);
     29   m_drawing_area = {};
     30 }
     31 
     32 void GPUBackend::UpdateSettings()
     33 {
     34   Sync(true);
     35 
     36   if (m_use_gpu_thread != g_settings.gpu_use_thread)
     37   {
     38     if (!g_settings.gpu_use_thread)
     39       StopGPUThread();
     40     else
     41       StartGPUThread();
     42   }
     43 }
     44 
     45 void GPUBackend::Shutdown()
     46 {
     47   StopGPUThread();
     48 }
     49 
     50 GPUBackendFillVRAMCommand* GPUBackend::NewFillVRAMCommand()
     51 {
     52   return static_cast<GPUBackendFillVRAMCommand*>(
     53     AllocateCommand(GPUBackendCommandType::FillVRAM, sizeof(GPUBackendFillVRAMCommand)));
     54 }
     55 
     56 GPUBackendUpdateVRAMCommand* GPUBackend::NewUpdateVRAMCommand(u32 num_words)
     57 {
     58   const u32 size = sizeof(GPUBackendUpdateVRAMCommand) + (num_words * sizeof(u16));
     59   GPUBackendUpdateVRAMCommand* cmd =
     60     static_cast<GPUBackendUpdateVRAMCommand*>(AllocateCommand(GPUBackendCommandType::UpdateVRAM, size));
     61   return cmd;
     62 }
     63 
     64 GPUBackendCopyVRAMCommand* GPUBackend::NewCopyVRAMCommand()
     65 {
     66   return static_cast<GPUBackendCopyVRAMCommand*>(
     67     AllocateCommand(GPUBackendCommandType::CopyVRAM, sizeof(GPUBackendCopyVRAMCommand)));
     68 }
     69 
     70 GPUBackendSetDrawingAreaCommand* GPUBackend::NewSetDrawingAreaCommand()
     71 {
     72   return static_cast<GPUBackendSetDrawingAreaCommand*>(
     73     AllocateCommand(GPUBackendCommandType::SetDrawingArea, sizeof(GPUBackendSetDrawingAreaCommand)));
     74 }
     75 
     76 GPUBackendUpdateCLUTCommand* GPUBackend::NewUpdateCLUTCommand()
     77 {
     78   return static_cast<GPUBackendUpdateCLUTCommand*>(
     79     AllocateCommand(GPUBackendCommandType::UpdateCLUT, sizeof(GPUBackendUpdateCLUTCommand)));
     80 }
     81 
     82 GPUBackendDrawPolygonCommand* GPUBackend::NewDrawPolygonCommand(u32 num_vertices)
     83 {
     84   const u32 size = sizeof(GPUBackendDrawPolygonCommand) + (num_vertices * sizeof(GPUBackendDrawPolygonCommand::Vertex));
     85   GPUBackendDrawPolygonCommand* cmd =
     86     static_cast<GPUBackendDrawPolygonCommand*>(AllocateCommand(GPUBackendCommandType::DrawPolygon, size));
     87   cmd->num_vertices = Truncate16(num_vertices);
     88   return cmd;
     89 }
     90 
     91 GPUBackendDrawRectangleCommand* GPUBackend::NewDrawRectangleCommand()
     92 {
     93   return static_cast<GPUBackendDrawRectangleCommand*>(
     94     AllocateCommand(GPUBackendCommandType::DrawRectangle, sizeof(GPUBackendDrawRectangleCommand)));
     95 }
     96 
     97 GPUBackendDrawLineCommand* GPUBackend::NewDrawLineCommand(u32 num_vertices)
     98 {
     99   const u32 size = sizeof(GPUBackendDrawLineCommand) + (num_vertices * sizeof(GPUBackendDrawLineCommand::Vertex));
    100   GPUBackendDrawLineCommand* cmd =
    101     static_cast<GPUBackendDrawLineCommand*>(AllocateCommand(GPUBackendCommandType::DrawLine, size));
    102   cmd->num_vertices = Truncate16(num_vertices);
    103   return cmd;
    104 }
    105 
    106 void* GPUBackend::AllocateCommand(GPUBackendCommandType command, u32 size)
    107 {
    108   // Ensure size is a multiple of 4 so we don't end up with an unaligned command.
    109   size = Common::AlignUpPow2(size, 4);
    110 
    111   for (;;)
    112   {
    113     u32 read_ptr = m_command_fifo_read_ptr.load();
    114     u32 write_ptr = m_command_fifo_write_ptr.load();
    115     if (read_ptr > write_ptr)
    116     {
    117       u32 available_size = read_ptr - write_ptr;
    118       while (available_size < (size + sizeof(GPUBackendCommandType)))
    119       {
    120         WakeGPUThread();
    121         read_ptr = m_command_fifo_read_ptr.load();
    122         available_size = (read_ptr > write_ptr) ? (read_ptr - write_ptr) : (COMMAND_QUEUE_SIZE - write_ptr);
    123       }
    124     }
    125     else
    126     {
    127       const u32 available_size = COMMAND_QUEUE_SIZE - write_ptr;
    128       if ((size + sizeof(GPUBackendCommand)) > available_size)
    129       {
    130         // allocate a dummy command to wrap the buffer around
    131         GPUBackendCommand* dummy_cmd = reinterpret_cast<GPUBackendCommand*>(&m_command_fifo_data[write_ptr]);
    132         dummy_cmd->type = GPUBackendCommandType::Wraparound;
    133         dummy_cmd->size = available_size;
    134         dummy_cmd->params.bits = 0;
    135         m_command_fifo_write_ptr.store(0);
    136         continue;
    137       }
    138     }
    139 
    140     GPUBackendCommand* cmd = reinterpret_cast<GPUBackendCommand*>(&m_command_fifo_data[write_ptr]);
    141     cmd->type = command;
    142     cmd->size = size;
    143     return cmd;
    144   }
    145 }
    146 
    147 u32 GPUBackend::GetPendingCommandSize() const
    148 {
    149   const u32 read_ptr = m_command_fifo_read_ptr.load();
    150   const u32 write_ptr = m_command_fifo_write_ptr.load();
    151   return (write_ptr >= read_ptr) ? (write_ptr - read_ptr) : (COMMAND_QUEUE_SIZE - read_ptr + write_ptr);
    152 }
    153 
    154 void GPUBackend::PushCommand(GPUBackendCommand* cmd)
    155 {
    156   if (!m_use_gpu_thread)
    157   {
    158     // single-thread mode
    159     if (cmd->type != GPUBackendCommandType::Sync)
    160       HandleCommand(cmd);
    161   }
    162   else
    163   {
    164     const u32 new_write_ptr = m_command_fifo_write_ptr.fetch_add(cmd->size) + cmd->size;
    165     DebugAssert(new_write_ptr <= COMMAND_QUEUE_SIZE);
    166     UNREFERENCED_VARIABLE(new_write_ptr);
    167     if (GetPendingCommandSize() >= THRESHOLD_TO_WAKE_GPU)
    168       WakeGPUThread();
    169   }
    170 }
    171 
    172 void GPUBackend::WakeGPUThread()
    173 {
    174   std::unique_lock<std::mutex> lock(m_sync_mutex);
    175   if (!m_gpu_thread_sleeping.load())
    176     return;
    177 
    178   m_wake_gpu_thread_cv.notify_one();
    179 }
    180 
    181 void GPUBackend::StartGPUThread()
    182 {
    183   m_gpu_loop_done.store(false);
    184   m_use_gpu_thread = true;
    185   m_gpu_thread.Start([this]() { RunGPULoop(); });
    186   INFO_LOG("GPU thread started.");
    187 }
    188 
    189 void GPUBackend::StopGPUThread()
    190 {
    191   if (!m_use_gpu_thread)
    192     return;
    193 
    194   m_gpu_loop_done.store(true);
    195   WakeGPUThread();
    196   m_gpu_thread.Join();
    197   m_use_gpu_thread = false;
    198   INFO_LOG("GPU thread stopped.");
    199 }
    200 
    201 void GPUBackend::Sync(bool allow_sleep)
    202 {
    203   if (!m_use_gpu_thread)
    204     return;
    205 
    206   GPUBackendSyncCommand* cmd =
    207     static_cast<GPUBackendSyncCommand*>(AllocateCommand(GPUBackendCommandType::Sync, sizeof(GPUBackendSyncCommand)));
    208   cmd->allow_sleep = allow_sleep;
    209   PushCommand(cmd);
    210   WakeGPUThread();
    211 
    212   m_sync_semaphore.Wait();
    213 }
    214 
    215 void GPUBackend::RunGPULoop()
    216 {
    217   static constexpr double SPIN_TIME_NS = 1 * 1000000;
    218   Common::Timer::Value last_command_time = 0;
    219 
    220   for (;;)
    221   {
    222     u32 write_ptr = m_command_fifo_write_ptr.load();
    223     u32 read_ptr = m_command_fifo_read_ptr.load();
    224     if (read_ptr == write_ptr)
    225     {
    226       const Common::Timer::Value current_time = Common::Timer::GetCurrentValue();
    227       if (Common::Timer::ConvertValueToNanoseconds(current_time - last_command_time) < SPIN_TIME_NS)
    228         continue;
    229 
    230       std::unique_lock<std::mutex> lock(m_sync_mutex);
    231       m_gpu_thread_sleeping.store(true);
    232       m_wake_gpu_thread_cv.wait(lock, [this]() { return m_gpu_loop_done.load() || GetPendingCommandSize() > 0; });
    233       m_gpu_thread_sleeping.store(false);
    234 
    235       if (m_gpu_loop_done.load())
    236         break;
    237       else
    238         continue;
    239     }
    240 
    241     if (write_ptr < read_ptr)
    242       write_ptr = COMMAND_QUEUE_SIZE;
    243 
    244     bool allow_sleep = false;
    245     while (read_ptr < write_ptr)
    246     {
    247       const GPUBackendCommand* cmd = reinterpret_cast<const GPUBackendCommand*>(&m_command_fifo_data[read_ptr]);
    248       read_ptr += cmd->size;
    249 
    250       switch (cmd->type)
    251       {
    252         case GPUBackendCommandType::Wraparound:
    253         {
    254           DebugAssert(read_ptr == COMMAND_QUEUE_SIZE);
    255           write_ptr = m_command_fifo_write_ptr.load();
    256           read_ptr = 0;
    257         }
    258         break;
    259 
    260         case GPUBackendCommandType::Sync:
    261         {
    262           DebugAssert(read_ptr == write_ptr);
    263           m_sync_semaphore.Post();
    264           allow_sleep = static_cast<const GPUBackendSyncCommand*>(cmd)->allow_sleep;
    265         }
    266         break;
    267 
    268         default:
    269           HandleCommand(cmd);
    270           break;
    271       }
    272     }
    273 
    274     last_command_time = allow_sleep ? 0 : Common::Timer::GetCurrentValue();
    275     m_command_fifo_read_ptr.store(read_ptr);
    276   }
    277 }
    278 
    279 void GPUBackend::HandleCommand(const GPUBackendCommand* cmd)
    280 {
    281   switch (cmd->type)
    282   {
    283     case GPUBackendCommandType::FillVRAM:
    284     {
    285       FlushRender();
    286       const GPUBackendFillVRAMCommand* ccmd = static_cast<const GPUBackendFillVRAMCommand*>(cmd);
    287       FillVRAM(ZeroExtend32(ccmd->x), ZeroExtend32(ccmd->y), ZeroExtend32(ccmd->width), ZeroExtend32(ccmd->height),
    288                ccmd->color, ccmd->params);
    289     }
    290     break;
    291 
    292     case GPUBackendCommandType::UpdateVRAM:
    293     {
    294       FlushRender();
    295       const GPUBackendUpdateVRAMCommand* ccmd = static_cast<const GPUBackendUpdateVRAMCommand*>(cmd);
    296       UpdateVRAM(ZeroExtend32(ccmd->x), ZeroExtend32(ccmd->y), ZeroExtend32(ccmd->width), ZeroExtend32(ccmd->height),
    297                  ccmd->data, ccmd->params);
    298     }
    299     break;
    300 
    301     case GPUBackendCommandType::CopyVRAM:
    302     {
    303       FlushRender();
    304       const GPUBackendCopyVRAMCommand* ccmd = static_cast<const GPUBackendCopyVRAMCommand*>(cmd);
    305       CopyVRAM(ZeroExtend32(ccmd->src_x), ZeroExtend32(ccmd->src_y), ZeroExtend32(ccmd->dst_x),
    306                ZeroExtend32(ccmd->dst_y), ZeroExtend32(ccmd->width), ZeroExtend32(ccmd->height), ccmd->params);
    307     }
    308     break;
    309 
    310     case GPUBackendCommandType::SetDrawingArea:
    311     {
    312       FlushRender();
    313       m_drawing_area = static_cast<const GPUBackendSetDrawingAreaCommand*>(cmd)->new_area;
    314       DrawingAreaChanged();
    315     }
    316     break;
    317 
    318     case GPUBackendCommandType::UpdateCLUT:
    319     {
    320       const GPUBackendUpdateCLUTCommand* ccmd = static_cast<const GPUBackendUpdateCLUTCommand*>(cmd);
    321       UpdateCLUT(ccmd->reg, ccmd->clut_is_8bit);
    322     }
    323     break;
    324 
    325     case GPUBackendCommandType::DrawPolygon:
    326     {
    327       DrawPolygon(static_cast<const GPUBackendDrawPolygonCommand*>(cmd));
    328     }
    329     break;
    330 
    331     case GPUBackendCommandType::DrawRectangle:
    332     {
    333       DrawRectangle(static_cast<const GPUBackendDrawRectangleCommand*>(cmd));
    334     }
    335     break;
    336 
    337     case GPUBackendCommandType::DrawLine:
    338     {
    339       DrawLine(static_cast<const GPUBackendDrawLineCommand*>(cmd));
    340     }
    341     break;
    342 
    343     default:
    344       UnreachableCode();
    345   }
    346 }