diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp index f4d5d076d..7f495323f 100644 --- a/src/core/gpu.cpp +++ b/src/core/gpu.cpp @@ -985,7 +985,7 @@ void GPU::UpdateCRTCDisplayParameters() if ((cs.display_vram_width != old_vram_width || cs.display_vram_height != old_vram_height) && g_settings.gpu_resolution_scale == 0) { - GPUThread::RunOnBackend([](GPUBackend* backend) { backend->UpdateResolutionScale(); }, false, false); + GPUBackend::QueueUpdateResolutionScale(); } } diff --git a/src/core/gpu_backend.cpp b/src/core/gpu_backend.cpp index a1bbe8e04..c61bc5027 100644 --- a/src/core/gpu_backend.cpp +++ b/src/core/gpu_backend.cpp @@ -125,10 +125,12 @@ bool GPUBackend::Initialize(bool clear_vram, Error* error) return true; } -void GPUBackend::UpdateSettings(const GPUSettings& old_settings) +bool GPUBackend::UpdateSettings(const GPUSettings& old_settings, Error* error) { if (g_gpu_settings.display_show_gpu_stats != old_settings.display_show_gpu_stats) GPUBackend::ResetStatistics(); + + return true; } GPUThreadCommand* GPUBackend::NewClearVRAMCommand() @@ -369,6 +371,20 @@ bool GPUBackend::AllocateMemorySaveStates(std::span sta return result; } +void GPUBackend::QueueUpdateResolutionScale() +{ + DebugAssert(!GPUThread::IsOnThread()); + + GPUThread::RunOnBackend( + [](GPUBackend* backend) { + Error error; + if (!backend->UpdateResolutionScale(&error)) [[unlikely]] + GPUThread::ReportFatalErrorAndShutdown( + fmt::format("Failed to update resolution scale: {}", error.GetDescription())); + }, + false, true); +} + void GPUBackend::HandleCommand(const GPUThreadCommand* cmd) { switch (cmd->type) @@ -753,3 +769,158 @@ void GPUBackend::RenderScreenshotToFile(const std::string_view path, DisplayScre }, false, false); } + +namespace { + +class GPUNullBackend final : public GPUBackend +{ +public: + GPUNullBackend(GPUPresenter& presenter); + ~GPUNullBackend() override; + + bool Initialize(bool upload_vram, Error* error) override; + bool UpdateSettings(const GPUSettings& old_settings, Error* error) override; + + u32 GetResolutionScale() const override; + bool UpdateResolutionScale(Error* error) override; + + void RestoreDeviceContext() override; + void FlushRender() override; + + void ReadVRAM(u32 x, u32 y, u32 width, u32 height) override; + void FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color, bool interlaced_rendering, + u8 interlaced_display_field) override; + void UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, bool set_mask, bool check_mask) override; + void CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height, bool set_mask, + bool check_mask) override; + + void DrawPolygon(const GPUBackendDrawPolygonCommand* cmd) override; + void DrawPrecisePolygon(const GPUBackendDrawPrecisePolygonCommand* cmd) override; + void DrawSprite(const GPUBackendDrawRectangleCommand* cmd) override; + void DrawLine(const GPUBackendDrawLineCommand* cmd) override; + void DrawPreciseLine(const GPUBackendDrawPreciseLineCommand* cmd) override; + + void DrawingAreaChanged() override; + void ClearCache() override; + void OnBufferSwapped() override; + void ClearVRAM() override; + + void UpdateDisplay(const GPUBackendUpdateDisplayCommand* cmd) override; + + void LoadState(const GPUBackendLoadStateCommand* cmd) override; + + bool AllocateMemorySaveState(System::MemorySaveState& mss, Error* error) override; + void DoMemoryState(StateWrapper& sw, System::MemorySaveState& mss) override; +}; + +} // namespace + +GPUNullBackend::GPUNullBackend(GPUPresenter& presenter) : GPUBackend(presenter) +{ +} + +GPUNullBackend::~GPUNullBackend() = default; + +bool GPUNullBackend::Initialize(bool upload_vram, Error* error) +{ + return GPUBackend::Initialize(upload_vram, error); +} + +bool GPUNullBackend::UpdateSettings(const GPUSettings& old_settings, Error* error) +{ + return GPUBackend::UpdateSettings(old_settings, error); +} + +u32 GPUNullBackend::GetResolutionScale() const +{ + return 1; +} + +bool GPUNullBackend::UpdateResolutionScale(Error* error) +{ + return true; +} + +void GPUNullBackend::RestoreDeviceContext() +{ +} + +void GPUNullBackend::FlushRender() +{ +} + +void GPUNullBackend::ReadVRAM(u32 x, u32 y, u32 width, u32 height) +{ +} + +void GPUNullBackend::FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color, bool interlaced_rendering, + u8 interlaced_display_field) +{ +} + +void GPUNullBackend::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, bool set_mask, bool check_mask) +{ +} + +void GPUNullBackend::CopyVRAM(u32 src_x, u32 src_y, u32 dst_x, u32 dst_y, u32 width, u32 height, bool set_mask, + bool check_mask) +{ +} + +void GPUNullBackend::DrawPolygon(const GPUBackendDrawPolygonCommand* cmd) +{ +} + +void GPUNullBackend::DrawPrecisePolygon(const GPUBackendDrawPrecisePolygonCommand* cmd) +{ +} + +void GPUNullBackend::DrawSprite(const GPUBackendDrawRectangleCommand* cmd) +{ +} + +void GPUNullBackend::DrawLine(const GPUBackendDrawLineCommand* cmd) +{ +} + +void GPUNullBackend::DrawPreciseLine(const GPUBackendDrawPreciseLineCommand* cmd) +{ +} + +void GPUNullBackend::DrawingAreaChanged() +{ +} + +void GPUNullBackend::ClearCache() +{ +} + +void GPUNullBackend::OnBufferSwapped() +{ +} + +void GPUNullBackend::ClearVRAM() +{ +} + +void GPUNullBackend::UpdateDisplay(const GPUBackendUpdateDisplayCommand* cmd) +{ +} + +void GPUNullBackend::LoadState(const GPUBackendLoadStateCommand* cmd) +{ +} + +bool GPUNullBackend::AllocateMemorySaveState(System::MemorySaveState& mss, Error* error) +{ + return false; +} + +void GPUNullBackend::DoMemoryState(StateWrapper& sw, System::MemorySaveState& mss) +{ +} + +std::unique_ptr GPUBackend::CreateNullBackend(GPUPresenter& presenter) +{ + return std::make_unique(presenter); +} diff --git a/src/core/gpu_backend.h b/src/core/gpu_backend.h index a3fee89c3..5baa6448b 100644 --- a/src/core/gpu_backend.h +++ b/src/core/gpu_backend.h @@ -56,6 +56,7 @@ public: static std::unique_ptr CreateHardwareBackend(GPUPresenter& presenter); static std::unique_ptr CreateSoftwareBackend(GPUPresenter& presenter); + static std::unique_ptr CreateNullBackend(GPUPresenter& presenter); static bool RenderScreenshotToBuffer(u32 width, u32 height, bool postfx, Image* out_image); static void RenderScreenshotToFile(const std::string_view path, DisplayScreenshotMode mode, u8 quality, @@ -67,6 +68,8 @@ public: static bool AllocateMemorySaveStates(std::span states, Error* error); + static void QueueUpdateResolutionScale(); + public: GPUBackend(GPUPresenter& presenter); virtual ~GPUBackend(); @@ -75,13 +78,13 @@ public: virtual bool Initialize(bool upload_vram, Error* error); - virtual void UpdateSettings(const GPUSettings& old_settings); + virtual bool UpdateSettings(const GPUSettings& old_settings, Error* error); /// Returns the current resolution scale. virtual u32 GetResolutionScale() const = 0; /// Updates the resolution scale when it's set to automatic. - virtual void UpdateResolutionScale() = 0; + virtual bool UpdateResolutionScale(Error* error) = 0; // Graphics API state reset/restore - call when drawing the UI etc. // TODO: replace with "invalidate cached state" diff --git a/src/core/gpu_hw.cpp b/src/core/gpu_hw.cpp index 78d84c900..521a551c8 100644 --- a/src/core/gpu_hw.cpp +++ b/src/core/gpu_hw.cpp @@ -277,11 +277,8 @@ bool GPU_HW::Initialize(bool upload_vram, Error* error) if (m_use_texture_cache) { - if (!GPUTextureCache::Initialize(this)) - { - ERROR_LOG("Failed to initialize texture cache, disabling."); - m_use_texture_cache = false; - } + if (!GPUTextureCache::Initialize(this, error)) + return false; } else { @@ -435,9 +432,10 @@ void GPU_HW::RestoreDeviceContext() m_batch_ubo_dirty = true; } -void GPU_HW::UpdateSettings(const GPUSettings& old_settings) +bool GPU_HW::UpdateSettings(const GPUSettings& old_settings, Error* error) { - GPUBackend::UpdateSettings(old_settings); + if (!GPUBackend::UpdateSettings(old_settings, error)) + return false; FlushRender(); @@ -543,21 +541,19 @@ void GPU_HW::UpdateSettings(const GPUSettings& old_settings) if (shaders_changed) { - Error error; - if (!CompilePipelines(&error)) + if (!CompilePipelines(error)) { - ERROR_LOG("Failed to recompile pipelines: {}", error.GetDescription()); - Panic("Failed to recompile pipelines."); + Error::AddPrefix(error, "Failed to recompile pipelines: "); + return false; } } else if (resolution_dependent_shaders_changed || downsampling_shaders_changed) { - Error error; - if ((resolution_dependent_shaders_changed && !CompileResolutionDependentPipelines(&error)) || - (downsampling_shaders_changed && !CompileDownsamplePipelines(&error))) + if ((resolution_dependent_shaders_changed && !CompileResolutionDependentPipelines(error)) || + (downsampling_shaders_changed && !CompileDownsamplePipelines(error))) { - ERROR_LOG("Failed to recompile resolution dependent pipelines: {}", error.GetDescription()); - Panic("Failed to recompile resolution dependent pipelines."); + Error::AddPrefix(error, "Failed to recompile resolution dependent pipelines: "); + return false; } } @@ -568,11 +564,10 @@ void GPU_HW::UpdateSettings(const GPUSettings& old_settings) g_gpu_device->PurgeTexturePool(); g_gpu_device->WaitForGPUIdle(); - Error error; - if (!CreateBuffers(&error)) + if (!CreateBuffers(error)) { - ERROR_LOG("Failed to recreate buffers: {}", error.GetDescription()); - Panic("Failed to recreate buffers."); + Error::AddPrefix(error, "Failed to recreate buffers: "); + return false; } UpdateDownsamplingLevels(); @@ -591,10 +586,10 @@ void GPU_HW::UpdateSettings(const GPUSettings& old_settings) if (m_use_texture_cache && !old_settings.gpu_texture_cache) { - if (!GPUTextureCache::Initialize(this)) + if (!GPUTextureCache::Initialize(this, error)) { - ERROR_LOG("Failed to initialize texture cache, disabling."); - m_use_texture_cache = false; + Error::AddPrefix(error, "Failed to initialize texture cache: "); + return false; } } else if (!m_use_texture_cache && old_settings.gpu_texture_cache) @@ -602,7 +597,8 @@ void GPU_HW::UpdateSettings(const GPUSettings& old_settings) GPUTextureCache::Shutdown(); } - GPUTextureCache::UpdateSettings(m_use_texture_cache, old_settings); + if (!GPUTextureCache::UpdateSettings(m_use_texture_cache, old_settings, error)) + return false; if (g_gpu_settings.gpu_downsample_mode != old_settings.gpu_downsample_mode || (g_gpu_settings.gpu_downsample_mode == GPUDownsampleMode::Box && @@ -622,6 +618,8 @@ void GPU_HW::UpdateSettings(const GPUSettings& old_settings) m_draw_mode.mode_reg.texture_mode == GPUTextureMode::Palette8Bit); } } + + return true; } void GPU_HW::CheckSettings() @@ -783,10 +781,12 @@ u32 GPU_HW::CalculateResolutionScale() const return std::clamp(scale, 1, GetMaxResolutionScale()); } -void GPU_HW::UpdateResolutionScale() +bool GPU_HW::UpdateResolutionScale(Error* error) { - if (CalculateResolutionScale() != m_resolution_scale) - UpdateSettings(g_settings); + if (CalculateResolutionScale() == m_resolution_scale) + return true; + + return UpdateSettings(g_settings, error); } GPUDownsampleMode GPU_HW::GetDownsampleMode(u32 resolution_scale) const @@ -900,8 +900,6 @@ GPUTexture::Format GPU_HW::GetDepthBufferFormat() const bool GPU_HW::CreateBuffers(Error* error) { - DestroyBuffers(); - // scale vram size to internal resolution const u32 texture_width = VRAM_WIDTH * m_resolution_scale; const u32 texture_height = VRAM_HEIGHT * m_resolution_scale; diff --git a/src/core/gpu_hw.h b/src/core/gpu_hw.h index 302bcf8e4..f696c1a67 100644 --- a/src/core/gpu_hw.h +++ b/src/core/gpu_hw.h @@ -67,10 +67,9 @@ public: void RestoreDeviceContext() override; void FlushRender() override; -protected: - void UpdateSettings(const GPUSettings& old_settings) override; + bool UpdateSettings(const GPUSettings& old_settings, Error* error) override; - void UpdateResolutionScale() override; + bool UpdateResolutionScale(Error* error) override; void FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color, bool interlaced_rendering, u8 active_line_lsb) override; void ReadVRAM(u32 x, u32 y, u32 width, u32 height) override; diff --git a/src/core/gpu_hw_texture_cache.cpp b/src/core/gpu_hw_texture_cache.cpp index ea738fa8b..bc04d6150 100644 --- a/src/core/gpu_hw_texture_cache.cpp +++ b/src/core/gpu_hw_texture_cache.cpp @@ -249,7 +249,7 @@ static bool IsDumpingVRAMWriteTextures(); static void UpdateVRAMTrackingState(); static void SetHashCacheTextureFormat(); -static bool CompilePipelines(); +static bool CompilePipelines(Error* error); static void DestroyPipelines(); static const Source* ReturnSource(Source* source, const GSVector4i uv_rect, PaletteRecordFlags flags); @@ -581,20 +581,20 @@ bool GPUTextureCache::IsDumpingVRAMWriteTextures() return (g_gpu_settings.texture_replacements.dump_textures && !s_state.config.dump_texture_pages); } -bool GPUTextureCache::Initialize(GPU_HW* backend) +bool GPUTextureCache::Initialize(GPU_HW* backend, Error* error) { s_state.hw_backend = backend; SetHashCacheTextureFormat(); ReloadTextureReplacements(false); UpdateVRAMTrackingState(); - if (!CompilePipelines()) + if (!CompilePipelines(error)) return false; return true; } -void GPUTextureCache::UpdateSettings(bool use_texture_cache, const GPUSettings& old_settings) +bool GPUTextureCache::UpdateSettings(bool use_texture_cache, const GPUSettings& old_settings, Error* error) { const bool prev_tracking_state = s_state.track_vram_writes; @@ -613,8 +613,11 @@ void GPUTextureCache::UpdateSettings(bool use_texture_cache, const GPUSettings& s_state.config.replacement_scale_linear_filter != old_replacement_scale_linear_filter) { DestroyPipelines(); - if (!CompilePipelines()) [[unlikely]] - Panic("Failed to compile pipelines on TC replacement settings change"); + if (!CompilePipelines(error)) [[unlikely]] + { + Error::AddPrefix(error, "Failed to compile pipelines on TC replacement settings change: "); + return false; + } } } @@ -625,6 +628,8 @@ void GPUTextureCache::UpdateSettings(bool use_texture_cache, const GPUSettings& if (s_state.track_vram_writes != prev_tracking_state) Invalidate(); + + return true; } bool GPUTextureCache::GetStateSize(StateWrapper& sw, u32* size) @@ -828,7 +833,7 @@ void GPUTextureCache::SetHashCacheTextureFormat() INFO_LOG("Using {} format for hash cache entries.", GPUTexture::GetFormatName(s_state.hash_cache_texture_format)); } -bool GPUTextureCache::CompilePipelines() +bool GPUTextureCache::CompilePipelines(Error* error) { if (!g_gpu_settings.texture_replacements.enable_texture_replacements) return true; diff --git a/src/core/gpu_hw_texture_cache.h b/src/core/gpu_hw_texture_cache.h index f8023da49..42ad5f3bf 100644 --- a/src/core/gpu_hw_texture_cache.h +++ b/src/core/gpu_hw_texture_cache.h @@ -5,6 +5,7 @@ #include "gpu_types.h" +class Error; class Image; class GPUTexture; class StateWrapper; @@ -103,8 +104,8 @@ struct Source TListNode hash_cache_ref; }; -bool Initialize(GPU_HW* backend); -void UpdateSettings(bool use_texture_cache, const GPUSettings& old_settings); +bool Initialize(GPU_HW* backend, Error* error); +bool UpdateSettings(bool use_texture_cache, const GPUSettings& old_settings, Error* error); bool GetStateSize(StateWrapper& sw, u32* size); bool DoState(StateWrapper& sw, bool skip); diff --git a/src/core/gpu_presenter.cpp b/src/core/gpu_presenter.cpp index 2fea1c54d..7590c7969 100644 --- a/src/core/gpu_presenter.cpp +++ b/src/core/gpu_presenter.cpp @@ -56,7 +56,7 @@ bool GPUPresenter::Initialize(Error* error) return true; } -void GPUPresenter::UpdateSettings(const GPUSettings& old_settings) +bool GPUPresenter::UpdateSettings(const GPUSettings& old_settings, Error* error) { if (g_gpu_settings.display_scaling != old_settings.display_scaling || g_gpu_settings.display_deinterlacing_mode != old_settings.display_deinterlacing_mode || @@ -69,11 +69,14 @@ void GPUPresenter::UpdateSettings(const GPUSettings& old_settings) if (!CompileDisplayPipelines( g_gpu_settings.display_scaling != old_settings.display_scaling, g_gpu_settings.display_deinterlacing_mode != old_settings.display_deinterlacing_mode, - g_gpu_settings.display_24bit_chroma_smoothing != old_settings.display_24bit_chroma_smoothing, nullptr)) + g_gpu_settings.display_24bit_chroma_smoothing != old_settings.display_24bit_chroma_smoothing, error)) { - Panic("Failed to compile display pipeline on settings change."); + Error::AddPrefix(error, "Failed to compile display pipeline on settings change:\n"); + return false; } } + + return true; } bool GPUPresenter::CompileDisplayPipelines(bool display, bool deinterlace, bool chroma_smoothing, Error* error) @@ -818,6 +821,7 @@ bool GPUPresenter::PresentFrame(GPUPresenter* presenter, GPUBackend* backend, bo if (pres == GPUDevice::PresentResult::DeviceLost) [[unlikely]] { ERROR_LOG("GPU device lost during present."); + GPUThread::ReportFatalErrorAndShutdown("GPU device lost. The log may contain more information."); return false; } diff --git a/src/core/gpu_presenter.h b/src/core/gpu_presenter.h index 47033928a..ccd84d3af 100644 --- a/src/core/gpu_presenter.h +++ b/src/core/gpu_presenter.h @@ -42,7 +42,7 @@ public: bool Initialize(Error* error); - void UpdateSettings(const GPUSettings& old_settings); + bool UpdateSettings(const GPUSettings& old_settings, Error* error); void ClearDisplay(); void ClearDisplayTexture(); diff --git a/src/core/gpu_sw.cpp b/src/core/gpu_sw.cpp index f85aa321c..3e913446f 100644 --- a/src/core/gpu_sw.cpp +++ b/src/core/gpu_sw.cpp @@ -65,8 +65,9 @@ void GPU_SW::ClearVRAM() std::memset(g_gpu_clut, 0, sizeof(g_gpu_clut)); } -void GPU_SW::UpdateResolutionScale() +bool GPU_SW::UpdateResolutionScale(Error* error) { + return true; } void GPU_SW::LoadState(const GPUBackendLoadStateCommand* cmd) diff --git a/src/core/gpu_sw.h b/src/core/gpu_sw.h index 8c2c6ee9d..a0e31cc56 100644 --- a/src/core/gpu_sw.h +++ b/src/core/gpu_sw.h @@ -27,7 +27,6 @@ public: u32 GetResolutionScale() const override; -protected: void ReadVRAM(u32 x, u32 y, u32 width, u32 height) override; void FillVRAM(u32 x, u32 y, u32 width, u32 height, u32 color, bool interlaced_rendering, u8 active_line_lsb) override; void UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, bool set_mask, bool check_mask) override; @@ -47,7 +46,7 @@ protected: void ClearVRAM() override; - void UpdateResolutionScale() override; + bool UpdateResolutionScale(Error* error) override; void LoadState(const GPUBackendLoadStateCommand* cmd) override; diff --git a/src/core/gpu_thread.cpp b/src/core/gpu_thread.cpp index 06f4534ad..675dd0ebd 100644 --- a/src/core/gpu_thread.cpp +++ b/src/core/gpu_thread.cpp @@ -186,7 +186,7 @@ void GPUThread::Internal::SetThreadEnabled(bool enabled) requested_fullscreen_ui, true, &error)) { ERROR_LOG("Reconfigure failed: {}", error.GetDescription()); - Panic("Failed to reconfigure when changing thread state."); + ReportFatalErrorAndShutdown(fmt::format("Reconfigure failed: {}", error.GetDescription())); } } @@ -780,7 +780,8 @@ bool GPUThread::CreateGPUBackendOnThread(GPURenderer renderer, bool upload_vram, s_state.requested_renderer = GPURenderer::Software; s_state.gpu_backend = GPUBackend::CreateSoftwareBackend(*s_state.gpu_presenter); - okay = s_state.gpu_backend->Initialize(upload_vram, &local_error); + if (!s_state.gpu_backend->Initialize(upload_vram, &local_error)) + Panic("Failed to initialize fallback software renderer"); } if (!okay) @@ -971,8 +972,14 @@ void GPUThread::UpdateSettingsOnThread(const GPUSettings& old_settings) PostProcessing::UpdateSettings(); - s_state.gpu_presenter->UpdateSettings(old_settings); - s_state.gpu_backend->UpdateSettings(old_settings); + Error error; + if (!s_state.gpu_presenter->UpdateSettings(old_settings, &error) || + !s_state.gpu_backend->UpdateSettings(old_settings, &error)) [[unlikely]] + { + ReportFatalErrorAndShutdown(fmt::format("Failed to update settings: {}", error.GetDescription())); + return; + } + if (ImGuiManager::UpdateDebugWindowConfig() || (PostProcessing::DisplayChain.IsActive() && !IsSystemPaused())) PresentFrameAndRestoreContext(); else @@ -1071,6 +1078,21 @@ void GPUThread::UpdateSettings(bool gpu_settings_changed, bool device_settings_c } } +void GPUThread::ReportFatalErrorAndShutdown(std::string_view reason) +{ + DebugAssert(IsOnThread()); + + std::string message = fmt::format("GPU thread shut down with fatal error:\n\n{}", reason); + Host::RunOnCPUThread([message = std::move(message)]() { System::AbnormalShutdown(message); }); + + // replace the renderer with a dummy/null backend, so that all commands get dropped + ERROR_LOG("Switching to null renderer: {}", reason); + s_state.gpu_backend.reset(); + s_state.gpu_backend = GPUBackend::CreateNullBackend(*s_state.gpu_presenter); + if (!s_state.gpu_backend->Initialize(false, nullptr)) [[unlikely]] + Panic("Failed to initialize null GPU backend"); +} + bool GPUThread::IsOnThread() { return (!s_state.use_gpu_thread || s_state.gpu_thread.IsCallingThread()); @@ -1193,7 +1215,11 @@ void GPUThread::DisplayWindowResizedOnThread() } if (g_gpu_settings.gpu_resolution_scale == 0) - s_state.gpu_backend->UpdateResolutionScale(); + { + Error error; + if (!s_state.gpu_backend->UpdateResolutionScale(&error)) [[unlikely]] + ReportFatalErrorAndShutdown(fmt::format("Failed to update resolution scale: {}", error.GetDescription())); + } } } diff --git a/src/core/gpu_thread.h b/src/core/gpu_thread.h index 3c1daaaa1..73f349a22 100644 --- a/src/core/gpu_thread.h +++ b/src/core/gpu_thread.h @@ -68,6 +68,9 @@ const WindowInfo& GetRenderWindowInfo(); void UpdateSettings(bool gpu_settings_changed, bool device_settings_changed); +/// Triggers an abnormal system shutdown and waits for it to destroy the backend. +void ReportFatalErrorAndShutdown(std::string_view reason); + bool IsOnThread(); bool IsUsingThread(); void RunOnThread(AsyncCallType func); diff --git a/src/core/system.cpp b/src/core/system.cpp index c64313b0d..f8d6a043f 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -2005,6 +2005,24 @@ void System::DestroySystem() Host::OnSystemDestroyed(); } +void System::AbnormalShutdown(const std::string_view reason) +{ + if (!IsValid()) + return; + + ERROR_LOG("Abnormal shutdown: {}", reason); + + Host::OnSystemAbnormalShutdown(reason); + + // Immediately switch to destroying and exit execution to get out of here. + s_state.state = State::Stopping; + std::atomic_thread_fence(std::memory_order_release); + if (s_state.system_executing) + InterruptExecution(); + else + DestroySystem(); +} + void System::ClearRunningGame() { UpdateSessionTime(s_state.running_game_serial); @@ -4636,8 +4654,11 @@ void System::CheckForSettingsChanges(const Settings& old_settings) Error error; if (!Bus::ReallocateMemoryMap(g_settings.export_shared_memory, &error)) [[unlikely]] { - ERROR_LOG(error.GetDescription()); - Panic("Failed to reallocate memory map. The log may contain more information."); + if (IsValid()) + { + AbnormalShutdown(fmt::format("Failed to reallocate memory map: {}", error.GetDescription())); + return; + } } } diff --git a/src/core/system_private.h b/src/core/system_private.h index 964f38a43..770300f84 100644 --- a/src/core/system_private.h +++ b/src/core/system_private.h @@ -50,6 +50,9 @@ void DisplayWindowResized(); /// Updates the internal GTE aspect ratio. Use with "match display" aspect ratio setting. void UpdateGTEAspectRatio(); +/// Immediately terminates the virtual machine, no state is saved. +void AbnormalShutdown(const std::string_view reason); + /// Performs mandatory hardware checks. bool PerformEarlyHardwareChecks(Error* error); @@ -96,6 +99,9 @@ void OnSystemPaused(); /// Called when the VM is resumed after being paused. void OnSystemResumed(); +/// Called when the VM abnormally exits because an error has occurred, and it cannot continue. +void OnSystemAbnormalShutdown(const std::string_view reason); + /// Called when performance metrics are updated, approximately once a second. void OnPerformanceCountersUpdated(const GPUBackend* gpu_backend); diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 95dcc7679..6420744a1 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -1045,6 +1045,18 @@ void Host::OnSystemDestroyed() emit g_emu_thread->systemDestroyed(); } +void Host::OnSystemAbnormalShutdown(const std::string_view reason) +{ + Host::ReportErrorAsync( + TRANSLATE_SV("QtHost", "Error"), + fmt::format( + TRANSLATE_FS("QtHost", + "Unfortunately, the virtual machine has abnormally shut down and cannot be recovered. Please use " + "the available support options for further assistance, and provide information about what you were " + "doing when the error occurred, as well as the details below:\n\n{}"), + reason)); +} + void Host::OnGPUThreadRunIdleChanged(bool is_active) { g_emu_thread->setGPUThreadRunIdle(is_active); diff --git a/src/duckstation-regtest/regtest_host.cpp b/src/duckstation-regtest/regtest_host.cpp index 31778350d..db21771d9 100644 --- a/src/duckstation-regtest/regtest_host.cpp +++ b/src/duckstation-regtest/regtest_host.cpp @@ -293,6 +293,11 @@ void Host::OnSystemResumed() // } +void Host::OnSystemAbnormalShutdown(const std::string_view reason) +{ + // Already logged in core. +} + void Host::OnGPUThreadRunIdleChanged(bool is_active) { //