void VulkanBuffer::sub_data(size_t off, size_t sz, const void *d)
{
- device.get_synchronizer().access(handle, off, sz);
+ device.get_synchronizer().write_buffer(handle, off, sz);
void *staging = device.get_transfer_queue().prepare_transfer(sz, [this, off, sz](VkCommandBuffer cmd_buf, VkBuffer staging_buf, size_t src_off){
const VulkanFunctions &vk = device.get_functions();
if(dynamic_cast<const SwapChainTexture *>(framebuffer->get_attachment(i)))
to_present = true;
if(!to_present)
- framebuffer->synchronize(clear);
+ framebuffer->prepare_image_layouts(clear);
VkRenderPass render_pass = device.get_pipeline_cache().get_render_pass(framebuffer->get_format(), clear, !clear_values, to_present);
framebuffer->refresh();
set_vulkan_object_name();
}
-void VulkanFramebuffer::synchronize(bool discard) const
+void VulkanFramebuffer::prepare_image_layouts(bool discard) const
{
for(const Framebuffer::Attachment &a: static_cast<const Framebuffer *>(this)->attachments)
- a.tex->synchronize(a.layer, get_vulkan_attachment_layout(get_components(a.tex->get_format())), discard);
+ a.tex->change_layout(0, a.level, get_vulkan_attachment_layout(get_components(a.tex->get_format())), discard);
}
void VulkanFramebuffer::set_debug_name(const string &name)
void update(unsigned) const;
void require_complete() const { }
- void synchronize(bool = false) const;
+ void prepare_image_layouts(bool = false) const;
void set_debug_name(const std::string &);
void set_vulkan_object_name() const;
#include <msp/core/algorithm.h>
#include "buffer.h"
#include "device.h"
+#include "error.h"
#include "texture.h"
#include "synchronizer.h"
#include "vulkan.h"
device(d)
{ }
-void Synchronizer::access(VkBuffer buffer, size_t offset, size_t size)
+void Synchronizer::write_buffer(VkBuffer buffer, size_t offset, size_t size)
{
- auto i = find_member(buffer_accesses, buffer, &BufferAccess::buffer);
- if(i==buffer_accesses.end())
+ auto i = lower_bound_member(buffer_accesses, buffer, &BufferAccess::buffer);
+ if(i==buffer_accesses.end() || i->buffer!=buffer)
{
- i = buffer_accesses.emplace(buffer_accesses.end());
+ i = buffer_accesses.emplace(i);
i->buffer = buffer;
i->offset = offset;
i->size = size;
i->pending_write = true;
}
-void Synchronizer::access(VkImage image, unsigned aspect, int layer, unsigned layout, bool discard)
+void Synchronizer::split_image_mipmap(VkImage image, unsigned aspect, unsigned n_levels)
{
- auto i = find_member(image_accesses, image, &ImageAccess::image);
- if(i==image_accesses.end())
+ if(!n_levels)
+ throw invalid_argument("Synchronizer::split_image_mipmap");
+
+ auto i = lower_bound_member(image_accesses, image, &ImageAccess::image);
+ if(i!=image_accesses.end() && i->image==image && i->level>=0)
+ return;
+
+ if(i!=image_accesses.end() && i->image==image && i->level==-1)
+ {
+ i = image_accesses.insert(i, n_levels-1, *i);
+ for(unsigned j=0; j<n_levels; ++i, ++j)
+ i->level = j;
+ }
+ else
+ {
+ ImageAccess access;
+ access.image = image;
+ access.aspect = aspect;
+ access.current_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+ access.pending_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
+
+ if(i->level==-2)
+ i = image_accesses.erase(i);
+
+ for(unsigned j=0; j<n_levels; ++i, ++j)
+ if(i==image_accesses.end() || i->image!=image || i->level>static_cast<int>(j))
+ {
+ i = image_accesses.insert(i, access);
+ i->level = j;
+ }
+ }
+}
+
+void Synchronizer::change_image_layout(VkImage image, unsigned aspect, int level, unsigned layout, bool discard)
+{
+ auto i = lower_bound_member(image_accesses, image, &ImageAccess::image);
+
+ if(level>=0)
+ {
+ if(i==image_accesses.end() || i->image!=image)
+ {
+ i = image_accesses.emplace(i);
+ i->image = image;
+ i->level = -2;
+ ++i;
+ }
+ else if(i->level==-1)
+ throw invalid_operation("Synchronizer::change_image_layout");
+ else
+ {
+ for(; (i!=image_accesses.end() && i->image==image && i->level<level); ++i) ;
+ }
+ }
+ else if(i!=image_accesses.end() && i->image==image && i->level==-2)
+ throw invalid_operation("Synchronizer::change_image_layout");
+
+ if(i==image_accesses.end() || i->image!=image || (level>=0 && i->level!=level))
{
- i = image_accesses.emplace(image_accesses.end());
+ i = image_accesses.emplace(i);
i->image = image;
i->aspect = aspect;
- i->layer = layer;
+ i->level = (level<0 ? -1 : level);
i->current_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
}
- if(discard)
- i->current_layout = VK_IMAGE_LAYOUT_UNDEFINED;
- if(layer!=i->layer)
- i->layer = -1;
- i->pending_layout = layout;
+ for(; (i!=image_accesses.end() && i->image==image && (level<0 || i->level==level)); ++i)
+ {
+ if(discard)
+ i->current_layout = VK_IMAGE_LAYOUT_UNDEFINED;
+ i->pending_layout = layout;
+ }
}
void Synchronizer::reset()
image_barriers.reserve(image_accesses.size());
for(const ImageAccess &i: image_accesses)
{
- if(i.pending_layout==i.current_layout)
+ if(i.level==-2 || i.pending_layout==i.current_layout)
continue;
image_barriers.emplace_back(VkImageMemoryBarrier{ });
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = handle_cast<::VkImage>(i.image);
barrier.subresourceRange.aspectMask = i.aspect;
- barrier.subresourceRange.baseMipLevel = 0;
- barrier.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS;
- if(i.layer>=0)
- {
- barrier.subresourceRange.baseArrayLayer = i.layer;
- barrier.subresourceRange.layerCount = 1;
- }
- else
- {
- barrier.subresourceRange.baseArrayLayer = 0;
- barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
- }
+ barrier.subresourceRange.baseMipLevel = max(i.level, 0);
+ barrier.subresourceRange.levelCount = (i.level<0 ? VK_REMAINING_MIP_LEVELS : 1);
+ barrier.subresourceRange.baseArrayLayer = 0;
+ barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS;
if(i.current_layout!=VK_IMAGE_LAYOUT_UNDEFINED)
src_stage |= (is_write_layout(i.current_layout) ? image_write_stages : image_read_stages);
}
}
+ bool sparse_levels = false;
for(auto i=image_accesses.begin(); i!=image_accesses.end(); )
{
- if(i->pending_layout==VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
+ if(i->level==-2)
+ {
+ sparse_levels = true;
+ ++i;
+ }
+ else if(i->pending_layout==VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
+ {
+ VkImage image = i->image;
i = image_accesses.erase(i);
+ if(i->image!=image)
+ {
+ if(sparse_levels)
+ {
+ auto j = prev(i);
+ if(j->level==-2)
+ i = image_accesses.erase(j);
+ }
+ sparse_levels = false;
+ }
+ }
else
{
i->current_layout = i->pending_layout;
{
VkImage image = 0;
unsigned aspect;
- int layer = -1;
+ int level = -1;
unsigned current_layout;
unsigned pending_layout;
};
public:
Synchronizer(Device &);
- void access(VkBuffer, std::size_t, std::size_t);
- void access(VkImage, unsigned, int, unsigned, bool);
+ void write_buffer(VkBuffer, std::size_t, std::size_t);
+ void split_image_mipmap(VkImage, unsigned, unsigned);
+ void change_image_layout(VkImage, unsigned, int, unsigned, bool);
void reset();
void barrier(VkCommandBuffer);
const Texture1D &self = *static_cast<const Texture1D *>(this);
unsigned level_size = self.get_level_size(level);
- synchronize(-1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, (x==0 && wd==level_size));
+ change_layout(self.levels, level, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, (x==0 && wd==level_size));
size_t data_size = wd*get_pixel_size(storage_fmt);
void *staging = device.get_transfer_queue().prepare_transfer(data_size,
const Texture2D &self = *static_cast<const Texture2D *>(this);
auto level_size = self.get_level_size(level);
- synchronize(-1, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, (x==0 && y==0 && wd==level_size.x && ht==level_size.y));
+ change_layout(self.levels, level, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, (x==0 && y==0 && wd==level_size.x && ht==level_size.y));
size_t data_size = wd*ht*get_pixel_size(storage_fmt);
void *staging = device.get_transfer_queue().prepare_transfer(data_size,
const Texture3D &self = *static_cast<const Texture3D *>(this);
auto level_size = self.get_level_size(level);
- int layer = (is_array() && dp==1 ? z : -1);
- synchronize(layer, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, (x==0 && y==0 && z==0 && wd==level_size.x && ht==level_size.y && dp==level_size.z));
+ bool discard = (x==0 && y==0 && z==0 && wd==level_size.x && ht==level_size.y && dp==level_size.z);
+ change_layout(self.levels, level, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, discard);
size_t data_size = wd*ht*dp*get_pixel_size(storage_fmt);
void *staging = device.get_transfer_queue().prepare_transfer(data_size,
memory_id = device.get_allocator().allocate(handle, DEVICE_MEMORY);
// Trigger a layout transition if the image is used before uploading data.
- synchronize(-1, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, true);
+ change_layout(0, -1, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, true);
}
VkImageViewCreateInfo view_info = { };
throw logic_error("VulkanTexture::generate_mipmap is unimplemented");
}
-void VulkanTexture::synchronize(int layer, unsigned layout, bool discard) const
+void VulkanTexture::change_layout(unsigned n_levels, int level, unsigned layout, bool discard) const
{
unsigned aspect = get_vulkan_aspect(get_components(static_cast<const Texture *>(this)->storage_fmt));
- device.get_synchronizer().access(handle, aspect, layer, layout, discard);
+ if(n_levels>0)
+ device.get_synchronizer().split_image_mipmap(handle, aspect, n_levels);
+ device.get_synchronizer().change_image_layout(handle, aspect, level, layout, discard);
}
void VulkanTexture::set_debug_name(const string &name)
void generate_mipmap();
- void synchronize(int, unsigned, bool = false) const;
+ void change_layout(unsigned, int, unsigned, bool) const;
void set_debug_name(const std::string &);
void set_vulkan_object_names() const;
const TextureCube &self = *static_cast<const TextureCube *>(this);
unsigned level_size = self.get_level_size(level);
- synchronize(face, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, (x==0 && y==0 && wd==level_size && ht==level_size));
+ change_layout(self.levels, level, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, (x==0 && y==0 && wd==level_size && ht==level_size));
size_t data_size = wd*ht*get_pixel_size(storage_fmt);
void *staging = device.get_transfer_queue().prepare_transfer(data_size,