diff --git a/src/xenia/gpu/command_processor.cc b/src/xenia/gpu/command_processor.cc index dd7bd2c6b..3cbf54bcd 100644 --- a/src/xenia/gpu/command_processor.cc +++ b/src/xenia/gpu/command_processor.cc @@ -117,7 +117,7 @@ void CommandProcessor::ClearCaches() {} void CommandProcessor::WorkerThreadMain() { context_->MakeCurrent(); if (!SetupContext()) { - xe::FatalError("Unable to setup command processor GL state"); + xe::FatalError("Unable to setup command processor internal state"); return; } diff --git a/src/xenia/gpu/graphics_system.cc b/src/xenia/gpu/graphics_system.cc index 8dfb9250f..d15cd279e 100644 --- a/src/xenia/gpu/graphics_system.cc +++ b/src/xenia/gpu/graphics_system.cc @@ -56,18 +56,17 @@ X_STATUS GraphicsSystem::Setup(cpu::Processor* processor, assert_null(target_window->context()); 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 - // framebuffers - // from it. + // framebuffers from it. processor_context = provider()->CreateOffscreenContext(); }); if (!processor_context) { xe::FatalError( - "Unable to initialize GL context. Xenia requires OpenGL 4.5. Ensure " - "you have the latest drivers for your GPU and that it supports " - "OpenGL " - "4.5. See http://xenia.jp/faq/ for more information."); + "Unable to initialize graphics context. Xenia requires OpenGL 4.5 or " + "Vulkan support. Ensure you have the latest drivers for your GPU and " + "that it supports OpenGL or Vulkan. See http://xenia.jp/faq/ for " + "more information."); return X_STATUS_UNSUCCESSFUL; } } @@ -86,6 +85,10 @@ X_STATUS GraphicsSystem::Setup(cpu::Processor* processor, target_window->on_painting.AddListener( [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. memory_->AddVirtualMappedRange( 0x7FC80000, 0xFFFF0000, 0x0000FFFF, this, @@ -123,17 +126,29 @@ X_STATUS GraphicsSystem::Setup(cpu::Processor* processor, } void GraphicsSystem::Shutdown() { - EndTracing(); + if (command_processor_) { + EndTracing(); + } - vsync_worker_running_ = false; - vsync_worker_thread_->Wait(0, 0, 0, nullptr); - vsync_worker_thread_.reset(); + if (command_processor_) { + command_processor_->Shutdown(); + // 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, diff --git a/src/xenia/gpu/graphics_system.h b/src/xenia/gpu/graphics_system.h index eda218f76..459dd7d3f 100644 --- a/src/xenia/gpu/graphics_system.h +++ b/src/xenia/gpu/graphics_system.h @@ -45,6 +45,7 @@ class GraphicsSystem { kernel::KernelState* kernel_state, ui::Window* target_window); virtual void Shutdown(); + virtual void Reset(); virtual std::unique_ptr Capture() { return nullptr; } diff --git a/src/xenia/gpu/vulkan/texture_cache.cc b/src/xenia/gpu/vulkan/texture_cache.cc index b2bdd5819..c2d88a894 100644 --- a/src/xenia/gpu/vulkan/texture_cache.cc +++ b/src/xenia/gpu/vulkan/texture_cache.cc @@ -314,8 +314,7 @@ TextureCache::Texture* TextureCache::AllocateTexture( VkResult status = vmaCreateImage(mem_allocator_, &image_info, &vma_reqs, &image, &alloc, &vma_info); if (status != VK_SUCCESS) { - // Crap. - assert_always(); + // Allocation failed. return nullptr; } @@ -332,10 +331,12 @@ TextureCache::Texture* TextureCache::AllocateTexture( } bool TextureCache::FreeTexture(Texture* texture) { - if (texture->in_flight_fence && - vkGetFenceStatus(*device_, texture->in_flight_fence) != VK_SUCCESS) { - // Texture still in flight. - return false; + if (texture->in_flight_fence) { + VkResult status = vkGetFenceStatus(*device_, texture->in_flight_fence); + if (status != VK_SUCCESS && status != VK_ERROR_DEVICE_LOST) { + // Texture still in flight. + return false; + } } if (texture->framebuffer) { @@ -1421,7 +1422,6 @@ bool TextureCache::SetupTextureBinding(VkCommandBuffer command_buffer, auto texture = Demand(texture_info, command_buffer, completion_fence); auto sampler = Demand(sampler_info); - // assert_true(texture != nullptr && sampler != nullptr); if (texture == nullptr || sampler == nullptr) { return false; } diff --git a/src/xenia/gpu/vulkan/vulkan_command_processor.cc b/src/xenia/gpu/vulkan/vulkan_command_processor.cc index 4653a71ed..6f23254f4 100644 --- a/src/xenia/gpu/vulkan/vulkan_command_processor.cc +++ b/src/xenia/gpu/vulkan/vulkan_command_processor.cc @@ -537,8 +537,6 @@ void VulkanCommandProcessor::PerformSwap(uint32_t frontbuffer_ptr, submit_info.pSignalSemaphores = nullptr; status = vkQueueSubmit(queue_, 1, &submit_info, current_batch_fence_); - CheckResult(status, "vkQueueSubmit"); - if (device_->is_renderdoc_attached() && capturing_) { device_->EndRenderDocFrameCapture(); capturing_ = false; diff --git a/src/xenia/gpu/vulkan/vulkan_graphics_system.cc b/src/xenia/gpu/vulkan/vulkan_graphics_system.cc index 83e8210ae..1e199fbba 100644 --- a/src/xenia/gpu/vulkan/vulkan_graphics_system.cc +++ b/src/xenia/gpu/vulkan/vulkan_graphics_system.cc @@ -260,8 +260,15 @@ void VulkanGraphicsSystem::Swap(xe::ui::UIEvent* e) { if (!command_processor_) { return; } + // Check for pending swap. auto& swap_state = command_processor_->swap_state(); + if (display_context_->WasLost()) { + // We're crashing. Cheese it. + swap_state.pending = false; + return; + } + { std::lock_guard lock(swap_state.mutex); if (!swap_state.pending) { diff --git a/src/xenia/ui/vulkan/blitter.cc b/src/xenia/ui/vulkan/blitter.cc index 251d02098..8e3e8a639 100644 --- a/src/xenia/ui/vulkan/blitter.cc +++ b/src/xenia/ui/vulkan/blitter.cc @@ -271,7 +271,11 @@ void Blitter::BlitTexture2D(VkCommandBuffer command_buffer, VkFence fence, // Acquire and update a descriptor set for this image. auto set = descriptor_pool_->AcquireEntry(descriptor_set_layout_); - assert_not_null(set); + if (!set) { + assert_always(); + descriptor_pool_->CancelBatch(); + return; + } VkWriteDescriptorSet write; write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; diff --git a/src/xenia/ui/vulkan/fenced_pools.h b/src/xenia/ui/vulkan/fenced_pools.h index 3c45bd792..53c29ec4f 100644 --- a/src/xenia/ui/vulkan/fenced_pools.h +++ b/src/xenia/ui/vulkan/fenced_pools.h @@ -50,7 +50,9 @@ class BaseFencedPool { while (pending_batch_list_head_) { auto batch = pending_batch_list_head_; 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. pending_batch_list_head_ = batch->next; if (batch == pending_batch_list_tail_) { diff --git a/src/xenia/ui/vulkan/vulkan_context.cc b/src/xenia/ui/vulkan/vulkan_context.cc index da3d323be..69b6fed91 100644 --- a/src/xenia/ui/vulkan/vulkan_context.cc +++ b/src/xenia/ui/vulkan/vulkan_context.cc @@ -37,11 +37,12 @@ VulkanContext::VulkanContext(VulkanProvider* provider, Window* target_window) : GraphicsContext(provider, target_window) {} VulkanContext::~VulkanContext() { + VkResult status; auto provider = static_cast(provider_); auto device = provider->device(); { std::lock_guard queue_lock(device->primary_queue_mutex()); - vkQueueWaitIdle(device->primary_queue()); + status = vkQueueWaitIdle(device->primary_queue()); } immediate_drawer_.reset(); swap_chain_.reset(); @@ -132,6 +133,8 @@ void VulkanContext::BeginSwap() { auto provider = static_cast(provider_); auto device = provider->device(); + VkResult status; + // 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 // start touching it. @@ -143,13 +146,17 @@ void VulkanContext::BeginSwap() { } } - // Acquire the next image and set it up for use. - swap_chain_->Begin(); + if (!context_lost_) { + // 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. std::lock_guard queue_lock(device->primary_queue_mutex()); - auto err = vkQueueWaitIdle(device->primary_queue()); - CheckResult(err, "vkQueueWaitIdle"); + status = vkQueueWaitIdle(device->primary_queue()); } void VulkanContext::EndSwap() { @@ -157,15 +164,21 @@ void VulkanContext::EndSwap() { auto provider = static_cast(provider_); auto device = provider->device(); - // Notify the presentation engine the image is ready. - // The contents must be in a coherent state. - swap_chain_->End(); + VkResult status; + + 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. // TODO(benvanik): is this required? std::lock_guard queue_lock(device->primary_queue_mutex()); - auto err = vkQueueWaitIdle(device->primary_queue()); - CheckResult(err, "vkQueueWaitIdle"); + status = vkQueueWaitIdle(device->primary_queue()); } std::unique_ptr VulkanContext::Capture() { diff --git a/src/xenia/ui/vulkan/vulkan_context.h b/src/xenia/ui/vulkan/vulkan_context.h index f8ec41f05..3665ffd78 100644 --- a/src/xenia/ui/vulkan/vulkan_context.h +++ b/src/xenia/ui/vulkan/vulkan_context.h @@ -38,11 +38,16 @@ class VulkanContext : public GraphicsContext { bool MakeCurrent() override; void ClearCurrent() override; + bool WasLost() override { return context_lost_; } + void BeginSwap() override; void EndSwap() override; std::unique_ptr Capture() override; + protected: + bool context_lost_ = false; + private: friend class VulkanProvider; diff --git a/src/xenia/ui/vulkan/vulkan_swap_chain.cc b/src/xenia/ui/vulkan/vulkan_swap_chain.cc index d1f1bdef7..45bd4cdb0 100644 --- a/src/xenia/ui/vulkan/vulkan_swap_chain.cc +++ b/src/xenia/ui/vulkan/vulkan_swap_chain.cc @@ -388,14 +388,18 @@ void VulkanSwapChain::Shutdown() { } } -bool VulkanSwapChain::Begin() { +VkResult VulkanSwapChain::Begin() { wait_and_signal_semaphores_.clear(); + VkResult status; + // Get the index of the next available swapchain image. - auto err = + status = vkAcquireNextImageKHR(*device_, handle, 0, image_available_semaphore_, nullptr, ¤t_buffer_index_); - CheckResult(err, "vkAcquireNextImageKHR"); + if (status != VK_SUCCESS) { + return status; + } // Wait for the acquire semaphore to be signaled so that the following // operations know they can start modifying the image. @@ -414,10 +418,12 @@ bool VulkanSwapChain::Begin() { wait_submit_info.pSignalSemaphores = &image_usage_semaphore_; { std::lock_guard queue_lock(device_->primary_queue_mutex()); - err = + status = vkQueueSubmit(device_->primary_queue(), 1, &wait_submit_info, nullptr); } - CheckResult(err, "vkQueueSubmit"); + if (status != VK_SUCCESS) { + return status; + } // Reset all command buffers. vkResetCommandBuffer(render_cmd_buffer_, 0); @@ -441,13 +447,19 @@ bool VulkanSwapChain::Begin() { begin_info.flags = VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT | VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; begin_info.pInheritanceInfo = &inherit_info; - err = vkBeginCommandBuffer(render_cmd_buffer_, &begin_info); - CheckResult(err, "vkBeginCommandBuffer"); + status = vkBeginCommandBuffer(render_cmd_buffer_, &begin_info); + CheckResult(status, "vkBeginCommandBuffer"); + if (status != VK_SUCCESS) { + return status; + } // Start recording the copy command buffer as well. begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; - err = vkBeginCommandBuffer(copy_cmd_buffer_, &begin_info); - CheckResult(err, "vkBeginCommandBuffer"); + status = vkBeginCommandBuffer(copy_cmd_buffer_, &begin_info); + CheckResult(status, "vkBeginCommandBuffer"); + if (status != VK_SUCCESS) { + return status; + } // First: Issue a command to clear the render target. 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, &clear_range); - return true; + return VK_SUCCESS; } -bool VulkanSwapChain::End() { +VkResult VulkanSwapChain::End() { auto& current_buffer = buffers_[current_buffer_index_]; + VkResult status; - auto err = vkEndCommandBuffer(render_cmd_buffer_); - CheckResult(err, "vkEndCommandBuffer"); + status = vkEndCommandBuffer(render_cmd_buffer_); + CheckResult(status, "vkEndCommandBuffer"); + if (status != VK_SUCCESS) { + return status; + } - err = vkEndCommandBuffer(copy_cmd_buffer_); - CheckResult(err, "vkEndCommandBuffer"); + status = vkEndCommandBuffer(copy_cmd_buffer_); + CheckResult(status, "vkEndCommandBuffer"); + if (status != VK_SUCCESS) { + return status; + } // 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; begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; begin_info.pNext = nullptr; begin_info.flags = 0; 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. VkImageMemoryBarrier pre_image_copy_barrier; @@ -580,9 +607,13 @@ bool VulkanSwapChain::End() { current_buffer.image_layout = post_image_memory_barrier.newLayout; - vkEndCommandBuffer(cmd_buffer_); - VkPipelineStageFlags wait_dst_stage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; + status = vkEndCommandBuffer(cmd_buffer_); + CheckResult(status, "vkEndCommandBuffer"); + if (status != VK_SUCCESS) { + return status; + } + VkPipelineStageFlags wait_dst_stage = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT; std::vector semaphores; for (size_t i = 0; i < wait_and_signal_semaphores_.size(); i++) { semaphores.push_back(wait_and_signal_semaphores_[i]); @@ -603,10 +634,13 @@ bool VulkanSwapChain::End() { render_submit_info.pSignalSemaphores = semaphores.data(); { std::lock_guard queue_lock(device_->primary_queue_mutex()); - err = vkQueueSubmit(device_->primary_queue(), 1, &render_submit_info, - nullptr); + status = vkQueueSubmit(device_->primary_queue(), 1, &render_submit_info, + nullptr); + } + + if (status != VK_SUCCESS) { + return status; } - CheckResult(err, "vkQueueSubmit"); // Queue the present of our current image. const VkSwapchainKHR swap_chains[] = {handle}; @@ -622,27 +656,30 @@ bool VulkanSwapChain::End() { present_info.pResults = nullptr; { std::lock_guard 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: break; case VK_SUBOPTIMAL_KHR: // We are not rendering at the right size - but the presentation engine // will scale the output for us. + status = VK_SUCCESS; break; case VK_ERROR_OUT_OF_DATE_KHR: // Lost presentation ability; need to recreate the swapchain. // TODO(benvanik): recreate swapchain. assert_always("Swapchain recreation not implemented"); break; + case VK_ERROR_DEVICE_LOST: + // Fatal. Device lost. + break; 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"); - return false; } - return true; + return status; } } // namespace vulkan diff --git a/src/xenia/ui/vulkan/vulkan_swap_chain.h b/src/xenia/ui/vulkan/vulkan_swap_chain.h index 7ad231d51..f35105d87 100644 --- a/src/xenia/ui/vulkan/vulkan_swap_chain.h +++ b/src/xenia/ui/vulkan/vulkan_swap_chain.h @@ -57,9 +57,9 @@ class VulkanSwapChain { void WaitAndSignalSemaphore(VkSemaphore sem); // Begins the swap operation, preparing state for rendering. - bool Begin(); + VkResult Begin(); // Ends the swap operation, finalizing rendering and presenting the results. - bool End(); + VkResult End(); private: struct Buffer { diff --git a/src/xenia/ui/window.cc b/src/xenia/ui/window.cc index 2edff242a..4e03b35c6 100644 --- a/src/xenia/ui/window.cc +++ b/src/xenia/ui/window.cc @@ -198,6 +198,11 @@ void Window::OnPaint(UIEvent* e) { ImGui::NewFrame(); context_->BeginSwap(); + if (context_->WasLost()) { + on_context_lost(e); + return; + } + ForEachListener([e](auto listener) { listener->OnPainting(e); }); on_painting(e); ForEachListener([e](auto listener) { listener->OnPaint(e); }); diff --git a/src/xenia/ui/window.h b/src/xenia/ui/window.h index 07831fe96..6cbc7a250 100644 --- a/src/xenia/ui/window.h +++ b/src/xenia/ui/window.h @@ -117,6 +117,8 @@ class Window { Delegate on_painting; Delegate on_paint; Delegate on_painted; + Delegate on_context_lost; + Delegate on_file_drop; Delegate on_key_down;