GPUDevice: Add recovery from lost device

This commit is contained in:
Stenzek 2024-09-07 12:48:44 +10:00
parent 3a4ac13c26
commit 6336c4ee1f
No known key found for this signature in database
22 changed files with 181 additions and 96 deletions

View File

@ -1993,7 +1993,7 @@ void GPU::SetDisplayTexture(GPUTexture* texture, GPUTexture* depth_buffer, s32 v
m_display_texture_view_height = view_height; m_display_texture_view_height = view_height;
} }
bool GPU::PresentDisplay() GPUDevice::PresentResult GPU::PresentDisplay()
{ {
FlushRender(); FlushRender();
@ -2004,7 +2004,8 @@ bool GPU::PresentDisplay()
return RenderDisplay(nullptr, display_rect, draw_rect, !g_settings.debugging.show_vram); return RenderDisplay(nullptr, display_rect, draw_rect, !g_settings.debugging.show_vram);
} }
bool GPU::RenderDisplay(GPUTexture* target, const GSVector4i display_rect, const GSVector4i draw_rect, bool postfx) GPUDevice::PresentResult GPU::RenderDisplay(GPUTexture* target, const GSVector4i display_rect,
const GSVector4i draw_rect, bool postfx)
{ {
GL_SCOPE_FMT("RenderDisplay: {}", draw_rect); GL_SCOPE_FMT("RenderDisplay: {}", draw_rect);
@ -2027,10 +2028,15 @@ bool GPU::RenderDisplay(GPUTexture* target, const GSVector4i display_rect, const
// Now we can apply the post chain. // Now we can apply the post chain.
GPUTexture* post_output_texture = PostProcessing::InternalChain.GetOutputTexture(); GPUTexture* post_output_texture = PostProcessing::InternalChain.GetOutputTexture();
if (PostProcessing::InternalChain.Apply(display_texture, m_display_depth_buffer, post_output_texture, if (const GPUDevice::PresentResult pres = PostProcessing::InternalChain.Apply(
GSVector4i(0, 0, display_texture_view_width, display_texture_view_height), display_texture, m_display_depth_buffer, post_output_texture,
display_texture_view_width, display_texture_view_height, GSVector4i(0, 0, display_texture_view_width, display_texture_view_height), display_texture_view_width,
m_crtc_state.display_width, m_crtc_state.display_height)) display_texture_view_height, m_crtc_state.display_width, m_crtc_state.display_height);
pres != GPUDevice::PresentResult::OK)
{
return pres;
}
else
{ {
display_texture_view_x = 0; display_texture_view_x = 0;
display_texture_view_y = 0; display_texture_view_y = 0;
@ -2057,8 +2063,9 @@ bool GPU::RenderDisplay(GPUTexture* target, const GSVector4i display_rect, const
{ {
if (target) if (target)
g_gpu_device->SetRenderTarget(target); g_gpu_device->SetRenderTarget(target);
else if (!g_gpu_device->BeginPresent(false)) else if (const GPUDevice::PresentResult pres = g_gpu_device->BeginPresent(false);
return false; pres != GPUDevice::PresentResult::OK)
return pres;
} }
if (display_texture) if (display_texture)
@ -2167,7 +2174,9 @@ bool GPU::RenderDisplay(GPUTexture* target, const GSVector4i display_rect, const
m_crtc_state.display_height); m_crtc_state.display_height);
} }
else else
return true; {
return GPUDevice::PresentResult::OK;
}
} }
bool GPU::SendDisplayToMediaCapture(MediaCapture* cap) bool GPU::SendDisplayToMediaCapture(MediaCapture* cap)
@ -2186,7 +2195,7 @@ bool GPU::SendDisplayToMediaCapture(MediaCapture* cap)
// Not cleared by RenderDisplay(). // Not cleared by RenderDisplay().
g_gpu_device->ClearRenderTarget(target, GPUDevice::DEFAULT_CLEAR_COLOR); g_gpu_device->ClearRenderTarget(target, GPUDevice::DEFAULT_CLEAR_COLOR);
if (!RenderDisplay(target, display_rect, draw_rect, postfx)) [[unlikely]] if (RenderDisplay(target, display_rect, draw_rect, postfx) != GPUDevice::PresentResult::OK) [[unlikely]]
return false; return false;
return cap->DeliverVideoFrame(target); return cap->DeliverVideoFrame(target);

View File

@ -8,6 +8,7 @@
#include "timing_event.h" #include "timing_event.h"
#include "types.h" #include "types.h"
#include "util/gpu_device.h"
#include "util/gpu_texture.h" #include "util/gpu_texture.h"
#include "common/bitfield.h" #include "common/bitfield.h"
@ -233,7 +234,7 @@ public:
bool show_osd_message); bool show_osd_message);
/// Draws the current display texture, with any post-processing. /// Draws the current display texture, with any post-processing.
bool PresentDisplay(); GPUDevice::PresentResult PresentDisplay();
/// Sends the current frame to media capture. /// Sends the current frame to media capture.
bool SendDisplayToMediaCapture(MediaCapture* cap); bool SendDisplayToMediaCapture(MediaCapture* cap);
@ -630,7 +631,8 @@ protected:
void SetDisplayTexture(GPUTexture* texture, GPUTexture* depth_texture, s32 view_x, s32 view_y, s32 view_width, void SetDisplayTexture(GPUTexture* texture, GPUTexture* depth_texture, s32 view_x, s32 view_y, s32 view_width,
s32 view_height); s32 view_height);
bool RenderDisplay(GPUTexture* target, const GSVector4i display_rect, const GSVector4i draw_rect, bool postfx); GPUDevice::PresentResult RenderDisplay(GPUTexture* target, const GSVector4i display_rect, const GSVector4i draw_rect,
bool postfx);
bool Deinterlace(u32 field, u32 line_skip); bool Deinterlace(u32 field, u32 line_skip);
bool DeinterlaceExtractField(u32 dst_bufidx, GPUTexture* src, u32 x, u32 y, u32 width, u32 height, u32 line_skip); bool DeinterlaceExtractField(u32 dst_bufidx, GPUTexture* src, u32 x, u32 y, u32 width, u32 height, u32 line_skip);

View File

@ -160,7 +160,7 @@ void Host::DisplayLoadingScreen(const char* message, int progress_min /*= -1*/,
// TODO: Glass effect or something. // TODO: Glass effect or something.
if (g_gpu_device->BeginPresent(false)) if (g_gpu_device->BeginPresent(false) == GPUDevice::PresentResult::OK)
{ {
g_gpu_device->RenderImGui(); g_gpu_device->RenderImGui();
g_gpu_device->EndPresent(false); g_gpu_device->EndPresent(false);

View File

@ -162,6 +162,7 @@ static void DestroySystem();
static bool CreateGPU(GPURenderer renderer, bool is_switching, Error* error); static bool CreateGPU(GPURenderer renderer, bool is_switching, Error* error);
static bool RecreateGPU(GPURenderer renderer, bool force_recreate_device = false, bool update_display = true); static bool RecreateGPU(GPURenderer renderer, bool force_recreate_device = false, bool update_display = true);
static void HandleHostGPUDeviceLost();
/// Updates the throttle period, call when target emulation speed changes. /// Updates the throttle period, call when target emulation speed changes.
static void UpdateThrottlePeriod(); static void UpdateThrottlePeriod();
@ -1202,6 +1203,45 @@ bool System::RecreateGPU(GPURenderer renderer, bool force_recreate_device, bool
return true; return true;
} }
void System::HandleHostGPUDeviceLost()
{
static Common::Timer::Value s_last_gpu_reset_time = 0;
static constexpr float MIN_TIME_BETWEEN_RESETS = 15.0f;
// If we're constantly crashing on something in particular, we don't want to end up in an
// endless reset loop.. that'd probably end up leaking memory and/or crashing us for other
// reasons. So just abort in such case.
const Common::Timer::Value current_time = Common::Timer::GetCurrentValue();
if (s_last_gpu_reset_time != 0 &&
Common::Timer::ConvertValueToSeconds(current_time - s_last_gpu_reset_time) < MIN_TIME_BETWEEN_RESETS)
{
Panic("Host GPU lost too many times, device is probably completely wedged.");
}
s_last_gpu_reset_time = current_time;
// Little bit janky, but because the device is lost, the VRAM readback is going to give us garbage.
// So back up what we have, it's probably missing bits, but whatever...
DynamicHeapArray<u8> vram_backup(VRAM_SIZE);
std::memcpy(vram_backup.data(), g_vram, VRAM_SIZE);
// Device lost, something went really bad.
// Let's just toss out everything, and try to hobble on.
if (!RecreateGPU(g_gpu->IsHardwareRenderer() ? g_settings.gpu_renderer : GPURenderer::Software, true, false))
{
Panic("Failed to recreate GS device after loss.");
return;
}
// Restore backed-up VRAM.
std::memcpy(g_vram, vram_backup.data(), VRAM_SIZE);
// First frame after reopening is definitely going to be trash, so skip it.
Host::AddIconOSDMessage(
"HostGPUDeviceLost", ICON_EMOJI_WARNING,
TRANSLATE_STR("System", "Host GPU device encountered an error and has recovered. This may cause broken rendering."),
Host::OSD_CRITICAL_ERROR_DURATION);
}
void System::LoadSettings(bool display_osd_messages) void System::LoadSettings(bool display_osd_messages)
{ {
std::unique_lock<std::mutex> lock = Host::GetSettingsLock(); std::unique_lock<std::mutex> lock = Host::GetSettingsLock();
@ -5713,13 +5753,13 @@ bool System::PresentDisplay(bool skip_present, bool explicit_present)
ImGuiManager::RenderOverlayWindows(); ImGuiManager::RenderOverlayWindows();
ImGuiManager::RenderDebugWindows(); ImGuiManager::RenderDebugWindows();
bool do_present; GPUDevice::PresentResult pres;
if (g_gpu && !skip_present) if (g_gpu && !skip_present)
do_present = g_gpu->PresentDisplay(); pres = g_gpu->PresentDisplay();
else else
do_present = g_gpu_device->BeginPresent(skip_present); pres = g_gpu_device->BeginPresent(skip_present);
if (do_present) if (pres == GPUDevice::PresentResult::OK)
{ {
g_gpu_device->RenderImGui(); g_gpu_device->RenderImGui();
g_gpu_device->EndPresent(explicit_present); g_gpu_device->EndPresent(explicit_present);
@ -5732,13 +5772,16 @@ bool System::PresentDisplay(bool skip_present, bool explicit_present)
} }
else else
{ {
if (pres == GPUDevice::PresentResult::DeviceLost) [[unlikely]]
HandleHostGPUDeviceLost();
// Still need to kick ImGui or it gets cranky. // Still need to kick ImGui or it gets cranky.
ImGui::Render(); ImGui::Render();
} }
ImGuiManager::NewFrame(); ImGuiManager::NewFrame();
return do_present; return (pres == GPUDevice::PresentResult::OK);
} }
void System::InvalidateDisplay() void System::InvalidateDisplay()

View File

@ -639,17 +639,17 @@ void D3D11Device::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle)
} }
} }
bool D3D11Device::BeginPresent(bool skip_present, u32 clear_color) GPUDevice::PresentResult D3D11Device::BeginPresent(bool skip_present, u32 clear_color)
{ {
if (skip_present) if (skip_present)
return false; return PresentResult::SkipPresent;
if (!m_swap_chain) if (!m_swap_chain)
{ {
// Note: Really slow on Intel... // Note: Really slow on Intel...
m_context->Flush(); m_context->Flush();
TrimTexturePool(); TrimTexturePool();
return false; return PresentResult::SkipPresent;
} }
// Check if we lost exclusive fullscreen. If so, notify the host, so it can switch to windowed mode. // Check if we lost exclusive fullscreen. If so, notify the host, so it can switch to windowed mode.
@ -660,7 +660,7 @@ bool D3D11Device::BeginPresent(bool skip_present, u32 clear_color)
{ {
Host::SetFullscreen(false); Host::SetFullscreen(false);
TrimTexturePool(); TrimTexturePool();
return false; return PresentResult::SkipPresent;
} }
// When using vsync, the time here seems to include the time for the buffer to become available. // When using vsync, the time here seems to include the time for the buffer to become available.
@ -677,7 +677,7 @@ bool D3D11Device::BeginPresent(bool skip_present, u32 clear_color)
m_current_render_pass_flags = GPUPipeline::NoRenderPassFlags; m_current_render_pass_flags = GPUPipeline::NoRenderPassFlags;
std::memset(m_current_render_targets.data(), 0, sizeof(m_current_render_targets)); std::memset(m_current_render_targets.data(), 0, sizeof(m_current_render_targets));
m_current_depth_target = nullptr; m_current_depth_target = nullptr;
return true; return PresentResult::OK;
} }
void D3D11Device::EndPresent(bool explicit_present) void D3D11Device::EndPresent(bool explicit_present)

View File

@ -104,7 +104,7 @@ public:
bool SetGPUTimingEnabled(bool enabled) override; bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override; float GetAndResetAccumulatedGPUTime() override;
bool BeginPresent(bool skip_present, u32 clear_color) override; PresentResult BeginPresent(bool skip_present, u32 clear_color) override;
void EndPresent(bool explicit_present) override; void EndPresent(bool explicit_present) override;
void SubmitPresent() override; void SubmitPresent() override;

View File

@ -532,6 +532,9 @@ ID3D12GraphicsCommandList4* D3D12Device::GetInitCommandList()
void D3D12Device::SubmitCommandList(bool wait_for_completion) void D3D12Device::SubmitCommandList(bool wait_for_completion)
{ {
if (m_device_was_lost) [[unlikely]]
return;
CommandList& res = m_command_lists[m_current_command_list]; CommandList& res = m_command_lists[m_current_command_list];
HRESULT hr; HRESULT hr;
@ -553,7 +556,8 @@ void D3D12Device::SubmitCommandList(bool wait_for_completion)
if (FAILED(hr)) [[unlikely]] if (FAILED(hr)) [[unlikely]]
{ {
ERROR_LOG("Closing init command list failed with HRESULT {:08X}", static_cast<unsigned>(hr)); ERROR_LOG("Closing init command list failed with HRESULT {:08X}", static_cast<unsigned>(hr));
Panic("TODO cannot continue"); m_device_was_lost = true;
return;
} }
} }
@ -562,7 +566,8 @@ void D3D12Device::SubmitCommandList(bool wait_for_completion)
if (FAILED(hr)) [[unlikely]] if (FAILED(hr)) [[unlikely]]
{ {
ERROR_LOG("Closing main command list failed with HRESULT {:08X}", static_cast<unsigned>(hr)); ERROR_LOG("Closing main command list failed with HRESULT {:08X}", static_cast<unsigned>(hr));
Panic("TODO cannot continue"); m_device_was_lost = true;
return;
} }
if (res.init_list_used) if (res.init_list_used)
@ -578,7 +583,12 @@ void D3D12Device::SubmitCommandList(bool wait_for_completion)
// Update fence when GPU has completed. // Update fence when GPU has completed.
hr = m_command_queue->Signal(m_fence.Get(), res.fence_counter); hr = m_command_queue->Signal(m_fence.Get(), res.fence_counter);
DebugAssertMsg(SUCCEEDED(hr), "Signal fence"); if (FAILED(hr))
{
ERROR_LOG("Signal command queue fence failed with HRESULT {:08X}", static_cast<unsigned>(hr));
m_device_was_lost = true;
return;
}
MoveToNextCommandList(); MoveToNextCommandList();
@ -606,6 +616,9 @@ void D3D12Device::SubmitCommandListAndRestartRenderPass(const std::string_view r
void D3D12Device::WaitForFence(u64 fence) void D3D12Device::WaitForFence(u64 fence)
{ {
if (m_device_was_lost) [[unlikely]]
return;
if (m_completed_fence_value >= fence) if (m_completed_fence_value >= fence)
return; return;
@ -1110,20 +1123,23 @@ void D3D12Device::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle)
} }
} }
bool D3D12Device::BeginPresent(bool frame_skip, u32 clear_color) GPUDevice::PresentResult D3D12Device::BeginPresent(bool frame_skip, u32 clear_color)
{ {
if (InRenderPass()) if (InRenderPass())
EndRenderPass(); EndRenderPass();
if (m_device_was_lost) [[unlikely]]
return PresentResult::DeviceLost;
if (frame_skip) if (frame_skip)
return false; return PresentResult::SkipPresent;
// If we're running surfaceless, kick the command buffer so we don't run out of descriptors. // If we're running surfaceless, kick the command buffer so we don't run out of descriptors.
if (!m_swap_chain) if (!m_swap_chain)
{ {
SubmitCommandList(false); SubmitCommandList(false);
TrimTexturePool(); TrimTexturePool();
return false; return PresentResult::SkipPresent;
} }
// TODO: Check if the device was lost. // TODO: Check if the device was lost.
@ -1136,11 +1152,11 @@ bool D3D12Device::BeginPresent(bool frame_skip, u32 clear_color)
{ {
Host::RunOnCPUThread([]() { Host::SetFullscreen(false); }); Host::RunOnCPUThread([]() { Host::SetFullscreen(false); });
TrimTexturePool(); TrimTexturePool();
return false; return PresentResult::SkipPresent;
} }
BeginSwapChainRenderPass(clear_color); BeginSwapChainRenderPass(clear_color);
return true; return PresentResult::OK;
} }
void D3D12Device::EndPresent(bool explicit_present) void D3D12Device::EndPresent(bool explicit_present)
@ -1165,6 +1181,8 @@ void D3D12Device::EndPresent(bool explicit_present)
void D3D12Device::SubmitPresent() void D3D12Device::SubmitPresent()
{ {
DebugAssert(m_swap_chain); DebugAssert(m_swap_chain);
if (m_device_was_lost) [[unlikely]]
return;
const UINT sync_interval = static_cast<UINT>(m_vsync_mode == GPUVSyncMode::FIFO); const UINT sync_interval = static_cast<UINT>(m_vsync_mode == GPUVSyncMode::FIFO);
const UINT flags = (m_vsync_mode == GPUVSyncMode::Disabled && m_using_allow_tearing) ? DXGI_PRESENT_ALLOW_TEARING : 0; const UINT flags = (m_vsync_mode == GPUVSyncMode::Disabled && m_using_allow_tearing) ? DXGI_PRESENT_ALLOW_TEARING : 0;

View File

@ -126,7 +126,7 @@ public:
bool SetGPUTimingEnabled(bool enabled) override; bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override; float GetAndResetAccumulatedGPUTime() override;
bool BeginPresent(bool skip_present, u32 clear_color) override; PresentResult BeginPresent(bool skip_present, u32 clear_color) override;
void EndPresent(bool explicit_present) override; void EndPresent(bool explicit_present) override;
void SubmitPresent() override; void SubmitPresent() override;
@ -300,6 +300,7 @@ private:
bool m_allow_tearing_supported = false; bool m_allow_tearing_supported = false;
bool m_using_allow_tearing = false; bool m_using_allow_tearing = false;
bool m_is_exclusive_fullscreen = false; bool m_is_exclusive_fullscreen = false;
bool m_device_was_lost = false;
D3D12DescriptorHeapManager m_descriptor_heap_manager; D3D12DescriptorHeapManager m_descriptor_heap_manager;
D3D12DescriptorHeapManager m_rtv_heap_manager; D3D12DescriptorHeapManager m_rtv_heap_manager;

View File

@ -481,6 +481,13 @@ public:
Full Full
}; };
enum class PresentResult : u32
{
OK,
SkipPresent,
DeviceLost,
};
struct Features struct Features
{ {
bool dual_source_blend : 1; bool dual_source_blend : 1;
@ -702,7 +709,7 @@ public:
virtual void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) = 0; virtual void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) = 0;
/// Returns false if the window was completely occluded. /// Returns false if the window was completely occluded.
virtual bool BeginPresent(bool skip_present, u32 clear_color = DEFAULT_CLEAR_COLOR) = 0; virtual PresentResult BeginPresent(bool skip_present, u32 clear_color = DEFAULT_CLEAR_COLOR) = 0;
virtual void EndPresent(bool explicit_submit) = 0; virtual void EndPresent(bool explicit_submit) = 0;
virtual void SubmitPresent() = 0; virtual void SubmitPresent() = 0;

View File

@ -265,7 +265,7 @@ public:
void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override; void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override;
bool BeginPresent(bool skip_present, u32 clear_color) override; PresentResult BeginPresent(bool skip_present, u32 clear_color) override;
void EndPresent(bool explicit_submit) override; void EndPresent(bool explicit_submit) override;
void SubmitPresent() override; void SubmitPresent() override;

View File

@ -2312,17 +2312,17 @@ id<MTLBlitCommandEncoder> MetalDevice::GetBlitEncoder(bool is_inline)
} }
} }
bool MetalDevice::BeginPresent(bool skip_present, u32 clear_color) GPUDevice::PresentResult MetalDevice::BeginPresent(bool skip_present, u32 clear_color)
{ {
@autoreleasepool @autoreleasepool
{ {
if (skip_present) if (skip_present)
return false; return PresentResult::SkipPresent;
if (m_layer == nil) if (m_layer == nil)
{ {
TrimTexturePool(); TrimTexturePool();
return false; return PresentResult::SkipPresent;
} }
EndAnyEncoding(); EndAnyEncoding();
@ -2331,7 +2331,7 @@ bool MetalDevice::BeginPresent(bool skip_present, u32 clear_color)
if (m_layer_drawable == nil) if (m_layer_drawable == nil)
{ {
TrimTexturePool(); TrimTexturePool();
return false; return PresentResult::SkipPresent;
} }
SetViewportAndScissor(0, 0, m_window_info.surface_width, m_window_info.surface_height); SetViewportAndScissor(0, 0, m_window_info.surface_width, m_window_info.surface_height);
@ -2351,7 +2351,7 @@ bool MetalDevice::BeginPresent(bool skip_present, u32 clear_color)
m_current_pipeline = nullptr; m_current_pipeline = nullptr;
m_current_depth_state = nil; m_current_depth_state = nil;
SetInitialEncoderState(); SetInitialEncoderState();
return true; return PresentResult::OK;
} }
} }

View File

@ -740,7 +740,7 @@ void OpenGLDevice::DestroyBuffers()
m_vertex_buffer.reset(); m_vertex_buffer.reset();
} }
bool OpenGLDevice::BeginPresent(bool skip_present, u32 clear_color) GPUDevice::PresentResult OpenGLDevice::BeginPresent(bool skip_present, u32 clear_color)
{ {
if (skip_present || m_window_info.type == WindowInfo::Type::Surfaceless) if (skip_present || m_window_info.type == WindowInfo::Type::Surfaceless)
{ {
@ -750,7 +750,7 @@ bool OpenGLDevice::BeginPresent(bool skip_present, u32 clear_color)
TrimTexturePool(); TrimTexturePool();
} }
return false; return PresentResult::SkipPresent;
} }
glBindFramebuffer(GL_FRAMEBUFFER, 0); glBindFramebuffer(GL_FRAMEBUFFER, 0);
@ -771,7 +771,7 @@ bool OpenGLDevice::BeginPresent(bool skip_present, u32 clear_color)
m_last_scissor = window_rc; m_last_scissor = window_rc;
UpdateViewport(); UpdateViewport();
UpdateScissor(); UpdateScissor();
return true; return PresentResult::OK;
} }
void OpenGLDevice::EndPresent(bool explicit_present) void OpenGLDevice::EndPresent(bool explicit_present)

View File

@ -104,7 +104,7 @@ public:
void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override; void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override;
bool BeginPresent(bool skip_present, u32 clear_color) override; PresentResult BeginPresent(bool skip_present, u32 clear_color) override;
void EndPresent(bool explicit_present) override; void EndPresent(bool explicit_present) override;
void SubmitPresent() override; void SubmitPresent() override;

View File

@ -619,9 +619,9 @@ void PostProcessing::Chain::DestroyTextures()
g_gpu_device->RecycleTexture(std::move(m_input_texture)); g_gpu_device->RecycleTexture(std::move(m_input_texture));
} }
bool PostProcessing::Chain::Apply(GPUTexture* input_color, GPUTexture* input_depth, GPUTexture* final_target, GPUDevice::PresentResult PostProcessing::Chain::Apply(GPUTexture* input_color, GPUTexture* input_depth,
GSVector4i final_rect, s32 orig_width, s32 orig_height, s32 native_width, GPUTexture* final_target, GSVector4i final_rect, s32 orig_width,
s32 native_height) s32 orig_height, s32 native_width, s32 native_height)
{ {
GL_SCOPE_FMT("{} Apply", m_section); GL_SCOPE_FMT("{} Apply", m_section);
@ -634,10 +634,12 @@ bool PostProcessing::Chain::Apply(GPUTexture* input_color, GPUTexture* input_dep
{ {
const bool is_final = (stage.get() == m_stages.back().get()); const bool is_final = (stage.get() == m_stages.back().get());
if (!stage->Apply(input_color, input_depth, is_final ? final_target : output, final_rect, orig_width, orig_height, if (const GPUDevice::PresentResult pres =
native_width, native_height, m_target_width, m_target_height)) stage->Apply(input_color, input_depth, is_final ? final_target : output, final_rect, orig_width, orig_height,
native_width, native_height, m_target_width, m_target_height);
pres != GPUDevice::PresentResult::OK)
{ {
return false; return pres;
} }
if (!is_final) if (!is_final)
@ -648,7 +650,7 @@ bool PostProcessing::Chain::Apply(GPUTexture* input_color, GPUTexture* input_dep
} }
} }
return true; return GPUDevice::PresentResult::OK;
} }
void PostProcessing::Initialize() void PostProcessing::Initialize()

View File

@ -134,8 +134,9 @@ public:
bool CheckTargets(GPUTexture::Format target_format, u32 target_width, u32 target_height, bool CheckTargets(GPUTexture::Format target_format, u32 target_width, u32 target_height,
ProgressCallback* progress = nullptr); ProgressCallback* progress = nullptr);
bool Apply(GPUTexture* input_color, GPUTexture* input_depth, GPUTexture* final_target, const GSVector4i final_rect, GPUDevice::PresentResult Apply(GPUTexture* input_color, GPUTexture* input_depth, GPUTexture* final_target,
s32 orig_width, s32 orig_height, s32 native_width, s32 native_height); const GSVector4i final_rect, s32 orig_width, s32 orig_height, s32 native_width,
s32 native_height);
private: private:
void ClearStagesWithError(const Error& error); void ClearStagesWithError(const Error& error);

View File

@ -3,15 +3,14 @@
#pragma once #pragma once
#include "postprocessing.h" #include "gpu_device.h"
#include "gpu_texture.h" #include "gpu_texture.h"
#include "postprocessing.h"
#include "common/gsvector.h" #include "common/gsvector.h"
#include "common/settings_interface.h" #include "common/settings_interface.h"
#include "common/timer.h" #include "common/timer.h"
#include "common/types.h" #include "common/types.h"
#include "gpu_device.h"
#include <array> #include <array>
#include <string> #include <string>
@ -49,9 +48,9 @@ public:
virtual bool CompilePipeline(GPUTexture::Format format, u32 width, u32 height, ProgressCallback* progress) = 0; virtual bool CompilePipeline(GPUTexture::Format format, u32 width, u32 height, ProgressCallback* progress) = 0;
virtual bool Apply(GPUTexture* input_color, GPUTexture* input_depth, GPUTexture* final_target, GSVector4i final_rect, virtual GPUDevice::PresentResult Apply(GPUTexture* input_color, GPUTexture* input_depth, GPUTexture* final_target,
s32 orig_width, s32 orig_height, s32 native_width, s32 native_height, u32 target_width, GSVector4i final_rect, s32 orig_width, s32 orig_height, s32 native_width,
u32 target_height) = 0; s32 native_height, u32 target_width, u32 target_height) = 0;
protected: protected:
using OptionList = std::vector<ShaderOption>; using OptionList = std::vector<ShaderOption>;

View File

@ -1491,9 +1491,10 @@ bool PostProcessing::ReShadeFXShader::ResizeOutput(GPUTexture::Format format, u3
return true; return true;
} }
bool PostProcessing::ReShadeFXShader::Apply(GPUTexture* input_color, GPUTexture* input_depth, GPUTexture* final_target, GPUDevice::PresentResult PostProcessing::ReShadeFXShader::Apply(GPUTexture* input_color, GPUTexture* input_depth,
GSVector4i final_rect, s32 orig_width, s32 orig_height, s32 native_width, GPUTexture* final_target, GSVector4i final_rect,
s32 native_height, u32 target_width, u32 target_height) s32 orig_width, s32 orig_height, s32 native_width,
s32 native_height, u32 target_width, u32 target_height)
{ {
GL_PUSH_FMT("PostProcessingShaderFX {}", m_name); GL_PUSH_FMT("PostProcessingShaderFX {}", m_name);
@ -1783,10 +1784,10 @@ bool PostProcessing::ReShadeFXShader::Apply(GPUTexture* input_color, GPUTexture*
if (pass.render_targets.size() == 1 && pass.render_targets[0] == OUTPUT_COLOR_TEXTURE && !final_target) if (pass.render_targets.size() == 1 && pass.render_targets[0] == OUTPUT_COLOR_TEXTURE && !final_target)
{ {
// Special case: drawing to final buffer. // Special case: drawing to final buffer.
if (!g_gpu_device->BeginPresent(false)) if (const GPUDevice::PresentResult pres = g_gpu_device->BeginPresent(false); pres != GPUDevice::PresentResult::OK)
{ {
GL_POP(); GL_POP();
return false; return pres;
} }
} }
else else
@ -1842,5 +1843,5 @@ bool PostProcessing::ReShadeFXShader::Apply(GPUTexture* input_color, GPUTexture*
GL_POP(); GL_POP();
m_frame_timer.Reset(); m_frame_timer.Reset();
return true; return GPUDevice::PresentResult::OK;
} }

View File

@ -31,9 +31,9 @@ public:
bool ResizeOutput(GPUTexture::Format format, u32 width, u32 height) override; bool ResizeOutput(GPUTexture::Format format, u32 width, u32 height) override;
bool CompilePipeline(GPUTexture::Format format, u32 width, u32 height, ProgressCallback* progress) override; bool CompilePipeline(GPUTexture::Format format, u32 width, u32 height, ProgressCallback* progress) override;
bool Apply(GPUTexture* input_color, GPUTexture* input_depth, GPUTexture* final_target, GSVector4i final_rect, GPUDevice::PresentResult Apply(GPUTexture* input_color, GPUTexture* input_depth, GPUTexture* final_target,
s32 orig_width, s32 orig_height, s32 native_width, s32 native_height, u32 target_width, GSVector4i final_rect, s32 orig_width, s32 orig_height, s32 native_width,
u32 target_height) override; s32 native_height, u32 target_width, u32 target_height) override;
private: private:
using TextureID = s32; using TextureID = s32;

View File

@ -167,17 +167,18 @@ bool PostProcessing::GLSLShader::CompilePipeline(GPUTexture::Format format, u32
return true; return true;
} }
bool PostProcessing::GLSLShader::Apply(GPUTexture* input_color, GPUTexture* input_depth, GPUTexture* final_target, GPUDevice::PresentResult PostProcessing::GLSLShader::Apply(GPUTexture* input_color, GPUTexture* input_depth,
GSVector4i final_rect, s32 orig_width, s32 orig_height, s32 native_width, GPUTexture* final_target, GSVector4i final_rect,
s32 native_height, u32 target_width, u32 target_height) s32 orig_width, s32 orig_height, s32 native_width,
s32 native_height, u32 target_width, u32 target_height)
{ {
GL_SCOPE_FMT("GLSL Shader {}", m_name); GL_SCOPE_FMT("GLSL Shader {}", m_name);
// Assumes final stage has been cleared already. // Assumes final stage has been cleared already.
if (!final_target) if (!final_target)
{ {
if (!g_gpu_device->BeginPresent(false)) if (const GPUDevice::PresentResult pres = g_gpu_device->BeginPresent(false); pres != GPUDevice::PresentResult::OK)
return false; return pres;
} }
else else
{ {
@ -196,7 +197,7 @@ bool PostProcessing::GLSLShader::Apply(GPUTexture* input_color, GPUTexture* inpu
static_cast<float>(PostProcessing::GetTimer().GetTimeSeconds())); static_cast<float>(PostProcessing::GetTimer().GetTimeSeconds()));
g_gpu_device->UnmapUniformBuffer(uniforms_size); g_gpu_device->UnmapUniformBuffer(uniforms_size);
g_gpu_device->Draw(3, 0); g_gpu_device->Draw(3, 0);
return true; return GPUDevice::PresentResult::OK;
} }
bool PostProcessing::GLSLShader::ResizeOutput(GPUTexture::Format format, u32 width, u32 height) bool PostProcessing::GLSLShader::ResizeOutput(GPUTexture::Format format, u32 width, u32 height)

View File

@ -24,9 +24,9 @@ public:
bool ResizeOutput(GPUTexture::Format format, u32 width, u32 height) override; bool ResizeOutput(GPUTexture::Format format, u32 width, u32 height) override;
bool CompilePipeline(GPUTexture::Format format, u32 width, u32 height, ProgressCallback* progress) override; bool CompilePipeline(GPUTexture::Format format, u32 width, u32 height, ProgressCallback* progress) override;
bool Apply(GPUTexture* input_color, GPUTexture* input_depth, GPUTexture* final_target, GSVector4i final_rect, GPUDevice::PresentResult Apply(GPUTexture* input_color, GPUTexture* input_depth, GPUTexture* final_target,
s32 orig_width, s32 orig_height, s32 native_width, s32 native_height, u32 target_width, GSVector4i final_rect, s32 orig_width, s32 orig_height, s32 native_width,
u32 target_height) override; s32 native_height, u32 target_width, u32 target_height) override;
private: private:
struct CommonUniforms struct CommonUniforms

View File

@ -1286,6 +1286,9 @@ bool VulkanDevice::SetGPUTimingEnabled(bool enabled)
void VulkanDevice::WaitForCommandBufferCompletion(u32 index) void VulkanDevice::WaitForCommandBufferCompletion(u32 index)
{ {
if (m_device_was_lost)
return;
// Wait for this command buffer to be completed. // Wait for this command buffer to be completed.
static constexpr u32 MAX_TIMEOUTS = 10; static constexpr u32 MAX_TIMEOUTS = 10;
u32 timeouts = 0; u32 timeouts = 0;
@ -1303,7 +1306,7 @@ void VulkanDevice::WaitForCommandBufferCompletion(u32 index)
else if (res != VK_SUCCESS) else if (res != VK_SUCCESS)
{ {
LOG_VULKAN_ERROR(res, TinyString::from_format("vkWaitForFences() for cmdbuffer {} failed: ", index)); LOG_VULKAN_ERROR(res, TinyString::from_format("vkWaitForFences() for cmdbuffer {} failed: ", index));
m_device_is_lost = true; m_device_was_lost = true;
return; return;
} }
} }
@ -1357,7 +1360,7 @@ void VulkanDevice::WaitForCommandBufferCompletion(u32 index)
void VulkanDevice::EndAndSubmitCommandBuffer(VulkanSwapChain* present_swap_chain, bool explicit_present) void VulkanDevice::EndAndSubmitCommandBuffer(VulkanSwapChain* present_swap_chain, bool explicit_present)
{ {
if (m_device_is_lost) if (m_device_was_lost) [[unlikely]]
return; return;
CommandBuffer& resources = m_frame_resources[m_current_frame]; CommandBuffer& resources = m_frame_resources[m_current_frame];
@ -1416,7 +1419,7 @@ void VulkanDevice::EndAndSubmitCommandBuffer(VulkanSwapChain* present_swap_chain
if (res != VK_SUCCESS) if (res != VK_SUCCESS)
{ {
LOG_VULKAN_ERROR(res, "vkQueueSubmit failed: "); LOG_VULKAN_ERROR(res, "vkQueueSubmit failed: ");
m_device_is_lost = true; m_device_was_lost = true;
return; return;
} }
@ -2339,28 +2342,23 @@ void VulkanDevice::SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle)
} }
} }
bool VulkanDevice::BeginPresent(bool frame_skip, u32 clear_color) GPUDevice::PresentResult VulkanDevice::BeginPresent(bool frame_skip, u32 clear_color)
{ {
if (InRenderPass()) if (InRenderPass())
EndRenderPass(); EndRenderPass();
if (m_device_was_lost) [[unlikely]]
return PresentResult::DeviceLost;
if (frame_skip) if (frame_skip)
return false; return PresentResult::SkipPresent;
// If we're running surfaceless, kick the command buffer so we don't run out of descriptors. // If we're running surfaceless, kick the command buffer so we don't run out of descriptors.
if (!m_swap_chain) if (!m_swap_chain)
{ {
SubmitCommandBuffer(false); SubmitCommandBuffer(false);
TrimTexturePool(); TrimTexturePool();
return false; return PresentResult::SkipPresent;
}
// Check if the device was lost.
if (m_device_is_lost)
{
Panic("Fixme"); // TODO
TrimTexturePool();
return false;
} }
VkResult res = m_swap_chain->AcquireNextImage(); VkResult res = m_swap_chain->AcquireNextImage();
@ -2382,7 +2380,7 @@ bool VulkanDevice::BeginPresent(bool frame_skip, u32 clear_color)
ERROR_LOG("Failed to recreate surface after loss"); ERROR_LOG("Failed to recreate surface after loss");
SubmitCommandBuffer(false); SubmitCommandBuffer(false);
TrimTexturePool(); TrimTexturePool();
return false; return PresentResult::SkipPresent;
} }
res = m_swap_chain->AcquireNextImage(); res = m_swap_chain->AcquireNextImage();
@ -2395,12 +2393,12 @@ bool VulkanDevice::BeginPresent(bool frame_skip, u32 clear_color)
// Still submit the command buffer, otherwise we'll end up with several frames waiting. // Still submit the command buffer, otherwise we'll end up with several frames waiting.
SubmitCommandBuffer(false); SubmitCommandBuffer(false);
TrimTexturePool(); TrimTexturePool();
return false; return PresentResult::SkipPresent;
} }
} }
BeginSwapChainRenderPass(clear_color); BeginSwapChainRenderPass(clear_color);
return true; return PresentResult::OK;
} }
void VulkanDevice::EndPresent(bool explicit_present) void VulkanDevice::EndPresent(bool explicit_present)
@ -2421,6 +2419,9 @@ void VulkanDevice::EndPresent(bool explicit_present)
void VulkanDevice::SubmitPresent() void VulkanDevice::SubmitPresent()
{ {
DebugAssert(m_swap_chain); DebugAssert(m_swap_chain);
if (m_device_was_lost) [[unlikely]]
return;
QueuePresent(m_swap_chain.get()); QueuePresent(m_swap_chain.get());
} }

View File

@ -142,7 +142,7 @@ public:
void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override; void SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle) override;
bool BeginPresent(bool skip_present, u32 clear_color) override; PresentResult BeginPresent(bool skip_present, u32 clear_color) override;
void EndPresent(bool explicit_present) override; void EndPresent(bool explicit_present) override;
void SubmitPresent() override; void SubmitPresent() override;
@ -414,7 +414,7 @@ private:
u64 m_completed_fence_counter = 0; u64 m_completed_fence_counter = 0;
u32 m_current_frame = 0; u32 m_current_frame = 0;
bool m_device_is_lost = false; bool m_device_was_lost = false;
std::unordered_map<RenderPassCacheKey, VkRenderPass, RenderPassCacheKeyHash> m_render_pass_cache; std::unordered_map<RenderPassCacheKey, VkRenderPass, RenderPassCacheKeyHash> m_render_pass_cache;
GPUFramebufferManager<VkFramebuffer, CreateFramebuffer, DestroyFramebuffer> m_framebuffer_manager; GPUFramebufferManager<VkFramebuffer, CreateFramebuffer, DestroyFramebuffer> m_framebuffer_manager;