GPUDevice: Add recovery from lost device

This commit is contained in:
Stenzek
2024-09-07 12:48:44 +10:00
parent 1c1b82ed66
commit 4b0c1fdbf2
22 changed files with 181 additions and 96 deletions

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)
return false;
return PresentResult::SkipPresent;
if (!m_swap_chain)
{
// Note: Really slow on Intel...
m_context->Flush();
TrimTexturePool();
return false;
return PresentResult::SkipPresent;
}
// 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);
TrimTexturePool();
return false;
return PresentResult::SkipPresent;
}
// 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;
std::memset(m_current_render_targets.data(), 0, sizeof(m_current_render_targets));
m_current_depth_target = nullptr;
return true;
return PresentResult::OK;
}
void D3D11Device::EndPresent(bool explicit_present)

View File

@@ -104,7 +104,7 @@ public:
bool SetGPUTimingEnabled(bool enabled) 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 SubmitPresent() override;

View File

@@ -532,6 +532,9 @@ ID3D12GraphicsCommandList4* D3D12Device::GetInitCommandList()
void D3D12Device::SubmitCommandList(bool wait_for_completion)
{
if (m_device_was_lost) [[unlikely]]
return;
CommandList& res = m_command_lists[m_current_command_list];
HRESULT hr;
@@ -553,7 +556,8 @@ void D3D12Device::SubmitCommandList(bool wait_for_completion)
if (FAILED(hr)) [[unlikely]]
{
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]]
{
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)
@@ -578,7 +583,12 @@ void D3D12Device::SubmitCommandList(bool wait_for_completion)
// Update fence when GPU has completed.
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();
@@ -606,6 +616,9 @@ void D3D12Device::SubmitCommandListAndRestartRenderPass(const std::string_view r
void D3D12Device::WaitForFence(u64 fence)
{
if (m_device_was_lost) [[unlikely]]
return;
if (m_completed_fence_value >= fence)
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())
EndRenderPass();
if (m_device_was_lost) [[unlikely]]
return PresentResult::DeviceLost;
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 (!m_swap_chain)
{
SubmitCommandList(false);
TrimTexturePool();
return false;
return PresentResult::SkipPresent;
}
// 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); });
TrimTexturePool();
return false;
return PresentResult::SkipPresent;
}
BeginSwapChainRenderPass(clear_color);
return true;
return PresentResult::OK;
}
void D3D12Device::EndPresent(bool explicit_present)
@@ -1165,6 +1181,8 @@ void D3D12Device::EndPresent(bool explicit_present)
void D3D12Device::SubmitPresent()
{
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 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;
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 SubmitPresent() override;
@@ -300,6 +300,7 @@ private:
bool m_allow_tearing_supported = false;
bool m_using_allow_tearing = false;
bool m_is_exclusive_fullscreen = false;
bool m_device_was_lost = false;
D3D12DescriptorHeapManager m_descriptor_heap_manager;
D3D12DescriptorHeapManager m_rtv_heap_manager;

View File

@@ -481,6 +481,13 @@ public:
Full
};
enum class PresentResult : u32
{
OK,
SkipPresent,
DeviceLost,
};
struct Features
{
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;
/// 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 SubmitPresent() = 0;

View File

@@ -265,7 +265,7 @@ public:
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 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
{
if (skip_present)
return false;
return PresentResult::SkipPresent;
if (m_layer == nil)
{
TrimTexturePool();
return false;
return PresentResult::SkipPresent;
}
EndAnyEncoding();
@@ -2331,7 +2331,7 @@ bool MetalDevice::BeginPresent(bool skip_present, u32 clear_color)
if (m_layer_drawable == nil)
{
TrimTexturePool();
return false;
return PresentResult::SkipPresent;
}
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_depth_state = nil;
SetInitialEncoderState();
return true;
return PresentResult::OK;
}
}

View File

@@ -740,7 +740,7 @@ void OpenGLDevice::DestroyBuffers()
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)
{
@@ -750,7 +750,7 @@ bool OpenGLDevice::BeginPresent(bool skip_present, u32 clear_color)
TrimTexturePool();
}
return false;
return PresentResult::SkipPresent;
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
@@ -771,7 +771,7 @@ bool OpenGLDevice::BeginPresent(bool skip_present, u32 clear_color)
m_last_scissor = window_rc;
UpdateViewport();
UpdateScissor();
return true;
return PresentResult::OK;
}
void OpenGLDevice::EndPresent(bool explicit_present)

View File

@@ -104,7 +104,7 @@ public:
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 SubmitPresent() override;

View File

@@ -619,9 +619,9 @@ void PostProcessing::Chain::DestroyTextures()
g_gpu_device->RecycleTexture(std::move(m_input_texture));
}
bool PostProcessing::Chain::Apply(GPUTexture* input_color, GPUTexture* input_depth, GPUTexture* final_target,
GSVector4i final_rect, s32 orig_width, s32 orig_height, s32 native_width,
s32 native_height)
GPUDevice::PresentResult PostProcessing::Chain::Apply(GPUTexture* input_color, GPUTexture* input_depth,
GPUTexture* final_target, GSVector4i final_rect, s32 orig_width,
s32 orig_height, s32 native_width, s32 native_height)
{
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());
if (!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))
if (const GPUDevice::PresentResult pres =
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)
@@ -648,7 +650,7 @@ bool PostProcessing::Chain::Apply(GPUTexture* input_color, GPUTexture* input_dep
}
}
return true;
return GPUDevice::PresentResult::OK;
}
void PostProcessing::Initialize()

View File

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

View File

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

View File

@@ -1491,9 +1491,10 @@ bool PostProcessing::ReShadeFXShader::ResizeOutput(GPUTexture::Format format, u3
return true;
}
bool PostProcessing::ReShadeFXShader::Apply(GPUTexture* input_color, GPUTexture* input_depth, GPUTexture* final_target,
GSVector4i final_rect, s32 orig_width, s32 orig_height, s32 native_width,
s32 native_height, u32 target_width, u32 target_height)
GPUDevice::PresentResult PostProcessing::ReShadeFXShader::Apply(GPUTexture* input_color, GPUTexture* input_depth,
GPUTexture* final_target, GSVector4i final_rect,
s32 orig_width, s32 orig_height, s32 native_width,
s32 native_height, u32 target_width, u32 target_height)
{
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)
{
// 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();
return false;
return pres;
}
}
else
@@ -1842,5 +1843,5 @@ bool PostProcessing::ReShadeFXShader::Apply(GPUTexture* input_color, GPUTexture*
GL_POP();
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 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,
s32 orig_width, s32 orig_height, s32 native_width, s32 native_height, u32 target_width,
u32 target_height) override;
GPUDevice::PresentResult Apply(GPUTexture* input_color, GPUTexture* input_depth, GPUTexture* final_target,
GSVector4i final_rect, s32 orig_width, s32 orig_height, s32 native_width,
s32 native_height, u32 target_width, u32 target_height) override;
private:
using TextureID = s32;

View File

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

View File

@@ -1286,6 +1286,9 @@ bool VulkanDevice::SetGPUTimingEnabled(bool enabled)
void VulkanDevice::WaitForCommandBufferCompletion(u32 index)
{
if (m_device_was_lost)
return;
// Wait for this command buffer to be completed.
static constexpr u32 MAX_TIMEOUTS = 10;
u32 timeouts = 0;
@@ -1303,7 +1306,7 @@ void VulkanDevice::WaitForCommandBufferCompletion(u32 index)
else if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, TinyString::from_format("vkWaitForFences() for cmdbuffer {} failed: ", index));
m_device_is_lost = true;
m_device_was_lost = true;
return;
}
}
@@ -1357,7 +1360,7 @@ void VulkanDevice::WaitForCommandBufferCompletion(u32 index)
void VulkanDevice::EndAndSubmitCommandBuffer(VulkanSwapChain* present_swap_chain, bool explicit_present)
{
if (m_device_is_lost)
if (m_device_was_lost) [[unlikely]]
return;
CommandBuffer& resources = m_frame_resources[m_current_frame];
@@ -1416,7 +1419,7 @@ void VulkanDevice::EndAndSubmitCommandBuffer(VulkanSwapChain* present_swap_chain
if (res != VK_SUCCESS)
{
LOG_VULKAN_ERROR(res, "vkQueueSubmit failed: ");
m_device_is_lost = true;
m_device_was_lost = true;
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())
EndRenderPass();
if (m_device_was_lost) [[unlikely]]
return PresentResult::DeviceLost;
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 (!m_swap_chain)
{
SubmitCommandBuffer(false);
TrimTexturePool();
return false;
}
// Check if the device was lost.
if (m_device_is_lost)
{
Panic("Fixme"); // TODO
TrimTexturePool();
return false;
return PresentResult::SkipPresent;
}
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");
SubmitCommandBuffer(false);
TrimTexturePool();
return false;
return PresentResult::SkipPresent;
}
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.
SubmitCommandBuffer(false);
TrimTexturePool();
return false;
return PresentResult::SkipPresent;
}
}
BeginSwapChainRenderPass(clear_color);
return true;
return PresentResult::OK;
}
void VulkanDevice::EndPresent(bool explicit_present)
@@ -2421,6 +2419,9 @@ void VulkanDevice::EndPresent(bool explicit_present)
void VulkanDevice::SubmitPresent()
{
DebugAssert(m_swap_chain);
if (m_device_was_lost) [[unlikely]]
return;
QueuePresent(m_swap_chain.get());
}

View File

@@ -142,7 +142,7 @@ public:
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 SubmitPresent() override;
@@ -414,7 +414,7 @@ private:
u64 m_completed_fence_counter = 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;
GPUFramebufferManager<VkFramebuffer, CreateFramebuffer, DestroyFramebuffer> m_framebuffer_manager;