Sfoglia il codice sorgente

vulkan: Recreate textures when format/dimensions change

Also split out FrameData class into a separate file
rdb 2 anni fa
parent
commit
c17becbee1

+ 1 - 0
panda/src/vulkandisplay/p3vulkandisplay_composite1.cxx

@@ -1,4 +1,5 @@
 #include "config_vulkandisplay.cxx"
+#include "vulkanFrameData.cxx"
 #include "vulkanGraphicsBuffer.cxx"
 #include "vulkanGraphicsPipe.cxx"
 #include "vulkanGraphicsStateGuardian.cxx"

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

@@ -0,0 +1,52 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file vulkanFrameData.cxx
+ * @author rdb
+ * @date 2023-06-14
+ */
+
+#include "vulkanFrameData.h"
+
+/**
+ *
+ */
+void VulkanFrameData::
+finish_downloads(VkDevice device) {
+  for (QueuedDownload &down : _download_queue) {
+    PTA_uchar target = down._texture->modify_ram_image();
+    size_t view_size = down._texture->get_ram_view_size();
+
+    if (auto data = down._block.map()) {
+      // The texture is upside down, so invert it.
+      size_t row_size = down._texture->get_x_size()
+                      * down._texture->get_num_components()
+                      * down._texture->get_component_width();
+      unsigned char *dst = target.p() + view_size * (down._view + 1) - row_size;
+      unsigned char *src = (unsigned char *)data;
+      unsigned char *src_end = src + view_size;
+      while (src < src_end) {
+        memcpy(dst, src, row_size);
+        src += row_size;
+        dst -= row_size;
+      }
+    } else {
+      vulkandisplay_cat.error()
+        << "Failed to map memory for RAM transfer.\n";
+    }
+
+    // We won't need this buffer any more.
+    vkDestroyBuffer(device, down._buffer, nullptr);
+
+    if (down._request != nullptr) {
+      down._request->finish();
+    }
+  }
+  _download_queue.clear();
+  _wait_for_finish = false;
+}

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

@@ -0,0 +1,63 @@
+/**
+ * PANDA 3D SOFTWARE
+ * Copyright (c) Carnegie Mellon University.  All rights reserved.
+ *
+ * All use of this software is subject to the terms of the revised BSD
+ * license.  You should have received a copy of this license along
+ * with this source code in a file named "LICENSE."
+ *
+ * @file vulkanFrameData.h
+ * @author rdb
+ * @date 2023-06-14
+ */
+
+#ifndef VULKANFRAMEDATA_H
+#define VULKANFRAMEDATA_H
+
+#include "config_vulkandisplay.h"
+#include "vulkanMemoryPage.h"
+#include "screenshotRequest.h"
+
+/**
+ * Stores all the data that has been collected between a begin_frame/end_frame
+ * pair, until the frame has finished rendering on the GPU.
+ */
+class VulkanFrameData {
+public:
+  void finish_downloads(VkDevice device);
+
+public:
+  uint64_t _frame_index = 0;
+  VkFence _fence = VK_NULL_HANDLE;
+  VkCommandBuffer _cmd = VK_NULL_HANDLE;
+  VkCommandBuffer _transfer_cmd = VK_NULL_HANDLE;
+
+  // Keep track of resources that should be deleted after this frame is done.
+  pvector<VulkanMemoryBlock> _pending_free;
+  pvector<VkBuffer> _pending_destroy_buffers;
+  pvector<VkBufferView> _pending_destroy_buffer_views;
+  pvector<VkFramebuffer> _pending_destroy_framebuffers;
+  pvector<VkImage> _pending_destroy_images;
+  pvector<VkImageView> _pending_destroy_image_views;
+  pvector<VkRenderPass> _pending_destroy_render_passes;
+  pvector<VkSampler> _pending_destroy_samplers;
+  pvector<VkDescriptorSet> _pending_free_descriptor_sets;
+
+  VkDeviceSize _uniform_buffer_head = 0;
+  VkDeviceSize _staging_buffer_head = 0;
+
+  // Queued buffer-to-RAM transfer.
+  struct QueuedDownload {
+    VkBuffer _buffer;
+    VulkanMemoryBlock _block;
+    PT(Texture) _texture;
+    int _view;
+    PT(ScreenshotRequest) _request;
+  };
+  typedef pvector<QueuedDownload> DownloadQueue;
+  DownloadQueue _download_queue;
+
+  bool _wait_for_finish = false;
+};
+
+#endif

+ 9 - 24
panda/src/vulkandisplay/vulkanGraphicsBuffer.cxx

@@ -536,6 +536,7 @@ destroy_framebuffer() {
   DCAST_INTO_V(vkgsg, _gsg);
   VkDevice device = vkgsg->_device;
 
+  // This shouldn't happen within a begin_frame/end_frame pair.
   nassertv(vkgsg->_frame_data == nullptr);
 
   // Make sure that the GSG's command buffer releases its resources.
@@ -550,27 +551,11 @@ destroy_framebuffer() {
 
   // Destroy the resources held for each attachment.
   for (Attachment &attach : _attachments) {
-    if (!attach._tc->_image_views.empty()) {
-      if (vkgsg->_last_frame_data != nullptr) {
-        vkgsg->_last_frame_data->_pending_destroy_image_views.insert(
-          vkgsg->_last_frame_data->_pending_destroy_image_views.end(),
-          attach._tc->_image_views.begin(), attach._tc->_image_views.end());
-        attach._tc->_image_views.clear();
-      } else {
-        attach._tc->destroy_views(device);
-      }
-    }
-
-    if (attach._tc->_image != VK_NULL_HANDLE) {
-      if (vkgsg->_last_frame_data != nullptr) {
-        vkgsg->_last_frame_data->_pending_destroy_images.push_back(attach._tc->_image);
-      } else {
-        vkDestroyImage(device, attach._tc->_image, nullptr);
-      }
-      attach._tc->_image = VK_NULL_HANDLE;
+    if (vkgsg->_last_frame_data != nullptr) {
+      attach._tc->release(*vkgsg->_last_frame_data);
+    } else {
+      attach._tc->destroy_now(device);
     }
-
-    attach._tc->update_data_size_bytes(0);
     delete attach._tc;
   }
   _attachments.clear();
@@ -662,10 +647,10 @@ create_attachment(RenderTexturePlane plane, VkFormat format) {
     usage |= VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
   }
 
-  VulkanTextureContext *tc;
-  tc = vkgsg->create_image(VK_IMAGE_TYPE_2D, format, extent, 1, 1,
-                           VK_SAMPLE_COUNT_1_BIT, usage);
-  if (tc == nullptr) {
+  VulkanTextureContext *tc = new VulkanTextureContext(vkgsg->get_prepared_objects());
+  if (!vkgsg->create_image(tc, VK_IMAGE_TYPE_2D, format, extent, 1, 1,
+                           VK_SAMPLE_COUNT_1_BIT, usage)) {
+    delete tc;
     return false;
   }
 

+ 58 - 120
panda/src/vulkandisplay/vulkanGraphicsStateGuardian.cxx

@@ -773,12 +773,30 @@ allocate_memory(VulkanMemoryBlock &block, const VkMemoryRequirements &reqs,
  */
 TextureContext *VulkanGraphicsStateGuardian::
 prepare_texture(Texture *texture) {
-  using std::swap;
-
   PStatTimer timer(_prepare_texture_pcollector);
 
+  VulkanTextureContext *tc = new VulkanTextureContext(get_prepared_objects(), texture);
+  if (tc != nullptr && create_texture(tc)) {
+    return tc;
+  } else {
+    delete tc;
+    return nullptr;
+  }
+}
+
+/**
+ *
+ */
+bool VulkanGraphicsStateGuardian::
+create_texture(VulkanTextureContext *tc) {
+  using std::swap;
+
   VulkanGraphicsPipe *vkpipe;
-  DCAST_INTO_R(vkpipe, get_pipe(), nullptr);
+  DCAST_INTO_R(vkpipe, get_pipe(), false);
+
+  nassertr(tc->_image == VK_NULL_HANDLE, false);
+
+  Texture *texture = tc->get_texture();
 
   VkImageCreateFlags flags = 0;
   VkImageType type;
@@ -822,7 +840,7 @@ prepare_texture(Texture *texture) {
   default:
     vulkandisplay_cat.error()
       << "Unsupported texture type " << texture->get_texture_type() << "!\n";
-    return nullptr;
+    return false;
   }
   const VkExtent3D orig_extent = extent;
 
@@ -859,14 +877,13 @@ prepare_texture(Texture *texture) {
     default:
       vulkandisplay_cat.error()
         << "Texture format " << format << " not supported.\n";
-      return nullptr;
+      return false;
     }
 
     // Update the properties for the new format.
     vkGetPhysicalDeviceFormatProperties(vkpipe->_gpu, format, &fmt_props);
   }
 
-  VulkanTextureContext *tc;
   if (!is_buffer) {
     // Image texture.  Is the size supported for this format?
     VkImageUsageFlags usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
@@ -883,7 +900,7 @@ prepare_texture(Texture *texture) {
       vulkandisplay_cat.error()
         << "Texture has too many layers, this format has a maximum of "
         << num_layers << "\n";
-      return nullptr;
+      return false;
     }
     int mipmap_begin = 0;
     while (extent.width > img_props.maxExtent.width ||
@@ -912,7 +929,7 @@ prepare_texture(Texture *texture) {
     int mipmap_end = mipmap_begin + 1;
     if (texture->uses_mipmaps()) {
       mipmap_end = texture->get_expected_num_mipmap_levels();
-      nassertr(mipmap_end > mipmap_begin, nullptr);
+      nassertr(mipmap_end > mipmap_begin, false);
     }
 
     // Do we need to generate any mipmaps?
@@ -947,10 +964,10 @@ prepare_texture(Texture *texture) {
       num_levels = img_props.maxMipLevels;
     }
 
-    tc = create_image(type, format, extent, num_levels, num_layers,
-                      VK_SAMPLE_COUNT_1_BIT, usage, flags);
-    nassertr_always(tc != nullptr, nullptr);
-    tc->set_texture(texture);
+    if (!create_image(tc, type, format, extent, num_levels, num_layers,
+                      VK_SAMPLE_COUNT_1_BIT, usage, flags)) {
+      return false;
+    }
     tc->_mipmap_begin = mipmap_begin;
     tc->_mipmap_end = mipmap_end;
     tc->_generate_mipmaps = generate_mipmaps;
@@ -1069,10 +1086,8 @@ prepare_texture(Texture *texture) {
       err = vkCreateImageView(_device, &view_info, nullptr, &image_view);
       if (err) {
         vulkan_error(err, "Failed to create image view for texture");
-        tc->destroy_views(_device);
-        vkDestroyImage(_device, tc->_image, nullptr);
-        delete tc;
-        return nullptr;
+        tc->destroy_now(_device);
+        return false;
       }
 
       tc->_image_views.push_back(image_view);
@@ -1085,7 +1100,7 @@ prepare_texture(Texture *texture) {
       vulkandisplay_cat.error()
         << "Buffer texture size " << extent.width << " is too large, maximum size is "
         << _max_buffer_texture_size << " texels\n";
-      return nullptr;
+      return false;
     }
 
     VkBufferUsageFlags usage = 0;
@@ -1106,7 +1121,7 @@ prepare_texture(Texture *texture) {
     VkDeviceSize total_size = view_size * num_views;
     if (!create_buffer(total_size, buffer, block, usage,
                        VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)) {
-      return nullptr;
+      return false;
     }
 
     if (vulkandisplay_cat.is_debug()) {
@@ -1135,15 +1150,13 @@ prepare_texture(Texture *texture) {
           vkDestroyBufferView(_device, buffer_view, nullptr);
         }
         vkDestroyBuffer(_device, buffer, nullptr);
-        delete tc;
-        return nullptr;
+        return false;
       }
 
       buffer_views.push_back(buffer_view);
       view_info.offset += view_size;
     }
 
-    tc = new VulkanTextureContext(get_prepared_objects(), texture);
     tc->_format = format;
     tc->_extent = extent;
     tc->_buffer = buffer;
@@ -1154,7 +1167,7 @@ prepare_texture(Texture *texture) {
 
   // We can't upload it at this point because the texture lock is currently
   // held, so accessing the RAM image will cause a deadlock.
-  return tc;
+  return true;
 }
 
 /**
@@ -1457,6 +1470,7 @@ update_texture(TextureContext *tc, bool force) {
 
   if (vtc->was_modified()) {
     Texture *tex = tc->get_texture();
+    int num_views = tex->get_num_views();
 
     VkExtent3D extent;
     extent.width = tex->get_x_size();
@@ -1474,7 +1488,7 @@ update_texture(TextureContext *tc, bool force) {
       extent.depth = 1;
       arrayLayers = tex->get_z_size();
     }
-    arrayLayers *= tex->get_num_views();
+    arrayLayers *= num_views;
 
     //VkFormat format = get_image_format(tex);
 
@@ -1482,10 +1496,13 @@ update_texture(TextureContext *tc, bool force) {
         extent.width != vtc->_extent.width ||
         extent.height != vtc->_extent.height ||
         extent.depth != vtc->_extent.depth ||
-        arrayLayers != vtc->_array_layers) {
-      // We need to recreate the image entirely. TODO!
-      std::cerr << "have to recreate image\n";
-      return false;
+        arrayLayers != vtc->_array_layers ||
+        (size_t)num_views != vtc->_image_views.size()) {
+      // We need to recreate the image entirely.
+      vtc->release(*_frame_data);
+      if (!create_texture(vtc)) {
+        return false;
+      }
     }
 
     if (!upload_texture(vtc)) {
@@ -1510,57 +1527,7 @@ release_texture(TextureContext *tc) {
   VulkanTextureContext *vtc;
   DCAST_INTO_V(vtc, tc);
 
-  if (vtc->_image != VK_NULL_HANDLE) {
-    _frame_data->_pending_destroy_images.push_back(vtc->_image);
-
-    if (vulkandisplay_cat.is_debug()) {
-      std::ostream &out = vulkandisplay_cat.debug()
-        << "Scheduling image " << vtc->_image;
-
-      if (!vtc->_image_views.empty()) {
-        out << " with views";
-        for (VkImageView image_view : vtc->_image_views) {
-          out << " " << image_view;
-        }
-      }
-
-      out << " for deletion\n";
-    }
-  }
-
-  if (!vtc->_image_views.empty()) {
-    _frame_data->_pending_destroy_image_views.insert(
-      _frame_data->_pending_destroy_image_views.end(),
-      vtc->_image_views.begin(), vtc->_image_views.end());
-  }
-
-  if (vtc->_buffer != VK_NULL_HANDLE) {
-    _frame_data->_pending_destroy_buffers.push_back(vtc->_buffer);
-
-    if (vulkandisplay_cat.is_debug()) {
-      std::ostream &out = vulkandisplay_cat.debug()
-        << "Scheduling buffer " << vtc->_buffer;
-
-      if (!vtc->_buffer_views.empty()) {
-        out << " with views";
-        for (VkBufferView buffer_view : vtc->_buffer_views) {
-          out << " " << buffer_view;
-        }
-      }
-
-      out << " for deletion\n";
-    }
-  }
-
-  if (!vtc->_buffer_views.empty()) {
-    _frame_data->_pending_destroy_buffer_views.insert(
-      _frame_data->_pending_destroy_buffer_views.end(),
-      vtc->_buffer_views.begin(), vtc->_buffer_views.end());
-  }
-
-  // Make sure that the memory remains untouched until the frame is over.
-  _frame_data->_pending_free.push_back(std::move(vtc->_block));
-
+  vtc->release(*_frame_data);
   delete vtc;
 }
 
@@ -2681,37 +2648,7 @@ finish_frame(FrameData &frame_data) {
   _staging_buffer_allocator.set_tail(frame_data._staging_buffer_head);
 
   // Process texture-to-RAM downloads.
-  for (QueuedDownload &down : frame_data._download_queue) {
-    PTA_uchar target = down._texture->modify_ram_image();
-    size_t view_size = down._texture->get_ram_view_size();
-
-    if (auto data = down._block.map()) {
-      // The texture is upside down, so invert it.
-      size_t row_size = down._texture->get_x_size()
-                      * down._texture->get_num_components()
-                      * down._texture->get_component_width();
-      unsigned char *dst = target.p() + view_size * (down._view + 1) - row_size;
-      unsigned char *src = (unsigned char *)data;
-      unsigned char *src_end = src + view_size;
-      while (src < src_end) {
-        memcpy(dst, src, row_size);
-        src += row_size;
-        dst -= row_size;
-      }
-    } else {
-      vulkandisplay_cat.error()
-        << "Failed to map memory for RAM transfer.\n";
-    }
-
-    // We won't need this buffer any more.
-    vkDestroyBuffer(_device, down._buffer, nullptr);
-
-    if (down._request != nullptr) {
-      down._request->finish();
-    }
-  }
-  frame_data._download_queue.clear();
-  frame_data._wait_for_finish = false;
+  frame_data.finish_downloads(_device);
 
   if (_last_frame_data == &frame_data) {
     _last_frame_data = nullptr;
@@ -3053,7 +2990,7 @@ do_extract_image(VulkanTextureContext *tc, Texture *tex, int view, int z, Screen
   VkDeviceSize buffer_size = tex->get_expected_ram_image_size();
 
   // Create a temporary buffer for transferring into.
-  QueuedDownload down;
+  FrameData::QueuedDownload down;
   if (!create_buffer(buffer_size, down._buffer, down._block,
                      VK_BUFFER_USAGE_TRANSFER_DST_BIT,
                      VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT)) {
@@ -3193,12 +3130,13 @@ create_buffer(VkDeviceSize size, VkBuffer &buffer, VulkanMemoryBlock &block,
 
 /**
  * Shared code for creating an image and allocating memory for it.
- * @return a VulkanTextureContext on success.
+ * @return true on success.
  */
-VulkanTextureContext *VulkanGraphicsStateGuardian::
-create_image(VkImageType type, VkFormat format, const VkExtent3D &extent,
-             uint32_t levels, uint32_t layers, VkSampleCountFlagBits samples,
-             VkImageUsageFlags usage, VkImageCreateFlags flags) {
+bool VulkanGraphicsStateGuardian::
+create_image(VulkanTextureContext *tc, VkImageType type, VkFormat format,
+             const VkExtent3D &extent, uint32_t levels, uint32_t layers,
+             VkSampleCountFlagBits samples, VkImageUsageFlags usage,
+             VkImageCreateFlags flags) {
   VkImageCreateInfo img_info;
   img_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
   img_info.pNext = nullptr;
@@ -3220,7 +3158,7 @@ create_image(VkImageType type, VkFormat format, const VkExtent3D &extent,
   VkResult err = vkCreateImage(_device, &img_info, nullptr, &image);
   if (err) {
     vulkan_error(err, "Failed to create image");
-    return nullptr;
+    return false;
   }
 
   // Get the memory requirements, and find an appropriate heap to alloc in.
@@ -3232,17 +3170,17 @@ create_image(VkImageType type, VkFormat format, const VkExtent3D &extent,
     vulkandisplay_cat.error()
       << "Failed to allocate " << mem_reqs.size << " bytes for image.\n";
     vkDestroyImage(_device, image, nullptr);
-    return nullptr;
+    return false;
   }
 
   // Bind memory to image.
   if (!block.bind_image(image)) {
     vulkan_error(err, "Failed to bind memory to multisample color image");
     vkDestroyImage(_device, image, nullptr);
-    return nullptr;
+    return false;
   }
 
-  VulkanTextureContext *tc = new VulkanTextureContext(get_prepared_objects(), image, format);
+  tc->_image = image;
   tc->_format = format;
   tc->_extent = extent;
   tc->_mip_levels = levels;
@@ -3251,7 +3189,7 @@ create_image(VkImageType type, VkFormat format, const VkExtent3D &extent,
 
   tc->set_resident(true);
   tc->update_data_size_bytes(mem_reqs.size);
-  return tc;
+  return true;
 }
 
 /**

+ 7 - 39
panda/src/vulkandisplay/vulkanGraphicsStateGuardian.h

@@ -15,6 +15,7 @@
 #define VULKANGRAPHICSSTATEGUARDIAN_H
 
 #include "config_vulkandisplay.h"
+#include "vulkanFrameData.h"
 #include "vulkanMemoryPage.h"
 #include "circularAllocator.h"
 
@@ -30,7 +31,7 @@ class VulkanVertexBufferContext;
  */
 class VulkanGraphicsStateGuardian final : public GraphicsStateGuardian {
 private:
-  struct FrameData;
+  typedef VulkanFrameData FrameData;
 
 public:
   VulkanGraphicsStateGuardian(GraphicsEngine *engine, VulkanGraphicsPipe *pipe,
@@ -48,6 +49,7 @@ public:
                        VkFlags required_flags, bool linear);
 
   virtual TextureContext *prepare_texture(Texture *tex);
+  bool create_texture(VulkanTextureContext *vtc);
   bool upload_texture(VulkanTextureContext *vtc);
   virtual bool update_texture(TextureContext *tc, bool force);
   virtual void release_texture(TextureContext *tc);
@@ -140,11 +142,10 @@ private:
 public:
   bool create_buffer(VkDeviceSize size, VkBuffer &buffer, VulkanMemoryBlock &block,
                      int usage_flags, VkMemoryPropertyFlagBits flags);
-  VulkanTextureContext *create_image(VkImageType type, VkFormat format,
-                                     const VkExtent3D &extent, uint32_t levels,
-                                     uint32_t layers, VkSampleCountFlagBits samples,
-                                     VkImageUsageFlags usage,
-                                     VkImageCreateFlags flags = 0);
+  bool create_image(VulkanTextureContext *tc, VkImageType type, VkFormat format,
+                    const VkExtent3D &extent, uint32_t levels, uint32_t layers,
+                    VkSampleCountFlagBits samples, VkImageUsageFlags usage,
+                    VkImageCreateFlags flags = 0);
 
   VkSemaphore create_semaphore();
 
@@ -263,39 +264,6 @@ private:
   pdeque<VulkanMemoryPage> _memory_pages;
   VkDeviceSize _total_allocated = 0u;
 
-  // Queued buffer-to-RAM transfer.
-  struct QueuedDownload {
-    VkBuffer _buffer;
-    VulkanMemoryBlock _block;
-    PT(Texture) _texture;
-    int _view;
-    PT(ScreenshotRequest) _request;
-  };
-  typedef pvector<QueuedDownload> DownloadQueue;
-
-  struct FrameData {
-    uint64_t _frame_index = 0;
-    VkFence _fence = VK_NULL_HANDLE;
-    VkCommandBuffer _cmd = VK_NULL_HANDLE;
-    VkCommandBuffer _transfer_cmd = VK_NULL_HANDLE;
-
-    // Keep track of resources that should be deleted after this frame is done.
-    pvector<VulkanMemoryBlock> _pending_free;
-    pvector<VkBuffer> _pending_destroy_buffers;
-    pvector<VkBufferView> _pending_destroy_buffer_views;
-    pvector<VkFramebuffer> _pending_destroy_framebuffers;
-    pvector<VkImage> _pending_destroy_images;
-    pvector<VkImageView> _pending_destroy_image_views;
-    pvector<VkRenderPass> _pending_destroy_render_passes;
-    pvector<VkSampler> _pending_destroy_samplers;
-    pvector<VkDescriptorSet> _pending_free_descriptor_sets;
-
-    VkDeviceSize _uniform_buffer_head = 0;
-    VkDeviceSize _staging_buffer_head = 0;
-
-    DownloadQueue _download_queue;
-    bool _wait_for_finish = false;
-  };
   static const size_t _frame_data_capacity = 5;
   FrameData _frame_data_pool[_frame_data_capacity];
   size_t _frame_data_head = _frame_data_capacity;

+ 20 - 27
panda/src/vulkandisplay/vulkanGraphicsWindow.cxx

@@ -812,32 +812,20 @@ destroy_swapchain() {
   for (SwapBuffer &buffer : _swap_buffers) {
     // Destroy the framebuffers that use the swapchain images.
     vkDestroyFramebuffer(device, buffer._framebuffer, nullptr);
-    buffer._tc->destroy_views(device);
-    buffer._tc->update_data_size_bytes(0);
+    buffer._tc->_image = VK_NULL_HANDLE;
+    buffer._tc->destroy_now(device);
     delete buffer._tc;
   }
   _swap_buffers.clear();
 
   if (_ms_color_tc != nullptr) {
-    _ms_color_tc->destroy_views(device);
-
-    if (_ms_color_tc->_image != VK_NULL_HANDLE) {
-      vkDestroyImage(device, _ms_color_tc->_image, nullptr);
-      _ms_color_tc->_image = VK_NULL_HANDLE;
-    }
-
+    _ms_color_tc->destroy_now(device);
     delete _ms_color_tc;
     _ms_color_tc = nullptr;
   }
 
   if (_depth_stencil_tc != nullptr) {
-    _depth_stencil_tc->destroy_views(device);
-
-    if (_depth_stencil_tc->_image != VK_NULL_HANDLE) {
-      vkDestroyImage(device, _depth_stencil_tc->_image, nullptr);
-      _depth_stencil_tc->_image = VK_NULL_HANDLE;
-    }
-
+    _depth_stencil_tc->destroy_now(device);
     delete _depth_stencil_tc;
     _depth_stencil_tc = nullptr;
   }
@@ -956,7 +944,9 @@ create_swapchain() {
   // Now create an image view for each image.
   for (uint32_t i = 0; i < num_images; ++i) {
     SwapBuffer &buffer = _swap_buffers[i];
-    buffer._tc = new VulkanTextureContext(pgo, images[i], swapchain_info.imageFormat);
+    buffer._tc = new VulkanTextureContext(pgo);
+    buffer._tc->_image = images[i];
+    buffer._tc->_format = swapchain_info.imageFormat;
     buffer._tc->_aspect_mask = VK_IMAGE_ASPECT_COLOR_BIT;
     buffer._tc->_extent = extent;
     buffer._tc->_mip_levels = 1;
@@ -993,14 +983,16 @@ create_swapchain() {
   _depth_stencil_tc = nullptr;
   VkImageView depth_stencil_view = VK_NULL_HANDLE;
   if (_depth_stencil_format != VK_FORMAT_UNDEFINED) {
-    _depth_stencil_tc = vkgsg->create_image(VK_IMAGE_TYPE_2D,
-      _depth_stencil_format, extent, 1, 1, _ms_count,
-      VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT);
+    _depth_stencil_tc = new VulkanTextureContext(pgo);
+    _depth_stencil_tc->_aspect_mask = _depth_stencil_aspect_mask;
 
-    if (_depth_stencil_tc == nullptr) {
+    if (!vkgsg->create_image(_depth_stencil_tc, VK_IMAGE_TYPE_2D,
+                             _depth_stencil_format, extent, 1, 1, _ms_count,
+                             VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT)) {
+      delete _depth_stencil_tc;
+      _depth_stencil_tc = nullptr;
       return false;
     }
-    _depth_stencil_tc->_aspect_mask = _depth_stencil_aspect_mask;
 
     VkImageViewCreateInfo view_info;
     view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
@@ -1036,11 +1028,12 @@ create_swapchain() {
   _ms_color_tc = nullptr;
   VkImageView ms_color_view = VK_NULL_HANDLE;
   if (_ms_count != VK_SAMPLE_COUNT_1_BIT) {
-    _ms_color_tc = vkgsg->create_image(VK_IMAGE_TYPE_2D,
-      swapchain_info.imageFormat, extent, 1, 1, _ms_count,
-      VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT);
-
-    if (_ms_color_tc == nullptr) {
+    _ms_color_tc = new VulkanTextureContext(pgo);
+    if (!vkgsg->create_image(_ms_color_tc, VK_IMAGE_TYPE_2D,
+                             swapchain_info.imageFormat, extent, 1, 1,
+                             _ms_count, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT)) {
+      delete _ms_color_tc;
+      _ms_color_tc = nullptr;
       return false;
     }
     _ms_color_tc->_aspect_mask = VK_IMAGE_ASPECT_COLOR_BIT;

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

@@ -11,6 +11,9 @@
  * @date 2018-06-30
  */
 
+#ifndef VULKANMEMORYPAGE_H
+#define VULKANMEMORYPAGE_H
+
 #include "config_vulkandisplay.h"
 #include "simpleAllocator.h"
 
@@ -118,3 +121,5 @@ public:
 };
 
 #include "vulkanMemoryPage.I"
+
+#endif

+ 0 - 21
panda/src/vulkandisplay/vulkanTextureContext.I

@@ -22,27 +22,6 @@ VulkanTextureContext(PreparedGraphicsObjects *pgo, Texture *texture) :
   _stage_mask(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT) {
 }
 
-/**
- * Constructs a Texture context that's not associated with a particular
- * texture, but tracks an already existing VkImage object.
- */
-INLINE VulkanTextureContext::
-VulkanTextureContext(PreparedGraphicsObjects *pgo, VkImage image, VkFormat format) :
-  TextureContext(pgo, nullptr),
-  _format(format),
-  _image(image),
-  //TODO: it is not clear to me what we should set srcStageMask to here.
-  _stage_mask(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT) {
-}
-
-/**
- * Sets the Texture object this context is for.
- */
-INLINE void VulkanTextureContext::
-set_texture(Texture *texture) {
-  _object = texture;
-}
-
 /**
  * Returns the VkImageView handle for the given view of the texture.
  */

+ 90 - 2
panda/src/vulkandisplay/vulkanTextureContext.cxx

@@ -12,23 +12,111 @@
  */
 
 #include "vulkanTextureContext.h"
+#include "vulkanFrameData.h"
 
 TypeHandle VulkanTextureContext::_type_handle;
 
 /**
- * Destroys the view handles associated with this context immediately.
+ * Schedules the deletion of the image resources for the end of the frame.
  */
 void VulkanTextureContext::
-destroy_views(VkDevice device) {
+release(VulkanFrameData &frame_data) {
+  if (_image != VK_NULL_HANDLE) {
+    frame_data._pending_destroy_images.push_back(_image);
+
+    if (vulkandisplay_cat.is_debug()) {
+      std::ostream &out = vulkandisplay_cat.debug()
+        << "Scheduling image " << _image;
+
+      if (!_image_views.empty()) {
+        out << " with views";
+        for (VkImageView image_view : _image_views) {
+          out << " " << image_view;
+        }
+      }
+
+      out << " for deletion\n";
+    }
+
+    _image = VK_NULL_HANDLE;
+  }
+
+  if (!_image_views.empty()) {
+    frame_data._pending_destroy_image_views.insert(
+      frame_data._pending_destroy_image_views.end(),
+      _image_views.begin(), _image_views.end());
+
+    _image_views.clear();
+  }
+
+  if (_buffer != VK_NULL_HANDLE) {
+    frame_data._pending_destroy_buffers.push_back(_buffer);
+
+    if (vulkandisplay_cat.is_debug()) {
+      std::ostream &out = vulkandisplay_cat.debug()
+        << "Scheduling buffer " << _buffer;
+
+      if (!_buffer_views.empty()) {
+        out << " with views";
+        for (VkBufferView buffer_view : _buffer_views) {
+          out << " " << buffer_view;
+        }
+      }
+
+      out << " for deletion\n";
+    }
+
+    _buffer = VK_NULL_HANDLE;
+  }
+
+  if (!_buffer_views.empty()) {
+    frame_data._pending_destroy_buffer_views.insert(
+      frame_data._pending_destroy_buffer_views.end(),
+      _buffer_views.begin(), _buffer_views.end());
+
+    _buffer_views.clear();
+  }
+
+  // Make sure that the memory remains untouched until the frame is over.
+  frame_data._pending_free.push_back(std::move(_block));
+
+  // The memory isn't free yet, but it can be reclaimed by the memory allocator
+  // if really necessary by waiting until the frame queue is empty.
+  update_data_size_bytes(0);
+
+  _format = VK_FORMAT_UNDEFINED;
+  _layout = VK_IMAGE_LAYOUT_UNDEFINED;
+}
+
+/**
+ * Destroys the handles associated with this context immediately.
+ */
+void VulkanTextureContext::
+destroy_now(VkDevice device) {
   for (VkImageView image_view : _image_views) {
     vkDestroyImageView(device, image_view, nullptr);
   }
   _image_views.clear();
 
+  if (_image != VK_NULL_HANDLE) {
+    vkDestroyImage(device, _image, nullptr);
+    _image = VK_NULL_HANDLE;
+  }
+
   for (VkBufferView buffer_view : _buffer_views) {
     vkDestroyBufferView(device, buffer_view, nullptr);
   }
   _buffer_views.clear();
+
+  if (_buffer != VK_NULL_HANDLE) {
+    vkDestroyBuffer(device, _buffer, nullptr);
+    _buffer = VK_NULL_HANDLE;
+  }
+
+  update_data_size_bytes(0);
+
+  _format = VK_FORMAT_UNDEFINED;
+  _layout = VK_IMAGE_LAYOUT_UNDEFINED;
 }
 
 /**

+ 4 - 6
panda/src/vulkandisplay/vulkanTextureContext.h

@@ -23,15 +23,13 @@
  */
 class EXPCL_VULKANDISPLAY VulkanTextureContext : public TextureContext {
 public:
-  INLINE VulkanTextureContext(PreparedGraphicsObjects *pgo, Texture *texture);
-  INLINE VulkanTextureContext(PreparedGraphicsObjects *pgo, VkImage image, VkFormat format);
+  INLINE VulkanTextureContext(PreparedGraphicsObjects *pgo, Texture *texture = nullptr);
   ~VulkanTextureContext() {};
 
   ALLOC_DELETED_CHAIN(VulkanTextureContext);
 
-  void destroy_views(VkDevice device);
-
-  INLINE void set_texture(Texture *texture);
+  void release(VulkanFrameData &frame_data);
+  void destroy_now(VkDevice device);
 
   INLINE const VkImageView &get_image_view(int view) const;
   INLINE const VkBufferView &get_buffer_view(int view) const;
@@ -47,7 +45,7 @@ public:
                   VkPipelineStageFlags dst_stage_mask, VkAccessFlags dst_access_mask);
 
 public:
-  VkFormat _format;
+  VkFormat _format = VK_FORMAT_UNDEFINED;
   VkExtent3D _extent;
   int _mipmap_begin = 0, _mipmap_end = 1;
   uint32_t _mip_levels = 1;