diff --git a/gfx/common/vksym.h b/gfx/common/vksym.h index 9a3ff5eee8..aa123d0356 100644 --- a/gfx/common/vksym.h +++ b/gfx/common/vksym.h @@ -191,6 +191,7 @@ typedef struct vulkan_context_fp /* Image commands */ PFN_vkCmdCopyImage vkCmdCopyImage; + PFN_vkCmdClearColorImage vkCmdClearColorImage; /* Pipeline commands */ PFN_vkCmdBindPipeline vkCmdBindPipeline; diff --git a/gfx/common/vulkan_common.c b/gfx/common/vulkan_common.c index fd3ff0af55..3c6f49e159 100644 --- a/gfx/common/vulkan_common.c +++ b/gfx/common/vulkan_common.c @@ -1153,6 +1153,7 @@ static bool vulkan_load_device_symbols(gfx_ctx_vulkan_data_t *vk) /* Image commands */ VK_GET_DEVICE_PROC_ADDR(CmdCopyImage); + VK_GET_DEVICE_PROC_ADDR(CmdClearColorImage); /* Vertex input descriptions */ VK_GET_DEVICE_PROC_ADDR(CmdBindVertexBuffers); diff --git a/gfx/drivers/vulkan.c b/gfx/drivers/vulkan.c index 013967a7e3..3bf140e625 100644 --- a/gfx/drivers/vulkan.c +++ b/gfx/drivers/vulkan.c @@ -584,6 +584,7 @@ static bool vulkan_init_default_filter_chain(vk_t *vk) info.swapchain.format = vk->context->swapchain_format; info.swapchain.render_pass = vk->render_pass; info.swapchain.num_indices = vk->context->num_swapchain_images; + info.original_format = vk->tex_fmt; vk->filter_chain = vulkan_filter_chain_create_default( &info, @@ -614,6 +615,7 @@ static bool vulkan_init_filter_chain_preset(vk_t *vk, const char *shader_path) info.swapchain.format = vk->context->swapchain_format; info.swapchain.render_pass = vk->render_pass; info.swapchain.num_indices = vk->context->num_swapchain_images; + info.original_format = vk->tex_fmt; vk->filter_chain = vulkan_filter_chain_create_from_preset( &info, shader_path, @@ -1495,6 +1497,7 @@ static bool vulkan_frame(void *data, const void *frame, return false; } + input.image = vk->hw.image->create_info.image; input.view = vk->hw.image->image_view; input.layout = vk->hw.image->image_layout; @@ -1520,6 +1523,7 @@ static bool vulkan_frame(void *data, const void *frame, else vulkan_transition_texture(vk, tex); + input.image = tex->image; input.view = tex->view; input.layout = tex->layout; input.width = tex->width; diff --git a/gfx/drivers_shader/shader_vulkan.cpp b/gfx/drivers_shader/shader_vulkan.cpp index 846d7ca3f2..45f55c5488 100644 --- a/gfx/drivers_shader/shader_vulkan.cpp +++ b/gfx/drivers_shader/shader_vulkan.cpp @@ -29,6 +29,34 @@ using namespace std; +static void image_layout_transition( + VkCommandBuffer cmd, VkImage image, + VkImageLayout old_layout, VkImageLayout new_layout, + VkAccessFlags srcAccess, VkAccessFlags dstAccess, + VkPipelineStageFlags srcStages, VkPipelineStageFlags dstStages) +{ + VkImageMemoryBarrier barrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; + + barrier.srcAccessMask = srcAccess; + barrier.dstAccessMask = dstAccess; + barrier.oldLayout = old_layout; + barrier.newLayout = new_layout; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.image = image; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.subresourceRange.levelCount = 1; + barrier.subresourceRange.layerCount = 1; + + VKFUNC(vkCmdPipelineBarrier)(cmd, + srcStages, + dstStages, + false, + 0, nullptr, + 0, nullptr, + 1, &barrier); +} + static uint32_t find_memory_type( const VkPhysicalDeviceMemoryProperties &mem_props, uint32_t device_reqs, uint32_t host_reqs) @@ -153,6 +181,9 @@ class Framebuffer VkFramebuffer get_framebuffer() const { return framebuffer; } VkRenderPass get_render_pass() const { return render_pass; } + void clear(VkCommandBuffer cmd); + void copy(VkCommandBuffer cmd, VkImage image, VkImageLayout layout); + private: VkDevice device = VK_NULL_HANDLE; const VkPhysicalDeviceMemoryProperties &memory_properties; @@ -211,6 +242,11 @@ class Pass return *framebuffer; } + Framebuffer *get_feedback_framebuffer() + { + return framebuffer_feedback.get(); + } + Size2D set_pass_info( const Size2D &max_original, const Size2D &max_source, @@ -222,6 +258,7 @@ class Pass size_t spirv_words); bool build(); + bool init_feedback(); void build_commands( DeferredDisposer &disposer, @@ -246,6 +283,13 @@ class Pass this->common = &common; } + const slang_reflection &get_reflection() const + { + return reflection; + } + + void end_frame(); + private: VkDevice device; const VkPhysicalDeviceMemoryProperties &memory_properties; @@ -273,6 +317,7 @@ class Pass vector vertex_shader; vector fragment_shader; unique_ptr framebuffer; + unique_ptr framebuffer_feedback; VkRenderPass swapchain_render_pass; void clear_vk(); @@ -280,12 +325,6 @@ class Pass bool init_pipeline_layout(); bool init_buffers(); - void image_layout_transition(VkDevice device, - VkCommandBuffer cmd, VkImage image, - VkImageLayout old_layout, VkImageLayout new_layout, - VkAccessFlags srcAccess, VkAccessFlags dstAccess, - VkPipelineStageFlags srcStages, VkPipelineStageFlags dstStages); - void set_texture(VkDescriptorSet set, unsigned binding, const Texture &texture); @@ -349,6 +388,7 @@ struct vulkan_filter_chain vector pass_info; vector>> deferred_calls; CommonResources common; + VkFormat original_format; vulkan_filter_chain_texture input_texture; @@ -364,6 +404,11 @@ struct vulkan_filter_chain void execute_deferred(); void set_num_sync_indices(unsigned num_indices); void set_swapchain_info(const vulkan_filter_chain_swapchain_info &info); + + bool init_history(); + void update_history(DeferredDisposer &disposer, VkCommandBuffer cmd); + vector> original_history; + bool require_clear = false; }; vulkan_filter_chain::vulkan_filter_chain( @@ -371,7 +416,8 @@ vulkan_filter_chain::vulkan_filter_chain( : device(info.device), memory_properties(*info.memory_properties), cache(info.pipeline_cache), - common(info.device, *info.memory_properties) + common(info.device, *info.memory_properties), + original_format(info.original_format) { max_input_size = { info.max_input_size.width, info.max_input_size.height }; set_swapchain_info(info.swapchain); @@ -466,6 +512,38 @@ void vulkan_filter_chain::flush() execute_deferred(); } +bool vulkan_filter_chain::init_history() +{ + original_history.clear(); + require_clear = false; + + size_t required_images = 0; + for (auto &pass : passes) + { + required_images = + max(required_images, + pass->get_reflection().semantic_textures[SLANG_TEXTURE_SEMANTIC_ORIGINAL_HISTORY].size()); + } + + if (required_images < 2) + return true; + + // We don't need to store array element #0, since it's aliased with the actual original. + required_images--; + original_history.reserve(required_images); + + for (unsigned i = 0; i < required_images; i++) + { + original_history.emplace_back(new Framebuffer(device, memory_properties, + max_input_size, original_format)); + } + + // On first frame, we need to clear the textures to a known state, but we need + // a command buffer for that, so just defer to first frame. + require_clear = true; + return true; +} + bool vulkan_filter_chain::init() { Size2D source = max_input_size; @@ -479,12 +557,29 @@ bool vulkan_filter_chain::init() return false; } + if (!init_history()) + return false; + return true; } void vulkan_filter_chain::build_offscreen_passes(VkCommandBuffer cmd, const VkViewport &vp) { + // First frame, make sure our history and feedback textures are in a clean state. + if (require_clear) + { + for (auto &texture : original_history) + texture->clear(cmd); + for (auto &pass : passes) + { + auto *fb = pass->get_feedback_framebuffer(); + if (fb) + fb->clear(cmd); + } + require_clear = false; + } + unsigned i; DeferredDisposer disposer(deferred_calls[current_sync_index]); const Texture original = { @@ -506,6 +601,54 @@ void vulkan_filter_chain::build_offscreen_passes(VkCommandBuffer cmd, } } +void vulkan_filter_chain::update_history(DeferredDisposer &disposer, VkCommandBuffer cmd) +{ + VkImageLayout src_layout = input_texture.layout; + // Transition input texture to something appropriate. + if (input_texture.layout != VK_IMAGE_LAYOUT_GENERAL) + { + image_layout_transition(cmd, + input_texture.image, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + 0, + VK_ACCESS_TRANSFER_READ_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT); + + src_layout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; + } + + unique_ptr tmp; + unique_ptr &back = original_history.back(); + swap(back, tmp); + + if (input_texture.width != tmp->get_size().width || + input_texture.height != tmp->get_size().height) + { + tmp->set_size(disposer, { input_texture.width, input_texture.height }); + } + + tmp->copy(cmd, input_texture.image, src_layout); + + // Transition input texture back. + if (input_texture.layout != VK_IMAGE_LAYOUT_GENERAL) + { + image_layout_transition(cmd, + input_texture.image, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + 0, + VK_ACCESS_SHADER_READ_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); + } + + // Should ring buffer, but we don't have *that* many passes. + move(begin(original_history), end(original_history) - 1, begin(original_history) + 1); + swap(original_history.front(), tmp); +} + void vulkan_filter_chain::build_viewport_pass( VkCommandBuffer cmd, const VkViewport &vp, const float *mvp) { @@ -528,6 +671,16 @@ void vulkan_filter_chain::build_viewport_pass( passes.back()->build_commands(disposer, cmd, original, source, vp, mvp); + + // If we need to keep old frames, copy it after fragment is complete. + // TODO: We can improve pipelining by figuring out which pass is the last that reads from + // the history and dispatch the copy earlier. + if (!original_history.empty()) + update_history(disposer, cmd); + + // For feedback FBOs, swap current and previous. + for (auto &pass : passes) + pass->end_frame(); } Buffer::Buffer(VkDevice device, @@ -978,8 +1131,29 @@ bool Pass::init_buffers() return true; } +void Pass::end_frame() +{ + if (framebuffer_feedback) + swap(framebuffer, framebuffer_feedback); +} + +bool Pass::init_feedback() +{ + if (final_pass) + return false; + + framebuffer_feedback = unique_ptr( + new Framebuffer(device, memory_properties, + current_framebuffer_size, + pass_info.rt_format)); + return true; +} + bool Pass::build() { + framebuffer.reset(); + framebuffer_feedback.reset(); + if (!final_pass) { framebuffer = unique_ptr( @@ -1001,34 +1175,6 @@ bool Pass::build() return true; } -void Pass::image_layout_transition(VkDevice device, - VkCommandBuffer cmd, VkImage image, - VkImageLayout old_layout, VkImageLayout new_layout, - VkAccessFlags srcAccess, VkAccessFlags dstAccess, - VkPipelineStageFlags srcStages, VkPipelineStageFlags dstStages) -{ - VkImageMemoryBarrier barrier = { VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER }; - - barrier.srcAccessMask = srcAccess; - barrier.dstAccessMask = dstAccess; - barrier.oldLayout = old_layout; - barrier.newLayout = new_layout; - barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - barrier.image = image; - barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - barrier.subresourceRange.levelCount = 1; - barrier.subresourceRange.layerCount = 1; - - VKFUNC(vkCmdPipelineBarrier)(cmd, - srcStages, - dstStages, - 0, - 0, nullptr, - 0, nullptr, - 1, &barrier); -} - void Pass::set_uniform_buffer(VkDescriptorSet set, unsigned binding, VkBuffer buffer, VkDeviceSize offset, @@ -1169,7 +1315,7 @@ void Pass::build_commands( if (!final_pass) { // Render. - image_layout_transition(device, cmd, + image_layout_transition(cmd, framebuffer->get_image(), VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, @@ -1247,7 +1393,6 @@ void Pass::build_commands( // Barrier to sync with next pass. image_layout_transition( - device, cmd, framebuffer->get_image(), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, @@ -1274,6 +1419,63 @@ Framebuffer::Framebuffer( init(nullptr); } +void Framebuffer::clear(VkCommandBuffer cmd) +{ + image_layout_transition(cmd, image, + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 0, VK_ACCESS_TRANSFER_WRITE_BIT, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT); + + VkClearColorValue color; + memset(&color, 0, sizeof(color)); + + VkImageSubresourceRange range; + memset(&range, 0, sizeof(range)); + range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + range.levelCount = 1; + range.layerCount = 1; + + VKFUNC(vkCmdClearColorImage)(cmd, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + &color, 1, &range); + + image_layout_transition(cmd, image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); +} + +void Framebuffer::copy(VkCommandBuffer cmd, + VkImage src_image, VkImageLayout src_layout) +{ + image_layout_transition(cmd, image, + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 0, VK_ACCESS_TRANSFER_WRITE_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT); + + VkImageCopy region; + memset(®ion, 0, sizeof(region)); + region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + region.srcSubresource.layerCount = 1; + region.dstSubresource = region.srcSubresource; + region.extent.width = size.width; + region.extent.height = size.height; + region.extent.depth = 1; + + VKFUNC(vkCmdCopyImage)(cmd, + src_image, src_layout, + image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, ®ion); + + image_layout_transition(cmd, image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT); +} + void Framebuffer::init(DeferredDisposer *disposer) { VkMemoryRequirements mem_reqs; @@ -1288,7 +1490,8 @@ void Framebuffer::init(DeferredDisposer *disposer) info.samples = VK_SAMPLE_COUNT_1_BIT; info.tiling = VK_IMAGE_TILING_OPTIMAL; info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | - VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; + VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT; info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; diff --git a/gfx/drivers_shader/shader_vulkan.h b/gfx/drivers_shader/shader_vulkan.h index 955af3efc0..bf05c305f8 100644 --- a/gfx/drivers_shader/shader_vulkan.h +++ b/gfx/drivers_shader/shader_vulkan.h @@ -37,6 +37,7 @@ enum vulkan_filter_chain_filter struct vulkan_filter_chain_texture { + VkImage image; VkImageView view; VkImageLayout layout; unsigned width; @@ -82,6 +83,7 @@ struct vulkan_filter_chain_create_info VkPipelineCache pipeline_cache; unsigned num_passes; + VkFormat original_format; struct { unsigned width, height;