Compare commits

6 Commits

Author SHA1 Message Date
Stenzek
8c669e38c8 VideoThread: Simplify reconfiguration
- Removes cross-thread access of FSUI state.
- Fixes lockup when startup fails.
2026-02-03 20:00:02 +10:00
Stenzek
1a607257fb System: Fix game settings not deloading after shutdown 2026-02-03 19:59:59 +10:00
Anderson Cardoso
14decb1667 Atualização Português do Brasil (#3697)
Atualizado para a última versão.
2026-02-03 14:21:38 +10:00
Davide Pesavento
0b7d3ce2f6 PostProcessing: Correctly update stage count when removing/clearing stages (#3698) 2026-02-03 14:21:27 +10:00
Stenzek
016c08b9c3 GPU/HW: Use clamped rect for sprites-as-fills
Fixes VRAM corruption in Tales of Phantasia with the texture cache
enabled.
2026-02-02 19:11:40 +10:00
Stenzek
0094344360 README: Add links/instructions to Windows installer 2026-02-02 16:31:55 +10:00
7 changed files with 1089 additions and 905 deletions

View File

@@ -76,18 +76,35 @@ For x86 machines (most systems), you will need a CPU that supports the SSE4.1 in
The main releases page is limited to the last 30 releases due to automatic updater limitations. Older releases can be downloaded from https://github.com/duckstation/old-releases/releases.
### Update Channels
The automatic updater in DuckStation has two channels: "Stable" and "Preview".
- "Stable Releases": Less frequent updates, and tracks the "latest" release on GitHub. Releases in this channel have had more testing.
- "Preview Releases": Built whenever a commit is pushed to the repository, and tracks the pre-release on GitHub. This channel contains builds which have had minimal testing, and may contain bugs or issues.
By default, the updater will track the channel you downloaded from. You can change the channel in `Settings -> Interface -> Updates`.
### Windows
DuckStation **requires** Windows 10/11, specifically version 1809 or newer. If you are still using Windows 7/8/8.1, DuckStation **will not run** on your operating system. Running these operating systems in 2023 should be considered a security risk, and I would recommend updating to something which receives vendor support.
If you must use an older operating system, [v0.1-5624](https://github.com/duckstation/old-releases/releases/tag/v0.1-5624) is the last version which will run. But do not expect to recieve any assistance, these builds are no longer supported.
DuckStation **requires** Windows 10/11, specifically version 1809 or newer. If you are still using Windows 7/8/8.1, DuckStation **will not run** on your operating system. Running these operating systems in 2026 should be considered a security risk, and I would recommend updating to something which receives vendor support.
If you must use an older operating system, [v0.1-5624](https://github.com/duckstation/old-releases/releases/tag/v0.1-5624) is the last version which will run. But do not expect to receive any assistance, these builds are no longer supported.
To download:
Windows builds are provided in two formats:
- **Installer (.exe, recommended):** An installer which extracts DuckStation to your user-local programs directory, and optionally creates Start Menu/Desktop shortcuts.
- **Archive (.zip):** A zip archive containing the prebuilt binary. Choose this option if you want a "portable" installation, or do not want to run an installer.
1. Go to https://github.com/stenzek/duckstation/releases/tag/latest, and download the Windows x64 build. This is a zip archive containing the prebuilt binary. If you have an ARM64 Windows machine such as Snapdragon, download the Windows ARM64 build.
2. Alternatively, direct download link: https://github.com/stenzek/duckstation/releases/download/latest/duckstation-windows-x64-release.zip
3. Extract the archive **to a subdirectory**. The archive has no root subdirectory, so extracting to the current directory will drop a bunch of files in your download directory if you do not extract to a subdirectory.
To use the installer, simply download the installer from the releases page, run it, and follow the prompts.
- Direct download link: https://github.com/stenzek/duckstation/releases/download/latest/duckstation-windows-x64-installer.exe
- ARM64 download link (Snapdragon laptops): https://github.com/stenzek/duckstation/releases/download/latest/duckstation-windows-arm64-installer.exe
- Legacy SSE2 installer (for pre-2008 CPUs): https://github.com/stenzek/duckstation/releases/download/latest/duckstation-windows-x64-sse2-installer.exe
Once downloaded and extracted, you can launch the emulator with `duckstation-qt-x64-ReleaseLTCG.exe`. Follow the Setup Wizard to get started.
The installer is still a new addition, so if you encounter issues please let us know via Discord.
To use the archive or portable installation, follow these steps:
1. Download https://github.com/stenzek/duckstation/releases/download/latest/duckstation-windows-x64-release.zip. If you have an ARM64 Windows machine such as Snapdragon, download `duckstation-windows-arm64-release.zip` instead.
2. Extract the archive **to a subdirectory**. The archive has no root subdirectory, so extracting to the current directory will drop a bunch of files in your download directory if you do not extract to a subdirectory.
3. If you want a portable installation (see [User Directories](#user-directories)), create an empty file named `portable.txt` in the same directory as the executable.
4. Once downloaded and extracted, you can launch the emulator with `duckstation-qt-x64-ReleaseLTCG.exe`. Follow the Setup Wizard to get started.
**If you get an error about `vcruntime140_1.dll` being missing, you will need to update your Visual C++ runtime.** You can do that from this page: https://support.microsoft.com/en-au/help/2977003/the-latest-supported-visual-c-downloads. Specifically, you want the x64 runtime, which can be downloaded from https://aka.ms/vs/17/release/vc_redist.x64.exe.
@@ -99,8 +116,9 @@ DuckStation is provided for x86_64/ARM32/ARM64 Linux in AppImage formats.
The AppImages require a distribution equivalent to Ubuntu 22.04 or newer to run.
1. Go to https://github.com/stenzek/duckstation/releases/tag/latest, and download `duckstation-x64.AppImage`.
2. Run `chmod a+x` on the downloaded AppImage -- following this step, the AppImage can be run like a typical executable.
1. Download https://github.com/stenzek/duckstation/releases/download/latest/DuckStation-x64.AppImage. If you have an ARM64 Linux machine, you should download `DuckStation-arm64.AppImage`.
2. Run `chmod a+x` on the downloaded AppImage -- following this step, the AppImage can be run like a typical executable. Alternatively, in your file manager of choice, enable execute permissions via the file properties dialog.
3. When running the AppImage for the first time, it will prompt to create a launcher shortcut.
If you were previously using the Flatpak package, to migrate your data from the Flatpak to the AppImage, you can run the following command:
```bash
@@ -117,7 +135,7 @@ macOS Ventura (13.3) is required, as this is also the minimum requirement for Qt
To download:
1. Go to https://github.com/stenzek/duckstation/releases/tag/latest, and download `duckstation-mac-release.zip`.
1. Download https://github.com/stenzek/duckstation/releases/download/latest/duckstation-mac-release.zip.
2. Extract the zip by double-clicking it.
3. Open `DuckStation.app`, optionally moving it to your desired location first.

View File

@@ -2766,6 +2766,16 @@ void GPU_HW::DrawLine(const GPUBackendDrawCommand* cmd, const GSVector4 bounds,
void GPU_HW::DrawSprite(const GPUBackendDrawRectangleCommand* cmd)
{
const GSVector2i pos = GSVector2i::load<true>(&cmd->x);
const GSVector2i size = GSVector2i::load<true>(&cmd->width).u16to32();
const GSVector4i rect = GSVector4i::xyxy(pos, pos.add32(size));
const GSVector4i clamped_rect = m_clamped_drawing_area.rintersect(rect);
if (clamped_rect.rempty())
{
GL_INS_FMT("Culling off-screen sprite {}", rect);
return;
}
// Treat non-textured sprite draws as fills, so we don't break the TC on framebuffer clears.
bool draw_with_software_renderer = m_draw_with_software_renderer;
if (m_use_texture_cache && !cmd->transparency_enable && !cmd->shading_enable && !cmd->texture_enable &&
@@ -2783,23 +2793,15 @@ void GPU_HW::DrawSprite(const GPUBackendDrawRectangleCommand* cmd)
}
else
{
GL_INS_FMT("Treating non-textured sprite as VRAM fill at {},{} size {}x{}", cmd->x, cmd->y, cmd->width,
cmd->height);
FillVRAM(cmd->x, cmd->y, cmd->width, cmd->height, cmd->color, cmd->interlaced_rendering, cmd->active_line_lsb);
const GSVector2i clamped_size = clamped_rect.rsize();
GL_INS_FMT("Treating non-textured sprite as VRAM fill at {},{} size {}x{} (clamped {})", cmd->x, cmd->y,
cmd->width, cmd->height, clamped_rect);
FillVRAM(clamped_rect.left, clamped_rect.top, clamped_size.x, clamped_size.y, cmd->color,
cmd->interlaced_rendering, cmd->active_line_lsb);
return;
}
}
const GSVector2i pos = GSVector2i::load<true>(&cmd->x);
const GSVector2i size = GSVector2i::load<true>(&cmd->width).u16to32();
const GSVector4i rect = GSVector4i::xyxy(pos, pos.add32(size));
const GSVector4i clamped_rect = m_clamped_drawing_area.rintersect(rect);
if (clamped_rect.rempty())
{
GL_INS_FMT("Culling off-screen sprite {}", rect);
return;
}
PrepareDraw(cmd);
SetBatchDepthBuffer(cmd, false);
SetBatchSpriteMode(cmd, m_allow_sprite_mode);

View File

@@ -2178,6 +2178,10 @@ void System::DestroySystem()
FullscreenUI::OnSystemDestroyed();
Host::OnSystemDestroyed();
// Revert to global settings.
UpdateGameSettingsLayer();
ApplySettings(true);
}
void System::AbnormalShutdown(const std::string_view reason)

View File

@@ -72,7 +72,8 @@ static void WakeThread();
static void WakeThreadIfSleeping();
static bool SleepThread(bool allow_sleep);
static bool CreateDeviceOnThread(RenderAPI api, bool fullscreen, bool preserve_imgui_on_failure, Error* error);
static bool CreateDeviceOnThread(RenderAPI api, bool fullscreen, bool start_fullscreen_ui,
bool preserve_imgui_on_failure, Error* error);
static void DestroyDeviceOnThread(bool preserve_imgui_state);
static void ResizeRenderWindowOnThread(u32 width, u32 height, float scale, float refresh_rate);
static void RecreateRenderWindowOnThread(bool fullscreen, bool allow_exclusive_fullscreen);
@@ -80,8 +81,8 @@ static void RenderWindowResizedOnThread();
static bool CheckExclusiveFullscreenOnThread();
static void ReconfigureOnThread(VideoThreadReconfigureCommand* cmd);
static bool CreateGPUBackendOnThread(GPURenderer renderer, bool upload_vram, const GPUSettings* old_settings,
Error* error);
static bool CreateGPUBackendOnThread(bool hardware_renderer, bool upload_vram, const GPUSettings* old_settings,
GPURenderer* out_renderer, Error* error);
static void DestroyGPUBackendOnThread();
static void DestroyGPUPresenterOnThread();
@@ -102,7 +103,8 @@ struct ALIGN_TO_CACHE_LINE State
Threading::ThreadHandle thread_handle;
Common::unique_aligned_ptr<u8[]> command_fifo_data;
WindowInfo render_window_info;
std::optional<GPURenderer> requested_renderer; // TODO: Non thread safe accessof this
std::optional<GPURenderer> requested_renderer;
bool requested_fullscreen_ui = false;
bool use_thread = false;
bool fullscreen_state = false;
@@ -118,7 +120,6 @@ struct ALIGN_TO_CACHE_LINE State
u8 run_idle_reasons = 0;
bool run_idle_flag = false;
GPUVSyncMode requested_vsync = GPUVSyncMode::Disabled;
bool requested_fullscreen_ui = false;
std::string game_title;
std::string game_serial;
std::string game_path;
@@ -540,21 +541,24 @@ bool VideoThread::Reconfigure(std::optional<GPURenderer> renderer, bool upload_v
{
INFO_LOG("Reconfiguring video thread.");
s_state.requested_renderer = renderer;
s_state.fullscreen_state = fullscreen.value_or(s_state.fullscreen_state);
const bool new_requested_fullscreen_ui = start_fullscreen_ui.value_or(s_state.requested_fullscreen_ui);
const bool new_fullscreen_state = fullscreen.value_or(s_state.fullscreen_state);
GPURenderer created_renderer = GPURenderer::Count;
VideoThreadReconfigureCommand::Result result = VideoThreadReconfigureCommand::Result::Failed;
bool result = false;
VideoThreadReconfigureCommand* cmd =
AllocateCommand<VideoThreadReconfigureCommand>(VideoThreadCommandType::Reconfigure);
cmd->renderer = s_state.requested_renderer;
cmd->fullscreen = s_state.fullscreen_state;
cmd->start_fullscreen_ui = start_fullscreen_ui;
cmd->renderer = renderer;
cmd->fullscreen = new_fullscreen_state;
cmd->start_fullscreen_ui = new_requested_fullscreen_ui;
cmd->vsync_mode = System::GetEffectiveVSyncMode();
cmd->present_skip_mode = System::GetEffectivePresentSkipMode();
cmd->force_recreate_device = recreate_device;
cmd->upload_vram = upload_vram;
cmd->error_ptr = error;
cmd->out_result = &result;
cmd->out_created_renderer = &created_renderer;
cmd->settings = g_settings;
if (!s_state.use_thread) [[unlikely]]
@@ -562,7 +566,23 @@ bool VideoThread::Reconfigure(std::optional<GPURenderer> renderer, bool upload_v
else
PushCommandAndSync(cmd, false);
return result;
// Update CPU thread state.
if (result == VideoThreadReconfigureCommand::Result::FailedWithDeviceLoss)
{
s_state.requested_renderer.reset();
s_state.requested_fullscreen_ui = false;
s_state.fullscreen_state = false;
return false;
}
// But the renderer may not have been successfully switched. Keep our CPU thread state in sync.
if (created_renderer == GPURenderer::Count)
s_state.requested_renderer.reset();
else
s_state.requested_renderer = renderer;
s_state.requested_fullscreen_ui = new_requested_fullscreen_ui;
s_state.fullscreen_state = new_fullscreen_state;
return (result == VideoThreadReconfigureCommand::Result::Success);
}
bool VideoThread::StartFullscreenUI(bool fullscreen, Error* error)
@@ -570,7 +590,7 @@ bool VideoThread::StartFullscreenUI(bool fullscreen, Error* error)
// Don't need to reconfigure if we already have a system.
if (System::IsValid())
{
RunOnThread([]() { s_state.requested_fullscreen_ui = true; });
s_state.requested_fullscreen_ui = true;
return true;
}
@@ -584,10 +604,10 @@ bool VideoThread::IsFullscreenUIRequested()
void VideoThread::StopFullscreenUI()
{
// Don't need to reconfigure if we already have a system.
// shouldn't be changing this while we have a system
if (System::IsValid())
{
RunOnThread([]() { s_state.requested_fullscreen_ui = false; });
s_state.requested_fullscreen_ui = false;
return;
}
@@ -607,7 +627,6 @@ bool VideoThread::CreateGPUBackend(GPURenderer renderer, bool upload_vram, std::
void VideoThread::DestroyGPUBackend()
{
Reconfigure(std::nullopt, false, std::nullopt, std::nullopt, false, nullptr);
s_state.requested_renderer.reset();
}
bool VideoThread::HasGPUBackend()
@@ -626,7 +645,8 @@ bool VideoThread::IsFullscreen()
return s_state.fullscreen_state;
}
bool VideoThread::CreateDeviceOnThread(RenderAPI api, bool fullscreen, bool preserve_imgui_on_failure, Error* error)
bool VideoThread::CreateDeviceOnThread(RenderAPI api, bool fullscreen, bool start_fullscreen_ui,
bool preserve_imgui_on_failure, Error* error)
{
DebugAssert(!g_gpu_device);
@@ -731,7 +751,7 @@ bool VideoThread::CreateDeviceOnThread(RenderAPI api, bool fullscreen, bool pres
static_cast<float>(sc->GetHeight()));
}
if (s_state.requested_fullscreen_ui)
if (start_fullscreen_ui)
FullscreenUI::Initialize();
if (const GPUSwapChain* swap_chain = g_gpu_device->GetMainSwapChain())
@@ -780,8 +800,8 @@ void VideoThread::DestroyDeviceOnThread(bool preserve_imgui_state)
s_state.render_window_info = WindowInfo();
}
bool VideoThread::CreateGPUBackendOnThread(GPURenderer renderer, bool upload_vram, const GPUSettings* old_settings,
Error* error)
bool VideoThread::CreateGPUBackendOnThread(bool hardware_renderer, bool upload_vram, const GPUSettings* old_settings,
GPURenderer* out_renderer, Error* error)
{
Error local_error;
@@ -811,9 +831,7 @@ bool VideoThread::CreateGPUBackendOnThread(GPURenderer renderer, bool upload_vra
ImGuiManager::UpdateDebugWindowConfig();
#endif
const bool is_hardware = (renderer != GPURenderer::Software);
if (is_hardware)
if (hardware_renderer)
s_state.gpu_backend = GPUBackend::CreateHardwareBackend();
else
s_state.gpu_backend = GPUBackend::CreateSoftwareBackend();
@@ -821,16 +839,19 @@ bool VideoThread::CreateGPUBackendOnThread(GPURenderer renderer, bool upload_vra
bool okay = s_state.gpu_backend->Initialize(upload_vram, &local_error);
if (!okay)
{
ERROR_LOG("Failed to create {} renderer: {}", Settings::GetRendererName(renderer), local_error.GetDescription());
ERROR_LOG("Failed to create {} renderer: {}", hardware_renderer ? "hardware" : "software",
local_error.GetDescription());
if (is_hardware && !System::IsStartupCancelled())
if (hardware_renderer && !System::IsStartupCancelled())
{
Host::AddIconOSDMessage(
OSDMessageType::Error, "GPUBackendCreationFailed", ICON_FA_PAINT_ROLLER,
fmt::format(TRANSLATE_FS("OSDMessage", "Failed to initialize {} renderer, falling back to software renderer."),
Settings::GetRendererName(s_state.requested_renderer.value())));
fmt::format(
"{}\n{}",
TRANSLATE_SV("OSDMessage", "Failed to initialize hardware renderer, falling back to software renderer."),
local_error.GetDescription()));
s_state.requested_renderer = GPURenderer::Software;
hardware_renderer = false;
s_state.gpu_backend = GPUBackend::CreateSoftwareBackend();
if (!s_state.gpu_backend->Initialize(upload_vram, &local_error))
Panic("Failed to initialize fallback software renderer");
@@ -844,6 +865,9 @@ bool VideoThread::CreateGPUBackendOnThread(GPURenderer renderer, bool upload_vra
}
}
*out_renderer =
hardware_renderer ? Settings::GetRendererForRenderAPI(g_gpu_device->GetRenderAPI()) : GPURenderer::Software;
g_gpu_device->SetGPUTimingEnabled(g_gpu_settings.display_show_gpu_usage);
s_state.gpu_backend->RestoreDeviceContext();
SetRunIdleReason(RunIdleReason::NoGPUBackend, false);
@@ -853,18 +877,19 @@ bool VideoThread::CreateGPUBackendOnThread(GPURenderer renderer, bool upload_vra
void VideoThread::ReconfigureOnThread(VideoThreadReconfigureCommand* cmd)
{
// Store state.
s_state.requested_fullscreen_ui = cmd->start_fullscreen_ui.value_or(s_state.requested_fullscreen_ui);
s_state.requested_vsync = cmd->vsync_mode;
VideoPresenter::SetPresentSkipMode(cmd->present_skip_mode);
// Are we shutting down everything?
if (!cmd->renderer.has_value() && !s_state.requested_fullscreen_ui)
if (!cmd->renderer.has_value() && !cmd->start_fullscreen_ui)
{
// Serial clear must be after backend destroy, otherwise textures won't dump.
DestroyGPUBackendOnThread();
DestroyGPUPresenterOnThread();
DestroyDeviceOnThread(false);
ClearGameInfoOnThread();
*cmd->out_result = VideoThreadReconfigureCommand::Result::Success;
*cmd->out_created_renderer = GPURenderer::Count;
return;
}
@@ -891,9 +916,10 @@ void VideoThread::ReconfigureOnThread(VideoThreadReconfigureCommand* cmd)
// Device recreation?
const RenderAPI current_api = g_gpu_device ? g_gpu_device->GetRenderAPI() : RenderAPI::None;
const RenderAPI expected_api =
(cmd->renderer.has_value() && cmd->renderer.value() == GPURenderer::Software && current_api != RenderAPI::None) ?
(!cmd->force_recreate_device && current_api != RenderAPI::None &&
(!cmd->renderer.has_value() || cmd->renderer.value() == GPURenderer::Software)) ?
current_api :
Settings::GetRenderAPIForRenderer(s_state.requested_renderer.value_or(g_gpu_settings.gpu_renderer));
Settings::GetRenderAPIForRenderer(cmd->renderer.value_or(g_gpu_settings.gpu_renderer));
if (cmd->force_recreate_device || !GPUDevice::IsSameRenderAPI(current_api, expected_api))
{
Timer timer;
@@ -901,7 +927,8 @@ void VideoThread::ReconfigureOnThread(VideoThreadReconfigureCommand* cmd)
DestroyDeviceOnThread(true);
Error local_error;
if (!CreateDeviceOnThread(expected_api, cmd->fullscreen, current_api != RenderAPI::None, &local_error))
if (!CreateDeviceOnThread(expected_api, cmd->fullscreen, cmd->start_fullscreen_ui, current_api != RenderAPI::None,
&local_error))
{
Host::AddIconOSDMessage(
OSDMessageType::Error, "DeviceSwitchFailed", ICON_FA_PAINT_ROLLER,
@@ -909,59 +936,66 @@ void VideoThread::ReconfigureOnThread(VideoThreadReconfigureCommand* cmd)
GPUDevice::RenderAPIToString(expected_api), GPUDevice::RenderAPIToString(current_api),
local_error.GetDescription()));
Host::ReleaseRenderWindow();
if (current_api == RenderAPI::None || !CreateDeviceOnThread(current_api, cmd->fullscreen, false, &local_error))
if (current_api == RenderAPI::None ||
!CreateDeviceOnThread(current_api, cmd->fullscreen, cmd->start_fullscreen_ui, false, &local_error))
{
if (cmd->error_ptr)
*cmd->error_ptr = local_error;
*cmd->out_result = false;
*cmd->out_result = VideoThreadReconfigureCommand::Result::FailedWithDeviceLoss;
*cmd->out_created_renderer = GPURenderer::Count;
return;
}
}
INFO_LOG("GPU device recreated in {:.2f}ms", timer.GetTimeMilliseconds());
INFO_LOG("GPU device created in {:.2f}ms", timer.GetTimeMilliseconds());
}
// Full shutdown case handled above.
Assert(cmd->renderer.has_value() || s_state.requested_fullscreen_ui);
if (cmd->renderer.has_value())
{
Timer timer;
// Do we want a renderer?
if (!(*cmd->out_result =
CreateGPUBackendOnThread(cmd->renderer.value(), cmd->upload_vram, &old_settings, cmd->error_ptr)))
if (CreateGPUBackendOnThread(cmd->renderer.value() != GPURenderer::Software, cmd->upload_vram, &old_settings,
cmd->out_created_renderer, cmd->error_ptr))
{
*cmd->out_result = VideoThreadReconfigureCommand::Result::Success;
INFO_LOG("GPU backend created in {:.2f}ms", timer.GetTimeMilliseconds());
}
else
{
// No renderer created.
*cmd->out_result = VideoThreadReconfigureCommand::Result::Failed;
*cmd->out_created_renderer = GPURenderer::Count;
// If we had a renderer, it means it was a switch, and we need to bail out the thread.
if (had_renderer)
{
VideoThread::ReportFatalErrorAndShutdown("Failed to switch GPU backend.");
*cmd->out_result = true;
}
else
{
// No point keeping the presenter around.
DestroyGPUBackendOnThread();
DestroyGPUPresenterOnThread();
ClearGameInfoOnThread();
// Drop device if we're not running FSUI.
if (!cmd->start_fullscreen_ui)
DestroyDeviceOnThread(false);
}
}
INFO_LOG("GPU device recreated in {:.2f}ms", timer.GetTimeMilliseconds());
}
else if (s_state.requested_fullscreen_ui)
else
{
// s_state.requested_fullscreen_ui starts FullscreenUI in CreateDeviceOnThread().
if (!(*cmd->out_result =
g_gpu_device || CreateDeviceOnThread(expected_api, cmd->fullscreen, false, cmd->error_ptr)))
{
return;
}
// Full shutdown case handled above. This is just for running FSUI.
DebugAssert(cmd->start_fullscreen_ui);
DebugAssert(g_gpu_device);
// Don't need to present game frames anymore.
DestroyGPUPresenterOnThread();
ClearGameInfoOnThread();
*cmd->out_result = VideoThreadReconfigureCommand::Result::Success;
}
}

View File

@@ -70,13 +70,21 @@ struct VideoThreadCommand
struct VideoThreadReconfigureCommand : public VideoThreadCommand
{
enum class Result : u8
{
Success,
Failed,
FailedWithDeviceLoss,
};
Error* error_ptr;
bool* out_result;
Result* out_result;
GPURenderer* out_created_renderer;
std::optional<GPURenderer> renderer;
std::optional<bool> start_fullscreen_ui;
GPUVSyncMode vsync_mode;
PresentSkipMode present_skip_mode;
bool fullscreen;
bool start_fullscreen_ui;
bool force_recreate_device;
bool upload_vram;
GPUSettings settings;

File diff suppressed because it is too large Load Diff

View File

@@ -367,8 +367,8 @@ void PostProcessing::Config::RemoveStage(SettingsInterface& si, const char* sect
const u32 new_stage_count = stage_count - 1;
si.ClearSection(GetStageConfigSection(section, new_stage_count));
// if game settings, wipe the field out so we can potentially remove the file
if (&si != Core::GetBaseSettingsLayer())
// if game settings and no stages left, wipe the field out so we can potentially remove the file
if (&si != Core::GetBaseSettingsLayer() && new_stage_count == 0)
si.DeleteValue(section, "StageCount");
else
si.SetUIntValue(section, "StageCount", new_stage_count);
@@ -414,9 +414,9 @@ void PostProcessing::Config::ClearStages(SettingsInterface& si, const char* sect
// if game settings, wipe the field out so we can potentially remove the file
if (&si != Core::GetBaseSettingsLayer())
si.SetUIntValue(section, "StageCount", 0);
else
si.DeleteValue(section, "StageCount");
else
si.SetUIntValue(section, "StageCount", 0);
}
PostProcessing::Chain::Chain(const char* section) : m_section(section)