[Vulkan] Better handling of device lost events (present fatal error dialog)

This commit is contained in:
DrChat 2017-12-18 14:27:00 -06:00
parent 76b577148d
commit b5d647d540
14 changed files with 154 additions and 65 deletions

View File

@ -117,7 +117,7 @@ void CommandProcessor::ClearCaches() {}
void CommandProcessor::WorkerThreadMain() { void CommandProcessor::WorkerThreadMain() {
context_->MakeCurrent(); context_->MakeCurrent();
if (!SetupContext()) { if (!SetupContext()) {
xe::FatalError("Unable to setup command processor GL state"); xe::FatalError("Unable to setup command processor internal state");
return; return;
} }

View File

@ -56,18 +56,17 @@ X_STATUS GraphicsSystem::Setup(cpu::Processor* processor,
assert_null(target_window->context()); assert_null(target_window->context());
target_window_->set_context(provider_->CreateContext(target_window_)); target_window_->set_context(provider_->CreateContext(target_window_));
// Setup the GL context the command processor will do all its drawing in. // Setup the context the command processor will do all its drawing in.
// It's shared with the display context so that we can resolve // It's shared with the display context so that we can resolve
// framebuffers // framebuffers from it.
// from it.
processor_context = provider()->CreateOffscreenContext(); processor_context = provider()->CreateOffscreenContext();
}); });
if (!processor_context) { if (!processor_context) {
xe::FatalError( xe::FatalError(
"Unable to initialize GL context. Xenia requires OpenGL 4.5. Ensure " "Unable to initialize graphics context. Xenia requires OpenGL 4.5 or "
"you have the latest drivers for your GPU and that it supports " "Vulkan support. Ensure you have the latest drivers for your GPU and "
"OpenGL " "that it supports OpenGL or Vulkan. See http://xenia.jp/faq/ for "
"4.5. See http://xenia.jp/faq/ for more information."); "more information.");
return X_STATUS_UNSUCCESSFUL; return X_STATUS_UNSUCCESSFUL;
} }
} }
@ -86,6 +85,10 @@ X_STATUS GraphicsSystem::Setup(cpu::Processor* processor,
target_window->on_painting.AddListener( target_window->on_painting.AddListener(
[this](xe::ui::UIEvent* e) { Swap(e); }); [this](xe::ui::UIEvent* e) { Swap(e); });
// Watch for context lost events.
target_window->on_context_lost.AddListener(
[this](xe::ui::UIEvent* e) { Reset(); });
// Let the processor know we want register access callbacks. // Let the processor know we want register access callbacks.
memory_->AddVirtualMappedRange( memory_->AddVirtualMappedRange(
0x7FC80000, 0xFFFF0000, 0x0000FFFF, this, 0x7FC80000, 0xFFFF0000, 0x0000FFFF, this,
@ -123,17 +126,29 @@ X_STATUS GraphicsSystem::Setup(cpu::Processor* processor,
} }
void GraphicsSystem::Shutdown() { void GraphicsSystem::Shutdown() {
EndTracing(); if (command_processor_) {
EndTracing();
}
vsync_worker_running_ = false; if (command_processor_) {
vsync_worker_thread_->Wait(0, 0, 0, nullptr); command_processor_->Shutdown();
vsync_worker_thread_.reset(); // TODO(benvanik): remove mapped range.
command_processor_.reset();
}
command_processor_->Shutdown(); if (vsync_worker_thread_) {
vsync_worker_running_ = false;
vsync_worker_thread_->Wait(0, 0, 0, nullptr);
vsync_worker_thread_.reset();
}
}
// TODO(benvanik): remove mapped range. void GraphicsSystem::Reset() {
// TODO(DrChat): Reset the system.
XELOGI("Context lost; Reset invoked");
Shutdown();
command_processor_.reset(); xe::FatalError("Graphics device lost (probably due to an internal error)");
} }
uint32_t GraphicsSystem::ReadRegisterThunk(void* ppc_context, uint32_t GraphicsSystem::ReadRegisterThunk(void* ppc_context,

View File

@ -45,6 +45,7 @@ class GraphicsSystem {
kernel::KernelState* kernel_state, kernel::KernelState* kernel_state,
ui::Window* target_window); ui::Window* target_window);
virtual void Shutdown(); virtual void Shutdown();
virtual void Reset();
virtual std::unique_ptr<xe::ui::RawImage> Capture() { return nullptr; } virtual std::unique_ptr<xe::ui::RawImage> Capture() { return nullptr; }

View File

@ -314,8 +314,7 @@ TextureCache::Texture* TextureCache::AllocateTexture(
VkResult status = vmaCreateImage(mem_allocator_, &image_info, &vma_reqs, VkResult status = vmaCreateImage(mem_allocator_, &image_info, &vma_reqs,
&image, &alloc, &vma_info); &image, &alloc, &vma_info);
if (status != VK_SUCCESS) { if (status != VK_SUCCESS) {
// Crap. // Allocation failed.
assert_always();
return nullptr; return nullptr;
} }
@ -332,10 +331,12 @@ TextureCache::Texture* TextureCache::AllocateTexture(
} }
bool TextureCache::FreeTexture(Texture* texture) { bool TextureCache::FreeTexture(Texture* texture) {
if (texture->in_flight_fence && if (texture->in_flight_fence) {
vkGetFenceStatus(*device_, texture->in_flight_fence) != VK_SUCCESS) { VkResult status = vkGetFenceStatus(*device_, texture->in_flight_fence);
// Texture still in flight. if (status != VK_SUCCESS && status != VK_ERROR_DEVICE_LOST) {
return false; // Texture still in flight.
return false;
}
} }
if (texture->framebuffer) { if (texture->framebuffer) {
@ -1421,7 +1422,6 @@ bool TextureCache::SetupTextureBinding(VkCommandBuffer command_buffer,
auto texture = Demand(texture_info, command_buffer, completion_fence); auto texture = Demand(texture_info, command_buffer, completion_fence);
auto sampler = Demand(sampler_info); auto sampler = Demand(sampler_info);
// assert_true(texture != nullptr && sampler != nullptr);
if (texture == nullptr || sampler == nullptr) { if (texture == nullptr || sampler == nullptr) {
return false; return false;
} }

View File

@ -537,8 +537,6 @@ void VulkanCommandProcessor::PerformSwap(uint32_t frontbuffer_ptr,
submit_info.pSignalSemaphores = nullptr; submit_info.pSignalSemaphores = nullptr;
status = vkQueueSubmit(queue_, 1, &submit_info, current_batch_fence_); status = vkQueueSubmit(queue_, 1, &submit_info, current_batch_fence_);
CheckResult(status, "vkQueueSubmit");
if (device_->is_renderdoc_attached() && capturing_) { if (device_->is_renderdoc_attached() && capturing_) {
device_->EndRenderDocFrameCapture(); device_->EndRenderDocFrameCapture();
capturing_ = false; capturing_ = false;

View File

@ -260,8 +260,15 @@ void VulkanGraphicsSystem::Swap(xe::ui::UIEvent* e) {
if (!command_processor_) { if (!command_processor_) {
return; return;
} }
// Check for pending swap. // Check for pending swap.
auto& swap_state = command_processor_->swap_state(); auto& swap_state = command_processor_->swap_state();
if (display_context_->WasLost()) {
// We're crashing. Cheese it.
swap_state.pending = false;
return;
}
{ {
std::lock_guard<std::mutex> lock(swap_state.mutex); std::lock_guard<std::mutex> lock(swap_state.mutex);
if (!swap_state.pending) { if (!swap_state.pending) {

View File

@ -271,7 +271,11 @@ void Blitter::BlitTexture2D(VkCommandBuffer command_buffer, VkFence fence,
// Acquire and update a descriptor set for this image. // Acquire and update a descriptor set for this image.
auto set = descriptor_pool_->AcquireEntry(descriptor_set_layout_); auto set = descriptor_pool_->AcquireEntry(descriptor_set_layout_);
assert_not_null(set); if (!set) {
assert_always();
descriptor_pool_->CancelBatch();
return;
}
VkWriteDescriptorSet write; VkWriteDescriptorSet write;
write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;

View File

@ -50,7 +50,9 @@ class BaseFencedPool {
while (pending_batch_list_head_) { while (pending_batch_list_head_) {
auto batch = pending_batch_list_head_; auto batch = pending_batch_list_head_;
assert_not_null(batch->fence); assert_not_null(batch->fence);
if (vkGetFenceStatus(device_, batch->fence) == VK_SUCCESS) {
VkResult status = vkGetFenceStatus(device_, batch->fence);
if (status == VK_SUCCESS || status == VK_ERROR_DEVICE_LOST) {
// Batch has completed. Reclaim. // Batch has completed. Reclaim.
pending_batch_list_head_ = batch->next; pending_batch_list_head_ = batch->next;
if (batch == pending_batch_list_tail_) { if (batch == pending_batch_list_tail_) {

View File

@ -37,11 +37,12 @@ VulkanContext::VulkanContext(VulkanProvider* provider, Window* target_window)
: GraphicsContext(provider, target_window) {} : GraphicsContext(provider, target_window) {}
VulkanContext::~VulkanContext() { VulkanContext::~VulkanContext() {
VkResult status;
auto provider = static_cast<VulkanProvider*>(provider_); auto provider = static_cast<VulkanProvider*>(provider_);
auto device = provider->device(); auto device = provider->device();
{ {
std::lock_guard<std::mutex> queue_lock(device->primary_queue_mutex()); std::lock_guard<std::mutex> queue_lock(device->primary_queue_mutex());
vkQueueWaitIdle(device->primary_queue()); status = vkQueueWaitIdle(device->primary_queue());
} }
immediate_drawer_.reset(); immediate_drawer_.reset();
swap_chain_.reset(); swap_chain_.reset();
@ -132,6 +133,8 @@ void VulkanContext::BeginSwap() {
auto provider = static_cast<VulkanProvider*>(provider_); auto provider = static_cast<VulkanProvider*>(provider_);
auto device = provider->device(); auto device = provider->device();
VkResult status;
// If we have a window see if it's been resized since we last swapped. // If we have a window see if it's been resized since we last swapped.
// If it has been, we'll need to reinitialize the swap chain before we // If it has been, we'll need to reinitialize the swap chain before we
// start touching it. // start touching it.
@ -143,13 +146,17 @@ void VulkanContext::BeginSwap() {
} }
} }
// Acquire the next image and set it up for use. if (!context_lost_) {
swap_chain_->Begin(); // Acquire the next image and set it up for use.
status = swap_chain_->Begin();
if (status == VK_ERROR_DEVICE_LOST) {
context_lost_ = true;
}
}
// TODO(benvanik): use a fence instead? May not be possible with target image. // TODO(benvanik): use a fence instead? May not be possible with target image.
std::lock_guard<std::mutex> queue_lock(device->primary_queue_mutex()); std::lock_guard<std::mutex> queue_lock(device->primary_queue_mutex());
auto err = vkQueueWaitIdle(device->primary_queue()); status = vkQueueWaitIdle(device->primary_queue());
CheckResult(err, "vkQueueWaitIdle");
} }
void VulkanContext::EndSwap() { void VulkanContext::EndSwap() {
@ -157,15 +164,21 @@ void VulkanContext::EndSwap() {
auto provider = static_cast<VulkanProvider*>(provider_); auto provider = static_cast<VulkanProvider*>(provider_);
auto device = provider->device(); auto device = provider->device();
// Notify the presentation engine the image is ready. VkResult status;
// The contents must be in a coherent state.
swap_chain_->End(); if (!context_lost_) {
// Notify the presentation engine the image is ready.
// The contents must be in a coherent state.
status = swap_chain_->End();
if (status == VK_ERROR_DEVICE_LOST) {
context_lost_ = true;
}
}
// Wait until the queue is idle. // Wait until the queue is idle.
// TODO(benvanik): is this required? // TODO(benvanik): is this required?
std::lock_guard<std::mutex> queue_lock(device->primary_queue_mutex()); std::lock_guard<std::mutex> queue_lock(device->primary_queue_mutex());
auto err = vkQueueWaitIdle(device->primary_queue()); status = vkQueueWaitIdle(device->primary_queue());
CheckResult(err, "vkQueueWaitIdle");
} }
std::unique_ptr<RawImage> VulkanContext::Capture() { std::unique_ptr<RawImage> VulkanContext::Capture() {

View File

@ -38,11 +38,16 @@ class VulkanContext : public GraphicsContext {
bool MakeCurrent() override; bool MakeCurrent() override;
void ClearCurrent() override; void ClearCurrent() override;
bool WasLost() override { return context_lost_; }
void BeginSwap() override; void BeginSwap() override;
void EndSwap() override; void EndSwap() override;
std::unique_ptr<RawImage> Capture() override; std::unique_ptr<RawImage> Capture() override;
protected:
bool context_lost_ = false;
private: private:
friend class VulkanProvider; friend class VulkanProvider;

View File

@ -388,14 +388,18 @@ void VulkanSwapChain::Shutdown() {
} }
} }
bool VulkanSwapChain::Begin() { VkResult VulkanSwapChain::Begin() {
wait_and_signal_semaphores_.clear(); wait_and_signal_semaphores_.clear();
VkResult status;
// Get the index of the next available swapchain image. // Get the index of the next available swapchain image.
auto err = status =
vkAcquireNextImageKHR(*device_, handle, 0, image_available_semaphore_, vkAcquireNextImageKHR(*device_, handle, 0, image_available_semaphore_,
nullptr, &current_buffer_index_); nullptr, &current_buffer_index_);
CheckResult(err, "vkAcquireNextImageKHR"); if (status != VK_SUCCESS) {
return status;
}
// Wait for the acquire semaphore to be signaled so that the following // Wait for the acquire semaphore to be signaled so that the following
// operations know they can start modifying the image. // operations know they can start modifying the image.
@ -414,10 +418,12 @@ bool VulkanSwapChain::Begin() {
wait_submit_info.pSignalSemaphores = &image_usage_semaphore_; wait_submit_info.pSignalSemaphores = &image_usage_semaphore_;
{ {
std::lock_guard<std::mutex> queue_lock(device_->primary_queue_mutex()); std::lock_guard<std::mutex> queue_lock(device_->primary_queue_mutex());
err = status =
vkQueueSubmit(device_->primary_queue(), 1, &wait_submit_info, nullptr); vkQueueSubmit(device_->primary_queue(), 1, &wait_submit_info, nullptr);
} }
CheckResult(err, "vkQueueSubmit"); if (status != VK_SUCCESS) {
return status;
}
// Reset all command buffers. // Reset all command buffers.
vkResetCommandBuffer(render_cmd_buffer_, 0); vkResetCommandBuffer(render_cmd_buffer_, 0);
@ -441,13 +447,19 @@ bool VulkanSwapChain::Begin() {
begin_info.flags = VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT | begin_info.flags = VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT |
VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
begin_info.pInheritanceInfo = &inherit_info; begin_info.pInheritanceInfo = &inherit_info;
err = vkBeginCommandBuffer(render_cmd_buffer_, &begin_info); status = vkBeginCommandBuffer(render_cmd_buffer_, &begin_info);
CheckResult(err, "vkBeginCommandBuffer"); CheckResult(status, "vkBeginCommandBuffer");
if (status != VK_SUCCESS) {
return status;
}
// Start recording the copy command buffer as well. // Start recording the copy command buffer as well.
begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
err = vkBeginCommandBuffer(copy_cmd_buffer_, &begin_info); status = vkBeginCommandBuffer(copy_cmd_buffer_, &begin_info);
CheckResult(err, "vkBeginCommandBuffer"); CheckResult(status, "vkBeginCommandBuffer");
if (status != VK_SUCCESS) {
return status;
}
// First: Issue a command to clear the render target. // First: Issue a command to clear the render target.
VkImageSubresourceRange clear_range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; VkImageSubresourceRange clear_range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
@ -466,27 +478,42 @@ bool VulkanSwapChain::Begin() {
VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clear_color, 1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clear_color, 1,
&clear_range); &clear_range);
return true; return VK_SUCCESS;
} }
bool VulkanSwapChain::End() { VkResult VulkanSwapChain::End() {
auto& current_buffer = buffers_[current_buffer_index_]; auto& current_buffer = buffers_[current_buffer_index_];
VkResult status;
auto err = vkEndCommandBuffer(render_cmd_buffer_); status = vkEndCommandBuffer(render_cmd_buffer_);
CheckResult(err, "vkEndCommandBuffer"); CheckResult(status, "vkEndCommandBuffer");
if (status != VK_SUCCESS) {
return status;
}
err = vkEndCommandBuffer(copy_cmd_buffer_); status = vkEndCommandBuffer(copy_cmd_buffer_);
CheckResult(err, "vkEndCommandBuffer"); CheckResult(status, "vkEndCommandBuffer");
if (status != VK_SUCCESS) {
return status;
}
// Build primary command buffer. // Build primary command buffer.
vkResetCommandBuffer(cmd_buffer_, 0); status = vkResetCommandBuffer(cmd_buffer_, 0);
CheckResult(status, "vkResetCommandBuffer");
if (status != VK_SUCCESS) {
return status;
}
VkCommandBufferBeginInfo begin_info; VkCommandBufferBeginInfo begin_info;
begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
begin_info.pNext = nullptr; begin_info.pNext = nullptr;
begin_info.flags = 0; begin_info.flags = 0;
begin_info.pInheritanceInfo = nullptr; begin_info.pInheritanceInfo = nullptr;
vkBeginCommandBuffer(cmd_buffer_, &begin_info); status = vkBeginCommandBuffer(cmd_buffer_, &begin_info);
CheckResult(status, "vkBeginCommandBuffer");
if (status != VK_SUCCESS) {
return status;
}
// Transition the image to a format we can copy to. // Transition the image to a format we can copy to.
VkImageMemoryBarrier pre_image_copy_barrier; VkImageMemoryBarrier pre_image_copy_barrier;
@ -580,9 +607,13 @@ bool VulkanSwapChain::End() {
current_buffer.image_layout = post_image_memory_barrier.newLayout; current_buffer.image_layout = post_image_memory_barrier.newLayout;
vkEndCommandBuffer(cmd_buffer_); status = vkEndCommandBuffer(cmd_buffer_);
VkPipelineStageFlags wait_dst_stage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; CheckResult(status, "vkEndCommandBuffer");
if (status != VK_SUCCESS) {
return status;
}
VkPipelineStageFlags wait_dst_stage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
std::vector<VkSemaphore> semaphores; std::vector<VkSemaphore> semaphores;
for (size_t i = 0; i < wait_and_signal_semaphores_.size(); i++) { for (size_t i = 0; i < wait_and_signal_semaphores_.size(); i++) {
semaphores.push_back(wait_and_signal_semaphores_[i]); semaphores.push_back(wait_and_signal_semaphores_[i]);
@ -603,10 +634,13 @@ bool VulkanSwapChain::End() {
render_submit_info.pSignalSemaphores = semaphores.data(); render_submit_info.pSignalSemaphores = semaphores.data();
{ {
std::lock_guard<std::mutex> queue_lock(device_->primary_queue_mutex()); std::lock_guard<std::mutex> queue_lock(device_->primary_queue_mutex());
err = vkQueueSubmit(device_->primary_queue(), 1, &render_submit_info, status = vkQueueSubmit(device_->primary_queue(), 1, &render_submit_info,
nullptr); nullptr);
}
if (status != VK_SUCCESS) {
return status;
} }
CheckResult(err, "vkQueueSubmit");
// Queue the present of our current image. // Queue the present of our current image.
const VkSwapchainKHR swap_chains[] = {handle}; const VkSwapchainKHR swap_chains[] = {handle};
@ -622,27 +656,30 @@ bool VulkanSwapChain::End() {
present_info.pResults = nullptr; present_info.pResults = nullptr;
{ {
std::lock_guard<std::mutex> queue_lock(device_->primary_queue_mutex()); std::lock_guard<std::mutex> queue_lock(device_->primary_queue_mutex());
err = vkQueuePresentKHR(device_->primary_queue(), &present_info); status = vkQueuePresentKHR(device_->primary_queue(), &present_info);
} }
switch (err) { switch (status) {
case VK_SUCCESS: case VK_SUCCESS:
break; break;
case VK_SUBOPTIMAL_KHR: case VK_SUBOPTIMAL_KHR:
// We are not rendering at the right size - but the presentation engine // We are not rendering at the right size - but the presentation engine
// will scale the output for us. // will scale the output for us.
status = VK_SUCCESS;
break; break;
case VK_ERROR_OUT_OF_DATE_KHR: case VK_ERROR_OUT_OF_DATE_KHR:
// Lost presentation ability; need to recreate the swapchain. // Lost presentation ability; need to recreate the swapchain.
// TODO(benvanik): recreate swapchain. // TODO(benvanik): recreate swapchain.
assert_always("Swapchain recreation not implemented"); assert_always("Swapchain recreation not implemented");
break; break;
case VK_ERROR_DEVICE_LOST:
// Fatal. Device lost.
break;
default: default:
XELOGE("Failed to queue present: %s", to_string(err)); XELOGE("Failed to queue present: %s", to_string(status));
assert_always("Unexpected queue present failure"); assert_always("Unexpected queue present failure");
return false;
} }
return true; return status;
} }
} // namespace vulkan } // namespace vulkan

View File

@ -57,9 +57,9 @@ class VulkanSwapChain {
void WaitAndSignalSemaphore(VkSemaphore sem); void WaitAndSignalSemaphore(VkSemaphore sem);
// Begins the swap operation, preparing state for rendering. // Begins the swap operation, preparing state for rendering.
bool Begin(); VkResult Begin();
// Ends the swap operation, finalizing rendering and presenting the results. // Ends the swap operation, finalizing rendering and presenting the results.
bool End(); VkResult End();
private: private:
struct Buffer { struct Buffer {

View File

@ -198,6 +198,11 @@ void Window::OnPaint(UIEvent* e) {
ImGui::NewFrame(); ImGui::NewFrame();
context_->BeginSwap(); context_->BeginSwap();
if (context_->WasLost()) {
on_context_lost(e);
return;
}
ForEachListener([e](auto listener) { listener->OnPainting(e); }); ForEachListener([e](auto listener) { listener->OnPainting(e); });
on_painting(e); on_painting(e);
ForEachListener([e](auto listener) { listener->OnPaint(e); }); ForEachListener([e](auto listener) { listener->OnPaint(e); });

View File

@ -117,6 +117,8 @@ class Window {
Delegate<UIEvent*> on_painting; Delegate<UIEvent*> on_painting;
Delegate<UIEvent*> on_paint; Delegate<UIEvent*> on_paint;
Delegate<UIEvent*> on_painted; Delegate<UIEvent*> on_painted;
Delegate<UIEvent*> on_context_lost;
Delegate<FileDropEvent*> on_file_drop; Delegate<FileDropEvent*> on_file_drop;
Delegate<KeyEvent*> on_key_down; Delegate<KeyEvent*> on_key_down;