graphics: runtime backend selection for dual-backend builds
This commit is contained in:
parent
fbd4e06a34
commit
9f91f90d60
7 changed files with 143 additions and 31 deletions
|
|
@ -28,10 +28,11 @@ endif()
|
|||
|
||||
# Graphics backend selection
|
||||
# D3D12: Windows only, ON by default.
|
||||
# Vulkan: Cross-platform, ON by default on Linux, OFF by default on Windows.
|
||||
# Vulkan: Cross-platform, ON by default everywhere. Windows builds both
|
||||
# backends by default; the active one is chosen at runtime (gpu_backend cvar).
|
||||
if(WIN32)
|
||||
option(REXGLUE_USE_D3D12 "Enable D3D12 graphics backend" ON)
|
||||
option(REXGLUE_USE_VULKAN "Enable Vulkan graphics backend" OFF)
|
||||
option(REXGLUE_USE_VULKAN "Enable Vulkan graphics backend" ON)
|
||||
else()
|
||||
set(REXGLUE_USE_D3D12 OFF)
|
||||
option(REXGLUE_USE_VULKAN "Enable Vulkan graphics backend" ON)
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ class SimpleSettingsDialog final : public ImGuiDialog {
|
|||
|
||||
// Staged setting values (committed by SaveVideo / SaveProfile).
|
||||
GraphicsDeviceList device_list_;
|
||||
int graphics_api_index_ = 0;
|
||||
int device_index_ = 0;
|
||||
int resolution_scale_index_ = 0;
|
||||
int frame_cap_index_ = 0;
|
||||
|
|
|
|||
|
|
@ -61,6 +61,18 @@ REXCVAR_DEFINE_INT32(scaled_resolve_texture_loads_from_shared_memory_max_pixels,
|
|||
REXCVAR_DEFINE_BOOL(occlusion_query_enable, true, "GPU", "Enable host occlusion query handling")
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
|
||||
// Skate 3 Hor+ ultrawide classifier controls shared by every graphics backend.
|
||||
// Defined here (not per backend) so builds with several backends compiled in
|
||||
// register them exactly once.
|
||||
REXCVAR_DEFINE_BOOL(skate3_ultrawide_skip_shadow_targets, true, "Skate 3",
|
||||
"Do not apply Skate 3 Hor+ NDC correction while rendering likely shadow maps")
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
REXCVAR_DEFINE_INT32(skate3_ultrawide_shadow_skip_mode, 2, "Skate 3",
|
||||
"Shadow pass skip mode: 1 = square atlases, 2 = plus large offscreen, "
|
||||
"3 = plus all depth-only")
|
||||
.range(1, 3)
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
|
||||
REXCVAR_DEFINE_STRING(readback_resolve, "none", "GPU",
|
||||
"Controls CPU readback of render-to-texture resolve results.\n"
|
||||
" none: Disable readback (default)\n"
|
||||
|
|
|
|||
|
|
@ -58,14 +58,9 @@ REXCVAR_DEFINE_BOOL(d3d12_gpu_timestamp_buckets, false, "GPU/D3D12",
|
|||
"the breakdown in the FPS overlay and a periodic profile log")
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
|
||||
REXCVAR_DEFINE_BOOL(skate3_ultrawide_skip_shadow_targets, true, "Skate 3",
|
||||
"Do not apply Skate 3 Hor+ NDC correction while rendering likely shadow maps")
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
REXCVAR_DEFINE_INT32(skate3_ultrawide_shadow_skip_mode, 2, "Skate 3",
|
||||
"Shadow pass skip mode: 1 = square atlases, 2 = plus large offscreen, "
|
||||
"3 = plus all depth-only")
|
||||
.range(1, 3)
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
// Defined in graphics/command_processor.cpp, shared with the Vulkan backend.
|
||||
REXCVAR_DECLARE(bool, skate3_ultrawide_skip_shadow_targets);
|
||||
REXCVAR_DECLARE(int32_t, skate3_ultrawide_shadow_skip_mode);
|
||||
REXCVAR_DEFINE_BOOL(skate3_ultrawide_fake_occlusion_queries, false, "Skate 3",
|
||||
"Return positive visibility query results while Skate 3 Hor+ ultrawide is "
|
||||
"active so side-of-frame assets remain resident")
|
||||
|
|
|
|||
|
|
@ -170,19 +170,9 @@ REXCVAR_DEFINE_INT32(vulkan_gpu_clock_log_interval_frames, 120, "GPU/Vulkan",
|
|||
.range(0, 36000)
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
|
||||
// Skate 3 ultrawide cvars. These mirror the Direct3D 12 backend's definitions
|
||||
// so the Hor+ classifier behaves identically on Vulkan; the D3D12 translation
|
||||
// unit is not compiled on this platform, so without these the queries below
|
||||
// would resolve to an unregistered flag (empty string -> false/0) and silently
|
||||
// disable the shadow-map skip.
|
||||
REXCVAR_DEFINE_BOOL(skate3_ultrawide_skip_shadow_targets, true, "Skate 3",
|
||||
"Do not apply Skate 3 Hor+ NDC correction while rendering likely shadow maps")
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
REXCVAR_DEFINE_INT32(skate3_ultrawide_shadow_skip_mode, 2, "Skate 3",
|
||||
"Shadow pass skip mode: 1 = square atlases, 2 = plus large offscreen, "
|
||||
"3 = plus all depth-only")
|
||||
.range(1, 3)
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
// The shared Skate 3 Hor+ classifier cvars (skate3_ultrawide_skip_shadow_targets,
|
||||
// skate3_ultrawide_shadow_skip_mode) live in graphics/command_processor.cpp so
|
||||
// every backend sees one registration; the queries below resolve them by name.
|
||||
REXCVAR_DEFINE_BOOL(skate3_ultrawide_skip_screen_space_targets, false, "Skate 3",
|
||||
"Exclude screen-space/orthographic (pre-divided XY) passes from Skate 3 Hor+ "
|
||||
"NDC correction. Off by default so the Vulkan default classifier matches the "
|
||||
|
|
|
|||
|
|
@ -222,6 +222,10 @@ std::vector<std::string_view> GetSimpleSettingsCvars() {
|
|||
} else if (device_cvar == "vulkan_device" && HasCvar("vulkan_device")) {
|
||||
cvars.push_back("vulkan_device");
|
||||
}
|
||||
// Persist the graphics API choice only on builds where it can be chosen.
|
||||
if (HasCvar("gpu_backend") && HasCvar("d3d12_adapter") && HasCvar("vulkan_device")) {
|
||||
cvars.push_back("gpu_backend");
|
||||
}
|
||||
if (HasCvar("skate3_ultrawide")) {
|
||||
cvars.push_back("skate3_ultrawide");
|
||||
}
|
||||
|
|
@ -289,7 +293,11 @@ int ResolutionIndexFromCvar() {
|
|||
}
|
||||
|
||||
bool HasHostFrameCapCvars() {
|
||||
return HasCvar("d3d12_present_frame_limiter") && HasCvar("d3d12_present_frame_limiter_fps");
|
||||
// The host present limiter is implemented by the D3D12 presenter; on builds
|
||||
// with both backends compiled in its cvars exist even while Vulkan renders,
|
||||
// so gate on the backend actually running.
|
||||
return HasCvar("d3d12_present_frame_limiter") && HasCvar("d3d12_present_frame_limiter_fps") &&
|
||||
GetGraphicsDeviceList().cvar_name != "vulkan_device";
|
||||
}
|
||||
|
||||
// The guest-side cap paces the game's render loop at the swap boundary, so
|
||||
|
|
@ -563,6 +571,26 @@ void SetBoolCvar(std::string_view name, bool value) {
|
|||
rex::cvar::SetFlagByName(name, value ? "true" : "false");
|
||||
}
|
||||
|
||||
// The Graphics API row only exists on builds with both backends compiled in;
|
||||
// each backend's UI provider registers its device cvar, so their joint
|
||||
// presence identifies such a build.
|
||||
bool HasGraphicsApiChoice() {
|
||||
return HasCvar("gpu_backend") && HasCvar("d3d12_adapter") && HasCvar("vulkan_device");
|
||||
}
|
||||
|
||||
// 0 = Direct3D 12, 1 = Vulkan. "auto" reads as the backend actually running,
|
||||
// so the row reflects reality even after a startup fallback.
|
||||
int GraphicsApiIndexFromCvar() {
|
||||
const std::string value = rex::cvar::Query<std::string>("gpu_backend");
|
||||
if (value == "vulkan") {
|
||||
return 1;
|
||||
}
|
||||
if (value == "d3d12") {
|
||||
return 0;
|
||||
}
|
||||
return GetGraphicsDeviceList().cvar_name == "vulkan_device" ? 1 : 0;
|
||||
}
|
||||
|
||||
bool TearingFromCvar() {
|
||||
if (HasCvar("d3d12_allow_variable_refresh_rate_and_tearing")) {
|
||||
return rex::cvar::Query<bool>("d3d12_allow_variable_refresh_rate_and_tearing");
|
||||
|
|
@ -960,6 +988,7 @@ void SimpleSettingsDialog::Show() {
|
|||
|
||||
void SimpleSettingsDialog::LoadSettingsFromCvars() {
|
||||
device_list_ = GetGraphicsDeviceList();
|
||||
graphics_api_index_ = HasGraphicsApiChoice() ? GraphicsApiIndexFromCvar() : 0;
|
||||
device_index_ = DeviceIndexFromCvar(device_list_);
|
||||
resolution_scale_index_ = ResolutionIndexFromCvar();
|
||||
frame_cap_index_ = FrameCapIndexFromCvar();
|
||||
|
|
@ -1007,7 +1036,8 @@ void SimpleSettingsDialog::LoadSettingsFromCvars() {
|
|||
}
|
||||
|
||||
bool SimpleSettingsDialog::HasSettingsChanges() const {
|
||||
return device_index_ != DeviceIndexFromCvar(device_list_) ||
|
||||
return (HasGraphicsApiChoice() && graphics_api_index_ != GraphicsApiIndexFromCvar()) ||
|
||||
device_index_ != DeviceIndexFromCvar(device_list_) ||
|
||||
resolution_scale_index_ != ResolutionIndexFromCvar() ||
|
||||
frame_cap_index_ != FrameCapIndexFromCvar() ||
|
||||
(HasCvar("skate3_ultrawide") &&
|
||||
|
|
@ -1082,6 +1112,12 @@ void SimpleSettingsDialog::SaveVideo() {
|
|||
aspect_ratio_index_ =
|
||||
std::clamp(aspect_ratio_index_, 0, static_cast<int>(kAspectRatioLabels.size()) - 1);
|
||||
field_of_view_ = std::clamp(field_of_view_, 40.0f, 120.0f);
|
||||
graphics_api_index_ = std::clamp(graphics_api_index_, 0, 1);
|
||||
// Written only when the selection changed, so an untouched row keeps the
|
||||
// cvar on "auto".
|
||||
if (HasGraphicsApiChoice() && graphics_api_index_ != GraphicsApiIndexFromCvar()) {
|
||||
rex::cvar::SetFlagByName("gpu_backend", graphics_api_index_ == 1 ? "vulkan" : "d3d12");
|
||||
}
|
||||
if (!device_list_.device_names.empty() && HasCvar(device_list_.cvar_name)) {
|
||||
device_index_ = std::clamp(device_index_, 0,
|
||||
static_cast<int>(device_list_.device_names.size()) - 1);
|
||||
|
|
@ -1207,6 +1243,22 @@ void SimpleSettingsDialog::BuildRows(std::vector<RowSpec>& rows, int category) {
|
|||
switch (category) {
|
||||
case 0: { // Video
|
||||
header("Display");
|
||||
if (HasGraphicsApiChoice()) {
|
||||
RowSpec row;
|
||||
row.kind = RowSpec::kEnum;
|
||||
row.label = "Graphics API";
|
||||
row.desc =
|
||||
"Which graphics API the game renders through. Both perform "
|
||||
"similarly - try the other one if you run into driver issues. "
|
||||
"Applied with Apply & Restart.";
|
||||
row.options = {"DirectX 12", "Vulkan"};
|
||||
row.index = &graphics_api_index_;
|
||||
row.reset = [this] {
|
||||
// The build default ("auto") prefers DirectX 12 on Windows.
|
||||
graphics_api_index_ = 0;
|
||||
};
|
||||
rows.push_back(std::move(row));
|
||||
}
|
||||
if (!device_list_.device_names.empty() && HasCvar(device_list_.cvar_name)) {
|
||||
RowSpec row;
|
||||
row.kind = RowSpec::kEnum;
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@
|
|||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace rex {
|
||||
|
||||
|
|
@ -209,6 +210,13 @@ void StartForcedExitWatchdog(const char* reason) {
|
|||
REXCVAR_DEFINE_BOOL(advanced_settings_overlay_enabled, true, "UI/Advanced",
|
||||
"Enable the developer cvar browser on F4");
|
||||
|
||||
REXCVAR_DEFINE_STRING(gpu_backend, "auto", "GPU",
|
||||
"Graphics API used for rendering (auto, d3d12, vulkan). Auto prefers "
|
||||
"Direct3D 12 when available. Applied at startup; when the selected API "
|
||||
"fails to initialize, any other compiled-in backend is tried.")
|
||||
.allowed({"auto", "d3d12", "vulkan"})
|
||||
.lifecycle(rex::cvar::Lifecycle::kRequiresRestart);
|
||||
|
||||
REXCVAR_DEFINE_BOOL(show_fps_counter, false, "UI", "Show the guest FPS counter overlay")
|
||||
.lifecycle(rex::cvar::Lifecycle::kHotReload);
|
||||
|
||||
|
|
@ -505,23 +513,76 @@ bool ReXApp::ConstructRuntime(const PathConfig& paths) {
|
|||
}
|
||||
|
||||
bool ReXApp::SetupPresentation() {
|
||||
// Candidate graphics backends in preference order. The gpu_backend cvar
|
||||
// (already loaded from the config/settings files by SetupEnvironment)
|
||||
// reorders the list at runtime; "auto" keeps Direct3D 12 first when it is
|
||||
// compiled in.
|
||||
struct GraphicsCandidate {
|
||||
const char* id; // gpu_backend cvar value
|
||||
const char* name; // log-facing name
|
||||
std::unique_ptr<rex::system::IGraphicsSystem> (*make)();
|
||||
};
|
||||
std::vector<GraphicsCandidate> candidates;
|
||||
#if REX_HAS_D3D12
|
||||
config_.graphics = REX_GRAPHICS_BACKEND(rex::graphics::d3d12::D3D12GraphicsSystem);
|
||||
#elif REX_HAS_VULKAN
|
||||
config_.graphics = REX_GRAPHICS_BACKEND(rex::graphics::vulkan::VulkanGraphicsSystem);
|
||||
candidates.push_back({"d3d12", "Direct3D 12", []() -> std::unique_ptr<rex::system::IGraphicsSystem> {
|
||||
return std::make_unique<rex::graphics::d3d12::D3D12GraphicsSystem>();
|
||||
}});
|
||||
#endif
|
||||
#if REX_HAS_VULKAN
|
||||
candidates.push_back({"vulkan", "Vulkan", []() -> std::unique_ptr<rex::system::IGraphicsSystem> {
|
||||
return std::make_unique<rex::graphics::vulkan::VulkanGraphicsSystem>();
|
||||
}});
|
||||
#endif
|
||||
const std::string requested = REXCVAR_GET(gpu_backend);
|
||||
if (requested != "auto" && !candidates.empty()) {
|
||||
bool available = false;
|
||||
for (size_t i = 0; i < candidates.size(); ++i) {
|
||||
if (requested == candidates[i].id) {
|
||||
GraphicsCandidate wanted = candidates[i];
|
||||
candidates.erase(candidates.begin() + i);
|
||||
candidates.insert(candidates.begin(), wanted);
|
||||
available = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!available) {
|
||||
REXLOG_WARN("gpu_backend={} is not available in this build; using {}", requested,
|
||||
candidates[0].id);
|
||||
}
|
||||
}
|
||||
|
||||
size_t candidate_index = 0;
|
||||
if (!candidates.empty()) {
|
||||
config_.graphics = candidates[candidate_index].make();
|
||||
}
|
||||
config_.audio_factory = REX_AUDIO_BACKEND(rex::audio::sdl::SDLAudioSystem);
|
||||
config_.input_factory = REX_INPUT_BACKEND(rex::input::CreateDefaultInputSystem);
|
||||
config_.kernel_init = rex::kernel::InitializeKernel;
|
||||
|
||||
rex::system::IGraphicsSystem* selected_graphics = config_.graphics.get();
|
||||
OnPreSetup(config_);
|
||||
// A consumer may substitute its own graphics system in OnPreSetup; the
|
||||
// candidate fallback below only applies to the stock selection.
|
||||
const bool stock_graphics = config_.graphics.get() == selected_graphics;
|
||||
|
||||
if (config_.graphics) {
|
||||
for (;;) {
|
||||
X_STATUS status = config_.graphics->SetupPresentation(&app_context());
|
||||
if (XFAILED(status)) {
|
||||
if (!XFAILED(status)) {
|
||||
if (stock_graphics && !candidates.empty()) {
|
||||
REXLOG_INFO("Graphics backend: {} (gpu_backend={})",
|
||||
candidates[candidate_index].name, requested);
|
||||
}
|
||||
break;
|
||||
}
|
||||
REXLOG_ERROR("Graphics presentation setup failed: {:08X}", status);
|
||||
if (!stock_graphics || candidate_index + 1 >= candidates.size()) {
|
||||
return false;
|
||||
}
|
||||
++candidate_index;
|
||||
REXLOG_WARN("Trying the {} graphics backend instead", candidates[candidate_index].name);
|
||||
config_.graphics = candidates[candidate_index].make();
|
||||
}
|
||||
}
|
||||
|
||||
// Create window
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue