Ver Fonte

vulkan: Gracefully handle VK_ERROR_DEVICE_LOST and VK_SUBOPTIMAL_KHR

Treat VK_SUBOPTIMAL_KHR the same as VK_ERROR_OUT_OF_DATE_KHR, recreate swapchain

Don't crash the application on device lost, just recreate the device and resources in the next frame

There's a possibility that the physical device is also lost, we're not handling that (yet)
rdb há 1 ano atrás
pai
commit
0fc1247128

+ 63 - 19
panda/src/vulkandisplay/vulkanGraphicsStateGuardian.cxx

@@ -77,9 +77,37 @@ VulkanGraphicsStateGuardian(GraphicsEngine *engine, VulkanGraphicsPipe *pipe,
   GraphicsStateGuardian(CS_default, engine, pipe),
   _graphics_queue_family_index(queue_family_index)
 {
-  reset();
+}
+
+/**
+ *
+ */
+VulkanGraphicsStateGuardian::
+~VulkanGraphicsStateGuardian() {
+  destroy_device();
+}
+
+/**
+ * Resets all internal state as if the gsg were newly created.
+ */
+void VulkanGraphicsStateGuardian::
+reset() {
+  if (_device != VK_NULL_HANDLE) {
+    close_gsg();
+    destroy_device();
+    _closing_gsg = false;
+    _prepared_objects = new PreparedGraphicsObjects;
+  }
+
+  GraphicsStateGuardian::reset();
+  _needs_reset = true;
   _is_valid = false;
 
+  _current_shader = nullptr;
+
+  VulkanGraphicsPipe *pipe;
+  DCAST_INTO_V(pipe, get_pipe());
+
   const VkPhysicalDeviceLimits &limits = pipe->_gpu_properties.limits;
   const VkPhysicalDeviceFeatures &features = pipe->_gpu_features;
 
@@ -115,6 +143,8 @@ VulkanGraphicsStateGuardian(GraphicsEngine *engine, VulkanGraphicsPipe *pipe,
 
     extensions.push_back(VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME);
     _supports_custom_border_colors = true;
+  } else {
+    _supports_custom_border_colors = false;
   }
 
   bool supports_null_descriptor = false;
@@ -143,6 +173,9 @@ VulkanGraphicsStateGuardian(GraphicsEngine *engine, VulkanGraphicsPipe *pipe,
     extensions.push_back(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME);
     _supports_vertex_attrib_divisor = true;
     _supports_vertex_attrib_zero_divisor = pipe->_gpu_supports_vertex_attrib_zero_divisor;
+  } else {
+    _supports_vertex_attrib_divisor = false;
+    _supports_vertex_attrib_zero_divisor = false;
   }
 
   // VK_EXT_extended_dynamic_state and VK_EXT_extended_dynamic_state2
@@ -157,6 +190,8 @@ VulkanGraphicsStateGuardian(GraphicsEngine *engine, VulkanGraphicsPipe *pipe,
 
     extensions.push_back(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
     _supports_extended_dynamic_state2 = true;
+  } else {
+    _supports_extended_dynamic_state2 = false;
   }
 
   // VK_EXT_extended_dynamic_state2
@@ -172,6 +207,8 @@ VulkanGraphicsStateGuardian(GraphicsEngine *engine, VulkanGraphicsPipe *pipe,
 
     extensions.push_back(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
     _supports_extended_dynamic_state2_patch_control_points = pipe->_gpu_supports_extended_dynamic_state2_patch_control_points;
+  } else {
+    _supports_extended_dynamic_state2_patch_control_points = false;
   }
 
   // VK_KHR_portability_subset
@@ -227,6 +264,8 @@ VulkanGraphicsStateGuardian(GraphicsEngine *engine, VulkanGraphicsPipe *pipe,
   err = vkCreateDevice(pipe->_gpu, &device_info, nullptr, &_device);
   if (err) {
     vulkan_error(err, "Failed to create device");
+    _is_valid = false;
+    _needs_reset = true;
     return;
   }
 
@@ -635,20 +674,15 @@ VulkanGraphicsStateGuardian(GraphicsEngine *engine, VulkanGraphicsPipe *pipe,
 }
 
 /**
- *
+ * Releases the device and all associated resources.
  */
-VulkanGraphicsStateGuardian::
-~VulkanGraphicsStateGuardian() {
+void VulkanGraphicsStateGuardian::
+destroy_device() {
   if (_device == VK_NULL_HANDLE) {
     nassertv(_memory_pages.empty());
     return;
   }
 
-  // And all the semaphores that were generated on this device.
-  for (VkSemaphore semaphore : _semaphores) {
-    vkDestroySemaphore(_device, semaphore, nullptr);
-  }
-
   // Remove the things we created in the constructor, in reverse order.
   vkDestroyBuffer(_device, _uniform_buffer, nullptr);
   vkDestroyDescriptorSetLayout(_device, _lattr_descriptor_set_layout, nullptr);
@@ -659,10 +693,12 @@ VulkanGraphicsStateGuardian::
 
   if (_null_vertex_buffer != VK_NULL_HANDLE) {
     vkDestroyBuffer(_device, _null_vertex_buffer, nullptr);
+    _null_vertex_buffer = VK_NULL_HANDLE;
   }
 
   if (_staging_buffer != VK_NULL_HANDLE) {
     vkDestroyBuffer(_device, _staging_buffer, nullptr);
+    _staging_buffer = VK_NULL_HANDLE;
   }
 
   for (FrameData &frame_data : _frame_data_pool) {
@@ -716,6 +752,7 @@ close_gsg() {
       _frame_data = nullptr;
       finish_frame(frame_data);
     }
+    _default_sc = nullptr;
   }
 
   GraphicsStateGuardian::close_gsg();
@@ -2624,6 +2661,9 @@ begin_frame(Thread *current_thread) {
     }
     else if (err) {
       vulkan_error(err, "Failure waiting for command buffer fence");
+      if (err == VK_ERROR_DEVICE_LOST) {
+        mark_new();
+      }
       return false;
     }
 
@@ -2716,6 +2756,9 @@ begin_frame(Thread *current_thread) {
   err = vkQueueSubmit(_queue, 1, &submit_info, _frame_data->_fence);
   if (err) {
     vulkan_error(err, "Error submitting queue");
+    if (err == VK_ERROR_DEVICE_LOST) {
+      mark_new();
+    }
   }
   _frame_data = nullptr;
   return false;
@@ -2845,6 +2888,9 @@ end_frame(Thread *current_thread, VkSemaphore wait_for, VkSemaphore signal_done)
   err = vkQueueSubmit(_queue, 1, &submit_info, _frame_data->_fence);
   if (err) {
     vulkan_error(err, "Error submitting queue");
+    if (err == VK_ERROR_DEVICE_LOST) {
+      mark_new();
+    }
     return;
   }
 
@@ -2857,6 +2903,9 @@ end_frame(Thread *current_thread, VkSemaphore wait_for, VkSemaphore signal_done)
     }
     if (err) {
       vulkan_error(err, "Failed to wait for command buffer execution");
+      if (err == VK_ERROR_DEVICE_LOST) {
+        mark_new();
+      }
       vkQueueWaitIdle(_queue);
     }
 
@@ -2881,7 +2930,9 @@ end_frame(Thread *current_thread, VkSemaphore wait_for, VkSemaphore signal_done)
 
     // Reset the used fences to unsignaled status.
     VkResult err = vkResetFences(_device, num_reset_fences, reset_fences);
-    nassertv(!err);
+    if (err != VK_SUCCESS) {
+      vulkan_error(err, "Error resetting fences");
+    }
   }
 
   _last_frame_data = _frame_data;
@@ -3174,14 +3225,6 @@ end_draw_primitives() {
   GraphicsStateGuardian::end_draw_primitives();
 }
 
-/**
- * Resets all internal state as if the gsg were newly created.
- */
-void VulkanGraphicsStateGuardian::
-reset() {
-  GraphicsStateGuardian::reset();
-}
-
 /**
  * Copy the pixels within the indicated display region from the framebuffer
  * into texture memory.
@@ -3600,7 +3643,6 @@ create_semaphore() {
   VkResult
   err = vkCreateSemaphore(_device, &semaphore_info, nullptr, &semaphore);
   nassertr_always(err == VK_SUCCESS, VK_NULL_HANDLE);
-  _semaphores.push_back(semaphore);
   return semaphore;
 }
 
@@ -4155,6 +4197,8 @@ make_compute_pipeline(VulkanShaderContext *sc) {
  */
 bool VulkanGraphicsStateGuardian::
 get_attrib_descriptor_set(VkDescriptorSet &out, VkDescriptorSetLayout layout, const RenderAttrib *attrib) {
+  nassertr(_current_shader != nullptr, false);
+
   // Look it up in the attribute map.
   auto it = _current_shader->_attrib_descriptor_set_map.find(attrib);
   if (it != _current_shader->_attrib_descriptor_set_map.end()) {

+ 3 - 5
panda/src/vulkandisplay/vulkanGraphicsStateGuardian.h

@@ -41,6 +41,9 @@ public:
                               uint32_t queue_family_index);
   virtual ~VulkanGraphicsStateGuardian();
 
+  virtual void reset();
+
+  void destroy_device();
   virtual void close_gsg();
 
   virtual std::string get_driver_vendor();
@@ -128,8 +131,6 @@ public:
                            bool force);
   virtual void end_draw_primitives();
 
-  virtual void reset();
-
   virtual bool framebuffer_copy_to_texture(Texture *tex, int view, int z,
                                            const DisplayRegion *dr,
                                            const RenderBuffer &rb);
@@ -233,9 +234,6 @@ private:
   VulkanTextureContext *_fb_depth_tc = nullptr;
   VkSampleCountFlagBits _fb_ms_count = VK_SAMPLE_COUNT_1_BIT;
 
-  // Remembers semaphores created on this device.
-  pvector<VkSemaphore> _semaphores;
-
   // Static "null" vertex buffer if nullDescriptor is not supported.
   VkBuffer _null_vertex_buffer = VK_NULL_HANDLE;
   VulkanMemoryBlock _null_vertex_memory;

+ 54 - 21
panda/src/vulkandisplay/vulkanGraphicsWindow.cxx

@@ -95,13 +95,32 @@ begin_frame(FrameMode mode, Thread *current_thread) {
 
   VulkanGraphicsStateGuardian *vkgsg;
   DCAST_INTO_R(vkgsg, _gsg, false);
-  //vkgsg->reset_if_new();
 
-  if (_current_clear_mask != _clear_mask) {
+  if (vkgsg->needs_reset()) {
+    vkQueueWaitIdle(vkgsg->_queue);
+    if (_image_available != VK_NULL_HANDLE) {
+      vkDestroySemaphore(vkgsg->_device, _image_available, nullptr);
+      _image_available = VK_NULL_HANDLE;
+    }
+    destroy_swapchain();
+    if (_render_pass != VK_NULL_HANDLE) {
+      vkDestroyRenderPass(vkgsg->_device, _render_pass, nullptr);
+      _render_pass = VK_NULL_HANDLE;
+    }
+    vkgsg->reset_if_new();
+  }
+
+  if (!vkgsg->is_valid()) {
+    return false;
+  }
+
+  if (_current_clear_mask != _clear_mask || _render_pass == VK_NULL_HANDLE) {
     // The clear flags have changed.  Recreate the render pass.  Note that the
     // clear flags don't factor into render pass compatibility, so we don't
     // need to recreate the framebuffer.
-    setup_render_pass();
+    if (!setup_render_pass()) {
+      return false;
+    }
   }
 
   if (_swapchain_size != _size) {
@@ -312,22 +331,26 @@ begin_flip() {
   present.pResults = results;
 
   err = vkQueuePresentKHR(queue, &present);
-  if (err == VK_ERROR_OUT_OF_DATE_KHR) {
+  if (err == VK_ERROR_OUT_OF_DATE_KHR || err == VK_SUBOPTIMAL_KHR) {
     // It's out of date.  We need to recreate the swap chain.
     if (vulkandisplay_cat.is_debug()) {
       vulkandisplay_cat.debug()
-        << "Swap chain out of date for VulkanGraphicsWindow " << this << "\n";
+        << "Swap chain "
+        << ((err == VK_SUBOPTIMAL_KHR) ? "suboptimal" : "out of date")
+        << " for VulkanGraphicsWindow " << this << "\n";
     }
     _swapchain_size.set(-1, -1);
     _flip_ready = false;
     return;
   }
-  else if (err == VK_SUBOPTIMAL_KHR) {
-    std::cerr << "suboptimal.\n";
-  }
   else if (err != VK_SUCCESS) {
     vulkan_error(err, "Error presenting queue");
 
+    if (err == VK_ERROR_DEVICE_LOST) {
+      vkgsg->mark_new();
+      _flip_ready = false;
+    }
+
     if (err == VK_ERROR_FULL_SCREEN_EXCLUSIVE_MODE_LOST_EXT || err == VK_ERROR_SURFACE_LOST_KHR) {
       _flip_ready = false;
     }
@@ -363,27 +386,31 @@ end_flip() {
   DCAST_INTO_V(vkgsg, _gsg);
 
   nassertv(_image_available == VK_NULL_HANDLE);
-
-  VkSemaphoreCreateInfo semaphore_info = {};
-  semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
-  VkResult err = vkCreateSemaphore(vkgsg->_device, &semaphore_info, nullptr, &_image_available);
-  nassertv_always(err == VK_SUCCESS);
+  _image_available = vkgsg->create_semaphore();
+  nassertv(_image_available != VK_NULL_HANDLE);
 
   // Get a new image for rendering into.  This may wait until a new image is
   // available.
+  VkResult
   err = vkAcquireNextImageKHR(vkgsg->_device, _swapchain, UINT64_MAX,
                               _image_available, VK_NULL_HANDLE, &_image_index);
-
-  if (err == VK_ERROR_OUT_OF_DATE_KHR) {
+  if (err == VK_ERROR_OUT_OF_DATE_KHR || err == VK_SUBOPTIMAL_KHR) {
     // It's out of date.  We need to recreate the swap chain.
     if (vulkandisplay_cat.is_debug()) {
       vulkandisplay_cat.debug()
-        << "Swap chain out of date for VulkanGraphicsWindow " << this << "\n";
+        << "Swap chain "
+        << ((err == VK_SUBOPTIMAL_KHR) ? "suboptimal" : "out of date")
+        << " for VulkanGraphicsWindow " << this << "\n";
     }
     _swapchain_size.set(-1, -1);
     _flip_ready = false;
     return;
   }
+  if (err == VK_ERROR_DEVICE_LOST) {
+    vkgsg->mark_new();
+    _flip_ready = false;
+    return;
+  }
 
   nassertv(err == VK_SUCCESS);
 
@@ -528,6 +555,7 @@ open_window() {
     DCAST_INTO_R(vkgsg, _gsg.p(), false);
   }
 
+  vkgsg->reset_if_new();
   if (!vkgsg->is_valid()) {
     _gsg.clear();
     vulkandisplay_cat.error()
@@ -831,6 +859,7 @@ destroy_swapchain() {
     buffer._tc->_image = VK_NULL_HANDLE;
     buffer._tc->destroy_now(device);
     delete buffer._tc;
+    vkDestroySemaphore(device, buffer._render_complete, nullptr);
   }
   _swap_buffers.clear();
 
@@ -852,6 +881,7 @@ destroy_swapchain() {
     _swapchain = VK_NULL_HANDLE;
   }
 
+  _swapchain_size.set(-1, -1);
   _image_index = 0;
 }
 
@@ -938,6 +968,9 @@ create_swapchain() {
   err = vkCreateSwapchainKHR(device, &swapchain_info, nullptr, &_swapchain);
   if (err) {
     vulkan_error(err, "Failed to create swap chain");
+    if (err == VK_ERROR_DEVICE_LOST) {
+      vkgsg->mark_new();
+    }
     return false;
   }
 
@@ -1124,11 +1157,8 @@ create_swapchain() {
   }
 
   // Create a semaphore for signalling the availability of an image.
-  VkSemaphoreCreateInfo semaphore_info = {};
-  semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
-
-  err = vkCreateSemaphore(vkgsg->_device, &semaphore_info, nullptr, &_image_available);
-  nassertr_always(err == VK_SUCCESS, false);
+  _image_available = vkgsg->create_semaphore();
+  nassertr(_image_available != VK_NULL_HANDLE, false);
 
   // We need to acquire an image before we continue rendering.
   _image_index = 0;
@@ -1137,6 +1167,9 @@ create_swapchain() {
                               _image_available, VK_NULL_HANDLE, &_image_index);
   if (err) {
     vulkan_error(err, "Failed to acquire swapchain image");
+    if (err == VK_ERROR_DEVICE_LOST) {
+      vkgsg->mark_new();
+    }
     return false;
   }