System: Add fatal error shutdown path

Switch to a null backend and shut down the system instead of crashing.
This commit is contained in:
Stenzek 2025-01-17 20:20:29 +10:00
parent d52bf795e4
commit 6cba825bac
No known key found for this signature in database
17 changed files with 313 additions and 59 deletions

View File

@ -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();
}
}

View File

@ -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<System::MemorySaveState> 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> GPUBackend::CreateNullBackend(GPUPresenter& presenter)
{
return std::make_unique<GPUNullBackend>(presenter);
}

View File

@ -56,6 +56,7 @@ public:
static std::unique_ptr<GPUBackend> CreateHardwareBackend(GPUPresenter& presenter);
static std::unique_ptr<GPUBackend> CreateSoftwareBackend(GPUPresenter& presenter);
static std::unique_ptr<GPUBackend> 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<System::MemorySaveState> 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"

View File

@ -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<u32>(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;

View File

@ -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;

View File

@ -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;

View File

@ -5,6 +5,7 @@
#include "gpu_types.h"
class Error;
class Image;
class GPUTexture;
class StateWrapper;
@ -103,8 +104,8 @@ struct Source
TListNode<Source> 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);

View File

@ -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;
}

View File

@ -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();

View File

@ -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)

View File

@ -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;

View File

@ -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()));
}
}
}

View File

@ -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);

View File

@ -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;
}
}
}

View File

@ -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);

View File

@ -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);

View File

@ -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)
{
//