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 }