Is this thing on?

This commit is contained in:
jbm11208 2026-01-16 20:59:16 -05:00
parent f5053868b4
commit 788ec56c8b
10 changed files with 196 additions and 0 deletions

View File

@ -147,6 +147,7 @@ void ConfigureGraphics::SetConfiguration() {
ui->disable_spirv_optimizer->setChecked(Settings::values.disable_spirv_optimizer.GetValue());
ui->toggle_async_shaders->setChecked(Settings::values.async_shader_compilation.GetValue());
ui->toggle_async_present->setChecked(Settings::values.async_presentation.GetValue());
ui->toggle_async_gpu->setChecked(Settings::values.async_gpu.GetValue());
if (Settings::IsConfiguringGlobal()) {
ui->toggle_shader_jit->setChecked(Settings::values.use_shader_jit.GetValue());
@ -162,6 +163,8 @@ void ConfigureGraphics::ApplyConfiguration() {
ui->toggle_async_shaders, async_shader_compilation);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.async_presentation,
ui->toggle_async_present, async_presentation);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.async_gpu,
ui->toggle_async_gpu, async_gpu);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.spirv_shader_gen,
ui->spirv_shader_gen, spirv_shader_gen);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.disable_spirv_optimizer,
@ -203,6 +206,7 @@ void ConfigureGraphics::SetupPerGameUI() {
Settings::values.async_shader_compilation.UsingGlobal());
ui->widget_texture_sampling->setEnabled(Settings::values.texture_sampling.UsingGlobal());
ui->toggle_async_present->setEnabled(Settings::values.async_presentation.UsingGlobal());
ui->toggle_async_gpu->setEnabled(Settings::values.async_gpu.UsingGlobal());
ui->graphics_api_combo->setEnabled(Settings::values.graphics_api.UsingGlobal());
ui->physical_device_combo->setEnabled(Settings::values.physical_device.UsingGlobal());
ui->delay_render_combo->setEnabled(
@ -243,6 +247,8 @@ void ConfigureGraphics::SetupPerGameUI() {
async_shader_compilation);
ConfigurationShared::SetColoredTristate(
ui->toggle_async_present, Settings::values.async_presentation, async_presentation);
ConfigurationShared::SetColoredTristate(ui->toggle_async_gpu, Settings::values.async_gpu,
async_gpu);
ConfigurationShared::SetColoredTristate(ui->spirv_shader_gen, Settings::values.spirv_shader_gen,
spirv_shader_gen);
ConfigurationShared::SetColoredTristate(ui->disable_spirv_optimizer,

View File

@ -41,6 +41,7 @@ private:
ConfigurationShared::CheckState use_vsync;
ConfigurationShared::CheckState async_shader_compilation;
ConfigurationShared::CheckState async_presentation;
ConfigurationShared::CheckState async_gpu;
ConfigurationShared::CheckState spirv_shader_gen;
ConfigurationShared::CheckState disable_spirv_optimizer;
std::unique_ptr<Ui::ConfigureGraphics> ui;

View File

@ -241,6 +241,16 @@
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="toggle_async_gpu">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Process GPU commands asynchronously on a separate thread, similar to real 3DS hardware. Improves performance in CPU-bound scenarios. Works with OpenGL and Software renderers (disabled with Vulkan due to rendering thread requirements).&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Enable async GPU</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@ -511,6 +511,7 @@ struct Values {
SwitchableSetting<bool> spirv_shader_gen{true, "spirv_shader_gen"};
SwitchableSetting<bool> disable_spirv_optimizer{true, "disable_spirv_optimizer"};
SwitchableSetting<bool> async_shader_compilation{false, "async_shader_compilation"};
SwitchableSetting<bool> async_gpu{true, "async_gpu"};
SwitchableSetting<bool> async_presentation{true, "async_presentation"};
SwitchableSetting<bool> use_hw_shader{true, "use_hw_shader"};
SwitchableSetting<bool> use_disk_shader_cache{true, "use_disk_shader_cache"};

View File

@ -11,6 +11,8 @@ add_library(video_core STATIC
debug_utils/debug_utils.h
gpu.cpp
gpu.h
gpu_command_queue.cpp
gpu_command_queue.h
gpu_debugger.h
gpu_impl.h
pica_types.h

View File

@ -5,12 +5,14 @@
#include "common/archives.h"
#include "common/hacks/hack_manager.h"
#include "common/microprofile.h"
#include "common/settings.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/service/gsp/gsp_gpu.h"
#include "core/hle/service/plgldr/plgldr.h"
#include "video_core/debug_utils/debug_utils.h"
#include "video_core/gpu.h"
#include "video_core/gpu_command_queue.h"
#include "video_core/gpu_debugger.h"
#include "video_core/gpu_impl.h"
#include "video_core/pica/pica_core.h"
@ -39,6 +41,14 @@ GPU::GPU(Core::System& system, Frontend::EmuWindow& emu_window,
// Bind the rasterizer to the PICA GPU
impl->pica.BindRasterizer(impl->rasterizer);
// Initialize GPU command queue if async GPU is enabled.
// Note: Async GPU is disabled for Vulkan as it causes threading issues with command buffer
// recording.
if (Settings::values.async_gpu.GetValue() &&
Settings::values.graphics_api.GetValue() != Settings::GraphicsAPI::Vulkan) {
impl->command_queue = std::make_unique<GPUCommandQueue>(*this);
}
}
GPU::~GPU() = default;
@ -85,6 +95,15 @@ void GPU::ClearAll(bool flush) {
}
void GPU::Execute(const Service::GSP::Command& command) {
// If async GPU is enabled, queue the command; otherwise execute it directly
if (impl->command_queue) {
impl->command_queue->QueueCommand(command);
} else {
ExecuteCommand(command);
}
}
void GPU::ExecuteCommand(const Service::GSP::Command& command) {
using Service::GSP::CommandId;
auto& regs = impl->pica.regs;

View File

@ -63,6 +63,9 @@ public:
/// Executes the provided GSP command.
void Execute(const Service::GSP::Command& command);
/// Executes a GPU command directly (internal use for async command processing).
void ExecuteCommand(const Service::GSP::Command& command);
/// Updates GPU display framebuffer configuration using the specified parameters.
void SetBufferSwap(u32 screen_id, const Service::GSP::FrameBufferInfo& info);

View File

@ -0,0 +1,91 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <condition_variable>
#include <mutex>
#include <thread>
#include "common/logging/log.h"
#include "video_core/gpu.h"
#include "video_core/gpu_command_queue.h"
namespace VideoCore {
GPUCommandQueue::GPUCommandQueue(GPU& gpu) : gpu{gpu} {
worker_thread = std::make_unique<std::thread>([this] { ProcessCommandQueue(); });
}
GPUCommandQueue::~GPUCommandQueue() {
Shutdown();
}
void GPUCommandQueue::QueueCommand(const Service::GSP::Command& command) {
{
std::lock_guard<std::mutex> lock(queue_mutex);
command_queue.push(command);
is_idle = false;
}
queue_cv.notify_one();
}
void GPUCommandQueue::WaitForIdle() {
std::unique_lock<std::mutex> lock(queue_mutex);
idle_cv.wait(lock, [this] { return is_idle; });
}
void GPUCommandQueue::Shutdown() {
{
std::lock_guard<std::mutex> lock(queue_mutex);
shutdown_requested = true;
}
queue_cv.notify_one();
if (worker_thread && worker_thread->joinable()) {
worker_thread->join();
}
}
bool GPUCommandQueue::IsIdle() const {
std::lock_guard<std::mutex> lock(queue_mutex);
return is_idle;
}
void GPUCommandQueue::ProcessCommandQueue() {
while (true) {
Service::GSP::Command command;
bool has_command = false;
{
std::unique_lock<std::mutex> lock(queue_mutex);
// Wait for commands or shutdown - no timeout, no artificial delays
queue_cv.wait(lock, [this] { return !command_queue.empty() || shutdown_requested; });
if (shutdown_requested && command_queue.empty()) {
break;
}
if (!command_queue.empty()) {
command = command_queue.front();
command_queue.pop();
has_command = true;
}
}
// Process the command outside the lock - no artificial delays
if (has_command) {
gpu.ExecuteCommand(command);
// Check if queue is now idle after processing this command
{
std::lock_guard<std::mutex> lock(queue_mutex);
if (command_queue.empty()) {
is_idle = true;
idle_cv.notify_all();
}
}
}
}
}
} // namespace VideoCore

View File

@ -0,0 +1,61 @@
// Copyright Citra Emulator Project / Azahar Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <condition_variable>
#include <memory>
#include <mutex>
#include <thread>
#include <queue>
#include "common/common_types.h"
#include "core/hle/service/gsp/gsp_gpu.h"
namespace VideoCore {
class GPU;
/**
* GPU Command Queue for asynchronous GPU command processing.
* Processes GPU commands on a dedicated worker thread, similar to real 3DS hardware.
*
* Design principles:
* - No artificial delays or busy-waiting
* - Worker thread sleeps when queue is empty (OS scheduler handles CPU allocation)
* - Logic thread not blocked when rendering
* - Efficient synchronization with condition variables
*/
class GPUCommandQueue {
public:
explicit GPUCommandQueue(GPU& gpu);
~GPUCommandQueue();
/// Queue a GPU command for processing
void QueueCommand(const Service::GSP::Command& command);
/// Wait for all queued commands to be processed
void WaitForIdle();
/// Shutdown the command queue and worker thread
void Shutdown();
/// Check if the queue is idle
[[nodiscard]] bool IsIdle() const;
private:
/// Worker thread function - processes commands without artificial delays
void ProcessCommandQueue();
GPU& gpu;
std::queue<Service::GSP::Command> command_queue;
mutable std::mutex queue_mutex;
std::condition_variable queue_cv;
std::condition_variable idle_cv;
std::unique_ptr<std::thread> worker_thread;
bool shutdown_requested{false};
bool is_idle{true};
};
} // namespace VideoCore

View File

@ -15,6 +15,7 @@
#include "video_core/gpu.h"
#include "video_core/gpu_debugger.h"
#include "video_core/gpu_impl.h"
#include "video_core/gpu_command_queue.h"
#include "video_core/pica/pica_core.h"
#include "video_core/pica/regs_lcd.h"
#include "video_core/renderer_base.h"
@ -33,6 +34,7 @@ struct GPU::Impl {
std::unique_ptr<RendererBase> renderer;
RasterizerInterface* rasterizer;
std::unique_ptr<SwRenderer::SwBlitter> sw_blitter;
std::unique_ptr<GPUCommandQueue> command_queue;
Core::TimingEventType* vblank_event;
Service::GSP::InterruptHandler signal_interrupt;