Browse Source

vulkan: Fix hazards, while reducing number of barriers

rdb 1 year ago
parent
commit
76f5505f05

+ 174 - 0
panda/src/vulkandisplay/vulkanFrameData.cxx

@@ -12,6 +12,180 @@
  */
  */
 
 
 #include "vulkanFrameData.h"
 #include "vulkanFrameData.h"
+#include "vulkanTextureContext.h"
+
+/**
+ * Ensures a pipeline barrier is created for an initial transition of the given
+ * texture, after the transfer commands have completed, but before the frist
+ * write.  These are pooled together.
+ *
+ * For now, may only be called for read barriers, not write, so access_mask
+ * may not contain any write bits.
+ *
+ * The _initial_src_layout, etc. members should have already been initialized
+ * in this frame.  Any transfers on the transfer queue must have already been
+ * performed for this texture.
+ *
+ * Returns false if this is actually not possible, due to a layout mismatch,
+ * in which case there can't be a pooled transition.
+ */
+bool VulkanFrameData::
+add_initial_barrier(VulkanTextureContext *tc, VkImageLayout layout,
+                    VkPipelineStageFlags stage_mask,
+                    VkAccessFlags access_mask) {
+  if (layout == tc->_layout && tc->_write_stage_mask == 0) {
+    // No write to sync with.
+    return true;
+  }
+
+  // These reads have already been synced.
+  if (layout == tc->_layout) {
+    stage_mask &= ~tc->_read_stage_mask;
+    if (stage_mask == 0) {
+      return true;
+    }
+  }
+
+  if (tc->_initial_dst_layout == VK_IMAGE_LAYOUT_UNDEFINED) {
+    // These are derived from the current stage of the texture, after any
+    // transfer commands but before any writes have been done.
+    tc->_initial_src_layout = tc->_layout;
+    tc->_initial_src_access_mask |= tc->_write_access_mask;
+    _initial_barrier_src_stage_mask |= tc->_write_stage_mask;
+
+    if (layout != tc->_layout) {
+      // If we change layout, wait for all reads to complete too.
+      _initial_barrier_src_stage_mask |= tc->_read_stage_mask;
+    }
+
+    // And this is what we are transitioning to.
+    tc->_initial_dst_layout = layout;
+    tc->_layout = layout;
+
+    _initial_barrier_textures.push_back(tc);
+    _initial_barrier_image_count += (tc->_image != VK_NULL_HANDLE);
+    _initial_barrier_buffer_count += (tc->_buffer != VK_NULL_HANDLE);
+  }
+  else if (tc->_initial_dst_layout != layout) {
+    return false;
+  }
+
+  tc->mark_read(stage_mask);
+  tc->_initial_dst_access_mask |= access_mask;
+  _initial_barrier_dst_stage_mask |= stage_mask;
+  return true;
+}
+
+/**
+ *
+ */
+bool VulkanFrameData::
+begin_transfer_cmd() {
+  static const VkCommandBufferBeginInfo begin_info = {
+    VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+    nullptr,
+    VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
+    nullptr,
+  };
+
+  VkResult err;
+  err = vkBeginCommandBuffer(_transfer_cmd, &begin_info);
+  if (err != VK_SUCCESS) {
+    vulkan_error(err, "Can't begin transfer command buffer");
+    return false;
+  }
+  return true;
+}
+
+/**
+ * Issues a pipeline barrier to all the initial transitions, and closes the
+ * transfer command buffer.
+ */
+void VulkanFrameData::
+end_transfer_cmd() {
+  nassertv(_transfer_cmd != VK_NULL_HANDLE);
+
+  if (_initial_barrier_dst_stage_mask != 0) {
+    VkImageMemoryBarrier *image_barriers = (VkImageMemoryBarrier *)alloca(sizeof(VkImageMemoryBarrier) * _initial_barrier_image_count);
+    VkBufferMemoryBarrier *buffer_barriers = (VkBufferMemoryBarrier *)alloca(sizeof(VkBufferMemoryBarrier) * _initial_barrier_buffer_count);
+
+    uint32_t ii = 0;
+    uint32_t bi = 0;
+    for (VulkanTextureContext *tc : _initial_barrier_textures) {
+      if (tc->_image != VK_NULL_HANDLE) {
+        VkImageMemoryBarrier &barrier = image_barriers[ii++];
+        barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
+        barrier.pNext = nullptr;
+        barrier.srcAccessMask = tc->_initial_src_access_mask;
+        barrier.dstAccessMask = tc->_initial_dst_access_mask;
+        barrier.oldLayout = tc->_initial_src_layout;
+        barrier.newLayout = tc->_initial_dst_layout;
+        barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+        barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+        barrier.image = tc->_image;
+        barrier.subresourceRange.aspectMask = tc->_aspect_mask;
+        barrier.subresourceRange.baseMipLevel = 0;
+        barrier.subresourceRange.levelCount = tc->_mip_levels;
+        barrier.subresourceRange.baseArrayLayer = 0;
+        barrier.subresourceRange.layerCount = tc->_array_layers;
+      }
+
+      if (tc->_buffer != VK_NULL_HANDLE) {
+        VkBufferMemoryBarrier &barrier = buffer_barriers[bi++];
+        barrier.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER;
+        barrier.pNext = nullptr;
+        barrier.srcAccessMask = tc->_initial_src_access_mask;
+        barrier.dstAccessMask = tc->_initial_dst_access_mask;
+        barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+        barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
+        barrier.buffer = tc->_buffer;
+        barrier.offset = 0;
+        barrier.size = VK_WHOLE_SIZE;
+      }
+    }
+    vkCmdPipelineBarrier(_transfer_cmd, _initial_barrier_src_stage_mask,
+                         _initial_barrier_dst_stage_mask, 0,
+                         0, nullptr, bi, buffer_barriers, ii, image_barriers);
+
+    _initial_barrier_textures.clear();
+    _initial_barrier_src_stage_mask = 0;
+    _initial_barrier_dst_stage_mask = 0;
+    _initial_barrier_image_count = 0;
+    _initial_barrier_buffer_count = 0;
+  }
+
+  vkEndCommandBuffer(_transfer_cmd);
+}
+
+/**
+ *
+ */
+bool VulkanFrameData::
+begin_render_cmd() {
+  static const VkCommandBufferBeginInfo begin_info = {
+    VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+    nullptr,
+    VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
+    nullptr,
+  };
+
+  VkResult err;
+  err = vkBeginCommandBuffer(_cmd, &begin_info);
+  if (err != VK_SUCCESS) {
+    vulkan_error(err, "Can't begin render command buffer");
+    return false;
+  }
+  return true;
+}
+
+/**
+ *
+ */
+void VulkanFrameData::
+end_render_cmd() {
+  nassertv(_cmd != VK_NULL_HANDLE);
+  vkEndCommandBuffer(_cmd);
+}
 
 
 /**
 /**
  *
  *

+ 26 - 0
panda/src/vulkandisplay/vulkanFrameData.h

@@ -18,12 +18,30 @@
 #include "vulkanMemoryPage.h"
 #include "vulkanMemoryPage.h"
 #include "screenshotRequest.h"
 #include "screenshotRequest.h"
 
 
+class VulkanTextureContext;
+
 /**
 /**
  * Stores all the data that has been collected between a begin_frame/end_frame
  * Stores all the data that has been collected between a begin_frame/end_frame
  * pair, until the frame has finished rendering on the GPU.
  * pair, until the frame has finished rendering on the GPU.
+ *
+ * At the moment, the frame is divided up into two command buffers, one
+ * collecting all the actions needed to prepare and upload the texture data
+ * (_transfer_cmd) and one containing the actual rendering (_cmd).  At the end
+ * of the transfer cmd we issue a barrier for preparing all the resources for
+ * their first use.  Both command buffers are submitted in gsg->end_frame().
  */
  */
 class VulkanFrameData {
 class VulkanFrameData {
 public:
 public:
+  bool add_initial_barrier(VulkanTextureContext *tc, VkImageLayout layout,
+                           VkPipelineStageFlags stage_mask,
+                           VkAccessFlags access_mask = 0);
+
+  bool begin_transfer_cmd();
+  void end_transfer_cmd();
+
+  bool begin_render_cmd();
+  void end_render_cmd();
+
   void finish_downloads(VkDevice device);
   void finish_downloads(VkDevice device);
 
 
 public:
 public:
@@ -36,6 +54,14 @@ public:
   // frame is allowed to start rendering (the image is available).
   // frame is allowed to start rendering (the image is available).
   VkSemaphore _wait_semaphore = VK_NULL_HANDLE;
   VkSemaphore _wait_semaphore = VK_NULL_HANDLE;
 
 
+  // Barriers that are aggregated for the beginning of the frame, put at the
+  // end of the transfer command buffer.
+  pvector<VulkanTextureContext *> _initial_barrier_textures;
+  VkPipelineStageFlags _initial_barrier_src_stage_mask = 0;
+  VkPipelineStageFlags _initial_barrier_dst_stage_mask = 0;
+  size_t _initial_barrier_image_count = 0;
+  size_t _initial_barrier_buffer_count = 0;
+
   // Keep track of resources that should be deleted after this frame is done.
   // Keep track of resources that should be deleted after this frame is done.
   pvector<VulkanMemoryBlock> _pending_free;
   pvector<VulkanMemoryBlock> _pending_free;
   pvector<VkBuffer> _pending_destroy_buffers;
   pvector<VkBuffer> _pending_destroy_buffers;

+ 36 - 8
panda/src/vulkandisplay/vulkanGraphicsBuffer.cxx

@@ -140,7 +140,8 @@ begin_frame(FrameMode mode, Thread *current_thread) {
 
 
   // Now that we have a command buffer, start our render pass.  First
   // Now that we have a command buffer, start our render pass.  First
   // transition the swapchain images into the valid state for rendering into.
   // transition the swapchain images into the valid state for rendering into.
-  VkCommandBuffer cmd = vkgsg->_frame_data->_cmd;
+  VulkanFrameData &frame_data = vkgsg->get_frame_data();
+  VkCommandBuffer cmd = frame_data._cmd;
 
 
   VkClearValue *clears = (VkClearValue *)
   VkClearValue *clears = (VkClearValue *)
     alloca(sizeof(VkClearValue) * _attachments.size());
     alloca(sizeof(VkClearValue) * _attachments.size());
@@ -159,7 +160,8 @@ begin_frame(FrameMode mode, Thread *current_thread) {
 
 
   for (size_t i = 0; i < _attachments.size(); ++i) {
   for (size_t i = 0; i < _attachments.size(); ++i) {
     Attachment &attach = _attachments[i];
     Attachment &attach = _attachments[i];
-    attach._tc->set_active(true);
+    nassertr(!attach._tc->is_used_this_frame(frame_data), false);
+    attach._tc->mark_used_this_frame(frame_data);
 
 
     VkImageLayout layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
     VkImageLayout layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
     VkAccessFlags write_access_mask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
     VkAccessFlags write_access_mask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
@@ -173,7 +175,7 @@ begin_frame(FrameMode mode, Thread *current_thread) {
       stage_mask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT
       stage_mask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT
                  | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
                  | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
       write_access_mask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
       write_access_mask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
-      read_access_mask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+      read_access_mask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT;
     }
     }
     else if (attach._plane == RTP_color) {
     else if (attach._plane == RTP_color) {
       vkgsg->_fb_color_tc = attach._tc;
       vkgsg->_fb_color_tc = attach._tc;
@@ -191,9 +193,12 @@ begin_frame(FrameMode mode, Thread *current_thread) {
       attach._tc->_read_stage_mask = stage_mask;
       attach._tc->_read_stage_mask = stage_mask;
       attach._tc->_write_stage_mask = stage_mask;
       attach._tc->_write_stage_mask = stage_mask;
       attach._tc->_write_access_mask = write_access_mask;
       attach._tc->_write_access_mask = write_access_mask;
-    } else {
-      attach._tc->transition(cmd, vkgsg->_graphics_queue_family_index,
-                             layout, stage_mask, read_access_mask | write_access_mask);
+    }
+    else if (attach._tc->_layout != layout ||
+             (attach._tc->_write_stage_mask & ~stage_mask) != 0 ||
+             (attach._tc->_read_stage_mask & ~stage_mask) != 0) {
+      frame_data.add_initial_barrier(attach._tc,
+        layout, stage_mask, read_access_mask | write_access_mask);
     }
     }
   }
   }
 
 
@@ -449,6 +454,15 @@ setup_render_pass() {
   VkAttachmentReference depth_reference;
   VkAttachmentReference depth_reference;
   depth_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
   depth_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
 
 
+  VkSubpassDependency dependency;
+  dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
+  dependency.dstSubpass = 0;
+  dependency.srcStageMask = 0;
+  dependency.dstStageMask = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT;
+  dependency.srcAccessMask = 0;
+  dependency.dstAccessMask = 0;
+  dependency.dependencyFlags = 0;
+
   if (_color_format != VK_FORMAT_UNDEFINED) {
   if (_color_format != VK_FORMAT_UNDEFINED) {
     VkAttachmentDescription &attach = attachments[ai];
     VkAttachmentDescription &attach = attachments[ai];
     attach.flags = 0;
     attach.flags = 0;
@@ -473,6 +487,11 @@ setup_render_pass() {
       attach.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
       attach.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
     }
     }
 
 
+    dependency.srcStageMask |= VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+    dependency.srcAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+    dependency.dstAccessMask |= VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
+                                VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+
     color_reference.attachment = ai++;
     color_reference.attachment = ai++;
   }
   }
 
 
@@ -505,9 +524,18 @@ setup_render_pass() {
       attach.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
       attach.stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE;
     }
     }
 
 
+    dependency.srcStageMask |= VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
+    dependency.dstStageMask |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
+    dependency.srcAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+    dependency.dstAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT;
+
     depth_reference.attachment = ai++;
     depth_reference.attachment = ai++;
   }
   }
 
 
+  if (dependency.srcStageMask == 0) {
+    dependency.srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
+  }
+
   VkSubpassDescription subpass;
   VkSubpassDescription subpass;
   subpass.flags = 0;
   subpass.flags = 0;
   subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
   subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
@@ -528,8 +556,8 @@ setup_render_pass() {
   pass_info.pAttachments = attachments;
   pass_info.pAttachments = attachments;
   pass_info.subpassCount = 1;
   pass_info.subpassCount = 1;
   pass_info.pSubpasses = &subpass;
   pass_info.pSubpasses = &subpass;
-  pass_info.dependencyCount = 0;
-  pass_info.pDependencies = nullptr;
+  pass_info.dependencyCount = 1;
+  pass_info.pDependencies = &dependency;
 
 
   VkRenderPass pass;
   VkRenderPass pass;
   VkResult
   VkResult

+ 10 - 0
panda/src/vulkandisplay/vulkanGraphicsStateGuardian.I

@@ -10,3 +10,13 @@
  * @author rdb
  * @author rdb
  * @date 2016-02-16
  * @date 2016-02-16
  */
  */
+
+/**
+ * Returns the current frame data object.
+ * May only be called between begin_frame() and end_frame().
+ */
+INLINE VulkanFrameData &VulkanGraphicsStateGuardian::
+get_frame_data() {
+  nassertr(_frame_data != nullptr, *_last_frame_data);
+  return *_frame_data;
+}

+ 93 - 75
panda/src/vulkandisplay/vulkanGraphicsStateGuardian.cxx

@@ -916,6 +916,76 @@ allocate_memory(VulkanMemoryBlock &block, const VkMemoryRequirements &reqs,
   return true;
   return true;
 }
 }
 
 
+/**
+ * Prepares the texture for the given usage of the texture, performing any
+ * updates as necessary.  If discard is true, the existing contents are
+ * disregarded, and any pending upload is discarded.
+ */
+VulkanTextureContext *VulkanGraphicsStateGuardian::
+use_texture(Texture *texture, VkImageLayout layout,
+            VkPipelineStageFlags stage_mask, VkAccessFlags access_mask,
+            bool discard) {
+  VulkanFrameData &frame_data = get_frame_data();
+
+  VulkanTextureContext *tc;
+  DCAST_INTO_R(tc, texture->prepare_now(_prepared_objects, this), nullptr);
+
+  // We only update the texture the first time it is used in a frame.
+  // Otherwise, we would have to invalidate the descriptor sets.
+  if (!tc->is_used_this_frame(frame_data)) {
+    if (tc->was_modified()) {
+      if (tc->needs_recreation()) {
+        tc->release(frame_data);
+        if (!create_texture(tc)) {
+          return nullptr;
+        }
+      }
+
+      // If discard is true, we are about to replace the texture contents, so we
+      // just skip the upload step and mark it as loaded anyway.
+      if (!discard && !upload_texture(tc)) {
+        return nullptr;
+      }
+
+      tc->mark_loaded();
+    }
+
+    tc->mark_used_this_frame(frame_data);
+    tc->enqueue_lru(&_prepared_objects->_graphics_memory_lru);
+  }
+
+  if (discard) {
+    // Flags it as not caring if the current contents get stomped over.
+    tc->discard();
+  }
+
+  bool is_write = 0 != (access_mask & (
+    VK_ACCESS_SHADER_WRITE_BIT |
+    VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT |
+    VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT |
+    VK_ACCESS_TRANSFER_WRITE_BIT |
+    VK_ACCESS_HOST_WRITE_BIT |
+    VK_ACCESS_MEMORY_WRITE_BIT));
+
+  // We pool together all reads before the first write in a frame into a single
+  // barrier, issued at the end of the transfer command buffer (so before all
+  // of the rendering).
+  if (tc->_last_write_frame != frame_data._frame_index) {
+    if (is_write) {
+      tc->_last_write_frame = frame_data._frame_index;
+    }
+    else if (frame_data.add_initial_barrier(tc, layout, stage_mask, access_mask)) {
+      return tc;
+    }
+  }
+
+  // We've already written to the texture from the GPU this frame, so we must
+  // issue a barrier in the middle of the command stream.  At the moment, we
+  // make no attempt to combine with other barriers, this may be suboptimal.
+  tc->transition(frame_data._cmd, _graphics_queue_family_index, layout, stage_mask, access_mask);
+  return tc;
+}
+
 /**
 /**
  * Creates whatever structures the GSG requires to represent the texture
  * Creates whatever structures the GSG requires to represent the texture
  * internally, and returns a newly-allocated TextureContext object with this
  * internally, and returns a newly-allocated TextureContext object with this
@@ -1389,6 +1459,12 @@ bool VulkanGraphicsStateGuardian::
 upload_texture(VulkanTextureContext *tc) {
 upload_texture(VulkanTextureContext *tc) {
   nassertr(_frame_data != nullptr, false);
   nassertr(_frame_data != nullptr, false);
 
 
+  // Textures can only be updated before the first time they are used in a
+  // frame.  This prevents out-of-order calls to transition(), which would
+  // otherwise generate invalid barriers, and also prevents invalid descriptor
+  // sets (which are also only updated the first time in a frame).
+  nassertr(!tc->is_used_this_frame(*_frame_data), false);
+
   Texture *texture = tc->get_texture();
   Texture *texture = tc->get_texture();
   VkImage image = tc->_image;
   VkImage image = tc->_image;
 
 
@@ -1711,35 +1787,7 @@ update_texture(TextureContext *tc, bool force) {
   DCAST_INTO_R(vtc, tc, false);
   DCAST_INTO_R(vtc, tc, false);
 
 
   if (vtc->was_modified()) {
   if (vtc->was_modified()) {
-    Texture *tex = tc->get_texture();
-    int num_views = tex->get_num_views();
-
-    VkExtent3D extent;
-    extent.width = tex->get_x_size();
-    extent.height = tex->get_y_size();
-    uint32_t arrayLayers;
-
-    if (tex->get_texture_type() == Texture::TT_3d_texture) {
-      extent.depth = tex->get_z_size();
-      arrayLayers = 1;
-    } else if (tex->get_texture_type() == Texture::TT_1d_texture_array) {
-      extent.height = 1;
-      extent.depth = 1;
-      arrayLayers = tex->get_y_size();
-    } else {
-      extent.depth = 1;
-      arrayLayers = tex->get_z_size();
-    }
-    arrayLayers *= num_views;
-
-    //VkFormat format = get_image_format(tex);
-
-    if (//format != vtc->_format ||
-        extent.width != vtc->_extent.width ||
-        extent.height != vtc->_extent.height ||
-        extent.depth != vtc->_extent.depth ||
-        arrayLayers != vtc->_array_layers ||
-        (size_t)num_views != vtc->_image_views.size()) {
+    if (vtc->needs_recreation()) {
       // We need to recreate the image entirely.
       // We need to recreate the image entirely.
       vtc->release(*_frame_data);
       vtc->release(*_frame_data);
       if (!create_texture(vtc)) {
       if (!create_texture(vtc)) {
@@ -2740,16 +2788,7 @@ begin_frame(Thread *current_thread) {
   _frame_data = &_frame_data_pool[_frame_data_head % _frame_data_capacity];
   _frame_data = &_frame_data_pool[_frame_data_head % _frame_data_capacity];
 
 
   // Begin the transfer command buffer, for preparing resources.
   // Begin the transfer command buffer, for preparing resources.
-  VkCommandBufferBeginInfo begin_info;
-  begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
-  begin_info.pNext = nullptr;
-  begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
-  begin_info.pInheritanceInfo = nullptr;
-
-  VkResult err;
-  err = vkBeginCommandBuffer(_frame_data->_transfer_cmd, &begin_info);
-  if (err) {
-    vulkan_error(err, "Can't begin transfer command buffer");
+  if (!_frame_data->begin_transfer_cmd()) {
     _frame_data = nullptr;
     _frame_data = nullptr;
     return false;
     return false;
   }
   }
@@ -2785,20 +2824,18 @@ begin_frame(Thread *current_thread) {
   // transfer command buffer, which is why we've begun it already.
   // transfer command buffer, which is why we've begun it already.
   if (GraphicsStateGuardian::begin_frame(current_thread)) {
   if (GraphicsStateGuardian::begin_frame(current_thread)) {
     // Now begin the main (ie. graphics) command buffer.
     // Now begin the main (ie. graphics) command buffer.
-    err = vkBeginCommandBuffer(_frame_data->_cmd, &begin_info);
-    if (!err) {
+    if (_frame_data->begin_render_cmd()) {
       // Bind the "null" vertex buffer.
       // Bind the "null" vertex buffer.
       const VkDeviceSize offset = 0;
       const VkDeviceSize offset = 0;
       _vkCmdBindVertexBuffers(_frame_data->_cmd, 0, 1, &_null_vertex_buffer, &offset);
       _vkCmdBindVertexBuffers(_frame_data->_cmd, 0, 1, &_null_vertex_buffer, &offset);
       return true;
       return true;
     }
     }
-    vulkan_error(err, "Can't begin command buffer");
   }
   }
 
 
   // We've already started putting stuff in the transfer command buffer, so now
   // We've already started putting stuff in the transfer command buffer, so now
   // we are obliged to submit it, even if we won't actually end up rendering
   // we are obliged to submit it, even if we won't actually end up rendering
   // anything in this frame.
   // anything in this frame.
-  vkEndCommandBuffer(_frame_data->_transfer_cmd);
+  _frame_data->end_transfer_cmd();
 
 
   VkSubmitInfo submit_info;
   VkSubmitInfo submit_info;
   submit_info.pNext = nullptr;
   submit_info.pNext = nullptr;
@@ -2811,7 +2848,7 @@ begin_frame(Thread *current_thread) {
   submit_info.signalSemaphoreCount = 0;
   submit_info.signalSemaphoreCount = 0;
   submit_info.pSignalSemaphores = nullptr;
   submit_info.pSignalSemaphores = nullptr;
 
 
-  err = vkQueueSubmit(_queue, 1, &submit_info, _frame_data->_fence);
+  VkResult err = vkQueueSubmit(_queue, 1, &submit_info, _frame_data->_fence);
   if (err) {
   if (err) {
     vulkan_error(err, "Error submitting queue");
     vulkan_error(err, "Error submitting queue");
     if (err == VK_ERROR_DEVICE_LOST) {
     if (err == VK_ERROR_DEVICE_LOST) {
@@ -2867,8 +2904,7 @@ void VulkanGraphicsStateGuardian::
 end_frame(Thread *current_thread, VkSemaphore wait_for, VkSemaphore signal_done) {
 end_frame(Thread *current_thread, VkSemaphore wait_for, VkSemaphore signal_done) {
   GraphicsStateGuardian::end_frame(current_thread);
   GraphicsStateGuardian::end_frame(current_thread);
 
 
-  nassertv(_frame_data->_transfer_cmd != VK_NULL_HANDLE);
-  vkEndCommandBuffer(_frame_data->_transfer_cmd);
+  _frame_data->end_transfer_cmd();
 
 
   // Note down the current watermark of the ring buffers.
   // Note down the current watermark of the ring buffers.
   _frame_data->_uniform_buffer_head = _uniform_buffer_allocator.get_head();
   _frame_data->_uniform_buffer_head = _uniform_buffer_allocator.get_head();
@@ -2910,8 +2946,7 @@ end_frame(Thread *current_thread, VkSemaphore wait_for, VkSemaphore signal_done)
                          0, nullptr, (uint32_t)num_downloads, barriers, 0, nullptr);
                          0, nullptr, (uint32_t)num_downloads, barriers, 0, nullptr);
   }
   }
 
 
-  nassertv(_frame_data->_cmd != VK_NULL_HANDLE);
-  vkEndCommandBuffer(_frame_data->_cmd);
+  _frame_data->end_render_cmd();
 
 
   VkCommandBuffer cmdbufs[] = {_frame_data->_transfer_cmd, _frame_data->_cmd};
   VkCommandBuffer cmdbufs[] = {_frame_data->_transfer_cmd, _frame_data->_cmd};
 
 
@@ -2929,7 +2964,8 @@ end_frame(Thread *current_thread, VkSemaphore wait_for, VkSemaphore signal_done)
 
 
   if (wait_for != VK_NULL_HANDLE) {
   if (wait_for != VK_NULL_HANDLE) {
     // We may need to wait until the attachments are available for writing.
     // We may need to wait until the attachments are available for writing.
-    static const VkPipelineStageFlags flags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+    // TOP_OF_PIPE placates the validation layer, not sure why it's needed.
+    static const VkPipelineStageFlags flags = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
     submit_info.waitSemaphoreCount = 1;
     submit_info.waitSemaphoreCount = 1;
     submit_info.pWaitSemaphores = &wait_for;
     submit_info.pWaitSemaphores = &wait_for;
     submit_info.pWaitDstStageMask = &flags;
     submit_info.pWaitDstStageMask = &flags;
@@ -3325,17 +3361,11 @@ framebuffer_copy_to_texture(Texture *tex, int view, int z,
     }
     }
   }
   }
 
 
-  PreparedGraphicsObjects *pgo = get_prepared_objects();
-
   VulkanTextureContext *tc;
   VulkanTextureContext *tc;
-  DCAST_INTO_R(tc, tex->prepare_now(pgo, this), false);
-
-  // Temporary, prepare_now should really deal with the resizing
-  if (tc->_extent.width != (uint32_t)tex->get_x_size() ||
-      tc->_extent.height != (uint32_t)tex->get_y_size()) {
-    pgo->release_texture(tc);
-    DCAST_INTO_R(tc, tex->prepare_now(pgo, this), false);
-  }
+  tc = use_texture(tex, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+                   VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT,
+                   true);
+  nassertr(tc != nullptr, false);
 
 
   nassertr(fbtc->_extent.width <= tc->_extent.width &&
   nassertr(fbtc->_extent.width <= tc->_extent.width &&
            fbtc->_extent.height <= tc->_extent.height &&
            fbtc->_extent.height <= tc->_extent.height &&
@@ -3350,10 +3380,6 @@ framebuffer_copy_to_texture(Texture *tex, int view, int z,
     VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
     VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
     VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_READ_BIT);
     VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_READ_BIT);
 
 
-  tc->transition(_frame_data->_cmd, _graphics_queue_family_index,
-    VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
-    VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT);
-
   if (fbtc->_format == tc->_format) {
   if (fbtc->_format == tc->_format) {
     // The formats are the same.  This is just an image copy.
     // The formats are the same.  This is just an image copy.
     VkImageCopy region;
     VkImageCopy region;
@@ -4344,15 +4370,6 @@ update_lattr_descriptor_set(VkDescriptorSet ds, const LightAttrib *attr) {
       texture = dummy;
       texture = dummy;
     }
     }
 
 
-    VulkanTextureContext *tc;
-    DCAST_INTO_R(tc, texture->prepare_now(0, _prepared_objects, this), false);
-
-    tc->set_active(true);
-    update_texture(tc, true);
-
-    // Transition the texture so that it can be read by the shader.  This has
-    // to happen on the transfer command buffer, since it can't happen during
-    // an active render pass.
     // We don't know at this point which stages is using them, and finding out
     // We don't know at this point which stages is using them, and finding out
     // would require duplication of descriptor sets, so we flag all stages.
     // would require duplication of descriptor sets, so we flag all stages.
     VkPipelineStageFlags stage_flags = 0
     VkPipelineStageFlags stage_flags = 0
@@ -4360,6 +4377,7 @@ update_lattr_descriptor_set(VkDescriptorSet ds, const LightAttrib *attr) {
       | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
       | VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
       | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT
       | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT
       ;
       ;
+
     if (_supported_shader_caps & ShaderModule::C_tessellation_shader) {
     if (_supported_shader_caps & ShaderModule::C_tessellation_shader) {
       //stage_flags |= VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT;
       //stage_flags |= VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT;
       //stage_flags |= VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT;
       //stage_flags |= VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT;
@@ -4368,9 +4386,9 @@ update_lattr_descriptor_set(VkDescriptorSet ds, const LightAttrib *attr) {
       //stage_flags |= VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT;
       //stage_flags |= VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT;
     }
     }
 
 
-    tc->transition(_frame_data->_transfer_cmd, _graphics_queue_family_index,
-                   VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
-                   stage_flags, VK_ACCESS_SHADER_READ_BIT);
+    VulkanTextureContext *tc;
+    tc = use_texture(texture, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+                     stage_flags, VK_ACCESS_SHADER_READ_BIT);
 
 
     VkDescriptorImageInfo &image_info = image_infos[i];
     VkDescriptorImageInfo &image_info = image_infos[i];
     image_info.sampler = VK_NULL_HANDLE;
     image_info.sampler = VK_NULL_HANDLE;

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

@@ -53,6 +53,10 @@ public:
   bool allocate_memory(VulkanMemoryBlock &block, const VkMemoryRequirements &reqs,
   bool allocate_memory(VulkanMemoryBlock &block, const VkMemoryRequirements &reqs,
                        VkFlags required_flags, bool linear);
                        VkFlags required_flags, bool linear);
 
 
+  VulkanTextureContext *use_texture(Texture *texture, VkImageLayout layout,
+                                    VkPipelineStageFlags stage_mask,
+                                    VkAccessFlags access_mask,
+                                    bool discard=false);
   virtual TextureContext *prepare_texture(Texture *tex);
   virtual TextureContext *prepare_texture(Texture *tex);
   bool create_texture(VulkanTextureContext *vtc);
   bool create_texture(VulkanTextureContext *vtc);
   bool upload_texture(VulkanTextureContext *vtc);
   bool upload_texture(VulkanTextureContext *vtc);
@@ -103,6 +107,7 @@ public:
   virtual void end_frame(Thread *current_thread);
   virtual void end_frame(Thread *current_thread);
   void end_frame(Thread *current_thread, VkSemaphore wait_for, VkSemaphore signal_done);
   void end_frame(Thread *current_thread, VkSemaphore wait_for, VkSemaphore signal_done);
   void finish_frame(FrameData &frame_data);
   void finish_frame(FrameData &frame_data);
+  INLINE FrameData &get_frame_data();
 
 
   virtual bool begin_draw_primitives(const GeomPipelineReader *geom_reader,
   virtual bool begin_draw_primitives(const GeomPipelineReader *geom_reader,
                                      const GeomVertexDataPipelineReader *data_reader,
                                      const GeomVertexDataPipelineReader *data_reader,

+ 64 - 28
panda/src/vulkandisplay/vulkanGraphicsWindow.cxx

@@ -150,13 +150,17 @@ begin_frame(FrameMode mode, Thread *current_thread) {
   nassertr(_image_index < _swap_buffers.size(), false);
   nassertr(_image_index < _swap_buffers.size(), false);
   SwapBuffer &buffer = _swap_buffers[_image_index];
   SwapBuffer &buffer = _swap_buffers[_image_index];
 
 
+  VulkanFrameData &frame_data = vkgsg->get_frame_data();
+
   VulkanTextureContext *color_tc;
   VulkanTextureContext *color_tc;
-  buffer._tc->set_active(true);
+  nassertr(!buffer._tc->is_used_this_frame(frame_data), false);
+  buffer._tc->mark_used_this_frame(frame_data);
 
 
   // If we have multisamples, we render to a different image, which we then
   // If we have multisamples, we render to a different image, which we then
   // resolve into the swap chain image.
   // resolve into the swap chain image.
   if (_ms_color_tc != nullptr) {
   if (_ms_color_tc != nullptr) {
-    _ms_color_tc->set_active(true);
+    nassertr(!_ms_color_tc->is_used_this_frame(frame_data), false);
+    _ms_color_tc->mark_used_this_frame(frame_data);
     color_tc = _ms_color_tc;
     color_tc = _ms_color_tc;
   } else {
   } else {
     color_tc = buffer._tc;
     color_tc = buffer._tc;
@@ -168,7 +172,7 @@ begin_frame(FrameMode mode, Thread *current_thread) {
 
 
   // Now that we have a command buffer, start our render pass.  First
   // Now that we have a command buffer, start our render pass.  First
   // transition the swapchain images into the valid state for rendering into.
   // transition the swapchain images into the valid state for rendering into.
-  VkCommandBuffer cmd = vkgsg->_frame_data->_cmd;
+  VkCommandBuffer cmd = frame_data._cmd;
 
 
   VkClearValue clears[2];
   VkClearValue clears[2];
 
 
@@ -198,13 +202,17 @@ begin_frame(FrameMode mode, Thread *current_thread) {
       //color_tc->clear_color_image(cmd, clears[0].color);
       //color_tc->clear_color_image(cmd, clears[0].color);
     }
     }
 
 
-    color_tc->transition(cmd, vkgsg->_graphics_queue_family_index,
-                         VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
-                         VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
-                         VK_ACCESS_COLOR_ATTACHMENT_READ_BIT |
-                         VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT);
-  } else {
-    // This transition will be made when the first subpass is started.
+    if (color_tc->_layout != VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL ||
+        (color_tc->_write_stage_mask & ~VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT) != 0 ||
+        (color_tc->_read_stage_mask & ~VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT) != 0) {
+      frame_data.add_initial_barrier(color_tc,
+        VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
+        VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+        VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT);
+    }
+  }
+  else {
+    // This transition will be made when the render pass ends.
     color_tc->_layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
     color_tc->_layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
     color_tc->_read_stage_mask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
     color_tc->_read_stage_mask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
     color_tc->_write_stage_mask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
     color_tc->_write_stage_mask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
@@ -219,22 +227,26 @@ begin_frame(FrameMode mode, Thread *current_thread) {
   }
   }
 
 
   if (_depth_stencil_tc != nullptr) {
   if (_depth_stencil_tc != nullptr) {
-    _depth_stencil_tc->set_active(true);
+    nassertr(!_depth_stencil_tc->is_used_this_frame(frame_data), false);
+    _depth_stencil_tc->mark_used_this_frame(frame_data);
 
 
     // Transition the depth-stencil image to a consistent state.
     // Transition the depth-stencil image to a consistent state.
     if (!get_clear_depth_active() || !get_clear_stencil_active()) {
     if (!get_clear_depth_active() || !get_clear_stencil_active()) {
-      _depth_stencil_tc->transition(cmd, vkgsg->_graphics_queue_family_index,
-        VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
-        VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
-        VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT);
-    } else {
+      if (_depth_stencil_tc->_layout != VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL ||
+          (_depth_stencil_tc->_write_stage_mask & ~VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT) != 0 ||
+          (_depth_stencil_tc->_read_stage_mask & ~VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT) != 0) {
+        frame_data.add_initial_barrier(_depth_stencil_tc,
+          VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
+          VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT,
+          VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT);
+      }
+    }
+    else {
       // This transition will be made when the first subpass is started.
       // This transition will be made when the first subpass is started.
       _depth_stencil_tc->_layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
       _depth_stencil_tc->_layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
       _depth_stencil_tc->_write_access_mask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
       _depth_stencil_tc->_write_access_mask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
-      _depth_stencil_tc->_write_stage_mask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
-                                             VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
-      _depth_stencil_tc->_read_stage_mask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT |
-                                            VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
+      _depth_stencil_tc->_write_stage_mask = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
+      _depth_stencil_tc->_read_stage_mask = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
     }
     }
 
 
     if (get_clear_depth_active() || get_clear_stencil_active()) {
     if (get_clear_depth_active() || get_clear_stencil_active()) {
@@ -278,17 +290,26 @@ end_frame(FrameMode mode, Thread *current_thread) {
     buffer._tc->mark_written(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
     buffer._tc->mark_written(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
                              VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT);
                              VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT);
 
 
+    if (_depth_stencil_tc != nullptr) {
+      _depth_stencil_tc->_layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+      _depth_stencil_tc->mark_written(
+        VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
+        VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT);
+    }
+
     // Now we can do copy-to-texture, now that the render pass has ended.
     // Now we can do copy-to-texture, now that the render pass has ended.
     copy_to_textures();
     copy_to_textures();
 
 
     signal_done = buffer._render_complete;
     signal_done = buffer._render_complete;
-  }
 
 
-  // If we copied the textures, transition it back to the present state.
-  buffer._tc->transition(cmd, vkgsg->_graphics_queue_family_index,
-                         VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
-                         VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
-                         VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT);
+    // If we copied the textures, transition it back to the present state.
+    if (buffer._tc->_layout != VK_IMAGE_LAYOUT_PRESENT_SRC_KHR) {
+      buffer._tc->transition(cmd, vkgsg->_graphics_queue_family_index,
+                             VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+                             VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+                             VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT);
+    }
+  }
 
 
   // Note: this will close the command buffer, and unsignal the previous
   // Note: this will close the command buffer, and unsignal the previous
   // frame's semaphore.
   // frame's semaphore.
@@ -760,6 +781,16 @@ setup_render_pass() {
   color_reference.attachment = 0;
   color_reference.attachment = 0;
   color_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
   color_reference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
 
 
+  VkSubpassDependency dependency;
+  dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
+  dependency.dstSubpass = 0;
+  dependency.srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
+  dependency.dstStageMask = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT;
+  dependency.srcAccessMask = 0;
+  dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT
+                           | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
+  dependency.dependencyFlags = 0;
+
   size_t i = 1;
   size_t i = 1;
   if (_depth_stencil_format) {
   if (_depth_stencil_format) {
     attachments[i].flags = 0;
     attachments[i].flags = 0;
@@ -789,6 +820,11 @@ setup_render_pass() {
     depth_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
     depth_reference.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
     subpass.pDepthStencilAttachment = &depth_reference;
     subpass.pDepthStencilAttachment = &depth_reference;
     ++i;
     ++i;
+
+    dependency.srcStageMask = VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
+    dependency.dstStageMask |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT;
+    dependency.srcAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
+    dependency.dstAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT;
   }
   }
 
 
   // Also create an attachment reference for the resolve target.
   // Also create an attachment reference for the resolve target.
@@ -817,8 +853,8 @@ setup_render_pass() {
   pass_info.pAttachments = attachments;
   pass_info.pAttachments = attachments;
   pass_info.subpassCount = 1;
   pass_info.subpassCount = 1;
   pass_info.pSubpasses = &subpass;
   pass_info.pSubpasses = &subpass;
-  pass_info.dependencyCount = 0;
-  pass_info.pDependencies = nullptr;
+  pass_info.dependencyCount = 1;
+  pass_info.pDependencies = &dependency;
 
 
   VkRenderPass pass;
   VkRenderPass pass;
   VkResult
   VkResult

+ 10 - 27
panda/src/vulkandisplay/vulkanShaderContext.cxx

@@ -574,19 +574,14 @@ fetch_descriptor(VulkanGraphicsStateGuardian *gsg, const Descriptor &desc,
       PT(Texture) texture = desc._binding->fetch_texture(state, id, sampler, view);
       PT(Texture) texture = desc._binding->fetch_texture(state, id, sampler, view);
 
 
       VulkanTextureContext *tc;
       VulkanTextureContext *tc;
-      DCAST_INTO_R(tc, texture->prepare_now(pgo, gsg), false);
+      tc = gsg->use_texture(texture,
+                            VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+                            desc._pipeline_stage_mask,
+                            VK_ACCESS_SHADER_READ_BIT);
 
 
       VulkanSamplerContext *sc;
       VulkanSamplerContext *sc;
       DCAST_INTO_R(sc, sampler.prepare_now(pgo, gsg), false);
       DCAST_INTO_R(sc, sampler.prepare_now(pgo, gsg), false);
 
 
-      tc->set_active(true);
-      gsg->update_texture(tc, true);
-
-      tc->transition(gsg->_frame_data->_transfer_cmd,
-                     gsg->_graphics_queue_family_index,
-                     VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
-                     desc._pipeline_stage_mask, VK_ACCESS_SHADER_READ_BIT);
-
       VkDescriptorImageInfo &image_info = *image_infos++;
       VkDescriptorImageInfo &image_info = *image_infos++;
       image_info.sampler = sc->_sampler;
       image_info.sampler = sc->_sampler;
       image_info.imageView = tc->get_image_view(view);
       image_info.imageView = tc->get_image_view(view);
@@ -603,15 +598,10 @@ fetch_descriptor(VulkanGraphicsStateGuardian *gsg, const Descriptor &desc,
       PT(Texture) texture = desc._binding->fetch_texture(state, id, sampler, view);
       PT(Texture) texture = desc._binding->fetch_texture(state, id, sampler, view);
 
 
       VulkanTextureContext *tc;
       VulkanTextureContext *tc;
-      DCAST_INTO_R(tc, texture->prepare_now(pgo, gsg), false);
-
-      tc->set_active(true);
-      gsg->update_texture(tc, true);
-
-      tc->transition(gsg->_frame_data->_transfer_cmd,
-                     gsg->_graphics_queue_family_index,
-                     VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
-                     desc._pipeline_stage_mask, VK_ACCESS_SHADER_READ_BIT);
+      tc = gsg->use_texture(texture,
+                            VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
+                            desc._pipeline_stage_mask,
+                            VK_ACCESS_SHADER_READ_BIT);
 
 
       VkBufferView &texel_buffer_view = *texel_buffer_views++;
       VkBufferView &texel_buffer_view = *texel_buffer_views++;
       texel_buffer_view = tc->get_buffer_view(view);
       texel_buffer_view = tc->get_buffer_view(view);
@@ -642,15 +632,8 @@ fetch_descriptor(VulkanGraphicsStateGuardian *gsg, const Descriptor &desc,
       }
       }
 
 
       VulkanTextureContext *tc;
       VulkanTextureContext *tc;
-      DCAST_INTO_R(tc, texture->prepare_now(pgo, gsg), false);
-
-      tc->set_active(true);
-      gsg->update_texture(tc, true);
-
-      tc->transition(gsg->_frame_data->_transfer_cmd,
-                     gsg->_graphics_queue_family_index,
-                     VK_IMAGE_LAYOUT_GENERAL,
-                     desc._pipeline_stage_mask, access_mask);
+      tc = gsg->use_texture(texture, VK_IMAGE_LAYOUT_GENERAL,
+                            desc._pipeline_stage_mask, access_mask);
 
 
       int view = gsg->get_current_tex_view_offset();
       int view = gsg->get_current_tex_view_offset();
       if (desc._type == VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER) {
       if (desc._type == VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER) {

+ 40 - 3
panda/src/vulkandisplay/vulkanTextureContext.I

@@ -44,23 +44,59 @@ get_buffer_view(int view) const {
   }
   }
 }
 }
 
 
+/**
+ * Returns true if the texture has been used this frame.  By "used" we mean
+ * that it has been accessed by a shader, not updated from the CPU.
+ */
+INLINE bool VulkanTextureContext::
+is_used_this_frame(VulkanFrameData &frame_data) const {
+  return frame_data._frame_index == _last_use_frame;
+}
+
+/**
+ * Marks the texture as having been used this frame.  By "used" we mean
+ * that it has been accessed by a shader, not updated from the CPU.
+ */
+INLINE void VulkanTextureContext::
+mark_used_this_frame(VulkanFrameData &frame_data) {
+  if (frame_data._frame_index != _last_use_frame) {
+    _last_use_frame = frame_data._frame_index;
+
+    // Good opportunity to initialize these fields.
+    _initial_src_layout = VK_IMAGE_LAYOUT_UNDEFINED;
+    _initial_dst_layout = VK_IMAGE_LAYOUT_UNDEFINED;
+    _initial_src_access_mask = 0;
+    _initial_dst_access_mask = 0;
+
+    // And to call this.
+    set_active(true);
+  }
+}
+
 /**
 /**
  * Indicates that the texture is being read from by the given stage.  Any
  * Indicates that the texture is being read from by the given stage.  Any
  * subsequent writes must wait until the given stage has passed the pipeline.
  * subsequent writes must wait until the given stage has passed the pipeline.
+ *
+ * Generally you should rely on gsg->use_texture() or tc->transition() to take
+ * care of this.
  */
  */
 INLINE void VulkanTextureContext::
 INLINE void VulkanTextureContext::
 mark_read(VkPipelineStageFlags stage) {
 mark_read(VkPipelineStageFlags stage) {
-  _read_stage_mask |= stage;
+  _read_stage_mask |= stage & ~VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
 }
 }
 
 
 /**
 /**
  * Indicates that the texture is being written by the given stage.  Any
  * Indicates that the texture is being written by the given stage.  Any
  * subsequent writes must wait until the given stage has passed the pipeline.
  * subsequent writes must wait until the given stage has passed the pipeline.
+ *
+ * Generally you should rely on gsg->use_texture() or tc->transition() to take
+ * care of this.
  */
  */
 INLINE void VulkanTextureContext::
 INLINE void VulkanTextureContext::
 mark_written(VkPipelineStageFlags stage, VkAccessFlags access_mask) {
 mark_written(VkPipelineStageFlags stage, VkAccessFlags access_mask) {
   _write_stage_mask |= stage;
   _write_stage_mask |= stage;
   _write_access_mask |= access_mask;
   _write_access_mask |= access_mask;
+  _read_stage_mask = 0;
 }
 }
 
 
 /**
 /**
@@ -69,8 +105,9 @@ mark_written(VkPipelineStageFlags stage, VkAccessFlags access_mask) {
  */
  */
 INLINE void VulkanTextureContext::
 INLINE void VulkanTextureContext::
 discard() {
 discard() {
-  // We don't clear _write_stage_mask, because we still want the execution
-  // dependency.
+  // We don't clear _write_stage_mask or _read_stage_mask, because we still
+  // want the execution dependency; we don't want the next write to stomp over
+  // the texture data while it's still being read.
   _layout = VK_IMAGE_LAYOUT_UNDEFINED;
   _layout = VK_IMAGE_LAYOUT_UNDEFINED;
   _write_access_mask = 0;
   _write_access_mask = 0;
 }
 }

+ 74 - 8
panda/src/vulkandisplay/vulkanTextureContext.cxx

@@ -16,6 +16,43 @@
 
 
 TypeHandle VulkanTextureContext::_type_handle;
 TypeHandle VulkanTextureContext::_type_handle;
 
 
+/**
+ * Returns true if the texture needs to be recreated because of a change to the
+ * size or format.
+ */
+bool VulkanTextureContext::
+needs_recreation() const {
+  Texture *tex = get_texture();
+  int num_views = tex->get_num_views();
+
+  VkExtent3D extent;
+  extent.width = tex->get_x_size();
+  extent.height = tex->get_y_size();
+  uint32_t arrayLayers;
+
+  if (tex->get_texture_type() == Texture::TT_3d_texture) {
+    extent.depth = tex->get_z_size();
+    arrayLayers = 1;
+  } else if (tex->get_texture_type() == Texture::TT_1d_texture_array) {
+    extent.height = 1;
+    extent.depth = 1;
+    arrayLayers = tex->get_y_size();
+  } else {
+    extent.depth = 1;
+    arrayLayers = tex->get_z_size();
+  }
+  arrayLayers *= num_views;
+
+  //VkFormat format = get_image_format(tex);
+
+  return (//format != _format ||
+          extent.width != _extent.width ||
+          extent.height != _extent.height ||
+          extent.depth != _extent.depth ||
+          arrayLayers != _array_layers ||
+          (size_t)num_views != _image_views.size());
+}
+
 /**
 /**
  * Schedules the deletion of the image resources for the end of the frame.
  * Schedules the deletion of the image resources for the end of the frame.
  */
  */
@@ -174,6 +211,10 @@ void VulkanTextureContext::
 clear_buffer(VkCommandBuffer cmd, uint32_t fill) {
 clear_buffer(VkCommandBuffer cmd, uint32_t fill) {
   nassertv(_buffer != VK_NULL_HANDLE);
   nassertv(_buffer != VK_NULL_HANDLE);
 
 
+  discard();
+  transition(cmd, 0,//vkgsg->_graphics_queue_family_index,
+    _layout, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT);
+
   vkCmdFillBuffer(cmd, _buffer, 0, VK_WHOLE_SIZE, fill);
   vkCmdFillBuffer(cmd, _buffer, 0, VK_WHOLE_SIZE, fill);
   mark_written(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT);
   mark_written(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT);
 }
 }
@@ -185,6 +226,12 @@ clear_buffer(VkCommandBuffer cmd, uint32_t fill) {
  *
  *
  * For a buffer texture, layout is ignored.
  * For a buffer texture, layout is ignored.
  *
  *
+ * Please be very aware on which command buffer the transition is happening:
+ * it is assumed that the command written by this call is also submitted in
+ * the same order as the transition() calls are made, so you may not call
+ * transition() on the transfer command buffer after having called it on the
+ * render command buffer!
+ *
  * Implicitly calls mark_read() or mark_written() depending on the access mask.
  * Implicitly calls mark_read() or mark_written() depending on the access mask.
  * Does not (yet) do inter-queue synchronization.
  * Does not (yet) do inter-queue synchronization.
  */
  */
@@ -220,9 +267,18 @@ transition(VkCommandBuffer cmd, uint32_t queue_family, VkImageLayout layout,
     }
     }
   }
   }
   else if (src_stage_mask == 0) {
   else if (src_stage_mask == 0) {
-    // This is a read-after-read, nothing to do here.
+    // No write has been done, nothing to do here.
     return;
     return;
   }
   }
+  else {
+    // We've already synchronized these reads since the last write.
+    dst_stage_mask &= ~_read_stage_mask;
+    if (dst_stage_mask == 0) {
+      // We could probably improve this by also early-outing if we've already
+      // synchronized a *preceding* stage.
+      return;
+    }
+  }
 
 
   VkImageMemoryBarrier img_barrier;
   VkImageMemoryBarrier img_barrier;
   if (_image != VK_NULL_HANDLE) {
   if (_image != VK_NULL_HANDLE) {
@@ -261,14 +317,24 @@ transition(VkCommandBuffer cmd, uint32_t queue_family, VkImageLayout layout,
   _layout = layout;
   _layout = layout;
 
 
   if (write_mask != 0) {
   if (write_mask != 0) {
-    // We are writing to it, remember that for next time.
+    // Remember which stages wrote to it and how.
     _write_stage_mask = dst_stage_mask;
     _write_stage_mask = dst_stage_mask;
     _write_access_mask = write_mask;
     _write_access_mask = write_mask;
-  } else {
-    // Note that layout transitions create an implicit execution dependency,
-    // so if we're not writing, we don't need to set _write_stage_mask here.
-    _write_stage_mask = 0;
-    _write_access_mask = 0;
+    _read_stage_mask = 0;
+  }
+  else {
+    // This is a read-after-write barrier.  It's possible that there will be
+    // another read later from a different (earlier) stage, which is why we
+    // don't zero out _write_stage_mask.  We can just check _read_stage_mask
+    // the next time to see what we have already synchronized with the write.
+    mark_read(dst_stage_mask);
+
+    if (dst_stage_mask & (VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT | VK_PIPELINE_STAGE_ALL_COMMANDS_BIT)) {
+      // Actually, looks like we've synchronized all stages.  We still do need
+      // to keep _read_access_mask, since a subsequent write still needs to
+      // wait for this read to complete.
+      _write_stage_mask = 0;
+      _write_access_mask = 0;
+    }
   }
   }
-  _read_stage_mask = dst_stage_mask & ~VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
 }
 }

+ 22 - 2
panda/src/vulkandisplay/vulkanTextureContext.h

@@ -28,12 +28,17 @@ public:
 
 
   ALLOC_DELETED_CHAIN(VulkanTextureContext);
   ALLOC_DELETED_CHAIN(VulkanTextureContext);
 
 
+  bool needs_recreation() const;
+
   void release(VulkanFrameData &frame_data);
   void release(VulkanFrameData &frame_data);
   void destroy_now(VkDevice device);
   void destroy_now(VkDevice device);
 
 
   INLINE VkImageView get_image_view(int view) const;
   INLINE VkImageView get_image_view(int view) const;
   INLINE VkBufferView get_buffer_view(int view) const;
   INLINE VkBufferView get_buffer_view(int view) const;
 
 
+  INLINE bool is_used_this_frame(VulkanFrameData &frame_data) const;
+  INLINE void mark_used_this_frame(VulkanFrameData &frame_data);
+
   INLINE void mark_read(VkPipelineStageFlags stage_mask);
   INLINE void mark_read(VkPipelineStageFlags stage_mask);
   INLINE void mark_written(VkPipelineStageFlags stage_mask,
   INLINE void mark_written(VkPipelineStageFlags stage_mask,
                            VkAccessFlags access_mask);
                            VkAccessFlags access_mask);
@@ -65,10 +70,25 @@ public:
   small_vector<VkBufferView> _buffer_views;
   small_vector<VkBufferView> _buffer_views;
   VulkanMemoryBlock _block;
   VulkanMemoryBlock _block;
 
 
+  // Frame number of the last time gsg->use_texture() was called.
+  uint64_t _last_use_frame = 0;
+
+  // These fields are managed by VulkanFrameData::add_initial_transition(),
+  // and are used to keep track of the transition we do at the beginning of a
+  // frame.
+  VkImageLayout _initial_src_layout = VK_IMAGE_LAYOUT_UNDEFINED;
+  VkImageLayout _initial_dst_layout = VK_IMAGE_LAYOUT_UNDEFINED;
+  VkAccessFlags _initial_src_access_mask = 0;
+  VkAccessFlags _initial_dst_access_mask = 0;
+
+  // Frame number of the last GPU write to this texture.
+  uint64_t _last_write_frame = 0;
+
+  // The "current" layout and access mask (as of the last command submitted)
   VkImageLayout _layout = VK_IMAGE_LAYOUT_UNDEFINED;
   VkImageLayout _layout = VK_IMAGE_LAYOUT_UNDEFINED;
   VkAccessFlags _write_access_mask = 0;
   VkAccessFlags _write_access_mask = 0;
-  VkPipelineStageFlags _write_stage_mask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
-  VkPipelineStageFlags _read_stage_mask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
+  VkPipelineStageFlags _write_stage_mask = 0;
+  VkPipelineStageFlags _read_stage_mask = 0;
 
 
 public:
 public:
   static TypeHandle get_class_type() {
   static TypeHandle get_class_type() {