From 205d290177de2dbf402af41b224b92b14865309a Mon Sep 17 00:00:00 2001 From: MaxsTechReview Date: Sat, 12 Sep 2026 16:40:28 -0400 Subject: [PATCH 1/2] Revert "Rework DIS frame generation: new C++ optical-flow chain" (#750) Restores the C optical-flow chain (vkr_dis.c and its shader set) so the per-pyramid-level variational refinement in #748 is the DIS work that is kept. This reverts commit 4e95335b. --- app/src/main/cpp/CMakeLists.txt | 25 +- app/src/main/cpp/winlator/vk/dis/dis.cpp | 306 --- app/src/main/cpp/winlator/vk/dis/dis.hpp | 100 - .../main/cpp/winlator/vk/dis/dis_chain.cpp | 859 -------- .../main/cpp/winlator/vk/dis/dis_chain.hpp | 118 -- app/src/main/cpp/winlator/vk/dis/vkr_dis.c | 1751 +++++++++++++++++ app/src/main/cpp/winlator/vk/dis/vkr_dis.cpp | 250 --- app/src/main/cpp/winlator/vk/dis/vkr_dis.h | 42 +- .../cpp/winlator/vk/framegen/fg_present.c | 34 +- .../cpp/winlator/vk/shaders/dis_densify.comp | 124 +- .../cpp/winlator/vk/shaders/dis_descent.comp | 92 - .../winlator/vk/shaders/dis_downsample.comp | 23 - .../cpp/winlator/vk/shaders/dis_gradient.comp | 38 + .../cpp/winlator/vk/shaders/dis_gray.comp | 28 - .../cpp/winlator/vk/shaders/dis_interp.comp | 75 - .../winlator/vk/shaders/dis_interpolate.comp | 99 + .../vk/shaders/dis_inverse_search.comp | 134 ++ .../cpp/winlator/vk/shaders/dis_luma.comp | 21 + .../cpp/winlator/vk/shaders/dis_luma_r16.comp | 21 + .../cpp/winlator/vk/shaders/dis_luma_r32.comp | 21 + .../winlator/vk/shaders/dis_propagate.comp | 129 +- .../cpp/winlator/vk/shaders/dis_sobel.comp | 22 - .../winlator/vk/shaders/dis_structure.comp | 45 - .../cpp/winlator/vk/shaders/dis_vr_add.comp | 26 + .../vk/shaders/dis_vr_add_resize.comp | 48 - .../cpp/winlator/vk/shaders/dis_vr_coef.comp | 77 + .../winlator/vk/shaders/dis_vr_coeffs.comp | 104 - .../cpp/winlator/vk/shaders/dis_vr_d1.comp | 23 + .../cpp/winlator/vk/shaders/dis_vr_d2.comp | 23 + .../cpp/winlator/vk/shaders/dis_vr_prep.comp | 30 + .../winlator/vk/shaders/dis_vr_prepare.comp | 51 - .../cpp/winlator/vk/shaders/dis_vr_sor.comp | 51 +- .../cpp/winlator/vk/shaders/dis_vr_w.comp | 32 + .../winlator/vk/shaders/dis_vr_weights.comp | 32 - app/src/main/cpp/winlator/vk/vk_dispatch.c | 1 - app/src/main/cpp/winlator/vk/vk_dispatch.h | 1 - app/src/main/cpp/winlator/vk/vk_renderer.c | 189 +- app/src/main/cpp/winlator/vk/vk_state.h | 1 - 38 files changed, 2582 insertions(+), 2464 deletions(-) delete mode 100644 app/src/main/cpp/winlator/vk/dis/dis.cpp delete mode 100644 app/src/main/cpp/winlator/vk/dis/dis.hpp delete mode 100644 app/src/main/cpp/winlator/vk/dis/dis_chain.cpp delete mode 100644 app/src/main/cpp/winlator/vk/dis/dis_chain.hpp create mode 100644 app/src/main/cpp/winlator/vk/dis/vkr_dis.c delete mode 100644 app/src/main/cpp/winlator/vk/dis/vkr_dis.cpp delete mode 100644 app/src/main/cpp/winlator/vk/shaders/dis_descent.comp delete mode 100644 app/src/main/cpp/winlator/vk/shaders/dis_downsample.comp create mode 100644 app/src/main/cpp/winlator/vk/shaders/dis_gradient.comp delete mode 100644 app/src/main/cpp/winlator/vk/shaders/dis_gray.comp delete mode 100644 app/src/main/cpp/winlator/vk/shaders/dis_interp.comp create mode 100644 app/src/main/cpp/winlator/vk/shaders/dis_interpolate.comp create mode 100644 app/src/main/cpp/winlator/vk/shaders/dis_inverse_search.comp create mode 100644 app/src/main/cpp/winlator/vk/shaders/dis_luma.comp create mode 100644 app/src/main/cpp/winlator/vk/shaders/dis_luma_r16.comp create mode 100644 app/src/main/cpp/winlator/vk/shaders/dis_luma_r32.comp delete mode 100644 app/src/main/cpp/winlator/vk/shaders/dis_sobel.comp delete mode 100644 app/src/main/cpp/winlator/vk/shaders/dis_structure.comp create mode 100644 app/src/main/cpp/winlator/vk/shaders/dis_vr_add.comp delete mode 100644 app/src/main/cpp/winlator/vk/shaders/dis_vr_add_resize.comp create mode 100644 app/src/main/cpp/winlator/vk/shaders/dis_vr_coef.comp delete mode 100644 app/src/main/cpp/winlator/vk/shaders/dis_vr_coeffs.comp create mode 100644 app/src/main/cpp/winlator/vk/shaders/dis_vr_d1.comp create mode 100644 app/src/main/cpp/winlator/vk/shaders/dis_vr_d2.comp create mode 100644 app/src/main/cpp/winlator/vk/shaders/dis_vr_prep.comp delete mode 100644 app/src/main/cpp/winlator/vk/shaders/dis_vr_prepare.comp create mode 100644 app/src/main/cpp/winlator/vk/shaders/dis_vr_w.comp delete mode 100644 app/src/main/cpp/winlator/vk/shaders/dis_vr_weights.comp diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index 8f8a18d91..f40134d88 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -102,19 +102,20 @@ set(SHADER_LIST "effect_colorblind:frag:effect_colorblind_frag" "effect_pixelate:frag:effect_pixelate_frag" "sgsr1:frag:sgsr1_frag" - "dis_gray:comp:dis_gray_comp" - "dis_downsample:comp:dis_downsample_comp" - "dis_sobel:comp:dis_sobel_comp" - "dis_structure:comp:dis_structure_comp" + "dis_luma_r16:comp:dis_luma_r16_comp" + "dis_luma_r32:comp:dis_luma_r32_comp" + "dis_gradient:comp:dis_gradient_comp" + "dis_inverse_search:comp:dis_inverse_search_comp" "dis_propagate:comp:dis_propagate_comp" - "dis_descent:comp:dis_descent_comp" "dis_densify:comp:dis_densify_comp" - "dis_vr_prepare:comp:dis_vr_prepare_comp" - "dis_vr_weights:comp:dis_vr_weights_comp" - "dis_vr_coeffs:comp:dis_vr_coeffs_comp" + "dis_interpolate:comp:dis_interpolate_comp" + "dis_vr_prep:comp:dis_vr_prep_comp" + "dis_vr_d1:comp:dis_vr_d1_comp" + "dis_vr_d2:comp:dis_vr_d2_comp" + "dis_vr_w:comp:dis_vr_w_comp" + "dis_vr_coef:comp:dis_vr_coef_comp" "dis_vr_sor:comp:dis_vr_sor_comp" - "dis_vr_add_resize:comp:dis_vr_add_resize_comp" - "dis_interp:comp:dis_interp_comp" + "dis_vr_add:comp:dis_vr_add_comp" ) set(SHADER_HEADERS "") @@ -178,9 +179,7 @@ add_library(winlator SHARED winlator/vk/lsfg/lsfg_jni.c winlator/vk/framegen/fg_present.c winlator/vk/framegen/fg_jni.c - winlator/vk/dis/dis.cpp - winlator/vk/dis/dis_chain.cpp - winlator/vk/dis/vkr_dis.cpp + winlator/vk/dis/vkr_dis.c ) add_dependencies(winlator winlator_shaders) diff --git a/app/src/main/cpp/winlator/vk/dis/dis.cpp b/app/src/main/cpp/winlator/vk/dis/dis.cpp deleted file mode 100644 index 2ed61596d..000000000 --- a/app/src/main/cpp/winlator/vk/dis/dis.cpp +++ /dev/null @@ -1,306 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "dis.hpp" - -#include -#include - -namespace dis { - -uint32_t GroupCount(uint32_t size, uint32_t tile_shift) { - return (size + (1u << tile_shift) - 1u) >> tile_shift; -} - -static VkPhysicalDeviceMemoryProperties g_memory_props{}; - -void SetDeviceMemoryProperties(const VkPhysicalDeviceMemoryProperties& props) { - g_memory_props = props; -} - -static uint32_t FindMemoryType(uint32_t bits, VkMemoryPropertyFlags want) { - for (uint32_t i = 0; i < g_memory_props.memoryTypeCount; i++) { - if (!(bits & (1u << i))) continue; - if ((g_memory_props.memoryTypes[i].propertyFlags & want) == want) return i; - } - return UINT32_MAX; -} - -DisImage::DisImage(VkDevice device_, VkExtent2D extent_, VkFormat format_, - VkImageUsageFlags usage, VkImageLayout initial_layout) - : device{device_}, - extent{std::max(1u, extent_.width), std::max(1u, extent_.height)}, - format{format_} { - VkImageCreateInfo ci{}; - ci.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - ci.imageType = VK_IMAGE_TYPE_2D; - ci.format = format; - ci.extent = {extent.width, extent.height, 1}; - ci.mipLevels = 1; - ci.arrayLayers = 1; - ci.samples = VK_SAMPLE_COUNT_1_BIT; - ci.tiling = VK_IMAGE_TILING_OPTIMAL; - ci.usage = usage; - ci.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - ci.initialLayout = initial_layout; - if (vkd.CreateImage(device, &ci, nullptr, &image) != VK_SUCCESS) { - image = VK_NULL_HANDLE; - return; - } - - VkMemoryRequirements req; - vkd.GetImageMemoryRequirements(device, image, &req); - - VkMemoryAllocateInfo ai{}; - ai.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; - ai.allocationSize = req.size; - ai.memoryTypeIndex = FindMemoryType(req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); - if (ai.memoryTypeIndex == UINT32_MAX || vkd.AllocateMemory(device, &ai, nullptr, &memory) != VK_SUCCESS) { - Release(); - return; - } - vkd.BindImageMemory(device, image, memory, 0); - - VkImageViewCreateInfo vi{}; - vi.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; - vi.image = image; - vi.viewType = VK_IMAGE_VIEW_TYPE_2D; - vi.format = format; - vi.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - vi.subresourceRange.levelCount = 1; - vi.subresourceRange.layerCount = 1; - if (vkd.CreateImageView(device, &vi, nullptr, &view) != VK_SUCCESS) { - view = VK_NULL_HANDLE; - Release(); - } -} - -DisImage::~DisImage() { - Release(); -} - -DisImage::DisImage(DisImage&& other) noexcept - : device{other.device}, image{other.image}, view{other.view}, memory{other.memory}, - extent{other.extent}, format{other.format} { - other.device = VK_NULL_HANDLE; - other.image = VK_NULL_HANDLE; - other.view = VK_NULL_HANDLE; - other.memory = VK_NULL_HANDLE; -} - -DisImage& DisImage::operator=(DisImage&& other) noexcept { - if (this != &other) { - Release(); - device = other.device; - image = other.image; - view = other.view; - memory = other.memory; - extent = other.extent; - format = other.format; - other.device = VK_NULL_HANDLE; - other.image = VK_NULL_HANDLE; - other.view = VK_NULL_HANDLE; - other.memory = VK_NULL_HANDLE; - } - return *this; -} - -void DisImage::Release() { - if (device == VK_NULL_HANDLE) return; - if (view) vkd.DestroyImageView(device, view, nullptr); - if (image) vkd.DestroyImage(device, image, nullptr); - if (memory) vkd.FreeMemory(device, memory, nullptr); - view = VK_NULL_HANDLE; - image = VK_NULL_HANDLE; - memory = VK_NULL_HANDLE; -} - -DisPass::DisPass(VkDevice device_, const uint32_t* spirv, size_t spirv_size, - const std::vector& bindings, - VkShaderStageFlagBits stage, VkPushConstantRange push) - : device{device_} { - descriptor_set_layout = CreateDescriptorSetLayout(device, bindings); - if (descriptor_set_layout == VK_NULL_HANDLE) { - Release(); - return; - } - - VkPipelineLayoutCreateInfo pl{}; - pl.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; - pl.setLayoutCount = 1; - pl.pSetLayouts = &descriptor_set_layout; - pl.pushConstantRangeCount = push.size > 0 ? 1u : 0u; - pl.pPushConstantRanges = push.size > 0 ? &push : nullptr; - if (vkd.CreatePipelineLayout(device, &pl, nullptr, &pipeline_layout) != VK_SUCCESS) { - Release(); - return; - } - - VkShaderModuleCreateInfo sm{}; - sm.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; - sm.codeSize = spirv_size; - sm.pCode = spirv; - VkShaderModule module = VK_NULL_HANDLE; - if (vkd.CreateShaderModule(device, &sm, nullptr, &module) != VK_SUCCESS) { - Release(); - return; - } - - VkPipelineShaderStageCreateInfo ss{}; - ss.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; - ss.stage = stage; - ss.module = module; - ss.pName = "main"; - - VkComputePipelineCreateInfo cp{}; - cp.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; - cp.stage = ss; - cp.layout = pipeline_layout; - if (vkd.CreateComputePipelines(device, VK_NULL_HANDLE, 1, &cp, nullptr, &pipeline) != - VK_SUCCESS) { - pipeline = VK_NULL_HANDLE; - } - vkd.DestroyShaderModule(device, module, nullptr); - if (pipeline == VK_NULL_HANDLE) Release(); -} - -DisPass::~DisPass() { - Release(); -} - -DisPass::DisPass(DisPass&& other) noexcept - : device{other.device}, descriptor_set_layout{other.descriptor_set_layout}, - pipeline_layout{other.pipeline_layout}, pipeline{other.pipeline} { - other.device = VK_NULL_HANDLE; - other.descriptor_set_layout = VK_NULL_HANDLE; - other.pipeline_layout = VK_NULL_HANDLE; - other.pipeline = VK_NULL_HANDLE; -} - -DisPass& DisPass::operator=(DisPass&& other) noexcept { - if (this != &other) { - Release(); - device = other.device; - descriptor_set_layout = other.descriptor_set_layout; - pipeline_layout = other.pipeline_layout; - pipeline = other.pipeline; - other.device = VK_NULL_HANDLE; - other.descriptor_set_layout = VK_NULL_HANDLE; - other.pipeline_layout = VK_NULL_HANDLE; - other.pipeline = VK_NULL_HANDLE; - } - return *this; -} - -void DisPass::Release() { - if (device == VK_NULL_HANDLE) return; - if (pipeline) vkd.DestroyPipeline(device, pipeline, nullptr); - if (pipeline_layout) vkd.DestroyPipelineLayout(device, pipeline_layout, nullptr); - if (descriptor_set_layout) vkd.DestroyDescriptorSetLayout(device, descriptor_set_layout, nullptr); - pipeline = VK_NULL_HANDLE; - pipeline_layout = VK_NULL_HANDLE; - descriptor_set_layout = VK_NULL_HANDLE; -} - -void DisPass::BindCompute(VkCommandBuffer cmd, VkDescriptorSet set) const { - vkd.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); - if (set != VK_NULL_HANDLE) { - vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline_layout, 0, 1, &set, 0, - nullptr); - } -} - -void DisPass::Push(VkCommandBuffer cmd, VkShaderStageFlags stage, uint32_t size, - const void* data) const { - vkd.CmdPushConstants(cmd, pipeline_layout, stage, 0, size, data); -} - -VkDescriptorSetLayout CreateDescriptorSetLayout(VkDevice device, - const std::vector& bindings) { - VkDescriptorSetLayoutCreateInfo ci{}; - ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; - ci.bindingCount = static_cast(bindings.size()); - ci.pBindings = bindings.data(); - VkDescriptorSetLayout layout = VK_NULL_HANDLE; - vkd.CreateDescriptorSetLayout(device, &ci, nullptr, &layout); - return layout; -} - -VkDescriptorPool CreateDescriptorPool(VkDevice device, uint32_t max_sets) { - const VkDescriptorType types[] = { - VK_DESCRIPTOR_TYPE_SAMPLER, - VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, - VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - }; - VkDescriptorPoolSize sizes[3]; - for (uint32_t i = 0; i < 3; i++) { - sizes[i].type = types[i]; - sizes[i].descriptorCount = 4096; - } - - VkDescriptorPoolCreateInfo ci{}; - ci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; - ci.maxSets = max_sets; - ci.poolSizeCount = 3; - ci.pPoolSizes = sizes; - - VkDescriptorPool pool = VK_NULL_HANDLE; - vkd.CreateDescriptorPool(device, &ci, nullptr, &pool); - return pool; -} - -VkDescriptorSet AllocateDescriptorSet(VkDevice device, VkDescriptorPool pool, - VkDescriptorSetLayout layout) { - if (pool == VK_NULL_HANDLE || layout == VK_NULL_HANDLE) return VK_NULL_HANDLE; - VkDescriptorSetAllocateInfo ai{}; - ai.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; - ai.descriptorPool = pool; - ai.descriptorSetCount = 1; - ai.pSetLayouts = &layout; - VkDescriptorSet set = VK_NULL_HANDLE; - vkd.AllocateDescriptorSets(device, &ai, &set); - return set; -} - -void WriteDescriptorSet(VkDevice device, VkDescriptorSet set, - const std::vector& bindings) { - if (set == VK_NULL_HANDLE) return; - - std::vector infos(bindings.size()); - std::vector writes(bindings.size()); - - for (size_t i = 0; i < bindings.size(); i++) { - infos[i] = VkDescriptorImageInfo{}; - infos[i].sampler = bindings[i].sampler; - infos[i].imageView = bindings[i].view; - infos[i].imageLayout = bindings[i].view != VK_NULL_HANDLE ? VK_IMAGE_LAYOUT_GENERAL - : VK_IMAGE_LAYOUT_UNDEFINED; - - writes[i] = VkWriteDescriptorSet{}; - writes[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; - writes[i].dstSet = set; - writes[i].dstBinding = static_cast(i); - writes[i].descriptorCount = 1; - writes[i].descriptorType = bindings[i].type; - writes[i].pImageInfo = &infos[i]; - } - - vkd.UpdateDescriptorSets(device, static_cast(writes.size()), writes.data(), 0, nullptr); -} - -VkSampler CreateSampler(VkDevice device) { - VkSamplerCreateInfo ci{}; - ci.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; - ci.magFilter = VK_FILTER_LINEAR; - ci.minFilter = VK_FILTER_LINEAR; - ci.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; - ci.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; - ci.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; - ci.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; - ci.minLod = 0.0f; - ci.maxLod = 0.0f; - VkSampler sampler = VK_NULL_HANDLE; - vkd.CreateSampler(device, &ci, nullptr, &sampler); - return sampler; -} - -} // namespace dis diff --git a/app/src/main/cpp/winlator/vk/dis/dis.hpp b/app/src/main/cpp/winlator/vk/dis/dis.hpp deleted file mode 100644 index 413ff5913..000000000 --- a/app/src/main/cpp/winlator/vk/dis/dis.hpp +++ /dev/null @@ -1,100 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later -// -// DIS (Dense Inverse Search) optical-flow frame interpolation, a Vulkan compute port of the -// WebGL2 implementation in dis_flow_webgl2_2.html (OpenCV dis_flow.cpp + variational -// refinement). Self-contained: shaders are authored in GLSL and compiled at build time, so no -// external shader payload (e.g., Lossless.dll) is required. - -#pragma once - -#include -#include -#include - -#include "../vk_dispatch.h" - -#define DIS_MAX_LEVELS 8u -#define DIS_MAX_TARGETS 7u - -namespace dis { - -class DisImage { -public: - DisImage() = default; - DisImage(VkDevice device, VkExtent2D extent, VkFormat format, VkImageUsageFlags usage, - VkImageLayout initial_layout = VK_IMAGE_LAYOUT_GENERAL); - ~DisImage(); - - DisImage(const DisImage&) = delete; - DisImage& operator=(const DisImage&) = delete; - DisImage(DisImage&& other) noexcept; - DisImage& operator=(DisImage&& other) noexcept; - - [[nodiscard]] VkImage Handle() const { return image; } - [[nodiscard]] VkImageView View() const { return view; } - [[nodiscard]] VkExtent2D Extent() const { return extent; } - [[nodiscard]] VkFormat Format() const { return format; } - [[nodiscard]] bool Valid() const { return image != VK_NULL_HANDLE; } - -private: - void Release(); - - VkDevice device{VK_NULL_HANDLE}; - VkImage image{VK_NULL_HANDLE}; - VkImageView view{VK_NULL_HANDLE}; - VkDeviceMemory memory{VK_NULL_HANDLE}; - VkExtent2D extent{}; - VkFormat format{VK_FORMAT_UNDEFINED}; -}; - -class DisPass { -public: - DisPass() = default; - DisPass(VkDevice device, const uint32_t* spirv, size_t spirv_size, - const std::vector& bindings, - VkShaderStageFlagBits stage = VK_SHADER_STAGE_COMPUTE_BIT, - VkPushConstantRange push = {}); - ~DisPass(); - - DisPass(const DisPass&) = delete; - DisPass& operator=(const DisPass&) = delete; - DisPass(DisPass&& other) noexcept; - DisPass& operator=(DisPass&& other) noexcept; - - [[nodiscard]] VkDescriptorSetLayout SetLayout() const { return descriptor_set_layout; } - [[nodiscard]] VkPipeline Pipeline() const { return pipeline; } - [[nodiscard]] VkPipelineLayout PipelineLayout() const { return pipeline_layout; } - [[nodiscard]] bool Valid() const { return pipeline != VK_NULL_HANDLE; } - - void BindCompute(VkCommandBuffer cmd, VkDescriptorSet set) const; - void Push(VkCommandBuffer cmd, VkShaderStageFlags stage, uint32_t size, const void* data) const; - -private: - void Release(); - - VkDevice device{VK_NULL_HANDLE}; - VkDescriptorSetLayout descriptor_set_layout{VK_NULL_HANDLE}; - VkPipelineLayout pipeline_layout{VK_NULL_HANDLE}; - VkPipeline pipeline{VK_NULL_HANDLE}; -}; - -struct DisSampledBinding { - VkDescriptorType type; - VkImageView view; - VkSampler sampler; -}; - -void SetDeviceMemoryProperties(const VkPhysicalDeviceMemoryProperties& props); - -VkDescriptorSetLayout CreateDescriptorSetLayout(VkDevice device, - const std::vector&); -VkDescriptorPool CreateDescriptorPool(VkDevice device, uint32_t max_sets); -VkDescriptorSet AllocateDescriptorSet(VkDevice device, VkDescriptorPool pool, - VkDescriptorSetLayout layout); -void WriteDescriptorSet(VkDevice device, VkDescriptorSet set, - const std::vector& bindings); -VkSampler CreateSampler(VkDevice device); - -uint32_t GroupCount(uint32_t size, uint32_t tile_shift); - -} // namespace dis diff --git a/app/src/main/cpp/winlator/vk/dis/dis_chain.cpp b/app/src/main/cpp/winlator/vk/dis/dis_chain.cpp deleted file mode 100644 index 9933831c0..000000000 --- a/app/src/main/cpp/winlator/vk/dis/dis_chain.cpp +++ /dev/null @@ -1,859 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "dis_chain.hpp" - -#include -#include -#include -#include - -#include "shaders/dis_gray_comp.spv.h" -#include "shaders/dis_downsample_comp.spv.h" -#include "shaders/dis_sobel_comp.spv.h" -#include "shaders/dis_structure_comp.spv.h" -#include "shaders/dis_propagate_comp.spv.h" -#include "shaders/dis_descent_comp.spv.h" -#include "shaders/dis_densify_comp.spv.h" -#include "shaders/dis_vr_prepare_comp.spv.h" -#include "shaders/dis_vr_weights_comp.spv.h" -#include "shaders/dis_vr_coeffs_comp.spv.h" -#include "shaders/dis_vr_sor_comp.spv.h" -#include "shaders/dis_vr_add_resize_comp.spv.h" -#include "shaders/dis_interp_comp.spv.h" - -namespace dis { - -namespace { - -constexpr uint32_t TILE_SHIFT = 3; // 8x8 workgroup - -// Algorithm parameters, matching dis_flow_webgl2_2.html defaults. -constexpr uint32_t PSZ = 8; -constexpr uint32_t PSTR = 4; -constexpr uint32_t FINEST = 2; -constexpr uint32_t GD_ITERS = 16; -constexpr uint32_t INNER_ITERS = GD_ITERS / 2; -constexpr uint32_t PROP_STEPS = 4; -constexpr uint32_t FP_ITERS = 5; -constexpr uint32_t SOR_ITERS = 5; -constexpr float ALPHA2 = 20.0f * 0.5f; -constexpr float DELTA2 = 5.0f * 0.5f; -constexpr float GAMMA2 = 10.0f * 0.5f; -constexpr float ZETA2 = 0.1f * 0.1f; -constexpr float EPS2 = 0.001f * 0.001f; -constexpr float OMEGA = 1.6f; -constexpr float DEBUG_MAG = 8.0f; - -struct GrayPC { - float dstSize[2]; - float rectOffset[2]; - float rectScale[2]; -}; - -struct StructurePC { - int32_t sparse[2]; - int32_t stride; - int32_t psz2; -}; - -struct PropPC { - float size[2]; - int32_t off[2]; - int32_t stride; -}; - -struct DescentPC { - float size[2]; - int32_t stride; - int32_t iters; -}; - -struct DensifyPC { - float size[2]; - int32_t sparse[2]; - int32_t stride; -}; - -struct VrPreparePC { - float size[2]; -}; - -struct VrWeightsPC { - float alpha2; - float eps2; -}; - -struct VrCoeffsPC { - float size[2]; - float delta2; - float gamma2; - float zeta2; - float eps2; -}; - -struct VrSorPC { - float omega; - int32_t parity; -}; - -struct VrAddPC { - float scale; -}; - -struct InterpPC { - float rectOffset[2]; - float rectScale[2]; - float invFlowSize[2]; - float t; - float mag; - int32_t debugMode; -}; - -VkDescriptorSetLayoutBinding Binding(uint32_t binding, VkDescriptorType type, - VkShaderStageFlags stage = VK_SHADER_STAGE_COMPUTE_BIT) { - VkDescriptorSetLayoutBinding b{}; - b.binding = binding; - b.descriptorType = type; - b.descriptorCount = 1; - b.stageFlags = stage; - return b; -} - -void ImageBarrier(VkCommandBuffer cmd, VkImage image, VkAccessFlags src_access, - VkAccessFlags dst_access, VkPipelineStageFlags src_stage, - VkPipelineStageFlags dst_stage) { - VkImageMemoryBarrier b{}; - b.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - b.srcAccessMask = src_access; - b.dstAccessMask = dst_access; - b.oldLayout = VK_IMAGE_LAYOUT_GENERAL; - b.newLayout = VK_IMAGE_LAYOUT_GENERAL; - b.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - b.image = image; - b.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - b.subresourceRange.levelCount = 1; - b.subresourceRange.layerCount = 1; - vkd.CmdPipelineBarrier(cmd, src_stage, dst_stage, 0, 0, nullptr, 0, nullptr, 1, &b); -} - -void ComputeToComputeBarrier(VkCommandBuffer cmd, VkImage image, VkAccessFlags src_access, - VkAccessFlags dst_access) { - ImageBarrier(cmd, image, src_access, dst_access, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT); -} - -// The interp target (composite ring / storage-capable swapchain image) is fully overwritten every -// generation, so discard its previous contents rather than tracking its layout across frames. -void DiscardImage(VkCommandBuffer cmd, VkImage image) { - VkImageMemoryBarrier b{}; - b.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; - b.srcAccessMask = 0; - b.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT; - b.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; - b.newLayout = VK_IMAGE_LAYOUT_GENERAL; - b.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; - b.image = image; - b.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - b.subresourceRange.levelCount = 1; - b.subresourceRange.layerCount = 1; - vkd.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &b); -} - -void ClearImage(VkCommandBuffer cmd, VkImage image) { - VkClearColorValue clear{}; - VkImageSubresourceRange range{}; - range.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; - range.levelCount = 1; - range.layerCount = 1; - - ImageBarrier(cmd, image, VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT, - VK_ACCESS_TRANSFER_WRITE_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT); - vkd.CmdClearColorImage(cmd, image, VK_IMAGE_LAYOUT_GENERAL, &clear, 1, &range); - ImageBarrier(cmd, image, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, - VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT); -} - -const DisImage& LevelS(const DisChain::Level& level, uint32_t index) { - return index == 0 ? level.S0 : level.S1; -} - -const DisImage& LevelDW(const DisChain::Level& level, uint32_t index) { - return index == 0 ? level.dW0 : level.dW1; -} - -} // namespace - -DisChain::DisChain(VkDevice device_, VkPhysicalDevice physical_device_) - : device{device_}, physical_device{physical_device_} {} - -DisChain::~DisChain() { - DestroyResources(); -} - -void DisChain::DestroyResources() { - if (device == VK_NULL_HANDLE) return; - - if (sampler) vkd.DestroySampler(device, sampler, nullptr); - if (descriptor_pool) vkd.DestroyDescriptorPool(device, descriptor_pool, nullptr); - - sampler = VK_NULL_HANDLE; - descriptor_pool = VK_NULL_HANDLE; - - gray_sets = {}; - down_sets = {}; - sobel_sets = {}; - structure_sets = {}; - propagate_sets = {}; - descent_sets = {}; - densify_sets = {}; - vr_prepare_sets = {}; - vr_weight_sets = {}; - vr_coeff_sets = {}; - vr_sor_sets = {}; - vr_add_sets = {}; - interp_sets = {}; - - for (auto& slot : pyr_img) slot = {}; - for (auto& slot : pyr_grad) slot = {}; - for (auto& level : levels) level = Level{}; - flow_full = DisImage(); - fullres_views = {}; - content_rect = {}; - - valid = false; -} - -bool DisChain::Build(VkExtent2D flow_extent_, VkExtent2D target_extent_) { - DestroyResources(); - - if (flow_extent_.width == 0 || flow_extent_.height == 0) return false; - - VkPhysicalDeviceMemoryProperties props{}; - vkd.GetPhysicalDeviceMemoryProperties(physical_device, &props); - SetDeviceMemoryProperties(props); - - flow_extent = flow_extent_; - target_extent = target_extent_; - - // Level count as in dis_flow_webgl2_2.html: - // coarsest = min(floor(log2(max/(4*psz))+0.5), floor(log2(min/psz))), clamped to 0..6. - const double maxd = static_cast(std::max(flow_extent.width, flow_extent.height)); - const double mind = static_cast(std::min(flow_extent.width, flow_extent.height)); - int32_t coarsest = static_cast( - std::min(std::floor(std::log2(maxd / (4.0 * PSZ)) + 0.5), - std::floor(std::log2(mind / static_cast(PSZ))))); - if (coarsest < 0) coarsest = 0; - if (coarsest > 6) coarsest = 6; - if (coarsest >= static_cast(DIS_MAX_LEVELS)) { - coarsest = static_cast(DIS_MAX_LEVELS) - 1; - } - int32_t finest = std::min(static_cast(FINEST), coarsest); - - coarsest_level = static_cast(coarsest); - finest_level = static_cast(finest); - num_levels = coarsest_level + 1; - - descriptor_pool = CreateDescriptorPool(device, 1024); - if (descriptor_pool == VK_NULL_HANDLE) return false; - - sampler = CreateSampler(device); - if (sampler == VK_NULL_HANDLE) return false; - - // Compute passes. - gray_pass = DisPass(device, dis_gray_comp, dis_gray_comp_size, - {Binding(0, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(1, VK_DESCRIPTOR_TYPE_SAMPLER), - Binding(2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE)}, - VK_SHADER_STAGE_COMPUTE_BIT, - {VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(GrayPC)}); - down_pass = DisPass(device, dis_downsample_comp, dis_downsample_comp_size, - {Binding(0, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE)}); - sobel_pass = DisPass(device, dis_sobel_comp, dis_sobel_comp_size, - {Binding(0, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(1, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE)}); - structure_pass = DisPass(device, dis_structure_comp, dis_structure_comp_size, - {Binding(0, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE), - Binding(3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE), - Binding(4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE)}, - VK_SHADER_STAGE_COMPUTE_BIT, - {VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(StructurePC)}); - propagate_pass = DisPass(device, dis_propagate_comp, dis_propagate_comp_size, - {Binding(0, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE)}, - VK_SHADER_STAGE_COMPUTE_BIT, - {VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(PropPC)}); - descent_pass = DisPass(device, dis_descent_comp, dis_descent_comp_size, - {Binding(0, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(6, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE)}, - VK_SHADER_STAGE_COMPUTE_BIT, - {VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(DescentPC)}); - densify_pass = DisPass(device, dis_densify_comp, dis_densify_comp_size, - {Binding(0, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE)}, - VK_SHADER_STAGE_COMPUTE_BIT, - {VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(DensifyPC)}); - vr_prepare_pass = DisPass(device, dis_vr_prepare_comp, dis_vr_prepare_comp_size, - {Binding(0, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(3, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE)}, - VK_SHADER_STAGE_COMPUTE_BIT, - {VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(VrPreparePC)}); - vr_weights_pass = DisPass(device, dis_vr_weights_comp, dis_vr_weights_comp_size, - {Binding(0, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE)}, - VK_SHADER_STAGE_COMPUTE_BIT, - {VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(VrWeightsPC)}); - vr_coeffs_pass = DisPass(device, dis_vr_coeffs_comp, dis_vr_coeffs_comp_size, - {Binding(0, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(4, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(5, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(6, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE), - Binding(7, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE)}, - VK_SHADER_STAGE_COMPUTE_BIT, - {VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(VrCoeffsPC)}); - vr_sor_pass = DisPass(device, dis_vr_sor_comp, dis_vr_sor_comp_size, - {Binding(0, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(3, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE)}, - VK_SHADER_STAGE_COMPUTE_BIT, - {VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(VrSorPC)}); - vr_add_resize_pass = DisPass(device, dis_vr_add_resize_comp, dis_vr_add_resize_comp_size, - {Binding(0, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(2, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE)}, - VK_SHADER_STAGE_COMPUTE_BIT, - {VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(VrAddPC)}); - interp_pass = DisPass(device, dis_interp_comp, dis_interp_comp_size, - {Binding(0, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(1, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(2, VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE), - Binding(3, VK_DESCRIPTOR_TYPE_SAMPLER), - Binding(4, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE)}, - VK_SHADER_STAGE_COMPUTE_BIT, - {VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(InterpPC)}); - - if (!gray_pass.Valid() || !down_pass.Valid() || !sobel_pass.Valid() || - !structure_pass.Valid() || !propagate_pass.Valid() || !descent_pass.Valid() || - !densify_pass.Valid() || !vr_prepare_pass.Valid() || !vr_weights_pass.Valid() || - !vr_coeffs_pass.Valid() || !vr_sor_pass.Valid() || !vr_add_resize_pass.Valid() || - !interp_pass.Valid()) { - return false; - } - - const VkImageUsageFlags gray_usage = - VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT; - const VkImageUsageFlags grad_usage = - VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT; - const VkImageUsageFlags work_usage = - VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT; - const VkImageUsageFlags clear_usage = - VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT; - - for (uint32_t slot = 0; slot < 2; slot++) { - for (uint32_t lvl = 0; lvl < num_levels; lvl++) { - VkExtent2D e{std::max(1u, flow_extent.width >> lvl), - std::max(1u, flow_extent.height >> lvl)}; - pyr_img[slot][lvl] = - DisImage(device, e, VK_FORMAT_R16_SFLOAT, gray_usage); - if (!pyr_img[slot][lvl].Valid()) return false; - if (lvl >= finest_level) { - pyr_grad[slot][lvl] = - DisImage(device, e, VK_FORMAT_R16G16_SFLOAT, grad_usage); - if (!pyr_grad[slot][lvl].Valid()) return false; - } - } - } - - for (uint32_t lvl = finest_level; lvl < num_levels; lvl++) { - Level& L = levels[lvl]; - L.active = true; - L.index = lvl; - L.extent = {std::max(1u, flow_extent.width >> lvl), - std::max(1u, flow_extent.height >> lvl)}; - const uint32_t ws = L.extent.width >= PSZ ? 1 + (L.extent.width - PSZ) / PSTR : 1; - const uint32_t hs = L.extent.height >= PSZ ? 1 + (L.extent.height - PSZ) / PSTR : 1; - L.sparse = {ws, hs}; - - L.U = DisImage(device, L.extent, VK_FORMAT_R32G32_SFLOAT, clear_usage); - L.S0 = DisImage(device, L.sparse, VK_FORMAT_R32G32_SFLOAT, work_usage); - L.S1 = DisImage(device, L.sparse, VK_FORMAT_R32G32_SFLOAT, work_usage); - L.ST = DisImage(device, L.sparse, VK_FORMAT_R32G32B32A32_SFLOAT, work_usage); - L.ST2 = DisImage(device, L.sparse, VK_FORMAT_R32_SFLOAT, work_usage); - L.d1 = DisImage(device, L.extent, VK_FORMAT_R32G32B32A32_SFLOAT, work_usage); - L.A = DisImage(device, L.extent, VK_FORMAT_R32G32B32A32_SFLOAT, work_usage); - L.B = DisImage(device, L.extent, VK_FORMAT_R32G32_SFLOAT, work_usage); - L.wt = DisImage(device, L.extent, VK_FORMAT_R32_SFLOAT, work_usage); - L.dW0 = DisImage(device, L.extent, VK_FORMAT_R32G32_SFLOAT, clear_usage); - L.dW1 = DisImage(device, L.extent, VK_FORMAT_R32G32_SFLOAT, clear_usage); - - if (!L.U.Valid() || !L.S0.Valid() || !L.S1.Valid() || !L.ST.Valid() || !L.ST2.Valid() || - !L.d1.Valid() || !L.A.Valid() || !L.B.Valid() || !L.wt.Valid() || !L.dW0.Valid() || - !L.dW1.Valid()) { - return false; - } - } - - flow_full = DisImage(device, flow_extent, VK_FORMAT_R32G32_SFLOAT, clear_usage); - if (!flow_full.Valid()) return false; - - // Descriptor sets. gray/interp are written at frame time; everything else is static. - for (uint32_t slot = 0; slot < 2; slot++) { - gray_sets[slot] = AllocateDescriptorSet(device, descriptor_pool, gray_pass.SetLayout()); - for (uint32_t lvl = 0; lvl < num_levels; lvl++) { - if (lvl >= 1) { - down_sets[slot][lvl] = - AllocateDescriptorSet(device, descriptor_pool, down_pass.SetLayout()); - WriteDescriptorSet(device, down_sets[slot][lvl], - {{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, - pyr_img[slot][lvl - 1].View(), VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - pyr_img[slot][lvl].View(), VK_NULL_HANDLE}}); - } - if (lvl >= finest_level) { - sobel_sets[slot][lvl] = - AllocateDescriptorSet(device, descriptor_pool, sobel_pass.SetLayout()); - WriteDescriptorSet(device, sobel_sets[slot][lvl], - {{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, - pyr_img[slot][lvl].View(), VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - pyr_grad[slot][lvl].View(), VK_NULL_HANDLE}}); - } - } - } - - for (uint32_t slot = 0; slot < 2; slot++) { - const uint32_t other = 1 - slot; - for (uint32_t lvl = finest_level; lvl < num_levels; lvl++) { - Level& L = levels[lvl]; - const VkImageView img_prev = pyr_img[other][lvl].View(); - const VkImageView img_cur = pyr_img[slot][lvl].View(); - const VkImageView grad_prev = pyr_grad[other][lvl].View(); - - structure_sets[slot][lvl] = - AllocateDescriptorSet(device, descriptor_pool, structure_pass.SetLayout()); - WriteDescriptorSet(device, structure_sets[slot][lvl], - {{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, grad_prev, VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, L.U.View(), VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, L.ST.View(), VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, L.ST2.View(), VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, L.S0.View(), VK_NULL_HANDLE}}); - - vr_prepare_sets[slot][lvl] = - AllocateDescriptorSet(device, descriptor_pool, vr_prepare_pass.SetLayout()); - WriteDescriptorSet(device, vr_prepare_sets[slot][lvl], - {{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, img_prev, VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, img_cur, VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, L.U.View(), VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, L.d1.View(), VK_NULL_HANDLE}}); - - for (uint32_t src = 0; src < 2; src++) { - propagate_sets[slot][lvl][src] = - AllocateDescriptorSet(device, descriptor_pool, propagate_pass.SetLayout()); - WriteDescriptorSet(device, propagate_sets[slot][lvl][src], - {{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, img_prev, VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, img_cur, VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, grad_prev, VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, - LevelS(L, src).View(), VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - LevelS(L, 1 - src).View(), VK_NULL_HANDLE}}); - - descent_sets[slot][lvl][src] = - AllocateDescriptorSet(device, descriptor_pool, descent_pass.SetLayout()); - WriteDescriptorSet(device, descent_sets[slot][lvl][src], - {{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, img_prev, VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, img_cur, VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, grad_prev, VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, - LevelS(L, src).View(), VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, L.ST.View(), VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, L.ST2.View(), VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - LevelS(L, 1 - src).View(), VK_NULL_HANDLE}}); - - densify_sets[slot][lvl][src] = - AllocateDescriptorSet(device, descriptor_pool, densify_pass.SetLayout()); - WriteDescriptorSet(device, densify_sets[slot][lvl][src], - {{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, - LevelS(L, src).View(), VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, img_prev, VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, img_cur, VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, L.U.View(), VK_NULL_HANDLE}}); - } - - for (uint32_t dw = 0; dw < 2; dw++) { - vr_coeff_sets[slot][lvl][dw] = - AllocateDescriptorSet(device, descriptor_pool, vr_coeffs_pass.SetLayout()); - WriteDescriptorSet(device, vr_coeff_sets[slot][lvl][dw], - {{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, L.d1.View(), VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, - LevelDW(L, dw).View(), VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, L.U.View(), VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, L.wt.View(), VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, img_prev, VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, img_cur, VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, L.A.View(), VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, L.B.View(), VK_NULL_HANDLE}}); - } - } - } - - for (uint32_t lvl = finest_level; lvl < num_levels; lvl++) { - Level& L = levels[lvl]; - for (uint32_t dw = 0; dw < 2; dw++) { - vr_weight_sets[lvl][dw] = - AllocateDescriptorSet(device, descriptor_pool, vr_weights_pass.SetLayout()); - WriteDescriptorSet(device, vr_weight_sets[lvl][dw], - {{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, L.U.View(), VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, - LevelDW(L, dw).View(), VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, L.wt.View(), VK_NULL_HANDLE}}); - - vr_sor_sets[lvl][dw] = - AllocateDescriptorSet(device, descriptor_pool, vr_sor_pass.SetLayout()); - WriteDescriptorSet(device, vr_sor_sets[lvl][dw], - {{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, L.A.View(), VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, L.B.View(), VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, L.wt.View(), VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, - LevelDW(L, dw).View(), VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, - LevelDW(L, 1 - dw).View(), VK_NULL_HANDLE}}); - - const VkImageView dst = (lvl > finest_level) - ? levels[lvl - 1].U.View() - : flow_full.View(); - vr_add_sets[lvl][dw] = - AllocateDescriptorSet(device, descriptor_pool, vr_add_resize_pass.SetLayout()); - WriteDescriptorSet(device, vr_add_sets[lvl][dw], - {{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, L.U.View(), VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, - LevelDW(L, dw).View(), VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, dst, VK_NULL_HANDLE}}); - } - } - - for (uint32_t slot = 0; slot < 2; slot++) { - for (uint32_t target = 0; target < DIS_MAX_TARGETS; target++) { - interp_sets[slot][target] = - AllocateDescriptorSet(device, descriptor_pool, interp_pass.SetLayout()); - } - } - - frame_count = 0; - last_count = 0; - valid = true; - return true; -} - -void DisChain::Process(VkCommandBuffer cmd, VkImage source, VkImageView fullres_view_cur, - VkImageView fullres_view_prev, VkRect2D content_rect_in, - VkExtent2D target_extent) { - if (!valid) return; - - const uint32_t cur = static_cast(frame_count % 2); - const uint32_t prev = 1 - cur; - last_count = frame_count; - - fullres_views[cur] = fullres_view_cur; - fullres_views[prev] = fullres_view_prev; - - // Sanitize the content rectangle: empty means full frame; clamp to the target. - VkRect2D rect = content_rect_in; - if (rect.extent.width == 0 || rect.extent.height == 0) { - rect = VkRect2D{{0, 0}, target_extent}; - } else { - int32_t x0 = std::max(0, rect.offset.x); - int32_t y0 = std::max(0, rect.offset.y); - int32_t x1 = std::min(static_cast(target_extent.width), - rect.offset.x + static_cast(rect.extent.width)); - int32_t y1 = std::min(static_cast(target_extent.height), - rect.offset.y + static_cast(rect.extent.height)); - if (x1 <= x0 || y1 <= y0) { - rect = VkRect2D{{0, 0}, target_extent}; - } else { - rect.offset = {x0, y0}; - rect.extent = {static_cast(x1 - x0), static_cast(y1 - y0)}; - } - } - content_rect = rect; - - // The composite was just rendered as a color attachment; make it visible to compute sampling. - ImageBarrier(cmd, source, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT); - - // 1. Luminance pyramid (content rectangle only). - WriteDescriptorSet(device, gray_sets[cur], - {{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, fullres_view_cur, VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_SAMPLER, VK_NULL_HANDLE, sampler}, - {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, pyr_img[cur][0].View(), VK_NULL_HANDLE}}); - { - GrayPC pc{}; - pc.dstSize[0] = static_cast(flow_extent.width); - pc.dstSize[1] = static_cast(flow_extent.height); - pc.rectOffset[0] = static_cast(rect.offset.x) / static_cast(target_extent.width); - pc.rectOffset[1] = static_cast(rect.offset.y) / static_cast(target_extent.height); - pc.rectScale[0] = static_cast(rect.extent.width) / static_cast(target_extent.width); - pc.rectScale[1] = static_cast(rect.extent.height) / static_cast(target_extent.height); - gray_pass.BindCompute(cmd, gray_sets[cur]); - gray_pass.Push(cmd, VK_SHADER_STAGE_COMPUTE_BIT, sizeof(pc), &pc); - vkd.CmdDispatch(cmd, GroupCount(flow_extent.width, TILE_SHIFT), - GroupCount(flow_extent.height, TILE_SHIFT), 1); - ComputeToComputeBarrier(cmd, pyr_img[cur][0].Handle(), VK_ACCESS_SHADER_WRITE_BIT, - VK_ACCESS_SHADER_READ_BIT); - } - - for (uint32_t lvl = 1; lvl < num_levels; lvl++) { - down_pass.BindCompute(cmd, down_sets[cur][lvl]); - const VkExtent2D e = pyr_img[cur][lvl].Extent(); - vkd.CmdDispatch(cmd, GroupCount(e.width, TILE_SHIFT), GroupCount(e.height, TILE_SHIFT), 1); - ComputeToComputeBarrier(cmd, pyr_img[cur][lvl].Handle(), VK_ACCESS_SHADER_WRITE_BIT, - VK_ACCESS_SHADER_READ_BIT); - } - - for (uint32_t lvl = finest_level; lvl < num_levels; lvl++) { - sobel_pass.BindCompute(cmd, sobel_sets[cur][lvl]); - const VkExtent2D e = pyr_grad[cur][lvl].Extent(); - vkd.CmdDispatch(cmd, GroupCount(e.width, TILE_SHIFT), GroupCount(e.height, TILE_SHIFT), 1); - ComputeToComputeBarrier(cmd, pyr_grad[cur][lvl].Handle(), VK_ACCESS_SHADER_WRITE_BIT, - VK_ACCESS_SHADER_READ_BIT); - } - - if (frame_count >= 1) { - for (int32_t li = static_cast(coarsest_level); - li >= static_cast(finest_level); --li) { - const uint32_t i = static_cast(li); - Level& L = levels[i]; - - if (i == coarsest_level) { - ClearImage(cmd, L.U.Handle()); - } - - // Structure tensor + initial sparse flow. The set keyed by `cur` binds the - // previous frame's image and gradient (img/grad at slot prev), matching the - // reference's G0 = pyr0.grad. - structure_pass.BindCompute(cmd, structure_sets[cur][i]); - { - StructurePC pc{}; - pc.sparse[0] = static_cast(L.sparse.width); - pc.sparse[1] = static_cast(L.sparse.height); - pc.stride = static_cast(PSTR); - pc.psz2 = static_cast(PSZ / 2); - structure_pass.Push(cmd, VK_SHADER_STAGE_COMPUTE_BIT, sizeof(pc), &pc); - vkd.CmdDispatch(cmd, GroupCount(L.sparse.width, TILE_SHIFT), - GroupCount(L.sparse.height, TILE_SHIFT), 1); - } - ComputeToComputeBarrier(cmd, L.ST.Handle(), VK_ACCESS_SHADER_WRITE_BIT, - VK_ACCESS_SHADER_READ_BIT); - ComputeToComputeBarrier(cmd, L.ST2.Handle(), VK_ACCESS_SHADER_WRITE_BIT, - VK_ACCESS_SHADER_READ_BIT); - ComputeToComputeBarrier(cmd, L.S0.Handle(), VK_ACCESS_SHADER_WRITE_BIT, - VK_ACCESS_SHADER_READ_BIT); - - // Two outer scans (forward/backward) of propagation + Gauss-Newton descent. - uint32_t sIdx = 0; - for (uint32_t outer = 0; outer < 2; outer++) { - const int32_t sgn = outer == 0 ? -1 : 1; - int32_t d = 1; - for (uint32_t k = 0; k < PROP_STEPS; k++, d *= 2) { - propagate_pass.BindCompute(cmd, propagate_sets[cur][i][sIdx]); - PropPC pc{}; - pc.size[0] = static_cast(L.extent.width); - pc.size[1] = static_cast(L.extent.height); - pc.off[0] = sgn * d; - pc.off[1] = 0; - pc.stride = static_cast(PSTR); - propagate_pass.Push(cmd, VK_SHADER_STAGE_COMPUTE_BIT, sizeof(pc), &pc); - vkd.CmdDispatch(cmd, GroupCount(L.sparse.width, TILE_SHIFT), - GroupCount(L.sparse.height, TILE_SHIFT), 1); - sIdx ^= 1; - ComputeToComputeBarrier(cmd, LevelS(L, sIdx).Handle(), - VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); - } - - descent_pass.BindCompute(cmd, descent_sets[cur][i][sIdx]); - DescentPC pc{}; - pc.size[0] = static_cast(L.extent.width); - pc.size[1] = static_cast(L.extent.height); - pc.stride = static_cast(PSTR); - pc.iters = static_cast(INNER_ITERS); - descent_pass.Push(cmd, VK_SHADER_STAGE_COMPUTE_BIT, sizeof(pc), &pc); - vkd.CmdDispatch(cmd, GroupCount(L.sparse.width, TILE_SHIFT), - GroupCount(L.sparse.height, TILE_SHIFT), 1); - sIdx ^= 1; - ComputeToComputeBarrier(cmd, LevelS(L, sIdx).Handle(), - VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); - } - - // Densify into the level flow. - densify_pass.BindCompute(cmd, densify_sets[cur][i][sIdx]); - { - DensifyPC pc{}; - pc.size[0] = static_cast(L.extent.width); - pc.size[1] = static_cast(L.extent.height); - pc.sparse[0] = static_cast(L.sparse.width); - pc.sparse[1] = static_cast(L.sparse.height); - pc.stride = static_cast(PSTR); - densify_pass.Push(cmd, VK_SHADER_STAGE_COMPUTE_BIT, sizeof(pc), &pc); - vkd.CmdDispatch(cmd, GroupCount(L.extent.width, TILE_SHIFT), - GroupCount(L.extent.height, TILE_SHIFT), 1); - } - ComputeToComputeBarrier(cmd, L.U.Handle(), VK_ACCESS_SHADER_WRITE_BIT, - VK_ACCESS_SHADER_READ_BIT); - - // Variational refinement. - ClearImage(cmd, L.dW0.Handle()); - uint32_t dw = 0; - vr_prepare_pass.BindCompute(cmd, vr_prepare_sets[cur][i]); - { - VrPreparePC pc{}; - pc.size[0] = static_cast(L.extent.width); - pc.size[1] = static_cast(L.extent.height); - vr_prepare_pass.Push(cmd, VK_SHADER_STAGE_COMPUTE_BIT, sizeof(pc), &pc); - vkd.CmdDispatch(cmd, GroupCount(L.extent.width, TILE_SHIFT), - GroupCount(L.extent.height, TILE_SHIFT), 1); - } - ComputeToComputeBarrier(cmd, L.d1.Handle(), VK_ACCESS_SHADER_WRITE_BIT, - VK_ACCESS_SHADER_READ_BIT); - - for (uint32_t f = 0; f < FP_ITERS; f++) { - vr_weights_pass.BindCompute(cmd, vr_weight_sets[i][dw]); - { - VrWeightsPC pc{}; - pc.alpha2 = ALPHA2; - pc.eps2 = EPS2; - vr_weights_pass.Push(cmd, VK_SHADER_STAGE_COMPUTE_BIT, sizeof(pc), &pc); - vkd.CmdDispatch(cmd, GroupCount(L.extent.width, TILE_SHIFT), - GroupCount(L.extent.height, TILE_SHIFT), 1); - } - ComputeToComputeBarrier(cmd, L.wt.Handle(), VK_ACCESS_SHADER_WRITE_BIT, - VK_ACCESS_SHADER_READ_BIT); - - vr_coeffs_pass.BindCompute(cmd, vr_coeff_sets[cur][i][dw]); - { - VrCoeffsPC pc{}; - pc.size[0] = static_cast(L.extent.width); - pc.size[1] = static_cast(L.extent.height); - pc.delta2 = DELTA2; - pc.gamma2 = GAMMA2; - pc.zeta2 = ZETA2; - pc.eps2 = EPS2; - vr_coeffs_pass.Push(cmd, VK_SHADER_STAGE_COMPUTE_BIT, sizeof(pc), &pc); - vkd.CmdDispatch(cmd, GroupCount(L.extent.width, TILE_SHIFT), - GroupCount(L.extent.height, TILE_SHIFT), 1); - } - ComputeToComputeBarrier(cmd, L.A.Handle(), VK_ACCESS_SHADER_WRITE_BIT, - VK_ACCESS_SHADER_READ_BIT); - ComputeToComputeBarrier(cmd, L.B.Handle(), VK_ACCESS_SHADER_WRITE_BIT, - VK_ACCESS_SHADER_READ_BIT); - - for (uint32_t s = 0; s < SOR_ITERS; s++) { - for (int32_t parity = 0; parity < 2; parity++) { - vr_sor_pass.BindCompute(cmd, vr_sor_sets[i][dw]); - VrSorPC pc{}; - pc.omega = OMEGA; - pc.parity = parity; - vr_sor_pass.Push(cmd, VK_SHADER_STAGE_COMPUTE_BIT, sizeof(pc), &pc); - vkd.CmdDispatch(cmd, GroupCount(L.extent.width, TILE_SHIFT), - GroupCount(L.extent.height, TILE_SHIFT), 1); - dw ^= 1; - ComputeToComputeBarrier(cmd, LevelDW(L, dw).Handle(), - VK_ACCESS_SHADER_WRITE_BIT, - VK_ACCESS_SHADER_READ_BIT); - } - } - } - - // Refined flow -> next finer level's U (scale 2) or flow_full (scale 2^finest). - const bool to_flow_full = (i == finest_level); - const VkExtent2D dst_extent = to_flow_full ? flow_extent : levels[i - 1].extent; - const VkImage dst_image = to_flow_full ? flow_full.Handle() : levels[i - 1].U.Handle(); - - vr_add_resize_pass.BindCompute(cmd, vr_add_sets[i][dw]); - VrAddPC addPc{}; - addPc.scale = to_flow_full - ? std::pow(2.0f, static_cast(finest_level)) - : 2.0f; - vr_add_resize_pass.Push(cmd, VK_SHADER_STAGE_COMPUTE_BIT, sizeof(addPc), &addPc); - vkd.CmdDispatch(cmd, GroupCount(dst_extent.width, TILE_SHIFT), - GroupCount(dst_extent.height, TILE_SHIFT), 1); - ComputeToComputeBarrier(cmd, dst_image, VK_ACCESS_SHADER_WRITE_BIT, - VK_ACCESS_SHADER_READ_BIT); - } - } else { - ClearImage(cmd, flow_full.Handle()); - } - - frame_count++; -} - -void DisChain::GenerateInto(VkCommandBuffer cmd, uint32_t generation, uint32_t generation_count, - VkImage target_image, VkImageView target_view, - VkExtent2D target_extent) { - if (!valid) return; - - const uint32_t cur = static_cast(last_count % 2); - const uint32_t prev = 1 - cur; - - VkDescriptorSet set = interp_sets[cur][generation]; - WriteDescriptorSet(device, set, - {{VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, fullres_views[prev], VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, fullres_views[cur], VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, flow_full.View(), VK_NULL_HANDLE}, - {VK_DESCRIPTOR_TYPE_SAMPLER, VK_NULL_HANDLE, sampler}, - {VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, target_view, VK_NULL_HANDLE}}); - - InterpPC ipc{}; - ipc.rectOffset[0] = static_cast(content_rect.offset.x) / static_cast(target_extent.width); - ipc.rectOffset[1] = static_cast(content_rect.offset.y) / static_cast(target_extent.height); - ipc.rectScale[0] = static_cast(content_rect.extent.width) / static_cast(target_extent.width); - ipc.rectScale[1] = static_cast(content_rect.extent.height) / static_cast(target_extent.height); - ipc.invFlowSize[0] = 1.0f / static_cast(flow_extent.width); - ipc.invFlowSize[1] = 1.0f / static_cast(flow_extent.height); - ipc.t = static_cast(generation + 1) / static_cast(generation_count + 1); - ipc.mag = DEBUG_MAG; - ipc.debugMode = debug_mode; - - DiscardImage(cmd, target_image); - - interp_pass.BindCompute(cmd, set); - interp_pass.Push(cmd, VK_SHADER_STAGE_COMPUTE_BIT, sizeof(ipc), &ipc); - vkd.CmdDispatch(cmd, GroupCount(target_extent.width, TILE_SHIFT), - GroupCount(target_extent.height, TILE_SHIFT), 1); - - ImageBarrier(cmd, target_image, VK_ACCESS_SHADER_WRITE_BIT, - VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT); -} - -void DisChain::ForgetTargets() { - // The interp descriptor sets are rewritten for every generation; nothing to invalidate. -} - -} // namespace dis diff --git a/app/src/main/cpp/winlator/vk/dis/dis_chain.hpp b/app/src/main/cpp/winlator/vk/dis/dis_chain.hpp deleted file mode 100644 index 3aea7bd39..000000000 --- a/app/src/main/cpp/winlator/vk/dis/dis_chain.hpp +++ /dev/null @@ -1,118 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#pragma once - -#include - -#include "dis.hpp" - -namespace dis { - -// Vulkan compute port of the WebGL2 DIS implementation in dis_flow_webgl2_2.html -// (OpenCV dis_flow.cpp + variational_refinement.cpp). -class DisChain { -public: - DisChain(VkDevice device, VkPhysicalDevice physical_device); - ~DisChain(); - - DisChain(const DisChain&) = delete; - DisChain& operator=(const DisChain&) = delete; - - bool Build(VkExtent2D flow_extent, VkExtent2D target_extent); - bool Valid() const { return valid; } - - // Builds the current frame's pyramid and, once a previous frame exists, computes the flow - // between the previous and current composite. content_rect is the letterboxed game area in - // swapchain pixels: only that region is read, has its flow computed, and is interpolated. - void Process(VkCommandBuffer cmd, VkImage source, VkImageView fullres_view_cur, - VkImageView fullres_view_prev, VkRect2D content_rect, VkExtent2D target_extent); - - // Warps prev/cur along the flow field at timestamp (generation+1)/(generation_count+1) and - // writes the result into target_image. - void GenerateInto(VkCommandBuffer cmd, uint32_t generation, uint32_t generation_count, - VkImage target_image, VkImageView target_view, VkExtent2D target_extent); - - void ForgetTargets(); - - void SetDebugMode(int32_t mode) { debug_mode = mode; } - - [[nodiscard]] uint64_t FrameCount() const { return frame_count; } - - struct Level { - bool active{false}; - uint32_t index{0}; - VkExtent2D extent{}; - VkExtent2D sparse{}; - DisImage U; - DisImage S0, S1; - DisImage ST, ST2; - DisImage d1; - DisImage A, B; - DisImage wt; - DisImage dW0, dW1; - }; - -private: - void DestroyResources(); - - VkDevice device{VK_NULL_HANDLE}; - VkPhysicalDevice physical_device{VK_NULL_HANDLE}; - - VkDescriptorPool descriptor_pool{VK_NULL_HANDLE}; - VkSampler sampler{VK_NULL_HANDLE}; - - DisPass gray_pass; - DisPass down_pass; - DisPass sobel_pass; - DisPass structure_pass; - DisPass propagate_pass; - DisPass descent_pass; - DisPass densify_pass; - DisPass vr_prepare_pass; - DisPass vr_weights_pass; - DisPass vr_coeffs_pass; - DisPass vr_sor_pass; - DisPass vr_add_resize_pass; - DisPass interp_pass; - - uint32_t num_levels{0}; - uint32_t coarsest_level{0}; - uint32_t finest_level{0}; - VkExtent2D flow_extent{}; - VkExtent2D target_extent{}; - VkRect2D content_rect{}; - - // Double-buffered luminance/gradient pyramids (prev/cur). - std::array, 2> pyr_img; - std::array, 2> pyr_grad; - - std::array levels; - DisImage flow_full; - - // Borrowed full-resolution frame views (the presenter's composite ring), sampled by the - // interpolation pass so the displayed frame keeps native detail. - std::array fullres_views{}; - - using SetPair = std::array; - - std::array gray_sets{}; - std::array, 2> down_sets{}; - std::array, 2> sobel_sets{}; - std::array, 2> structure_sets{}; - std::array, 2> propagate_sets{}; - std::array, 2> descent_sets{}; - std::array, 2> densify_sets{}; - std::array, 2> vr_prepare_sets{}; - std::array, DIS_MAX_LEVELS> vr_weight_sets{}; - std::array, 2> vr_coeff_sets{}; - std::array, DIS_MAX_LEVELS> vr_sor_sets{}; - std::array, DIS_MAX_LEVELS> vr_add_sets{}; - std::array, 2> interp_sets{}; - - uint64_t frame_count{0}; - uint64_t last_count{0}; - int32_t debug_mode{0}; - bool valid{false}; -}; - -} // namespace dis diff --git a/app/src/main/cpp/winlator/vk/dis/vkr_dis.c b/app/src/main/cpp/winlator/vk/dis/vkr_dis.c new file mode 100644 index 000000000..66f72d3b6 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/dis/vkr_dis.c @@ -0,0 +1,1751 @@ +#include "vkr_dis.h" + +#include "../vk_dispatch.h" +#include "shaders/dis_luma_r16_comp.spv.h" +#include "shaders/dis_luma_r32_comp.spv.h" +#include "shaders/dis_gradient_comp.spv.h" +#include "shaders/dis_inverse_search_comp.spv.h" +#include "shaders/dis_propagate_comp.spv.h" +#include "shaders/dis_densify_comp.spv.h" +#include "shaders/dis_interpolate_comp.spv.h" +#include "shaders/dis_vr_prep_comp.spv.h" +#include "shaders/dis_vr_d1_comp.spv.h" +#include "shaders/dis_vr_d2_comp.spv.h" +#include "shaders/dis_vr_w_comp.spv.h" +#include "shaders/dis_vr_coef_comp.spv.h" +#include "shaders/dis_vr_sor_comp.spv.h" +#include "shaders/dis_vr_add_comp.spv.h" + +#include +#include +#include +#include +#include + +#include + +#define DIS_LOGI(...) __android_log_print(ANDROID_LOG_INFO, "VkrDis", __VA_ARGS__) +#define DIS_LOGW(...) __android_log_print(ANDROID_LOG_WARN, "VkrDis", __VA_ARGS__) + +#define DIS_LOCAL_SIZE 8u +#define DIS_PATCH_STRIDE 3u +#define DIS_MIN_EXTENT 16u + +#define DIS_DEFAULT_FLOW_MIN_SIDE 180u +#define DIS_FLOW_MIN_SIDE_FLOOR 64u +#define DIS_FLOW_MIN_SIDE_CEIL 1080u +#define DIS_MAX_LEVELS 8u +#define DIS_MAX_DESCRIPTOR_WRITES 256u + +#define DIS_SLOTS 3u + +#define DIS_PROP_STEPS_MAX 4u + +#define DIS_SRC_SMOOTHING 0.15f +#define DIS_SRC_STALE_NS 500000000ull +#define DIS_MIN_RATE_SAMPLES 12u + +#define DIS_PLAN_LOG_NS 5000000000ull + +#define DIS_RATIO_SLACK 0.12f + +#define DIS_MIN_GEN_RATIO 1.45f + +#define DIS_RATIO_HYST 0.05f + +#define DIS_VR_ALPHA 20.0f +#define DIS_VR_DELTA 5.0f +#define DIS_VR_GAMMA 10.0f +#define DIS_VR_OMEGA 1.6f +#define DIS_VR_ZETA 0.1f +#define DIS_VR_EPS 0.001f + +#define DIS_SET_SAMPLERS 5u +#define DIS_SET_STORAGE 1u +#define DIS_SHARED_SETS_PER_LEVEL 6u +#define DIS_VR_SHARED_SETS 7u +#define DIS_VR_SAMPLER_BINDINGS 8u +#define DIS_VR_STORAGE_BINDINGS 2u +#define DIS_VR_FIRST_STORAGE 8u + +typedef struct { + VkImage image; + VkDeviceMemory memory; + VkExtent2D extent; + VkFormat format; + uint32_t mip_levels; +} DisImage; + +typedef struct { + VkPipeline pipeline; +} DisPass; + +typedef struct { + VkWriteDescriptorSet w[DIS_MAX_DESCRIPTOR_WRITES]; + VkDescriptorImageInfo img[DIS_MAX_DESCRIPTOR_WRITES]; + uint32_t count; +} DisBatch; + +struct VkrDis { + VkDevice device; + VkPhysicalDevice physical_device; + VkPhysicalDeviceMemoryProperties mem_props; + + uint32_t flow_min_side; + uint32_t target_fps; + float refresh_rate; + + VkExtent2D built_extent; + VkExtent2D built_full_extent; + VkrDisContentRect content; + uint32_t built_min_side; + VkFormat built_format; + uint32_t levels; + bool built; + bool unavailable; + bool layouts_primed; + bool formats_audited; + bool manual_flow_filter; + bool debug_flow; + + DisImage color[DIS_SLOTS]; + DisImage flow_color[DIS_SLOTS]; + DisImage grad; + DisImage flow_luma[DIS_SLOTS]; + VkFormat luma_format; + DisImage flow_sparse[DIS_MAX_LEVELS]; + DisImage flow_sparse_b[DIS_MAX_LEVELS]; + DisImage flow_dense; + DisImage interp_out; + + DisImage vr_prep; + DisImage vr_d1; + DisImage vr_d2; + DisImage vr_A; + DisImage vr_B; + DisImage vr_wt; + DisImage vr_dw[2]; + DisImage flow_refined; + + VkImageView view_color[DIS_SLOTS]; + VkImageView view_flow_color[DIS_SLOTS][DIS_MAX_LEVELS]; + VkImageView view_flow_luma[DIS_SLOTS][DIS_MAX_LEVELS]; + VkImageView view_grad[DIS_MAX_LEVELS]; + VkImageView view_sparse[DIS_MAX_LEVELS]; + VkImageView view_sparse_b[DIS_MAX_LEVELS]; + VkImageView view_dense[DIS_MAX_LEVELS]; + VkImageView view_interp_out; + VkImageView view_vr_prep; + VkImageView view_vr_d1; + VkImageView view_vr_d2; + VkImageView view_vr_A; + VkImageView view_vr_B; + VkImageView view_vr_wt; + VkImageView view_vr_dw[2]; + VkImageView view_flow_refined; + + VkSampler sampler; + + VkDescriptorSetLayout set_layout; + VkPipelineLayout pipeline_layout; + VkDescriptorPool pool; + VkDescriptorSet luma_sets[DIS_SLOTS][DIS_MAX_LEVELS]; + VkDescriptorSet grad_sets[DIS_SLOTS][DIS_MAX_LEVELS]; + VkDescriptorSet inverse_sets[DIS_SLOTS][DIS_MAX_LEVELS]; + VkDescriptorSet densify_sets[DIS_SLOTS][DIS_MAX_LEVELS]; + VkDescriptorSet prop_ab_sets[DIS_SLOTS][DIS_MAX_LEVELS]; + VkDescriptorSet prop_ba_sets[DIS_SLOTS][DIS_MAX_LEVELS]; + VkDescriptorSet interp_sets[DIS_SLOTS]; + + VkDescriptorSetLayout vr_set_layout; + VkPipelineLayout vr_pipeline_layout; + VkDescriptorSet vr_prep_sets[DIS_SLOTS]; + VkDescriptorSet vr_d1_set; + VkDescriptorSet vr_d2_set; + VkDescriptorSet vr_w_set; + VkDescriptorSet vr_coef_set; + VkDescriptorSet vr_sor_ab_set; + VkDescriptorSet vr_sor_ba_set; + VkDescriptorSet vr_add_set; + + DisPass pass_luma; + DisPass pass_gradient; + DisPass pass_inverse; + DisPass pass_propagate; + DisPass pass_densify; + DisPass pass_interp; + DisPass pass_vr_prep; + DisPass pass_vr_d1; + DisPass pass_vr_d2; + DisPass pass_vr_w; + DisPass pass_vr_coef; + DisPass pass_vr_sor; + DisPass pass_vr_add; + + uint64_t frame_count; + int prev_idx; + int next_idx; + uint32_t active_slot; + uint32_t last_generations; + + uint64_t src_sample_ns; + uint64_t src_last_frames; + float src_frame_accum; + float src_time_accum; + float src_interval; + uint32_t src_samples; + + float smoothed_desired; + + int planned_gen; + uint32_t gen_high_streak; + uint32_t gen_low_streak; + + uint64_t plan_log_ns; + int plan_log_gen; +}; + +typedef struct { + float lesser; + float upper; + float normVal; +} DisGradientPC; + +typedef struct { + int level; + int coarseLevel; +} DisInversePC; + +typedef struct { + int dist; +} DisPropPC; + +typedef struct { + float t; + int debugMode; +} DisInterpPC; + +typedef struct { + float alpha2; + float eps2; +} DisVrWPC; + +typedef struct { + float delta2; + float gamma2; + float zeta2; + float eps2; +} DisVrCoefPC; + +typedef struct { + float omega; + int parity; +} DisVrSorPC; + +static uint64_t dis_now_ns(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (uint64_t)ts.tv_sec * 1000000000ULL + (uint64_t)ts.tv_nsec; +} + +static uint32_t dis_find_memory_type(VkrDis* d, uint32_t bits, VkMemoryPropertyFlags props) { + for (uint32_t i = 0; i < d->mem_props.memoryTypeCount; i++) { + if ((bits & (1u << i)) && + (d->mem_props.memoryTypes[i].propertyFlags & props) == props) { + return i; + } + } + return UINT32_MAX; +} + +static void dis_compute_barrier(VkCommandBuffer cmd) { + VkMemoryBarrier mb; + memset(&mb, 0, sizeof(mb)); + mb.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER; + mb.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT; + mb.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; + vkd.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, 1, &mb, 0, NULL, 0, NULL); +} + +static void dis_barrier(VkCommandBuffer cmd, VkImage image, VkImageLayout from, VkImageLayout to, + VkPipelineStageFlags src_stage, VkPipelineStageFlags dst_stage, + VkAccessFlags src_access, VkAccessFlags dst_access) { + VkImageMemoryBarrier b; + memset(&b, 0, sizeof(b)); + b.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + b.srcAccessMask = src_access; + b.dstAccessMask = dst_access; + b.oldLayout = from; + b.newLayout = to; + b.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + b.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + b.image = image; + b.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + b.subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; + b.subresourceRange.layerCount = 1; + vkd.CmdPipelineBarrier(cmd, src_stage, dst_stage, 0, 0, NULL, 0, NULL, 1, &b); +} + +static uint32_t dis_collect_images(VkrDis* d, DisImage** out, uint32_t cap) { + uint32_t n = 0; + #define DIS_PUSH(img) do { if (n < cap) out[n++] = (img); } while (0) + for (uint32_t s = 0; s < DIS_SLOTS; s++) { + DIS_PUSH(&d->color[s]); + DIS_PUSH(&d->flow_color[s]); + DIS_PUSH(&d->flow_luma[s]); + } + DIS_PUSH(&d->grad); + for (uint32_t l = 0; l < DIS_MAX_LEVELS; l++) { + DIS_PUSH(&d->flow_sparse[l]); + DIS_PUSH(&d->flow_sparse_b[l]); + } + DIS_PUSH(&d->flow_dense); + DIS_PUSH(&d->interp_out); + DIS_PUSH(&d->vr_prep); + DIS_PUSH(&d->vr_d1); + DIS_PUSH(&d->vr_d2); + DIS_PUSH(&d->vr_A); + DIS_PUSH(&d->vr_B); + DIS_PUSH(&d->vr_wt); + DIS_PUSH(&d->vr_dw[0]); + DIS_PUSH(&d->vr_dw[1]); + DIS_PUSH(&d->flow_refined); + #undef DIS_PUSH + return n; +} + +#define DIS_MAX_OWNED_IMAGES 40u + +static void dis_prime_layouts(VkrDis* d, VkCommandBuffer cmd) { + if (d->layouts_primed) return; + + DisImage* imgs[DIS_MAX_OWNED_IMAGES]; + const uint32_t n = dis_collect_images(d, imgs, DIS_MAX_OWNED_IMAGES); + + VkImageMemoryBarrier bars[DIS_MAX_OWNED_IMAGES]; + uint32_t count = 0; + for (uint32_t i = 0; i < n; i++) { + if (!imgs[i]->image) continue; + VkImageMemoryBarrier* b = &bars[count++]; + memset(b, 0, sizeof(*b)); + b->sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + b->srcAccessMask = 0; + b->dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT | + VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; + b->oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; + b->newLayout = VK_IMAGE_LAYOUT_GENERAL; + b->srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + b->dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + b->image = imgs[i]->image; + b->subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + b->subresourceRange.levelCount = VK_REMAINING_MIP_LEVELS; + b->subresourceRange.layerCount = 1; + } + if (count == 0) return; + + vkd.CmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + VK_PIPELINE_STAGE_TRANSFER_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + 0, 0, NULL, 0, NULL, count, bars); + d->layouts_primed = true; +} + +static VkFormat dis_pick_luma_format(VkrDis* d) { + const VkFormatFeatureFlags need = VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT | + VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | + VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT; + const VkFormat candidates[2] = {VK_FORMAT_R16_SFLOAT, VK_FORMAT_R32_SFLOAT}; + for (uint32_t i = 0; i < 2; i++) { + VkFormatProperties fp; + memset(&fp, 0, sizeof(fp)); + vkd.GetPhysicalDeviceFormatProperties(d->physical_device, candidates[i], &fp); + if ((fp.optimalTilingFeatures & need) == need) { + if (i != 0) DIS_LOGI("R16F unusable for the luminance plane; using R32F"); + return candidates[i]; + } + } + return VK_FORMAT_UNDEFINED; +} + +static bool dis_create_image(VkrDis* d, DisImage* out, uint32_t w, uint32_t h, VkFormat format, + uint32_t mip_levels, VkImageUsageFlags usage) { + memset(out, 0, sizeof(*out)); + out->extent.width = w; + out->extent.height = h; + out->format = format; + out->mip_levels = mip_levels; + + VkImageCreateInfo ic; + memset(&ic, 0, sizeof(ic)); + ic.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + ic.imageType = VK_IMAGE_TYPE_2D; + ic.format = format; + ic.extent.width = w; + ic.extent.height = h; + ic.extent.depth = 1; + ic.mipLevels = mip_levels; + ic.arrayLayers = 1; + ic.samples = VK_SAMPLE_COUNT_1_BIT; + ic.tiling = VK_IMAGE_TILING_OPTIMAL; + ic.usage = usage; + ic.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + ic.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + if (vkd.CreateImage(d->device, &ic, NULL, &out->image) != VK_SUCCESS) return false; + + VkMemoryRequirements mr; + vkd.GetImageMemoryRequirements(d->device, out->image, &mr); + uint32_t type = dis_find_memory_type(d, mr.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + if (type == UINT32_MAX) return false; + VkMemoryAllocateInfo ai; + memset(&ai, 0, sizeof(ai)); + ai.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; + ai.allocationSize = mr.size; + ai.memoryTypeIndex = type; + if (vkd.AllocateMemory(d->device, &ai, NULL, &out->memory) != VK_SUCCESS) return false; + vkd.BindImageMemory(d->device, out->image, out->memory, 0); + return true; +} + +static void dis_destroy_image(VkrDis* d, DisImage* img) { + if (img->image) vkd.DestroyImage(d->device, img->image, NULL); + if (img->memory) vkd.FreeMemory(d->device, img->memory, NULL); + memset(img, 0, sizeof(*img)); +} + +static bool dis_create_view(VkrDis* d, VkImage image, VkFormat format, uint32_t base_level, + uint32_t level_count, VkImageView* out) { + VkImageViewCreateInfo vi; + memset(&vi, 0, sizeof(vi)); + vi.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + vi.image = image; + vi.viewType = VK_IMAGE_VIEW_TYPE_2D; + vi.format = format; + vi.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + vi.subresourceRange.baseMipLevel = base_level; + vi.subresourceRange.levelCount = level_count; + vi.subresourceRange.layerCount = 1; + return vkd.CreateImageView(d->device, &vi, NULL, out) == VK_SUCCESS; +} + +static void dis_destroy_view(VkrDis* d, VkImageView* view) { + if (*view) vkd.DestroyImageView(d->device, *view, NULL); + *view = VK_NULL_HANDLE; +} + +static VkPipeline dis_create_compute_pipeline_with_layout(VkrDis* d, const uint32_t* code, + size_t code_size, VkPipelineLayout layout, + const VkSpecializationInfo* spec) { + VkShaderModuleCreateInfo smi; + memset(&smi, 0, sizeof(smi)); + smi.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; + smi.codeSize = code_size; + smi.pCode = code; + VkShaderModule sm; + if (vkd.CreateShaderModule(d->device, &smi, NULL, &sm) != VK_SUCCESS) return VK_NULL_HANDLE; + + VkPipelineShaderStageCreateInfo stage; + memset(&stage, 0, sizeof(stage)); + stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + stage.stage = VK_SHADER_STAGE_COMPUTE_BIT; + stage.module = sm; + stage.pName = "main"; + stage.pSpecializationInfo = spec; + + VkComputePipelineCreateInfo pci; + memset(&pci, 0, sizeof(pci)); + pci.sType = VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO; + pci.stage = stage; + pci.layout = layout; + + VkPipeline pipeline; + VkResult res = vkd.CreateComputePipelines(d->device, VK_NULL_HANDLE, 1, &pci, NULL, &pipeline); + vkd.DestroyShaderModule(d->device, sm, NULL); + return res == VK_SUCCESS ? pipeline : VK_NULL_HANDLE; +} + +static VkPipeline dis_create_compute_pipeline(VkrDis* d, const uint32_t* code, size_t code_size) { + return dis_create_compute_pipeline_with_layout(d, code, code_size, d->pipeline_layout, NULL); +} + +static bool dis_create_pipelines(VkrDis* d) { + VkDescriptorSetLayoutBinding bindings[6]; + memset(bindings, 0, sizeof(bindings)); + for (uint32_t i = 0; i < 5; i++) { + bindings[i].binding = i; + bindings[i].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + bindings[i].descriptorCount = 1; + bindings[i].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + } + bindings[5].binding = 5; + bindings[5].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; + bindings[5].descriptorCount = 1; + bindings[5].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + + VkDescriptorSetLayoutCreateInfo li; + memset(&li, 0, sizeof(li)); + li.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + li.bindingCount = 6; + li.pBindings = bindings; + if (vkd.CreateDescriptorSetLayout(d->device, &li, NULL, &d->set_layout) != VK_SUCCESS) { + return false; + } + + VkPushConstantRange pcr; + memset(&pcr, 0, sizeof(pcr)); + pcr.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + pcr.offset = 0; + pcr.size = 32; + + VkPipelineLayoutCreateInfo pli; + memset(&pli, 0, sizeof(pli)); + pli.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + pli.setLayoutCount = 1; + pli.pSetLayouts = &d->set_layout; + pli.pushConstantRangeCount = 1; + pli.pPushConstantRanges = &pcr; + if (vkd.CreatePipelineLayout(d->device, &pli, NULL, &d->pipeline_layout) != VK_SUCCESS) { + return false; + } + + const uint32_t shared_sets = DIS_SLOTS * DIS_MAX_LEVELS * DIS_SHARED_SETS_PER_LEVEL + + DIS_SLOTS; + const uint32_t vr_sets = DIS_SLOTS + + DIS_VR_SHARED_SETS; + const uint32_t total_sets = shared_sets + vr_sets; + + VkDescriptorPoolSize sizes[2]; + memset(sizes, 0, sizeof(sizes)); + sizes[0].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + sizes[0].descriptorCount = shared_sets * DIS_SET_SAMPLERS + + vr_sets * DIS_VR_SAMPLER_BINDINGS; + sizes[1].type = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; + sizes[1].descriptorCount = shared_sets * DIS_SET_STORAGE + + vr_sets * DIS_VR_STORAGE_BINDINGS; + VkDescriptorPoolCreateInfo pci; + memset(&pci, 0, sizeof(pci)); + pci.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + pci.maxSets = total_sets; + pci.poolSizeCount = 2; + pci.pPoolSizes = sizes; + if (vkd.CreateDescriptorPool(d->device, &pci, NULL, &d->pool) != VK_SUCCESS) { + return false; + } + + VkDescriptorSetLayoutBinding vr_bindings[DIS_VR_SAMPLER_BINDINGS + DIS_VR_STORAGE_BINDINGS]; + memset(vr_bindings, 0, sizeof(vr_bindings)); + for (uint32_t i = 0; i < DIS_VR_SAMPLER_BINDINGS; i++) { + vr_bindings[i].binding = i; + vr_bindings[i].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + vr_bindings[i].descriptorCount = 1; + vr_bindings[i].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + } + for (uint32_t i = 0; i < DIS_VR_STORAGE_BINDINGS; i++) { + vr_bindings[DIS_VR_SAMPLER_BINDINGS + i].binding = DIS_VR_FIRST_STORAGE + i; + vr_bindings[DIS_VR_SAMPLER_BINDINGS + i].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; + vr_bindings[DIS_VR_SAMPLER_BINDINGS + i].descriptorCount = 1; + vr_bindings[DIS_VR_SAMPLER_BINDINGS + i].stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + } + + VkDescriptorSetLayoutCreateInfo vr_li; + memset(&vr_li, 0, sizeof(vr_li)); + vr_li.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; + vr_li.bindingCount = DIS_VR_SAMPLER_BINDINGS + DIS_VR_STORAGE_BINDINGS; + vr_li.pBindings = vr_bindings; + if (vkd.CreateDescriptorSetLayout(d->device, &vr_li, NULL, &d->vr_set_layout) != VK_SUCCESS) { + return false; + } + + VkPushConstantRange vr_pcr; + memset(&vr_pcr, 0, sizeof(vr_pcr)); + vr_pcr.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT; + vr_pcr.offset = 0; + vr_pcr.size = 32; + + VkPipelineLayoutCreateInfo vr_pli; + memset(&vr_pli, 0, sizeof(vr_pli)); + vr_pli.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; + vr_pli.setLayoutCount = 1; + vr_pli.pSetLayouts = &d->vr_set_layout; + vr_pli.pushConstantRangeCount = 1; + vr_pli.pPushConstantRanges = &vr_pcr; + if (vkd.CreatePipelineLayout(d->device, &vr_pli, NULL, &d->vr_pipeline_layout) != VK_SUCCESS) { + return false; + } + + d->pass_luma.pipeline = d->luma_format == VK_FORMAT_R16_SFLOAT + ? dis_create_compute_pipeline(d, dis_luma_r16_comp, dis_luma_r16_comp_size) + : dis_create_compute_pipeline(d, dis_luma_r32_comp, dis_luma_r32_comp_size); + d->pass_gradient.pipeline = dis_create_compute_pipeline(d, dis_gradient_comp, dis_gradient_comp_size); + d->pass_inverse.pipeline = dis_create_compute_pipeline(d, dis_inverse_search_comp, dis_inverse_search_comp_size); + d->pass_propagate.pipeline = dis_create_compute_pipeline(d, dis_propagate_comp, dis_propagate_comp_size); + d->pass_densify.pipeline = dis_create_compute_pipeline(d, dis_densify_comp, dis_densify_comp_size); + const VkBool32 manual_filter = d->manual_flow_filter ? 1u : 0u; + VkSpecializationMapEntry spec_entry; + memset(&spec_entry, 0, sizeof(spec_entry)); + spec_entry.constantID = 0; + spec_entry.offset = 0; + spec_entry.size = sizeof(manual_filter); + VkSpecializationInfo spec; + memset(&spec, 0, sizeof(spec)); + spec.mapEntryCount = 1; + spec.pMapEntries = &spec_entry; + spec.dataSize = sizeof(manual_filter); + spec.pData = &manual_filter; + d->pass_interp.pipeline = dis_create_compute_pipeline_with_layout( + d, dis_interpolate_comp, dis_interpolate_comp_size, d->pipeline_layout, &spec); + + d->pass_vr_prep.pipeline = dis_create_compute_pipeline_with_layout(d, dis_vr_prep_comp, dis_vr_prep_comp_size, d->vr_pipeline_layout, NULL); + d->pass_vr_d1.pipeline = dis_create_compute_pipeline_with_layout(d, dis_vr_d1_comp, dis_vr_d1_comp_size, d->vr_pipeline_layout, NULL); + d->pass_vr_d2.pipeline = dis_create_compute_pipeline_with_layout(d, dis_vr_d2_comp, dis_vr_d2_comp_size, d->vr_pipeline_layout, NULL); + d->pass_vr_w.pipeline = dis_create_compute_pipeline_with_layout(d, dis_vr_w_comp, dis_vr_w_comp_size, d->vr_pipeline_layout, NULL); + d->pass_vr_coef.pipeline = dis_create_compute_pipeline_with_layout(d, dis_vr_coef_comp, dis_vr_coef_comp_size, d->vr_pipeline_layout, NULL); + d->pass_vr_sor.pipeline = dis_create_compute_pipeline_with_layout(d, dis_vr_sor_comp, dis_vr_sor_comp_size, d->vr_pipeline_layout, NULL); + d->pass_vr_add.pipeline = dis_create_compute_pipeline_with_layout(d, dis_vr_add_comp, dis_vr_add_comp_size, d->vr_pipeline_layout, NULL); + + if (!d->pass_gradient.pipeline || !d->pass_inverse.pipeline || !d->pass_propagate.pipeline || + !d->pass_densify.pipeline || !d->pass_interp.pipeline || + !d->pass_vr_prep.pipeline || !d->pass_vr_d1.pipeline || !d->pass_vr_d2.pipeline || + !d->pass_vr_w.pipeline || !d->pass_vr_coef.pipeline || !d->pass_vr_sor.pipeline || + !d->pass_vr_add.pipeline) { + return false; + } + return true; +} + +static bool dis_create_sampler(VkrDis* d) { + VkSamplerCreateInfo si; + memset(&si, 0, sizeof(si)); + si.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; + si.magFilter = VK_FILTER_LINEAR; + si.minFilter = VK_FILTER_LINEAR; + si.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST; + si.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + si.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + si.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; + si.minLod = 0.0f; + si.maxLod = (float)DIS_MAX_LEVELS; + return vkd.CreateSampler(d->device, &si, NULL, &d->sampler) == VK_SUCCESS; +} + +static void dis_flow_extent(uint32_t min_side, uint32_t content_w, uint32_t content_h, + uint32_t* out_w, uint32_t* out_h) { + const uint32_t minor = content_w < content_h ? content_w : content_h; + if (minor == 0 || min_side == 0 || min_side >= minor) { + *out_w = content_w; + *out_h = content_h; + return; + } + const double k = (double)min_side / (double)minor; + *out_w = (uint32_t)((double)content_w * k + 0.5); + *out_h = (uint32_t)((double)content_h * k + 0.5); +} + +static uint32_t dis_levels_for(uint32_t w, uint32_t h) { + uint32_t levels = 1; + while ((w >> levels) >= 16 && (h >> levels) >= 16 && levels < DIS_MAX_LEVELS) { + levels++; + } + return levels; +} + +static uint32_t dis_sparse_extent(uint32_t extent) { + return extent > 8u ? 1u + (extent - 8u) / DIS_PATCH_STRIDE : 1u; +} + +static uint32_t dis_prop_steps_for(uint32_t level, uint32_t levels, uint32_t floor_steps) { + static const uint32_t profile[DIS_PROP_STEPS_MAX] = {4u, 3u, 2u, 1u}; + const uint32_t from_coarse = (levels - 1u) - level; + const uint32_t base = from_coarse < DIS_PROP_STEPS_MAX ? profile[from_coarse] : 1u; + return base > floor_steps ? base : floor_steps; +} + +typedef struct { + uint32_t vr_fixed_point; + uint32_t vr_sor; + uint32_t prop_floor; +} DisRefine; + +static DisRefine dis_refine_for(uint32_t generations) { + if (generations >= 3u) { + const DisRefine r = {2u, 5u, 2u}; + return r; + } + if (generations == 2u) { + const DisRefine r = {2u, 4u, 1u}; + return r; + } + const DisRefine r = {1u, 3u, 1u}; + return r; +} + +static void dis_batch_flush(VkrDis* d, DisBatch* b) { + if (b->count == 0) return; + vkd.UpdateDescriptorSets(d->device, b->count, b->w, 0, NULL); + b->count = 0; +} + +static void dis_batch_sampled(VkrDis* d, DisBatch* b, VkDescriptorSet set, uint32_t binding, + VkImageView view, VkSampler sampler) { + if (b->count == DIS_MAX_DESCRIPTOR_WRITES) dis_batch_flush(d, b); + VkDescriptorImageInfo* info = &b->img[b->count]; + memset(info, 0, sizeof(*info)); + info->sampler = sampler; + info->imageView = view; + info->imageLayout = VK_IMAGE_LAYOUT_GENERAL; + VkWriteDescriptorSet* w = &b->w[b->count]; + memset(w, 0, sizeof(*w)); + w->sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + w->dstSet = set; + w->dstBinding = binding; + w->descriptorCount = 1; + w->descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; + w->pImageInfo = info; + b->count++; +} + +static void dis_batch_storage(VkrDis* d, DisBatch* b, VkDescriptorSet set, uint32_t binding, + VkImageView view) { + if (b->count == DIS_MAX_DESCRIPTOR_WRITES) dis_batch_flush(d, b); + VkDescriptorImageInfo* info = &b->img[b->count]; + memset(info, 0, sizeof(*info)); + info->imageView = view; + info->imageLayout = VK_IMAGE_LAYOUT_GENERAL; + VkWriteDescriptorSet* w = &b->w[b->count]; + memset(w, 0, sizeof(*w)); + w->sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + w->dstSet = set; + w->dstBinding = binding; + w->descriptorCount = 1; + w->descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_IMAGE; + w->pImageInfo = info; + b->count++; +} + +static void dis_write_all_descriptors(VkrDis* d) { + DisBatch b; + memset(&b, 0, sizeof(b)); + const uint32_t L = d->levels; + const uint32_t coarse = L - 1; + + for (uint32_t s = 0; s < DIS_SLOTS; s++) { + const uint32_t next = s; + const uint32_t prev = (s + DIS_SLOTS - 1u) % DIS_SLOTS; + + for (uint32_t l = 0; l < L; l++) { + dis_batch_sampled(d, &b, d->luma_sets[s][l], 0, d->view_flow_color[next][l], d->sampler); + dis_batch_storage(d, &b, d->luma_sets[s][l], 5, d->view_flow_luma[next][l]); + + dis_batch_sampled(d, &b, d->grad_sets[s][l], 0, d->view_flow_luma[prev][l], d->sampler); + dis_batch_storage(d, &b, d->grad_sets[s][l], 5, d->view_grad[l]); + + dis_batch_sampled(d, &b, d->inverse_sets[s][l], 0, d->view_flow_luma[prev][l], d->sampler); + dis_batch_sampled(d, &b, d->inverse_sets[s][l], 1, d->view_flow_luma[next][l], d->sampler); + dis_batch_sampled(d, &b, d->inverse_sets[s][l], 2, d->view_grad[l], d->sampler); + dis_batch_sampled(d, &b, d->inverse_sets[s][l], 3, + d->view_dense[l + 1 < L ? l + 1 : coarse], d->sampler); + dis_batch_sampled(d, &b, d->inverse_sets[s][l], 4, d->view_dense[coarse], d->sampler); + dis_batch_storage(d, &b, d->inverse_sets[s][l], 5, d->view_sparse[l]); + + dis_batch_sampled(d, &b, d->prop_ab_sets[s][l], 0, d->view_flow_luma[prev][l], d->sampler); + dis_batch_sampled(d, &b, d->prop_ab_sets[s][l], 1, d->view_flow_luma[next][l], d->sampler); + dis_batch_sampled(d, &b, d->prop_ab_sets[s][l], 2, d->view_sparse[l], d->sampler); + dis_batch_storage(d, &b, d->prop_ab_sets[s][l], 5, d->view_sparse_b[l]); + + dis_batch_sampled(d, &b, d->prop_ba_sets[s][l], 0, d->view_flow_luma[prev][l], d->sampler); + dis_batch_sampled(d, &b, d->prop_ba_sets[s][l], 1, d->view_flow_luma[next][l], d->sampler); + dis_batch_sampled(d, &b, d->prop_ba_sets[s][l], 2, d->view_sparse_b[l], d->sampler); + dis_batch_storage(d, &b, d->prop_ba_sets[s][l], 5, d->view_sparse[l]); + + dis_batch_sampled(d, &b, d->densify_sets[s][l], 0, d->view_sparse[l], d->sampler); + dis_batch_sampled(d, &b, d->densify_sets[s][l], 1, d->view_flow_luma[prev][l], d->sampler); + dis_batch_sampled(d, &b, d->densify_sets[s][l], 2, d->view_flow_luma[next][l], d->sampler); + dis_batch_storage(d, &b, d->densify_sets[s][l], 5, d->view_dense[l]); + } + + dis_batch_sampled(d, &b, d->interp_sets[s], 0, d->view_color[prev], d->sampler); + dis_batch_sampled(d, &b, d->interp_sets[s], 1, d->view_color[next], d->sampler); + dis_batch_sampled(d, &b, d->interp_sets[s], 2, d->view_flow_refined, d->sampler); + dis_batch_storage(d, &b, d->interp_sets[s], 5, d->view_interp_out); + + dis_batch_sampled(d, &b, d->vr_prep_sets[s], 0, d->view_flow_color[prev][0], d->sampler); + dis_batch_sampled(d, &b, d->vr_prep_sets[s], 1, d->view_flow_color[next][0], d->sampler); + dis_batch_sampled(d, &b, d->vr_prep_sets[s], 2, d->view_dense[0], d->sampler); + dis_batch_storage(d, &b, d->vr_prep_sets[s], DIS_VR_FIRST_STORAGE, d->view_vr_prep); + dis_batch_storage(d, &b, d->vr_prep_sets[s], DIS_VR_FIRST_STORAGE + 1, d->view_vr_dw[0]); + } + + dis_batch_sampled(d, &b, d->vr_d1_set, 0, d->view_vr_prep, d->sampler); + dis_batch_storage(d, &b, d->vr_d1_set, DIS_VR_FIRST_STORAGE, d->view_vr_d1); + + dis_batch_sampled(d, &b, d->vr_d2_set, 0, d->view_vr_d1, d->sampler); + dis_batch_storage(d, &b, d->vr_d2_set, DIS_VR_FIRST_STORAGE, d->view_vr_d2); + + dis_batch_sampled(d, &b, d->vr_w_set, 0, d->view_dense[0], d->sampler); + dis_batch_sampled(d, &b, d->vr_w_set, 1, d->view_vr_dw[0], d->sampler); + dis_batch_storage(d, &b, d->vr_w_set, DIS_VR_FIRST_STORAGE, d->view_vr_wt); + + dis_batch_sampled(d, &b, d->vr_coef_set, 0, d->view_vr_prep, d->sampler); + dis_batch_sampled(d, &b, d->vr_coef_set, 1, d->view_vr_d1, d->sampler); + dis_batch_sampled(d, &b, d->vr_coef_set, 2, d->view_vr_d2, d->sampler); + dis_batch_sampled(d, &b, d->vr_coef_set, 3, d->view_vr_dw[0], d->sampler); + dis_batch_sampled(d, &b, d->vr_coef_set, 4, d->view_dense[0], d->sampler); + dis_batch_sampled(d, &b, d->vr_coef_set, 5, d->view_vr_wt, d->sampler); + dis_batch_storage(d, &b, d->vr_coef_set, DIS_VR_FIRST_STORAGE, d->view_vr_A); + dis_batch_storage(d, &b, d->vr_coef_set, DIS_VR_FIRST_STORAGE + 1, d->view_vr_B); + + dis_batch_sampled(d, &b, d->vr_sor_ab_set, 0, d->view_vr_A, d->sampler); + dis_batch_sampled(d, &b, d->vr_sor_ab_set, 1, d->view_vr_B, d->sampler); + dis_batch_sampled(d, &b, d->vr_sor_ab_set, 2, d->view_vr_wt, d->sampler); + dis_batch_sampled(d, &b, d->vr_sor_ab_set, 3, d->view_vr_dw[0], d->sampler); + dis_batch_storage(d, &b, d->vr_sor_ab_set, DIS_VR_FIRST_STORAGE, d->view_vr_dw[1]); + + dis_batch_sampled(d, &b, d->vr_sor_ba_set, 0, d->view_vr_A, d->sampler); + dis_batch_sampled(d, &b, d->vr_sor_ba_set, 1, d->view_vr_B, d->sampler); + dis_batch_sampled(d, &b, d->vr_sor_ba_set, 2, d->view_vr_wt, d->sampler); + dis_batch_sampled(d, &b, d->vr_sor_ba_set, 3, d->view_vr_dw[1], d->sampler); + dis_batch_storage(d, &b, d->vr_sor_ba_set, DIS_VR_FIRST_STORAGE, d->view_vr_dw[0]); + + dis_batch_sampled(d, &b, d->vr_add_set, 0, d->view_dense[0], d->sampler); + dis_batch_sampled(d, &b, d->vr_add_set, 1, d->view_vr_dw[0], d->sampler); + dis_batch_storage(d, &b, d->vr_add_set, DIS_VR_FIRST_STORAGE, d->view_flow_refined); + + dis_batch_flush(d, &b); +} + +static void dis_destroy_views(VkrDis* d) { + for (uint32_t s = 0; s < DIS_SLOTS; s++) dis_destroy_view(d, &d->view_color[s]); + dis_destroy_view(d, &d->view_interp_out); + dis_destroy_view(d, &d->view_vr_prep); + dis_destroy_view(d, &d->view_vr_d1); + dis_destroy_view(d, &d->view_vr_d2); + dis_destroy_view(d, &d->view_vr_A); + dis_destroy_view(d, &d->view_vr_B); + dis_destroy_view(d, &d->view_vr_wt); + dis_destroy_view(d, &d->view_vr_dw[0]); + dis_destroy_view(d, &d->view_vr_dw[1]); + dis_destroy_view(d, &d->view_flow_refined); + for (uint32_t l = 0; l < DIS_MAX_LEVELS; l++) { + for (uint32_t s = 0; s < DIS_SLOTS; s++) { + dis_destroy_view(d, &d->view_flow_color[s][l]); + dis_destroy_view(d, &d->view_flow_luma[s][l]); + } + dis_destroy_view(d, &d->view_grad[l]); + dis_destroy_view(d, &d->view_sparse[l]); + dis_destroy_view(d, &d->view_sparse_b[l]); + dis_destroy_view(d, &d->view_dense[l]); + } +} + +static void dis_destroy_images(VkrDis* d) { + dis_destroy_views(d); + for (uint32_t s = 0; s < DIS_SLOTS; s++) { + dis_destroy_image(d, &d->color[s]); + dis_destroy_image(d, &d->flow_color[s]); + dis_destroy_image(d, &d->flow_luma[s]); + } + dis_destroy_image(d, &d->grad); + for (uint32_t l = 0; l < DIS_MAX_LEVELS; l++) { + dis_destroy_image(d, &d->flow_sparse[l]); + dis_destroy_image(d, &d->flow_sparse_b[l]); + } + dis_destroy_image(d, &d->flow_dense); + dis_destroy_image(d, &d->interp_out); + dis_destroy_image(d, &d->vr_prep); + dis_destroy_image(d, &d->vr_d1); + dis_destroy_image(d, &d->vr_d2); + dis_destroy_image(d, &d->vr_A); + dis_destroy_image(d, &d->vr_B); + dis_destroy_image(d, &d->vr_wt); + dis_destroy_image(d, &d->vr_dw[0]); + dis_destroy_image(d, &d->vr_dw[1]); + dis_destroy_image(d, &d->flow_refined); +} + +static bool dis_create_resources(VkrDis* d, uint32_t w, uint32_t h, uint32_t full_w, + uint32_t full_h, VkFormat format) { + dis_destroy_images(d); + d->layouts_primed = false; + + const uint32_t L = d->levels; + for (uint32_t s = 0; s < DIS_SLOTS; s++) { + if (!dis_create_image(d, &d->color[s], full_w, full_h, format, 1, + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT)) return false; + if (!dis_create_image(d, &d->flow_color[s], w, h, format, L, + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | + VK_IMAGE_USAGE_TRANSFER_DST_BIT)) return false; + if (!dis_create_image(d, &d->flow_luma[s], w, h, d->luma_format, L, + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT)) return false; + } + if (!dis_create_image(d, &d->grad, w, h, VK_FORMAT_R32G32_SFLOAT, L, + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT)) return false; + for (uint32_t l = 0; l < L; l++) { + const uint32_t spw = dis_sparse_extent(w >> l); + const uint32_t sph = dis_sparse_extent(h >> l); + if (!dis_create_image(d, &d->flow_sparse[l], spw, sph, VK_FORMAT_R32G32B32A32_SFLOAT, 1, + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT)) return false; + if (!dis_create_image(d, &d->flow_sparse_b[l], spw, sph, VK_FORMAT_R32G32B32A32_SFLOAT, 1, + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT)) return false; + } + if (!dis_create_image(d, &d->flow_dense, w, h, VK_FORMAT_R32G32_SFLOAT, L, + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT)) return false; + if (!dis_create_image(d, &d->interp_out, full_w, full_h, VK_FORMAT_R8G8B8A8_UNORM, 1, + VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT)) { + return false; + } + + if (!dis_create_image(d, &d->vr_prep, w, h, VK_FORMAT_R32G32_SFLOAT, 1, + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT)) return false; + if (!dis_create_image(d, &d->vr_d1, w, h, VK_FORMAT_R32G32B32A32_SFLOAT, 1, + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT)) return false; + if (!dis_create_image(d, &d->vr_d2, w, h, VK_FORMAT_R32G32B32A32_SFLOAT, 1, + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT)) return false; + if (!dis_create_image(d, &d->vr_A, w, h, VK_FORMAT_R32G32B32A32_SFLOAT, 1, + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT)) return false; + if (!dis_create_image(d, &d->vr_B, w, h, VK_FORMAT_R32G32_SFLOAT, 1, + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT)) return false; + if (!dis_create_image(d, &d->vr_wt, w, h, VK_FORMAT_R32_SFLOAT, 1, + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT)) return false; + if (!dis_create_image(d, &d->vr_dw[0], w, h, VK_FORMAT_R32G32_SFLOAT, 1, + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT)) return false; + if (!dis_create_image(d, &d->vr_dw[1], w, h, VK_FORMAT_R32G32_SFLOAT, 1, + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT)) return false; + if (!dis_create_image(d, &d->flow_refined, w, h, VK_FORMAT_R32G32_SFLOAT, 1, + VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT)) return false; + + for (uint32_t s = 0; s < DIS_SLOTS; s++) { + if (!dis_create_view(d, d->color[s].image, format, 0, 1, &d->view_color[s])) return false; + } + for (uint32_t l = 0; l < L; l++) { + for (uint32_t s = 0; s < DIS_SLOTS; s++) { + if (!dis_create_view(d, d->flow_color[s].image, format, l, 1, + &d->view_flow_color[s][l])) return false; + if (!dis_create_view(d, d->flow_luma[s].image, d->luma_format, l, 1, + &d->view_flow_luma[s][l])) return false; + } + if (!dis_create_view(d, d->grad.image, VK_FORMAT_R32G32_SFLOAT, l, 1, &d->view_grad[l])) return false; + if (!dis_create_view(d, d->flow_sparse[l].image, VK_FORMAT_R32G32B32A32_SFLOAT, 0, 1, &d->view_sparse[l])) return false; + if (!dis_create_view(d, d->flow_sparse_b[l].image, VK_FORMAT_R32G32B32A32_SFLOAT, 0, 1, &d->view_sparse_b[l])) return false; + if (!dis_create_view(d, d->flow_dense.image, VK_FORMAT_R32G32_SFLOAT, l, 1, &d->view_dense[l])) return false; + } + if (!dis_create_view(d, d->interp_out.image, VK_FORMAT_R8G8B8A8_UNORM, 0, 1, &d->view_interp_out)) return false; + if (!dis_create_view(d, d->vr_prep.image, VK_FORMAT_R32G32_SFLOAT, 0, 1, &d->view_vr_prep)) return false; + if (!dis_create_view(d, d->vr_d1.image, VK_FORMAT_R32G32B32A32_SFLOAT, 0, 1, &d->view_vr_d1)) return false; + if (!dis_create_view(d, d->vr_d2.image, VK_FORMAT_R32G32B32A32_SFLOAT, 0, 1, &d->view_vr_d2)) return false; + if (!dis_create_view(d, d->vr_A.image, VK_FORMAT_R32G32B32A32_SFLOAT, 0, 1, &d->view_vr_A)) return false; + if (!dis_create_view(d, d->vr_B.image, VK_FORMAT_R32G32_SFLOAT, 0, 1, &d->view_vr_B)) return false; + if (!dis_create_view(d, d->vr_wt.image, VK_FORMAT_R32_SFLOAT, 0, 1, &d->view_vr_wt)) return false; + if (!dis_create_view(d, d->vr_dw[0].image, VK_FORMAT_R32G32_SFLOAT, 0, 1, &d->view_vr_dw[0])) return false; + if (!dis_create_view(d, d->vr_dw[1].image, VK_FORMAT_R32G32_SFLOAT, 0, 1, &d->view_vr_dw[1])) return false; + if (!dis_create_view(d, d->flow_refined.image, VK_FORMAT_R32G32_SFLOAT, 0, 1, &d->view_flow_refined)) return false; + + vkr_dis_reset(d); + dis_write_all_descriptors(d); + return true; +} + +static bool dis_alloc(VkrDis* d, VkDescriptorSetLayout layout, uint32_t count, + VkDescriptorSet* out) { + VkDescriptorSetLayout layouts[8]; + if (count > 8) return false; + for (uint32_t i = 0; i < count; i++) layouts[i] = layout; + VkDescriptorSetAllocateInfo ai; + memset(&ai, 0, sizeof(ai)); + ai.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; + ai.descriptorPool = d->pool; + ai.descriptorSetCount = count; + ai.pSetLayouts = layouts; + const VkResult res = vkd.AllocateDescriptorSets(d->device, &ai, out); + if (res != VK_SUCCESS) { + DIS_LOGW("DIS descriptor allocation failed (%d) asking for %u sets", (int)res, count); + return false; + } + return true; +} + +static bool dis_allocate_sets(VkrDis* d) { + for (uint32_t s = 0; s < DIS_SLOTS; s++) { + for (uint32_t l = 0; l < DIS_MAX_LEVELS; l++) { + VkDescriptorSet sets[6]; + if (!dis_alloc(d, d->set_layout, 6, sets)) return false; + d->grad_sets[s][l] = sets[0]; + d->inverse_sets[s][l] = sets[1]; + d->densify_sets[s][l] = sets[2]; + d->prop_ab_sets[s][l] = sets[3]; + d->prop_ba_sets[s][l] = sets[4]; + d->luma_sets[s][l] = sets[5]; + } + if (!dis_alloc(d, d->set_layout, 1, &d->interp_sets[s])) return false; + if (!dis_alloc(d, d->vr_set_layout, 1, &d->vr_prep_sets[s])) return false; + } + + VkDescriptorSet vr_sets[7]; + if (!dis_alloc(d, d->vr_set_layout, 7, vr_sets)) return false; + d->vr_d1_set = vr_sets[0]; + d->vr_d2_set = vr_sets[1]; + d->vr_w_set = vr_sets[2]; + d->vr_coef_set = vr_sets[3]; + d->vr_sor_ab_set = vr_sets[4]; + d->vr_sor_ba_set = vr_sets[5]; + d->vr_add_set = vr_sets[6]; + return true; +} + +static void dis_dispatch(VkrDis* d, VkCommandBuffer cmd, VkPipeline pipeline, VkDescriptorSet set, + uint32_t w, uint32_t h) { + vkd.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); + vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pipeline_layout, 0, 1, &set, + 0, NULL); + vkd.CmdDispatch(cmd, (w + DIS_LOCAL_SIZE - 1) / DIS_LOCAL_SIZE, + (h + DIS_LOCAL_SIZE - 1) / DIS_LOCAL_SIZE, 1); +} + +static void dis_blit_rect(VkCommandBuffer cmd, + VkImage src, int32_t sx, int32_t sy, uint32_t sw, uint32_t sh, + VkImage dst, int32_t dx, int32_t dy, uint32_t dw, uint32_t dh, + VkFilter filter) { + VkImageBlit blit; + memset(&blit, 0, sizeof(blit)); + blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.srcSubresource.layerCount = 1; + blit.srcOffsets[0].x = sx; + blit.srcOffsets[0].y = sy; + blit.srcOffsets[1].x = sx + (int32_t)sw; + blit.srcOffsets[1].y = sy + (int32_t)sh; + blit.srcOffsets[1].z = 1; + blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.dstSubresource.layerCount = 1; + blit.dstOffsets[0].x = dx; + blit.dstOffsets[0].y = dy; + blit.dstOffsets[1].x = dx + (int32_t)dw; + blit.dstOffsets[1].y = dy + (int32_t)dh; + blit.dstOffsets[1].z = 1; + vkd.CmdBlitImage(cmd, src, VK_IMAGE_LAYOUT_GENERAL, dst, VK_IMAGE_LAYOUT_GENERAL, 1, &blit, + filter); +} + +static void dis_blit_mip(VkCommandBuffer cmd, VkImage img, uint32_t src_level, uint32_t dst_level, + uint32_t src_w, uint32_t src_h, uint32_t dst_w, uint32_t dst_h) { + VkImageBlit blit; + memset(&blit, 0, sizeof(blit)); + blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.srcSubresource.mipLevel = src_level; + blit.srcSubresource.layerCount = 1; + blit.srcOffsets[1].x = (int32_t)src_w; + blit.srcOffsets[1].y = (int32_t)src_h; + blit.srcOffsets[1].z = 1; + blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + blit.dstSubresource.mipLevel = dst_level; + blit.dstSubresource.layerCount = 1; + blit.dstOffsets[1].x = (int32_t)dst_w; + blit.dstOffsets[1].y = (int32_t)dst_h; + blit.dstOffsets[1].z = 1; + vkd.CmdBlitImage(cmd, img, VK_IMAGE_LAYOUT_GENERAL, img, VK_IMAGE_LAYOUT_GENERAL, 1, &blit, + VK_FILTER_LINEAR); +} + +static const char* dis_format_name(VkFormat f) { + switch (f) { + case VK_FORMAT_R16_SFLOAT: return "R16_SFLOAT"; + case VK_FORMAT_R32_SFLOAT: return "R32_SFLOAT"; + case VK_FORMAT_R32G32_SFLOAT: return "R32G32_SFLOAT"; + case VK_FORMAT_R32G32B32A32_SFLOAT: return "R32G32B32A32_SFLOAT"; + case VK_FORMAT_R8G8B8A8_UNORM: return "R8G8B8A8_UNORM"; + case VK_FORMAT_UNDEFINED: return "none"; + default: return "format"; + } +} + +static void dis_missing_features(VkFormatFeatureFlags missing, char* out, size_t cap) { + out[0] = '\0'; + const struct { VkFormatFeatureFlags bit; const char* name; } names[] = { + {VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT, "STORAGE_IMAGE"}, + {VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT, "SAMPLED_IMAGE"}, + {VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT, "SAMPLED_IMAGE_FILTER_LINEAR"}, + {VK_FORMAT_FEATURE_BLIT_SRC_BIT, "BLIT_SRC"}, + {VK_FORMAT_FEATURE_BLIT_DST_BIT, "BLIT_DST"}, + }; + for (uint32_t i = 0; i < sizeof(names) / sizeof(names[0]); i++) { + if (!(missing & names[i].bit)) continue; + if (out[0]) strncat(out, "+", cap - strlen(out) - 1); + strncat(out, names[i].name, cap - strlen(out) - 1); + } + if (!out[0]) strncat(out, "none", cap - 1); +} + +static bool dis_audit_formats(VkrDis* d) { + const VkFormatFeatureFlags STORE = VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT; + const VkFormatFeatureFlags READ = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT; + const VkFormatFeatureFlags FILTER = VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT; + + if (d->luma_format == VK_FORMAT_UNDEFINED) { + DIS_LOGW("DIS needs a single-channel float plane it can both write and filter; " + "neither R16_SFLOAT nor R32_SFLOAT qualifies on this device"); + return false; + } + + const struct { + VkFormat format; + VkFormatFeatureFlags need; + const char* purpose; + } reqs[] = { + {VK_FORMAT_R32G32_SFLOAT, STORE | READ, "optical flow"}, + {VK_FORMAT_R32G32B32A32_SFLOAT, STORE | READ, "sparse flow and refinement"}, + {VK_FORMAT_R32_SFLOAT, STORE | READ, "refinement weights"}, + {VK_FORMAT_R8G8B8A8_UNORM, STORE | VK_FORMAT_FEATURE_BLIT_SRC_BIT, + "interpolated output"}, + {d->luma_format, STORE | READ | FILTER, "luminance plane"}, + }; + + char line[512]; + line[0] = '\0'; + bool ok = true; + + for (uint32_t i = 0; i < sizeof(reqs) / sizeof(reqs[0]); i++) { + VkFormatProperties fp; + memset(&fp, 0, sizeof(fp)); + vkd.GetPhysicalDeviceFormatProperties(d->physical_device, reqs[i].format, &fp); + const VkFormatFeatureFlags missing = reqs[i].need & ~fp.optimalTilingFeatures; + + char entry[96]; + snprintf(entry, sizeof(entry), "%s%s=%s", line[0] ? " " : "", + dis_format_name(reqs[i].format), missing ? "MISSING" : "ok"); + strncat(line, entry, sizeof(line) - strlen(line) - 1); + + if (missing) { + char names[256]; + dis_missing_features(missing, names, sizeof(names)); + DIS_LOGW("DIS needs %s on %s for %s, and this device does not report it", + names, dis_format_name(reqs[i].format), reqs[i].purpose); + ok = false; + } + } + + VkFormatProperties flow_fp; + memset(&flow_fp, 0, sizeof(flow_fp)); + vkd.GetPhysicalDeviceFormatProperties(d->physical_device, VK_FORMAT_R32G32_SFLOAT, &flow_fp); + d->manual_flow_filter = (flow_fp.optimalTilingFeatures & FILTER) == 0; + + DIS_LOGI("DIS format support: %s | flow filtering: %s", line, + d->manual_flow_filter ? "in shader (driver cannot filter R32G32_SFLOAT)" + : "sampler"); + return ok; +} + +VkrDis* vkr_dis_create(VkDevice device, VkPhysicalDevice physical_device) { + if (device == VK_NULL_HANDLE || physical_device == VK_NULL_HANDLE) return NULL; + + VkrDis* d = (VkrDis*)calloc(1, sizeof(VkrDis)); + d->device = device; + d->physical_device = physical_device; + d->flow_min_side = DIS_DEFAULT_FLOW_MIN_SIDE; + d->target_fps = 0; + d->refresh_rate = 0.0f; + d->plan_log_gen = -1; + vkd.GetPhysicalDeviceMemoryProperties(physical_device, &d->mem_props); + d->luma_format = dis_pick_luma_format(d); + if (!dis_audit_formats(d)) { + DIS_LOGW("DIS cannot run on this device's format support; frame generation stays off"); + vkr_dis_destroy(d); + return NULL; + } + + if (!dis_create_sampler(d) || !dis_create_pipelines(d)) { + DIS_LOGW("DIS shaders could not be built; frame generation stays off"); + vkr_dis_destroy(d); + return NULL; + } + if (!dis_allocate_sets(d)) { + DIS_LOGW("DIS descriptor sets could not be allocated; frame generation stays off"); + vkr_dis_destroy(d); + return NULL; + } + DIS_LOGI("DIS frame generation ready"); + return d; +} + +void vkr_dis_destroy(VkrDis* d) { + if (!d) return; + dis_destroy_images(d); + if (d->sampler) vkd.DestroySampler(d->device, d->sampler, NULL); + if (d->pass_luma.pipeline) vkd.DestroyPipeline(d->device, d->pass_luma.pipeline, NULL); + if (d->pass_gradient.pipeline) vkd.DestroyPipeline(d->device, d->pass_gradient.pipeline, NULL); + if (d->pass_inverse.pipeline) vkd.DestroyPipeline(d->device, d->pass_inverse.pipeline, NULL); + if (d->pass_propagate.pipeline) vkd.DestroyPipeline(d->device, d->pass_propagate.pipeline, NULL); + if (d->pass_densify.pipeline) vkd.DestroyPipeline(d->device, d->pass_densify.pipeline, NULL); + if (d->pass_interp.pipeline) vkd.DestroyPipeline(d->device, d->pass_interp.pipeline, NULL); + if (d->pass_vr_prep.pipeline) vkd.DestroyPipeline(d->device, d->pass_vr_prep.pipeline, NULL); + if (d->pass_vr_d1.pipeline) vkd.DestroyPipeline(d->device, d->pass_vr_d1.pipeline, NULL); + if (d->pass_vr_d2.pipeline) vkd.DestroyPipeline(d->device, d->pass_vr_d2.pipeline, NULL); + if (d->pass_vr_w.pipeline) vkd.DestroyPipeline(d->device, d->pass_vr_w.pipeline, NULL); + if (d->pass_vr_coef.pipeline) vkd.DestroyPipeline(d->device, d->pass_vr_coef.pipeline, NULL); + if (d->pass_vr_sor.pipeline) vkd.DestroyPipeline(d->device, d->pass_vr_sor.pipeline, NULL); + if (d->pass_vr_add.pipeline) vkd.DestroyPipeline(d->device, d->pass_vr_add.pipeline, NULL); + if (d->pool) vkd.DestroyDescriptorPool(d->device, d->pool, NULL); + if (d->pipeline_layout) vkd.DestroyPipelineLayout(d->device, d->pipeline_layout, NULL); + if (d->vr_pipeline_layout) vkd.DestroyPipelineLayout(d->device, d->vr_pipeline_layout, NULL); + if (d->set_layout) vkd.DestroyDescriptorSetLayout(d->device, d->set_layout, NULL); + if (d->vr_set_layout) vkd.DestroyDescriptorSetLayout(d->device, d->vr_set_layout, NULL); + free(d); +} + +void vkr_dis_configure(VkrDis* d, uint32_t flow_min_side, uint32_t target_fps, float refresh_rate) { + if (!d) return; + uint32_t side = flow_min_side < DIS_FLOW_MIN_SIDE_FLOOR ? DIS_FLOW_MIN_SIDE_FLOOR + : (flow_min_side > DIS_FLOW_MIN_SIDE_CEIL ? DIS_FLOW_MIN_SIDE_CEIL + : flow_min_side); + d->flow_min_side = side; + d->target_fps = target_fps; + d->refresh_rate = refresh_rate > 0.0f ? refresh_rate : 0.0f; +} + +void vkr_dis_set_debug_flow(VkrDis* d, bool debug_flow) { + if (!d) return; + d->debug_flow = debug_flow; +} + +bool vkr_dis_needs_rebuild(const VkrDis* d, uint32_t width, uint32_t height, VkFormat format, + VkrDisContentRect content) { + if (!d || d->unavailable) return false; + return !d->built || d->built_full_extent.width != width || + d->built_full_extent.height != height || d->built_format != format || + d->built_min_side != d->flow_min_side || + d->content.width != content.width || d->content.height != content.height; +} + +bool vkr_dis_prepare(VkrDis* d, uint32_t width, uint32_t height, VkFormat format, + VkrDisContentRect content) { + if (!d || d->unavailable) return false; + if (width == 0 || height == 0 || format == VK_FORMAT_UNDEFINED) return false; + + if (content.width < DIS_MIN_EXTENT || content.height < DIS_MIN_EXTENT || + content.x < 0 || content.y < 0 || + (uint32_t)content.x + content.width > width || + (uint32_t)content.y + content.height > height) { + content.x = 0; + content.y = 0; + content.width = width; + content.height = height; + } + d->content = content; + + uint32_t w, h; + dis_flow_extent(d->flow_min_side, content.width, content.height, &w, &h); + if (w < DIS_MIN_EXTENT) w = DIS_MIN_EXTENT; + if (h < DIS_MIN_EXTENT) h = DIS_MIN_EXTENT; + + const uint32_t levels = dis_levels_for(w, h); + + if (d->built && d->built_extent.width == w && d->built_extent.height == h && + d->built_full_extent.width == width && d->built_full_extent.height == height && + d->built_format == format && d->built_min_side == d->flow_min_side && d->levels == levels && + d->content.width == content.width && d->content.height == content.height) { + d->content.x = content.x; + d->content.y = content.y; + return true; + } + + if (!d->formats_audited) { + d->formats_audited = true; + const VkFormatFeatureFlags need = VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT | + VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT | + VK_FORMAT_FEATURE_BLIT_SRC_BIT | + VK_FORMAT_FEATURE_BLIT_DST_BIT; + VkFormatProperties fp; + memset(&fp, 0, sizeof(fp)); + vkd.GetPhysicalDeviceFormatProperties(d->physical_device, format, &fp); + const VkFormatFeatureFlags missing = need & ~fp.optimalTilingFeatures; + if (missing) { + char names[256]; + dis_missing_features(missing, names, sizeof(names)); + DIS_LOGW("DIS needs %s on the guest frame format (%d) and this device does not " + "report it; frame generation stays off", names, (int)format); + d->unavailable = true; + return false; + } + } + + d->levels = levels; + + if (!dis_create_resources(d, w, h, content.width, content.height, format)) { + DIS_LOGW("DIS resource build failed at %ux%u; frame generation unavailable", w, h); + dis_destroy_images(d); + d->unavailable = true; + return false; + } + + d->built_extent.width = w; + d->built_extent.height = h; + d->built_full_extent.width = width; + d->built_full_extent.height = height; + d->built_format = format; + d->built_min_side = d->flow_min_side; + d->built = true; + d->frame_count = 0; + d->prev_idx = 0; + d->next_idx = 0; + d->active_slot = 0; + d->last_generations = 0; + DIS_LOGI("DIS resources built at %ux%u (flow min side %u, %u levels); content rect " + "%dx%d+%d+%d inside a %ux%u composite", + w, h, d->flow_min_side, levels, (int)content.width, (int)content.height, + (int)content.x, (int)content.y, width, height); + return true; +} + +static void dis_track_source(VkrDis* d, uint64_t now, uint64_t source_frames) { + if (d->src_sample_ns == 0) { + d->src_sample_ns = now; + d->src_last_frames = source_frames; + return; + } + + const uint64_t dt = now - d->src_sample_ns; + if (dt == 0) return; + d->src_sample_ns = now; + + const uint64_t drawn = + source_frames > d->src_last_frames ? source_frames - d->src_last_frames : 0; + d->src_last_frames = source_frames; + + if (dt > DIS_SRC_STALE_NS) { + d->src_frame_accum = 0.0f; + d->src_time_accum = 0.0f; + d->src_interval = 0.0f; + d->src_samples = 0; + return; + } + + const float elapsed = (float)dt * 1.0e-9f; + d->src_frame_accum += ((float)drawn - d->src_frame_accum) * DIS_SRC_SMOOTHING; + d->src_time_accum += (elapsed - d->src_time_accum) * DIS_SRC_SMOOTHING; + d->src_interval = + d->src_frame_accum > 0.01f ? d->src_time_accum / d->src_frame_accum : 0.0f; + if (d->src_samples < DIS_MIN_RATE_SAMPLES) d->src_samples++; +} + +static int dis_gen_for_ratio(float ratio, uint32_t capacity) { + float outputs = ceilf(ratio - DIS_RATIO_SLACK); + if (outputs < 2.0f) outputs = 2.0f; + int gen = (int)outputs - 1; + if (gen < 1) gen = 1; + if (gen > (int)capacity) gen = (int)capacity; + return gen; +} + +static void dis_log_plan(VkrDis* d, uint64_t now, float source_rate, float desired, + float ratio, uint32_t capacity) { + if (d->planned_gen == d->plan_log_gen && now - d->plan_log_ns < DIS_PLAN_LOG_NS) return; + d->plan_log_gen = d->planned_gen; + d->plan_log_ns = now; + DIS_LOGI("DIS plan: source %.1f fps, target %.1f fps, ratio %.2f -> %d generated " + "(capacity %u, output %.1f fps)", + (double)source_rate, (double)desired, (double)ratio, d->planned_gen, capacity, + (double)(source_rate * (float)(d->planned_gen + 1))); +} + +uint32_t vkr_dis_plan(VkrDis* d, uint32_t capacity, uint64_t source_frames) { + if (!d || d->unavailable || !d->built) return 0; + if (capacity > VKR_DIS_MAX_GENERATIONS) capacity = VKR_DIS_MAX_GENERATIONS; + + const uint64_t now = dis_now_ns(); + dis_track_source(d, now, source_frames); + + if (capacity == 0 || d->frame_count < 2 || d->src_samples < DIS_MIN_RATE_SAMPLES || + d->src_interval <= 0.0f) { + d->planned_gen = 0; + d->gen_high_streak = 0; + d->gen_low_streak = 0; + return 0; + } + + const float source_rate = 1.0f / d->src_interval; + float desired = d->target_fps > 0 ? (float)d->target_fps : d->refresh_rate; + if (d->refresh_rate > 0.0f && desired > d->refresh_rate) desired = d->refresh_rate; + if (desired <= 0.0f) return 0; + + if (d->smoothed_desired <= 0.0f) { + d->smoothed_desired = desired; + } else { + d->smoothed_desired += (desired - d->smoothed_desired) * 0.25f; + } + const float eff_desired = d->smoothed_desired; + + const float ratio = eff_desired / source_rate; + + if (ratio <= 1.0f) { + d->planned_gen = 0; + d->gen_high_streak = 0; + d->gen_low_streak = 0; + dis_log_plan(d, now, source_rate, eff_desired, ratio, capacity); + return 0; + } + + const int cur = d->planned_gen > (int)capacity ? (int)capacity : d->planned_gen; + const bool generate = cur > 0 ? (ratio >= DIS_MIN_GEN_RATIO - DIS_RATIO_HYST) + : (ratio >= DIS_MIN_GEN_RATIO); + + int raw = 0; + if (generate) { + const int want_up = dis_gen_for_ratio(ratio - DIS_RATIO_HYST, capacity); + const int want_down = dis_gen_for_ratio(ratio + DIS_RATIO_HYST, capacity); + raw = cur; + if (want_up > cur) { + raw = want_up; + } else if (want_down < cur) { + raw = want_down; + } + if (raw < 1) raw = 1; + } + + if (raw > d->planned_gen) { + d->gen_low_streak = 0; + d->gen_high_streak++; + if (d->gen_high_streak >= 2) { + d->planned_gen = raw; + d->gen_high_streak = 0; + } + } else if (raw < d->planned_gen) { + d->gen_high_streak = 0; + d->gen_low_streak++; + if (d->gen_low_streak >= 3) { + d->planned_gen = raw; + d->gen_low_streak = 0; + } + } else { + d->gen_high_streak = 0; + d->gen_low_streak = 0; + } + + dis_log_plan(d, now, source_rate, eff_desired, ratio, capacity); + return (uint32_t)d->planned_gen; +} + +void vkr_dis_process(VkrDis* d, VkCommandBuffer cmd, VkImage source, uint32_t width, + uint32_t height, uint32_t generations) { + if (!d || !d->built || d->unavailable) return; + + d->last_generations = generations; + + dis_prime_layouts(d, cmd); + + const DisRefine refine = dis_refine_for(generations); + const uint32_t L = d->levels; + const uint32_t coarse = L - 1; + const uint32_t w = d->built_extent.width; + const uint32_t h = d->built_extent.height; + const uint32_t full_w = d->content.width; + const uint32_t full_h = d->content.height; + const int32_t cx = d->content.x; + const int32_t cy = d->content.y; + (void)width; (void)height; + + const uint32_t slot = (uint32_t)(d->frame_count % DIS_SLOTS); + DisImage* full_dst = &d->color[slot]; + DisImage* flow_dst = &d->flow_color[slot]; + + dis_barrier(cmd, full_dst->image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT); + dis_blit_rect(cmd, source, cx, cy, full_w, full_h, + full_dst->image, 0, 0, full_w, full_h, VK_FILTER_LINEAR); + dis_barrier(cmd, full_dst->image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); + + dis_barrier(cmd, flow_dst->image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_SHADER_READ_BIT, VK_ACCESS_TRANSFER_WRITE_BIT); + dis_blit_rect(cmd, source, cx, cy, full_w, full_h, + flow_dst->image, 0, 0, w, h, VK_FILTER_LINEAR); + for (uint32_t l = 1; l < L; l++) { + dis_barrier(cmd, flow_dst->image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); + dis_blit_mip(cmd, flow_dst->image, l - 1, l, w >> (l - 1), h >> (l - 1), w >> l, h >> l); + } + dis_barrier(cmd, flow_dst->image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT); + + d->next_idx = (int)slot; + d->prev_idx = (int)((slot + DIS_SLOTS - 1u) % DIS_SLOTS); + d->active_slot = slot; + d->frame_count++; + + for (uint32_t l = 0; l < L; l++) { + const uint32_t lw = w >> l; + const uint32_t lh = h >> l; + vkd.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pass_luma.pipeline); + vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pipeline_layout, 0, 1, + &d->luma_sets[slot][l], 0, NULL); + vkd.CmdDispatch(cmd, (lw + DIS_LOCAL_SIZE - 1) / DIS_LOCAL_SIZE, + (lh + DIS_LOCAL_SIZE - 1) / DIS_LOCAL_SIZE, 1); + } + + dis_compute_barrier(cmd); + + if (generations == 0 && !d->debug_flow) return; + + DisGradientPC gpc; + gpc.lesser = 3.0f; + gpc.upper = 10.0f; + gpc.normVal = 1.0f / (2.0f * 10.0f + 4.0f * 3.0f); + + for (uint32_t l = 0; l < L; l++) { + const uint32_t lw = w >> l; + const uint32_t lh = h >> l; + vkd.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pass_gradient.pipeline); + vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pipeline_layout, 0, 1, + &d->grad_sets[slot][l], 0, NULL); + vkd.CmdPushConstants(cmd, d->pipeline_layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(gpc), &gpc); + vkd.CmdDispatch(cmd, (lw + DIS_LOCAL_SIZE - 1) / DIS_LOCAL_SIZE, + (lh + DIS_LOCAL_SIZE - 1) / DIS_LOCAL_SIZE, 1); + } + + dis_compute_barrier(cmd); + + for (uint32_t li = 0; li < L; li++) { + const uint32_t l = coarse - li; + const uint32_t lw = w >> l; + const uint32_t lh = h >> l; + const uint32_t spw = dis_sparse_extent(lw); + const uint32_t sph = dis_sparse_extent(lh); + + DisInversePC ipc; + ipc.level = (int)l; + ipc.coarseLevel = (int)coarse; + vkd.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pass_inverse.pipeline); + vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pipeline_layout, 0, 1, + &d->inverse_sets[slot][l], 0, NULL); + vkd.CmdPushConstants(cmd, d->pipeline_layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(ipc), &ipc); + vkd.CmdDispatch(cmd, (spw + DIS_LOCAL_SIZE - 1) / DIS_LOCAL_SIZE, + (sph + DIS_LOCAL_SIZE - 1) / DIS_LOCAL_SIZE, 1); + + dis_compute_barrier(cmd); + + uint32_t prop_passes = dis_prop_steps_for(l, L, refine.prop_floor); + const uint32_t prop_doubling = prop_passes; + if (prop_passes & 1u) prop_passes++; + + for (uint32_t k = 0; k < prop_passes; k++) { + VkDescriptorSet prop_set = + (k & 1u) ? d->prop_ba_sets[slot][l] : d->prop_ab_sets[slot][l]; + DisPropPC ppc; + ppc.dist = k < prop_doubling ? (int)(1u << k) : 1; + vkd.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pass_propagate.pipeline); + vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pipeline_layout, 0, + 1, &prop_set, 0, NULL); + vkd.CmdPushConstants(cmd, d->pipeline_layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, + sizeof(ppc), &ppc); + vkd.CmdDispatch(cmd, (spw + DIS_LOCAL_SIZE - 1) / DIS_LOCAL_SIZE, + (sph + DIS_LOCAL_SIZE - 1) / DIS_LOCAL_SIZE, 1); + + dis_compute_barrier(cmd); + } + + dis_dispatch(d, cmd, d->pass_densify.pipeline, d->densify_sets[slot][l], lw, lh); + + dis_compute_barrier(cmd); + } + + const uint32_t gw = (w + DIS_LOCAL_SIZE - 1) / DIS_LOCAL_SIZE; + const uint32_t gh = (h + DIS_LOCAL_SIZE - 1) / DIS_LOCAL_SIZE; + + vkd.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pass_vr_prep.pipeline); + vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->vr_pipeline_layout, 0, 1, + &d->vr_prep_sets[slot], 0, NULL); + vkd.CmdDispatch(cmd, gw, gh, 1); + dis_compute_barrier(cmd); + + vkd.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pass_vr_d1.pipeline); + vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->vr_pipeline_layout, 0, 1, + &d->vr_d1_set, 0, NULL); + vkd.CmdDispatch(cmd, gw, gh, 1); + dis_compute_barrier(cmd); + + vkd.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pass_vr_d2.pipeline); + vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->vr_pipeline_layout, 0, 1, + &d->vr_d2_set, 0, NULL); + vkd.CmdDispatch(cmd, gw, gh, 1); + dis_compute_barrier(cmd); + + for (uint32_t k = 0; k < refine.vr_fixed_point; k++) { + DisVrWPC wpc; + wpc.alpha2 = DIS_VR_ALPHA * 0.5f; + wpc.eps2 = DIS_VR_EPS * DIS_VR_EPS; + vkd.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pass_vr_w.pipeline); + vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->vr_pipeline_layout, 0, 1, + &d->vr_w_set, 0, NULL); + vkd.CmdPushConstants(cmd, d->vr_pipeline_layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, + sizeof(wpc), &wpc); + vkd.CmdDispatch(cmd, gw, gh, 1); + dis_compute_barrier(cmd); + + DisVrCoefPC cpc; + cpc.delta2 = DIS_VR_DELTA * 0.5f; + cpc.gamma2 = DIS_VR_GAMMA * 0.5f; + cpc.zeta2 = DIS_VR_ZETA * DIS_VR_ZETA; + cpc.eps2 = DIS_VR_EPS * DIS_VR_EPS; + vkd.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pass_vr_coef.pipeline); + vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->vr_pipeline_layout, 0, 1, + &d->vr_coef_set, 0, NULL); + vkd.CmdPushConstants(cmd, d->vr_pipeline_layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, + sizeof(cpc), &cpc); + vkd.CmdDispatch(cmd, gw, gh, 1); + dis_compute_barrier(cmd); + + for (uint32_t s = 0; s < refine.vr_sor; s++) { + DisVrSorPC spc; + spc.omega = DIS_VR_OMEGA; + spc.parity = 0; + vkd.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pass_vr_sor.pipeline); + vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->vr_pipeline_layout, 0, + 1, &d->vr_sor_ab_set, 0, NULL); + vkd.CmdPushConstants(cmd, d->vr_pipeline_layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, + sizeof(spc), &spc); + vkd.CmdDispatch(cmd, gw, gh, 1); + dis_compute_barrier(cmd); + + spc.parity = 1; + vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->vr_pipeline_layout, 0, + 1, &d->vr_sor_ba_set, 0, NULL); + vkd.CmdPushConstants(cmd, d->vr_pipeline_layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, + sizeof(spc), &spc); + vkd.CmdDispatch(cmd, gw, gh, 1); + dis_compute_barrier(cmd); + } + } + + vkd.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pass_vr_add.pipeline); + vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->vr_pipeline_layout, 0, 1, + &d->vr_add_set, 0, NULL); + vkd.CmdDispatch(cmd, gw, gh, 1); + + dis_compute_barrier(cmd); +} + +static void dis_render_into(VkrDis* d, VkCommandBuffer cmd, float t, int debug_mode, + VkImage target_image, uint32_t width, uint32_t height, + VkImage base_image) { + const uint32_t w = d->content.width; + const uint32_t h = d->content.height; + + DisInterpPC ipc; + ipc.t = t; + ipc.debugMode = debug_mode; + + vkd.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pass_interp.pipeline); + vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pipeline_layout, 0, 1, + &d->interp_sets[d->active_slot], 0, NULL); + vkd.CmdPushConstants(cmd, d->pipeline_layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(ipc), &ipc); + vkd.CmdDispatch(cmd, (w + DIS_LOCAL_SIZE - 1) / DIS_LOCAL_SIZE, + (h + DIS_LOCAL_SIZE - 1) / DIS_LOCAL_SIZE, 1); + + dis_barrier(cmd, d->interp_out.image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, + VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_SHADER_WRITE_BIT, VK_ACCESS_TRANSFER_READ_BIT); + + dis_barrier(cmd, target_image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, + VK_ACCESS_TRANSFER_WRITE_BIT); + + const uint32_t comp_w = d->built_full_extent.width; + const uint32_t comp_h = d->built_full_extent.height; + const float tsx = comp_w > 0 ? (float)width / (float)comp_w : 1.0f; + const float tsy = comp_h > 0 ? (float)height / (float)comp_h : 1.0f; + const int32_t tx = (int32_t)((float)d->content.x * tsx + 0.5f); + const int32_t ty = (int32_t)((float)d->content.y * tsy + 0.5f); + uint32_t tw = (uint32_t)((float)w * tsx + 0.5f); + uint32_t th = (uint32_t)((float)h * tsy + 0.5f); + if (tw == 0) tw = 1; + if (th == 0) th = 1; + if ((uint32_t)tx + tw > width) tw = width - (uint32_t)tx; + if ((uint32_t)ty + th > height) th = height - (uint32_t)ty; + + if (base_image != VK_NULL_HANDLE) { + const int32_t cx = d->content.x; + const int32_t cy = d->content.y; + const int32_t cr = cx + (int32_t)w; + const int32_t cb = cy + (int32_t)h; + const int32_t trx = tx + (int32_t)tw; + const int32_t tby = ty + (int32_t)th; + const int32_t src[4][4] = { + {0, 0, cx, (int32_t)comp_h}, + {cr, 0, (int32_t)comp_w - cr, (int32_t)comp_h}, + {cx, 0, (int32_t)w, cy}, + {cx, cb, (int32_t)w, (int32_t)comp_h - cb}, + }; + const int32_t dst[4][4] = { + {0, 0, tx, (int32_t)height}, + {trx, 0, (int32_t)width - trx, (int32_t)height}, + {tx, 0, (int32_t)tw, ty}, + {tx, tby, (int32_t)tw, (int32_t)height - tby}, + }; + bool any_strip = false; + for (uint32_t i = 0; i < 4; i++) { + if (src[i][2] <= 0 || src[i][3] <= 0 || dst[i][2] <= 0 || dst[i][3] <= 0) continue; + dis_blit_rect(cmd, base_image, src[i][0], src[i][1], + (uint32_t)src[i][2], (uint32_t)src[i][3], + target_image, dst[i][0], dst[i][1], + (uint32_t)dst[i][2], (uint32_t)dst[i][3], VK_FILTER_LINEAR); + any_strip = true; + } + if (any_strip) { + dis_barrier(cmd, target_image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_TRANSFER_WRITE_BIT); + } + } + + dis_blit_rect(cmd, d->interp_out.image, 0, 0, w, h, + target_image, tx, ty, tw, th, VK_FILTER_LINEAR); + + dis_barrier(cmd, d->interp_out.image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, + VK_ACCESS_TRANSFER_READ_BIT, VK_ACCESS_SHADER_WRITE_BIT); +} + +void vkr_dis_generate_into(VkrDis* d, VkCommandBuffer cmd, uint32_t generation, + uint32_t target_index, VkImage target_image, VkImageView target_view, + uint32_t width, uint32_t height, VkImage base_image) { + (void)target_index; + (void)target_view; + if (!d || !d->built || d->unavailable) return; + if (d->last_generations == 0) return; + + const float t = (float)(generation + 1) / (float)(d->last_generations + 1); + dis_render_into(d, cmd, t, d->debug_flow ? 1 : 0, target_image, width, height, base_image); +} + +void vkr_dis_debug_into(VkrDis* d, VkCommandBuffer cmd, VkImage target_image, uint32_t width, + uint32_t height) { + if (!d || !d->built || d->unavailable || !d->debug_flow) return; + if (d->frame_count < 2) return; + dis_render_into(d, cmd, 0.5f, 1, target_image, width, height, VK_NULL_HANDLE); +} + +void vkr_dis_forget_targets(VkrDis* d) { + (void)d; +} + +void vkr_dis_reset(VkrDis* d) { + if (!d) return; + d->frame_count = 0; + d->last_generations = 0; + d->prev_idx = 0; + d->next_idx = 0; + d->active_slot = 0; + d->src_sample_ns = 0; + d->src_last_frames = 0; + d->src_frame_accum = 0.0f; + d->src_time_accum = 0.0f; + d->src_interval = 0.0f; + d->src_samples = 0; + d->smoothed_desired = 0.0f; + d->planned_gen = 0; + d->gen_high_streak = 0; + d->gen_low_streak = 0; + d->plan_log_gen = -1; + d->plan_log_ns = 0; +} diff --git a/app/src/main/cpp/winlator/vk/dis/vkr_dis.cpp b/app/src/main/cpp/winlator/vk/dis/vkr_dis.cpp deleted file mode 100644 index df67d0a57..000000000 --- a/app/src/main/cpp/winlator/vk/dis/vkr_dis.cpp +++ /dev/null @@ -1,250 +0,0 @@ -// SPDX-License-Identifier: GPL-3.0-or-later - -#include "vkr_dis.h" - -#include "dis_chain.hpp" - -#include -#include -#include -#include - -#include - -#define DIS_LOGI(...) __android_log_print(ANDROID_LOG_INFO, "VkrDis", __VA_ARGS__) -#define DIS_LOGW(...) __android_log_print(ANDROID_LOG_WARN, "VkrDis", __VA_ARGS__) - -struct VkrDis { - VkDevice device{VK_NULL_HANDLE}; - VkPhysicalDevice physical_device{VK_NULL_HANDLE}; - - std::unique_ptr chain; - - VkExtent2D built_flow_extent{}; - VkExtent2D built_target_extent{}; - VkFormat built_format{VK_FORMAT_UNDEFINED}; - - VkExtent2D peak_guest_extent{}; - float flow_scale{1.0f}; - - uint32_t target_rate{}; - uint32_t multiplier{}; - float refresh_rate{}; - float source_rate{}; - - uint64_t last_source_frames{}; - uint64_t last_source_time{}; - - uint64_t frame_count{}; - size_t last_generations{}; - uint64_t plan_calls{}; - uint32_t debug_mode{}; - bool unavailable{}; -}; - -namespace { - -constexpr uint64_t DIS_TELEMETRY_INTERVAL = 60; - -constexpr float DIS_FLOW_SCALE_MIN = 0.25f; -constexpr float DIS_FLOW_SCALE_MAX = 1.0f; - -constexpr float DIS_SOURCE_RATE_SMOOTHING = 0.5f; -constexpr float DIS_FALLBACK_SOURCE_RATE = 60.0f; -constexpr float DIS_FALLBACK_TARGET_RATE = 120.0f; - -uint64_t DisNowNs() { - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return static_cast(ts.tv_sec) * 1000000000ULL + static_cast(ts.tv_nsec); -} - -VkExtent2D ComputeFlowExtent(const VkrDis* dis, uint32_t width, uint32_t height) { - uint32_t fw = dis->peak_guest_extent.width != 0 ? dis->peak_guest_extent.width : width; - uint32_t fh = dis->peak_guest_extent.height != 0 ? dis->peak_guest_extent.height : height; - - const float scale = std::clamp(dis->flow_scale, DIS_FLOW_SCALE_MIN, DIS_FLOW_SCALE_MAX); - fw = std::max(16u, static_cast(std::lroundf(static_cast(fw) * scale))); - fh = std::max(16u, static_cast(std::lroundf(static_cast(fh) * scale))); - return VkExtent2D{fw, fh}; -} - -} // namespace - -VkrDis* vkr_dis_create(VkDevice device, VkPhysicalDevice physical_device) { - if (device == VK_NULL_HANDLE || physical_device == VK_NULL_HANDLE) return nullptr; - - auto* dis = new VkrDis(); - dis->device = device; - dis->physical_device = physical_device; - DIS_LOGI("frame generation (DIS) ready"); - return dis; -} - -void vkr_dis_destroy(VkrDis* dis) { - delete dis; -} - -void vkr_dis_configure(VkrDis* dis, uint32_t target_rate, float flow_scale, - float refresh_rate, float source_rate) { - if (!dis) return; - - dis->target_rate = target_rate; - dis->refresh_rate = refresh_rate; - if (source_rate > 0.0f) dis->source_rate = source_rate; - dis->flow_scale = std::clamp(flow_scale, DIS_FLOW_SCALE_MIN, DIS_FLOW_SCALE_MAX); -} - -void vkr_dis_set_guest_extent(VkrDis* dis, uint32_t width, uint32_t height) { - if (!dis || width == 0 || height == 0) return; - dis->peak_guest_extent.width = std::max(dis->peak_guest_extent.width, width); - dis->peak_guest_extent.height = std::max(dis->peak_guest_extent.height, height); -} - -void vkr_dis_set_refresh_rate(VkrDis* dis, float refresh_rate) { - if (!dis) return; - dis->refresh_rate = refresh_rate; -} - -void vkr_dis_set_multiplier(VkrDis* dis, uint32_t multiplier) { - if (!dis) return; - dis->multiplier = multiplier >= 2 ? multiplier : 0u; -} - -bool vkr_dis_needs_rebuild(const VkrDis* dis, uint32_t width, uint32_t height, VkFormat format) { - if (!dis || dis->unavailable) return false; - const VkExtent2D flow_extent = ComputeFlowExtent(dis, width, height); - return !dis->chain || dis->built_target_extent.width != width || - dis->built_target_extent.height != height || dis->built_format != format || - dis->built_flow_extent.width != flow_extent.width || - dis->built_flow_extent.height != flow_extent.height; -} - -bool vkr_dis_prepare(VkrDis* dis, uint32_t width, uint32_t height, VkFormat format) { - if (!dis || dis->unavailable) return false; - if (width == 0 || height == 0 || format == VK_FORMAT_UNDEFINED) return false; - - if (!vkr_dis_needs_rebuild(dis, width, height, format)) { - return dis->chain && dis->chain->Valid(); - } - - const VkExtent2D flow_extent = ComputeFlowExtent(dis, width, height); - - dis->chain.reset(); - dis->chain = std::make_unique(dis->device, dis->physical_device); - if (!dis->chain->Build(flow_extent, VkExtent2D{width, height})) { - DIS_LOGW("DIS chain build failed at flow %ux%u target %ux%u; frame generation unavailable", - flow_extent.width, flow_extent.height, width, height); - dis->chain.reset(); - dis->unavailable = true; - return false; - } - - dis->built_flow_extent = flow_extent; - dis->built_target_extent = VkExtent2D{width, height}; - dis->built_format = format; - dis->chain->SetDebugMode(static_cast(dis->debug_mode)); - dis->frame_count = 0; - dis->plan_calls = 0; - dis->source_rate = 0.0f; - DIS_LOGI("DIS chain built at flow %ux%u, target %ux%u, scale %.2f, guest %ux%u", - flow_extent.width, flow_extent.height, width, height, (double)dis->flow_scale, - dis->peak_guest_extent.width, dis->peak_guest_extent.height); - return true; -} - -uint32_t vkr_dis_plan(VkrDis* dis, uint32_t capacity, uint64_t source_frames) { - if (!dis || dis->unavailable) return 0; - - // Track the source rate immediately (no warm-up, no back-off). - const uint64_t now = DisNowNs(); - if (dis->last_source_time != 0) { - const uint64_t frames_delta = - source_frames > dis->last_source_frames ? source_frames - dis->last_source_frames : 0; - const uint64_t time_delta = now - dis->last_source_time; - if (frames_delta > 0 && time_delta > 0) { - const float instant = - static_cast(frames_delta) * 1.0e9f / static_cast(time_delta); - dis->source_rate = dis->source_rate > 0.0f - ? dis->source_rate + (instant - dis->source_rate) * - DIS_SOURCE_RATE_SMOOTHING - : instant; - } - } - dis->last_source_frames = source_frames; - dis->last_source_time = now; - - // The DIS chain needs a previous and current frame before it can interpolate. - if (dis->frame_count < 1) return 0; - - const uint32_t ceiling = std::min(capacity, VKR_DIS_MAX_GENERATIONS); - const bool explicit_multiplier = dis->multiplier >= 2; - int32_t generations; - if (explicit_multiplier) { - generations = static_cast(dis->multiplier) - 1; - } else { - const float src = - dis->source_rate > 1.0f ? dis->source_rate : DIS_FALLBACK_SOURCE_RATE; - const float target = static_cast(dis->target_rate > 0 ? dis->target_rate - : DIS_FALLBACK_TARGET_RATE); - generations = static_cast(std::lroundf(target / src)) - 1; - } - generations = std::clamp(generations, 0, static_cast(ceiling)); - - if ((dis->plan_calls++ % DIS_TELEMETRY_INTERVAL) == 0) { - if (explicit_multiplier && static_cast(generations) + 1 < dis->multiplier) { - DIS_LOGW("DIS multiplier x%u capped to x%d by generation capacity %u", - dis->multiplier, generations + 1, capacity); - } - DIS_LOGI("dis plan gen=%d mult=%u cap=%u src=%.1f target=%.0f refresh=%.1f", - generations, dis->multiplier, capacity, (double)dis->source_rate, - (double)dis->target_rate, (double)dis->refresh_rate); - } - - return static_cast(generations); -} - -void vkr_dis_process(VkrDis* dis, VkCommandBuffer cmd, VkImage source, - VkImageView fullres_view_cur, VkImageView fullres_view_prev, - uint32_t width, uint32_t height, VkRect2D content_rect, - uint32_t generations) { - if (!dis || !dis->chain || !dis->chain->Valid()) return; - - dis->frame_count++; - dis->last_generations = generations; - - dis->chain->Process(cmd, source, fullres_view_cur, fullres_view_prev, content_rect, - VkExtent2D{width, height}); -} - -void vkr_dis_generate_into(VkrDis* dis, VkCommandBuffer cmd, uint32_t generation, - uint32_t target_index, VkImage target_image, VkImageView target_view, - uint32_t width, uint32_t height) { - if (!dis || !dis->chain || !dis->chain->Valid()) return; - - dis->chain->GenerateInto(cmd, generation, static_cast(dis->last_generations), - target_image, target_view, VkExtent2D{width, height}); -} - -void vkr_dis_forget_targets(VkrDis* dis) { - if (!dis || !dis->chain) return; - dis->chain->ForgetTargets(); -} - -void vkr_dis_set_debug_mode(VkrDis* dis, uint32_t mode) { - if (!dis) return; - dis->debug_mode = mode; - if (dis->chain) dis->chain->SetDebugMode(static_cast(mode)); -} - -bool vkr_dis_debug_ready(const VkrDis* dis) { - return dis && dis->chain && dis->chain->Valid() && dis->chain->FrameCount() >= 2; -} - -void vkr_dis_reset(VkrDis* dis) { - if (!dis) return; - dis->peak_guest_extent = VkExtent2D{}; - dis->source_rate = 0.0f; - dis->last_source_frames = 0; - dis->last_source_time = 0; -} diff --git a/app/src/main/cpp/winlator/vk/dis/vkr_dis.h b/app/src/main/cpp/winlator/vk/dis/vkr_dis.h index 6d90c25cc..146fa190d 100644 --- a/app/src/main/cpp/winlator/vk/dis/vkr_dis.h +++ b/app/src/main/cpp/winlator/vk/dis/vkr_dis.h @@ -10,44 +10,44 @@ extern "C" { #endif #define VKR_DIS_MAX_GENERATIONS 3u -#define VKR_DIS_MAX_TARGETS 7u typedef struct VkrDis VkrDis; +typedef struct VkrDisContentRect { + int32_t x; + int32_t y; + uint32_t width; + uint32_t height; +} VkrDisContentRect; + VkrDis* vkr_dis_create(VkDevice device, VkPhysicalDevice physical_device); void vkr_dis_destroy(VkrDis* dis); -void vkr_dis_configure(VkrDis* dis, uint32_t target_rate, float flow_scale, - float refresh_rate, float source_rate); - -void vkr_dis_set_refresh_rate(VkrDis* dis, float refresh_rate); - -// Explicit frame multiplier (2..4). 0 restores the target/source-rate based plan. -void vkr_dis_set_multiplier(VkrDis* dis, uint32_t multiplier); +void vkr_dis_configure(VkrDis* dis, uint32_t flow_min_side, uint32_t target_fps, + float refresh_rate); -void vkr_dis_set_guest_extent(VkrDis* dis, uint32_t width, uint32_t height); +void vkr_dis_set_debug_flow(VkrDis* dis, bool debug_flow); -bool vkr_dis_needs_rebuild(const VkrDis* dis, uint32_t width, uint32_t height, VkFormat format); +bool vkr_dis_needs_rebuild(const VkrDis* dis, uint32_t width, uint32_t height, + VkFormat format, VkrDisContentRect content); -bool vkr_dis_prepare(VkrDis* dis, uint32_t width, uint32_t height, VkFormat format); +bool vkr_dis_prepare(VkrDis* dis, uint32_t width, uint32_t height, VkFormat format, + VkrDisContentRect content); uint32_t vkr_dis_plan(VkrDis* dis, uint32_t capacity, uint64_t source_frames); void vkr_dis_process(VkrDis* dis, VkCommandBuffer cmd, VkImage source, - VkImageView fullres_view_cur, VkImageView fullres_view_prev, - uint32_t width, uint32_t height, VkRect2D content_rect, - uint32_t generations); + uint32_t width, uint32_t height, uint32_t generations); void vkr_dis_generate_into(VkrDis* dis, VkCommandBuffer cmd, uint32_t generation, - uint32_t target_index, VkImage target_image, VkImageView target_view, - uint32_t width, uint32_t height); + uint32_t target_index, VkImage target_image, + VkImageView target_view, uint32_t width, uint32_t height, + VkImage base_image); -void vkr_dis_forget_targets(VkrDis* dis); - -void vkr_dis_set_debug_mode(VkrDis* dis, uint32_t mode); +void vkr_dis_debug_into(VkrDis* dis, VkCommandBuffer cmd, VkImage target_image, + uint32_t width, uint32_t height); -// True once the chain has a valid flow field to visualize (needs a previous/current pair). -bool vkr_dis_debug_ready(const VkrDis* dis); +void vkr_dis_forget_targets(VkrDis* dis); void vkr_dis_reset(VkrDis* dis); diff --git a/app/src/main/cpp/winlator/vk/framegen/fg_present.c b/app/src/main/cpp/winlator/vk/framegen/fg_present.c index f948436c3..26c79a6d0 100644 --- a/app/src/main/cpp/winlator/vk/framegen/fg_present.c +++ b/app/src/main/cpp/winlator/vk/framegen/fg_present.c @@ -703,24 +703,13 @@ static uint32_t fg_dis_target(uint32_t multiplier, uint32_t target_rate, float s return 0; } -// The DIS presets pick the flow pyramid's minimum side (180/252/360 px) relative to 720p. The -// reworked chain scales a fixed fraction of the guest extent instead, so convert with 720p as -// the reference resolution. -static float fg_dis_flow_scale(uint32_t min_side) { - if (min_side == 0) min_side = FG_DIS_MIN_SIDE_DEFAULT; - float scale = (float)min_side / 720.0f; - if (scale < 0.25f) scale = 0.25f; - if (scale > 1.0f) scale = 1.0f; - return scale; -} - static void fg_engine_configure(FgPresenter* fg, uint32_t multiplier, uint32_t target_rate, float flow_scale, float refresh_rate, float source_rate, uint32_t dis_min_side) { if (fg->active_engine == FG_ENGINE_DIS) { if (!fg->dis) return; - vkr_dis_configure(fg->dis, fg_dis_target(multiplier, target_rate, source_rate), - fg_dis_flow_scale(dis_min_side), refresh_rate, source_rate); + vkr_dis_configure(fg->dis, dis_min_side ? dis_min_side : FG_DIS_MIN_SIDE_DEFAULT, + fg_dis_target(multiplier, target_rate, source_rate), refresh_rate); return; } if (!fg->lsfg) return; @@ -826,7 +815,6 @@ static void fg_record_and_present(FgPresenter* fg, FgImport* source, AImage* ima uint32_t planned = 0; if (fg->active_engine == FG_ENGINE_DIS) { - vkr_dis_set_guest_extent(fg->dis, source->width, source->height); planned = vkr_dis_plan(fg->dis, capacity, fg->source_frames); } else if (fg->lsfg) { vkr_lsfg_set_guest_extent(fg->lsfg, source->width, source->height); @@ -891,11 +879,8 @@ static void fg_record_and_present(FgPresenter* fg, FgImport* source, AImage* ima VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_TRANSFER_READ_BIT); if (fg->active_engine == FG_ENGINE_DIS) { - const VkRect2D content = {{0, 0}, {fg->extent.width, fg->extent.height}}; - FgTarget* previous = - &fg->targets[(fg->frame_index + FG_FRAMES_IN_FLIGHT - 1) % FG_FRAMES_IN_FLIGHT]; - vkr_dis_process(fg->dis, f->cmd, composite->image, composite->view, previous->view, - fg->extent.width, fg->extent.height, content, planned); + vkr_dis_process(fg->dis, f->cmd, composite->image, fg->extent.width, fg->extent.height, + gen_count); } else if (fg->lsfg) { vkr_lsfg_process(fg->lsfg, f->cmd, composite->image, fg->extent.width, fg->extent.height, gen_count); @@ -905,7 +890,8 @@ static void fg_record_and_present(FgPresenter* fg, FgImport* source, AImage* ima FgTarget* generated = &fg->targets[FG_FRAMES_IN_FLIGHT + g]; if (fg->active_engine == FG_ENGINE_DIS) { vkr_dis_generate_into(fg->dis, f->cmd, g, FG_FRAMES_IN_FLIGHT + g, generated->image, - generated->view, fg->extent.width, fg->extent.height); + generated->view, fg->extent.width, fg->extent.height, + VK_NULL_HANDLE); fg_barrier(f->cmd, generated->image, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_ACCESS_TRANSFER_WRITE_BIT, @@ -1029,13 +1015,15 @@ static void fg_reset_swapchain(FgPresenter* fg) { static bool fg_prepare_chain(FgPresenter* fg) { if (fg->active_engine == FG_ENGINE_DIS) { if (!fg->dis) return false; - if (!vkr_dis_needs_rebuild(fg->dis, fg->extent.width, fg->extent.height, - fg->target_format)) { + const VkrDisContentRect content = {0, 0, fg->extent.width, fg->extent.height}; + if (!vkr_dis_needs_rebuild(fg->dis, fg->extent.width, fg->extent.height, fg->target_format, + content)) { return true; } vkDeviceWaitIdle(fg->device); vkr_dis_forget_targets(fg->dis); - return vkr_dis_prepare(fg->dis, fg->extent.width, fg->extent.height, fg->target_format); + return vkr_dis_prepare(fg->dis, fg->extent.width, fg->extent.height, fg->target_format, + content); } if (!fg->lsfg) return false; diff --git a/app/src/main/cpp/winlator/vk/shaders/dis_densify.comp b/app/src/main/cpp/winlator/vk/shaders/dis_densify.comp index 344311171..e667e78ff 100644 --- a/app/src/main/cpp/winlator/vk/shaders/dis_densify.comp +++ b/app/src/main/cpp/winlator/vk/shaders/dis_densify.comp @@ -1,65 +1,79 @@ #version 450 -#extension GL_EXT_samplerless_texture_functions : require -// Densification (FS_DENSIFY): weighted average of the overlapping patch flows at every pixel. +precision highp float; +precision highp int; -layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; +#define DIS_DENSIFY_GUIDE_SIGMA 18.0 -#define PSZ 8 - -layout(set = 0, binding = 0) uniform texture2D u_S; -layout(set = 0, binding = 1) uniform texture2D u_I0; -layout(set = 0, binding = 2) uniform texture2D u_I1; -layout(set = 0, binding = 3, rg32f) uniform image2D o_U; - -layout(push_constant) uniform PC { - vec2 u_size; - ivec2 u_sparse; - int u_stride; -} pc; - -float sampleI1(vec2 pos) { - ivec2 sz = textureSize(u_I1, 0); - vec2 mx = vec2(sz - ivec2(1)); - vec2 q = clamp(pos, vec2(0.0), mx); - vec2 b = floor(q); - vec2 f = q - b; - ivec2 i0 = ivec2(b); - float a = texelFetch(u_I1, i0, 0).r; - float bb = texelFetch(u_I1, min(i0 + ivec2(1, 0), sz - 1), 0).r; - float c = texelFetch(u_I1, min(i0 + ivec2(0, 1), sz - 1), 0).r; - float d = texelFetch(u_I1, min(i0 + ivec2(1, 1), sz - 1), 0).r; - return mix(mix(a, bb, f.x), mix(c, d, f.x), f.y); -} +#define DIS_DENSIFY_MATCH_FLOOR_RMS 1.5 -int lo(int c) { - int a = c - PSZ + 1; - return a <= 0 ? 0 : (a + pc.u_stride - 1) / pc.u_stride; -} +#define DIS_DENSIFY_TAPS 9 + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +layout(set = 0, binding = 0) uniform sampler2D sparseFlowMap; +layout(set = 0, binding = 1) uniform sampler2D lastImage; +layout(set = 0, binding = 2) uniform sampler2D nextImage; +layout(set = 0, binding = 5, rg32f) uniform image2D denseFlowMap; void main() { - ivec2 p = ivec2(gl_GlobalInvocationID.xy); - ivec2 sz = imageSize(o_U); - if (p.x >= sz.x || p.y >= sz.y) return; - - float i0 = texelFetch(u_I0, p, 0).r; - - int is1 = clamp(p.y / pc.u_stride, 0, pc.u_sparse.y - 1); - int js1 = clamp(p.x / pc.u_stride, 0, pc.u_sparse.x - 1); - int is0 = min(lo(p.y), is1); - int js0 = min(lo(p.x), js1); - - vec2 sum = vec2(0.0); - float sumC = 0.0; - for (int is = is0; is <= is1; is++) { - for (int js = js0; js <= js1; js++) { - vec2 S = texelFetch(u_S, ivec2(js, is), 0).rg; - vec2 pos = clamp(vec2(p) + S, vec2(0.0), pc.u_size - 1.0 - 1e-3); - float diff = sampleI1(pos) - i0; - float coef = 1.0 / max(1.0, abs(diff)); - sum += coef * S; - sumC += coef; + ivec2 pix = ivec2(gl_GlobalInvocationID.xy); + ivec2 denseSize = imageSize(denseFlowMap); + if (pix.x >= denseSize.x || pix.y >= denseSize.y) return; + + ivec2 sparseSize = textureSize(sparseFlowMap, 0); + if (sparseSize.x <= 0 || sparseSize.y <= 0) { + imageStore(denseFlowMap, pix, vec4(0.0, 0.0, 0.0, 1.0)); + return; + } + + vec2 invDenseSize = 1.0 / vec2(denseSize); + vec2 uv = (vec2(pix) + 0.5) * invDenseSize; + float lastLum = textureLod(lastImage, uv, 0.0).x; + + vec4 cand[DIS_DENSIFY_TAPS]; + int candCount = 0; + float bestSsd = -1.0; + + ivec2 s0 = ivec2(pix.x / 3, pix.y / 3) - 2; + for (int dy = 0; dy <= 2; dy++) { + for (int dx = 0; dx <= 2; dx++) { + ivec2 s = s0 + ivec2(dx, dy); + if (s.x < 0 || s.y < 0 || s.x >= sparseSize.x || s.y >= sparseSize.y) continue; + vec4 f = texelFetch(sparseFlowMap, s, 0); + f.w = textureLod(lastImage, (vec2(s * 3) + 4.0) * invDenseSize, 0.0).x; + cand[candCount] = f; + candCount++; + if (f.z >= 0.0 && (bestSsd < 0.0 || f.z < bestSsd)) bestSsd = f.z; } } - imageStore(o_U, p, vec4(sumC > 0.0 ? sum / sumC : vec2(0.0), 0.0, 0.0)); + + const float floorSsd = + 64.0 * DIS_DENSIFY_MATCH_FLOOR_RMS * DIS_DENSIFY_MATCH_FLOOR_RMS; + const float invGuide2 = 1.0 / (DIS_DENSIFY_GUIDE_SIGMA * DIS_DENSIFY_GUIDE_SIGMA); + float refSsd = max(bestSsd, floorSsd); + + vec2 acc = vec2(0.0); + float accW = 0.0; + + for (int i = 0; i < candCount; i++) { + vec4 flow = cand[i]; + + float diff = textureLod(nextImage, uv + flow.xy, 0.0).x - lastLum; + float w = 1.0 / max(abs(diff), 1.0); + + if (bestSsd >= 0.0 && flow.z >= 0.0) { + float r = flow.z / refSsd; + w /= 1.0 + r * r; + } + + float dl = flow.w - lastLum; + w /= 1.0 + dl * dl * invGuide2; + + acc += flow.xy * w; + accW += w; + } + + vec2 denseFlow = accW > 0.0 ? acc / accW : vec2(0.0); + imageStore(denseFlowMap, pix, vec4(denseFlow, 0.0, 1.0)); } diff --git a/app/src/main/cpp/winlator/vk/shaders/dis_descent.comp b/app/src/main/cpp/winlator/vk/shaders/dis_descent.comp deleted file mode 100644 index d62ee7c46..000000000 --- a/app/src/main/cpp/winlator/vk/shaders/dis_descent.comp +++ /dev/null @@ -1,92 +0,0 @@ -#version 450 -#extension GL_EXT_samplerless_texture_functions : require - -// processPatchMeanNorm / processPatch + Gauss-Newton steps with the precomputed inverse -// structure tensor (FS_DESCENT). - -layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; - -#define PSZ 8 -#define N float(PSZ * PSZ) - -const float EPSD = 0.001; -const float INF = 1e10; - -layout(set = 0, binding = 0) uniform texture2D u_I0; -layout(set = 0, binding = 1) uniform texture2D u_I1; -layout(set = 0, binding = 2) uniform texture2D u_grad; -layout(set = 0, binding = 3) uniform texture2D u_S; -layout(set = 0, binding = 4) uniform texture2D u_ST; -layout(set = 0, binding = 5) uniform texture2D u_ST2; -layout(set = 0, binding = 6, rg32f) uniform image2D o_S; - -layout(push_constant) uniform PC { - vec2 u_size; - int u_stride; - int u_iters; -} pc; - -float sampleI1(vec2 pos) { - ivec2 sz = textureSize(u_I1, 0); - vec2 mx = vec2(sz - ivec2(1)); - vec2 q = clamp(pos, vec2(0.0), mx); - vec2 b = floor(q); - vec2 f = q - b; - ivec2 i0 = ivec2(b); - float a = texelFetch(u_I1, i0, 0).r; - float bb = texelFetch(u_I1, min(i0 + ivec2(1, 0), sz - 1), 0).r; - float c = texelFetch(u_I1, min(i0 + ivec2(0, 1), sz - 1), 0).r; - float d = texelFetch(u_I1, min(i0 + ivec2(1, 1), sz - 1), 0).r; - return mix(mix(a, bb, f.x), mix(c, d, f.x), f.y); -} - -vec2 clampPos(vec2 pos) { - return clamp(pos, vec2(1.0 - float(PSZ)), pc.u_size - 1.0); -} - -float fetchI1(vec2 pos) { - return sampleI1(pos); -} - -void main() { - ivec2 s = ivec2(gl_GlobalInvocationID.xy); - ivec2 sz = imageSize(o_S); - if (s.x >= sz.x || s.y >= sz.y) return; - - ivec2 org = s * pc.u_stride; - vec2 S0 = texelFetch(u_S, s, 0).rg; - - vec4 t = texelFetch(u_ST, s, 0); - float detH = t.x * t.y - t.z * t.z; - if (abs(detH) < EPSD) detH = EPSD; - float invH11 = t.y / detH; - float invH12 = -t.z / detH; - float invH22 = t.x / detH; - vec2 gradSum = vec2(t.w, texelFetch(u_ST2, s, 0).r); - - vec2 cur = S0; - float prevSSD = INF; - for (int it = 0; it < pc.u_iters; it++) { - vec2 pos = clampPos(vec2(org) + cur); - float sd = 0.0, sd2 = 0.0; - vec2 sIg = vec2(0.0); - for (int dy = 0; dy < PSZ; dy++) { - for (int dx = 0; dx < PSZ; dx++) { - ivec2 q = org + ivec2(dx, dy); - float diff = fetchI1(pos + vec2(float(dx), float(dy))) - - texelFetch(u_I0, q, 0).r; - vec2 g = texelFetch(u_grad, q, 0).rg; - sd += diff; - sd2 += diff * diff; - sIg += diff * g; - } - } - vec2 dU = sIg - sd * gradSum / N; - float SSD = sd2 - sd * sd / N; - cur -= vec2(invH11 * dU.x + invH12 * dU.y, invH12 * dU.x + invH22 * dU.y); - if (SSD >= prevSSD) break; - prevSSD = SSD; - } - - imageStore(o_S, s, vec4((length(cur - S0) <= float(PSZ)) ? cur : S0, 0.0, 1.0)); -} diff --git a/app/src/main/cpp/winlator/vk/shaders/dis_downsample.comp b/app/src/main/cpp/winlator/vk/shaders/dis_downsample.comp deleted file mode 100644 index 9296c4468..000000000 --- a/app/src/main/cpp/winlator/vk/shaders/dis_downsample.comp +++ /dev/null @@ -1,23 +0,0 @@ -#version 450 -#extension GL_EXT_samplerless_texture_functions : require - -// 2x2 box downsample (INTER_AREA equivalent at factor 2) for the luminance pyramid. - -layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; - -layout(set = 0, binding = 0) uniform texture2D src; -layout(set = 0, binding = 1, r16f) uniform image2D dst; - -void main() { - ivec2 p = ivec2(gl_GlobalInvocationID.xy); - ivec2 sz = imageSize(dst); - if (p.x >= sz.x || p.y >= sz.y) return; - - ivec2 mx = textureSize(src, 0) - 1; - ivec2 s = p * 2; - float a = texelFetch(src, min(s, mx), 0).r; - float b = texelFetch(src, min(s + ivec2(1, 0), mx), 0).r; - float c = texelFetch(src, min(s + ivec2(0, 1), mx), 0).r; - float d = texelFetch(src, min(s + ivec2(1, 1), mx), 0).r; - imageStore(dst, p, vec4(0.25 * (a + b + c + d), 0.0, 0.0, 0.0)); -} diff --git a/app/src/main/cpp/winlator/vk/shaders/dis_gradient.comp b/app/src/main/cpp/winlator/vk/shaders/dis_gradient.comp new file mode 100644 index 000000000..c99524d35 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/dis_gradient.comp @@ -0,0 +1,38 @@ +#version 450 + +precision highp float; +precision highp int; + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +layout(set = 0, binding = 0) uniform sampler2D lumaMap; +layout(set = 0, binding = 5, rg32f) uniform image2D gradientMap; + +layout(push_constant) uniform PC { + float lesser; + float upper; + float normVal; +} pc; + +void main() { + ivec2 pix = ivec2(gl_GlobalInvocationID.xy); + ivec2 sz = imageSize(gradientMap); + if (pix.x >= sz.x || pix.y >= sz.y) return; + + ivec2 mx = sz - 1; + float a00 = texelFetch(lumaMap, clamp(pix + ivec2(-1, -1), ivec2(0), mx), 0).x; + float a10 = texelFetch(lumaMap, clamp(pix + ivec2(0, -1), ivec2(0), mx), 0).x; + float a20 = texelFetch(lumaMap, clamp(pix + ivec2(1, -1), ivec2(0), mx), 0).x; + float a01 = texelFetch(lumaMap, clamp(pix + ivec2(-1, 0), ivec2(0), mx), 0).x; + float a21 = texelFetch(lumaMap, clamp(pix + ivec2(1, 0), ivec2(0), mx), 0).x; + float a02 = texelFetch(lumaMap, clamp(pix + ivec2(-1, 1), ivec2(0), mx), 0).x; + float a12 = texelFetch(lumaMap, clamp(pix + ivec2(0, 1), ivec2(0), mx), 0).x; + float a22 = texelFetch(lumaMap, clamp(pix + ivec2(1, 1), ivec2(0), mx), 0).x; + + float sx = (pc.lesser * a00 + pc.upper * a01 + pc.lesser * a02) + - (pc.lesser * a20 + pc.upper * a21 + pc.lesser * a22); + float sy = (pc.lesser * a00 + pc.upper * a10 + pc.lesser * a20) + - (pc.lesser * a02 + pc.upper * a12 + pc.lesser * a22); + + imageStore(gradientMap, pix, vec4(sx * pc.normVal, sy * pc.normVal, 0.0, 0.0)); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/dis_gray.comp b/app/src/main/cpp/winlator/vk/shaders/dis_gray.comp deleted file mode 100644 index 529b451c3..000000000 --- a/app/src/main/cpp/winlator/vk/shaders/dis_gray.comp +++ /dev/null @@ -1,28 +0,0 @@ -#version 450 -#extension GL_EXT_samplerless_texture_functions : require - -// Luminance pass: current composited frame (content rectangle) -> R16F level 0, in the same -// 0..255 scale the reference uses (CV_8U equivalent). - -layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; - -layout(set = 0, binding = 0) uniform texture2D srcColor; -layout(set = 0, binding = 1) uniform sampler samp; -layout(set = 0, binding = 2, r16f) uniform image2D dstGray; - -layout(push_constant) uniform PC { - vec2 dstSize; - vec2 rectOffset; - vec2 rectScale; -} pc; - -void main() { - ivec2 p = ivec2(gl_GlobalInvocationID.xy); - if (p.x >= int(pc.dstSize.x) || p.y >= int(pc.dstSize.y)) return; - - vec2 local = (vec2(p) + 0.5) / pc.dstSize; - vec2 uv = pc.rectOffset + local * pc.rectScale; - vec3 c = textureLod(sampler2D(srcColor, samp), uv, 0.0).rgb; - float g = dot(c, vec3(0.299, 0.587, 0.114)) * 255.0; - imageStore(dstGray, p, vec4(g, 0.0, 0.0, 0.0)); -} diff --git a/app/src/main/cpp/winlator/vk/shaders/dis_interp.comp b/app/src/main/cpp/winlator/vk/shaders/dis_interp.comp deleted file mode 100644 index 9e246a3c2..000000000 --- a/app/src/main/cpp/winlator/vk/shaders/dis_interp.comp +++ /dev/null @@ -1,75 +0,0 @@ -#version 450 -#extension GL_EXT_samplerless_texture_functions : require - -// Frame generation: warps previous/current along the flow field at timestamp t. Flow values are -// in working-resolution pixels (flow_full); they are converted to frame UV and the content -// rectangle is honoured everywhere, so the black bars are never read or written. - -layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; - -layout(set = 0, binding = 0) uniform texture2D prevColor; -layout(set = 0, binding = 1) uniform texture2D nextColor; -layout(set = 0, binding = 2) uniform texture2D flowTex; -layout(set = 0, binding = 3) uniform sampler samp; -layout(set = 0, binding = 4) writeonly uniform image2D target; - -layout(push_constant) uniform PC { - vec2 rectOffset; - vec2 rectScale; - vec2 invFlowSize; - float t; - float mag; - int debugMode; -} pc; - -vec3 hsv2rgb(vec3 c) { - vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); - vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); - return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); -} - -vec2 bilerpFlow(vec2 uv) { - ivec2 sz = textureSize(flowTex, 0); - vec2 pos = uv * vec2(sz) - 0.5; - vec2 mx = vec2(sz - ivec2(1)); - vec2 q = clamp(pos, vec2(0.0), mx); - vec2 b = floor(q); - vec2 f = q - b; - ivec2 i0 = ivec2(b); - vec2 a = texelFetch(flowTex, i0, 0).rg; - vec2 bb = texelFetch(flowTex, min(i0 + ivec2(1, 0), sz - 1), 0).rg; - vec2 c = texelFetch(flowTex, min(i0 + ivec2(0, 1), sz - 1), 0).rg; - vec2 d = texelFetch(flowTex, min(i0 + ivec2(1, 1), sz - 1), 0).rg; - return mix(mix(a, bb, f.x), mix(c, d, f.x), f.y); -} - -void main() { - ivec2 pix = ivec2(gl_GlobalInvocationID.xy); - ivec2 sz = imageSize(target); - if (pix.x >= sz.x || pix.y >= sz.y) return; - - vec2 uv = (vec2(pix) + 0.5) / vec2(sz); - vec2 scale = max(pc.rectScale, vec2(1e-6)); - vec2 local = (uv - pc.rectOffset) / scale; - if (local.x < 0.0 || local.x > 1.0 || local.y < 0.0 || local.y > 1.0) { - imageStore(target, pix, vec4(0.0, 0.0, 0.0, 1.0)); - return; - } - - vec2 flowPx = bilerpFlow(local); - vec2 fFrame = (flowPx * pc.invFlowSize) * scale; - - if (pc.debugMode == 1) { - float m = length(flowPx) / max(pc.mag, 1e-5); - float hue = atan(flowPx.y, flowPx.x) / 6.2831853 + 0.5; - vec3 fc = hsv2rgb(vec3(hue, clamp(m, 0.0, 1.0), min(1.0, 0.15 + m))); - imageStore(target, pix, vec4(fc, 1.0)); - return; - } - - vec2 uv0 = clamp(uv - pc.t * fFrame, pc.rectOffset, pc.rectOffset + scale); - vec2 uv1 = clamp(uv + (1.0 - pc.t) * fFrame, pc.rectOffset, pc.rectOffset + scale); - vec3 c0 = textureLod(sampler2D(prevColor, samp), uv0, 0.0).xyz; - vec3 c1 = textureLod(sampler2D(nextColor, samp), uv1, 0.0).xyz; - imageStore(target, pix, vec4(mix(c0, c1, pc.t), 1.0)); -} diff --git a/app/src/main/cpp/winlator/vk/shaders/dis_interpolate.comp b/app/src/main/cpp/winlator/vk/shaders/dis_interpolate.comp new file mode 100644 index 000000000..081952c93 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/dis_interpolate.comp @@ -0,0 +1,99 @@ +#version 450 + +precision highp float; +precision highp int; + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +layout(set = 0, binding = 0) uniform sampler2D prevColor; +layout(set = 0, binding = 1) uniform sampler2D nextColor; +layout(set = 0, binding = 2) uniform sampler2D flowTex; +layout(set = 0, binding = 5, rgba8) uniform image2D outImage; + +layout(push_constant) uniform PC { + float t; + int debugMode; +} pc; + +layout(constant_id = 0) const int manualFlowFilter = 0; + +vec2 sampleFlow(vec2 uv) { + if (manualFlowFilter == 0) return textureLod(flowTex, uv, 0.0).xy; + + vec2 sz = vec2(textureSize(flowTex, 0)); + vec2 p = uv * sz - 0.5; + vec2 frac = fract(p); + ivec2 i0 = ivec2(floor(p)); + ivec2 mx = ivec2(sz) - 1; + + vec2 a = texelFetch(flowTex, clamp(i0, ivec2(0), mx), 0).xy; + vec2 b = texelFetch(flowTex, clamp(i0 + ivec2(1, 0), ivec2(0), mx), 0).xy; + vec2 c = texelFetch(flowTex, clamp(i0 + ivec2(0, 1), ivec2(0), mx), 0).xy; + vec2 e = texelFetch(flowTex, clamp(i0 + ivec2(1, 1), ivec2(0), mx), 0).xy; + return mix(mix(a, b, frac.x), mix(c, e, frac.x), frac.y); +} + +vec3 hsv2rgb(vec3 c) { + vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); + vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); + return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); +} + +void main() { + ivec2 pix = ivec2(gl_GlobalInvocationID.xy); + ivec2 size = imageSize(outImage); + if (pix.x >= size.x || pix.y >= size.y) return; + + vec2 uv = (vec2(pix) + 0.5) / vec2(size); + vec2 texel = 1.0 / vec2(size); + + const float GUARD_PX = 20.0; + vec2 guard = GUARD_PX * texel; + + vec2 dEdge = min(uv, 1.0 - uv); + vec2 ramp = clamp((dEdge - guard) / max(guard, texel), 0.0, 1.0); + vec2 eased = ramp * ramp * (3.0 - 2.0 * ramp); + float edgeMix = min(eased.x, eased.y); + + vec2 f = sampleFlow(uv); + if (edgeMix < 1.0) { + vec2 fInner = sampleFlow(clamp(uv, guard, 1.0 - guard)); + f = mix(fInner, f, edgeMix); + } + + if (pc.debugMode != 0) { + float m = length(f) * float(size.x) / 16.0; + float hue = atan(f.y, f.x) / 6.2831853 + 0.5; + vec3 fc = hsv2rgb(vec3(hue, clamp(m, 0.0, 1.0), min(1.0, 0.15 + m))); + imageStore(outImage, pix, vec4(fc, 1.0)); + return; + } + + vec2 uv0 = uv - pc.t * f; + vec2 uv1 = uv + (1.0 - pc.t) * f; + + vec3 c0 = textureLod(prevColor, clamp(uv0, vec2(0.0), vec2(1.0)), 0.0).xyz; + vec3 c1 = textureLod(nextColor, clamp(uv1, vec2(0.0), vec2(1.0)), 0.0).xyz; + + vec3 single = pc.t < 0.5 ? c0 : c1; + + const float FEATHER_PX = 8.0; + vec2 feather = FEATHER_PX / vec2(size); + vec2 e0 = max(max(-uv0, uv0 - vec2(1.0)), vec2(0.0)) / feather; + vec2 e1 = max(max(-uv1, uv1 - vec2(1.0)), vec2(0.0)) / feather; + float out0 = clamp(max(e0.x, e0.y), 0.0, 1.0); + float out1 = clamp(max(e1.x, e1.y), 0.0, 1.0); + + float w0 = (1.0 - pc.t) * (1.0 - out0); + float w1 = pc.t * (1.0 - out1); + float wsum = w0 + w1; + + vec3 result = wsum > 1e-4 + ? (c0 * w0 + c1 * w1) / wsum + : (out0 <= out1 ? c0 : c1); + + float occl = smoothstep(0.10, 0.40, dot(abs(c0 - c1), vec3(1.0))); + result = mix(result, single, occl); + + imageStore(outImage, pix, vec4(result, 1.0)); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/dis_inverse_search.comp b/app/src/main/cpp/winlator/vk/shaders/dis_inverse_search.comp new file mode 100644 index 000000000..618f60698 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/dis_inverse_search.comp @@ -0,0 +1,134 @@ +#version 450 + +precision highp float; +precision highp int; + +#define DIS_INVERSE_ITERS 8 + +#define DIS_MAX_MATCH_RMS 36.0 + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +layout(set = 0, binding = 0) uniform sampler2D lastLumaMap; +layout(set = 0, binding = 1) uniform sampler2D nextLumaMap; +layout(set = 0, binding = 2) uniform sampler2D lastGradientMap; +layout(set = 0, binding = 3) uniform sampler2D flowMap; +layout(set = 0, binding = 4) uniform sampler2D lastFlowMap; +layout(set = 0, binding = 5, rgba32f) uniform image2D sparseFlowMap; + +layout(push_constant) uniform PC { + int level; + int coarseLevel; +} pc; + +float uluminance(vec3 c) { + return (0.299 * c.x + 0.587 * c.y + 0.114 * c.z) * 255.0; +} + +float myDeterminant(mat2 m) { + return m[0][0] * m[1][1] - m[0][1] * m[1][0]; +} + +mat2 myInverse(mat2 m) { + float det = myDeterminant(m); + if (abs(det) < 1e-10) return mat2(0.0); + return mat2(m[1][1], -m[0][1], -m[1][0], m[0][0]) / det; +} + +void main() { + const float patchSize = 8.0; + + ivec2 pixSparse = ivec2(gl_GlobalInvocationID.xy); + ivec2 sparseSize = imageSize(sparseFlowMap); + if (pixSparse.x >= sparseSize.x || pixSparse.y >= sparseSize.y) return; + + ivec2 pix = pixSparse * 3; + ivec2 denseSize = textureSize(lastLumaMap, 0); + ivec2 denseMax = denseSize - 1; + + float lastImageData[64]; + vec2 gradData[64]; + + vec2 gradSum = vec2(0.0); + mat2 H = mat2(0.0); + + for (int i = 0; i < 8; i++) { + for (int j = 0; j < 8; j++) { + ivec2 q = clamp(pix + ivec2(i, j), ivec2(0), denseMax); + gradData[i * 8 + j] = -texelFetch(lastGradientMap, q, 0).xy; + + H[0][0] += gradData[i * 8 + j].x * gradData[i * 8 + j].x; + H[1][1] += gradData[i * 8 + j].y * gradData[i * 8 + j].y; + H[0][1] += gradData[i * 8 + j].x * gradData[i * 8 + j].y; + + lastImageData[i * 8 + j] = + texelFetch(lastLumaMap, q, 0).x; + + gradSum += gradData[i * 8 + j]; + } + } + + H[1][0] = H[0][1]; + if (myDeterminant(H) < 1e-6) { + H[0][0] += 1e-6; + H[1][1] += 1e-6; + } + mat2 H_inv = myInverse(H); + + vec2 flow; + if (pc.level == pc.coarseLevel) { + flow = vec2(0.0); + } else { + ivec2 fmMax = textureSize(flowMap, 0) - 1; + vec4 cf = texelFetch(flowMap, clamp(ivec2(pix / 2) + 2, ivec2(0), fmMax), 0); + flow = cf.xy * vec2(denseSize); + if (any(isnan(flow)) || any(isinf(flow))) flow = vec2(0.0); + } + vec2 initialFlow = flow; + + vec2 invImageSize = 1.0 / vec2(denseSize); + const float N = 64.0; + + const float N_INV = 1.0 / N; + float prevSSD = 1e10; + for (int iter = 0; iter < DIS_INVERSE_ITERS; iter++) { + vec2 warpOrigin = clamp(vec2(pix) + flow, vec2(0.0), vec2(denseSize) - patchSize); + float sd = 0.0; + float sd2 = 0.0; + vec2 sIg = vec2(0.0); + + for (int i = 0; i < 8; i++) { + for (int j = 0; j < 8; j++) { + vec2 tc = (warpOrigin + vec2(i, j) + 0.5) * invImageSize; + float warped = textureLod(nextLumaMap, tc, 0.0).x; + float diff = warped - lastImageData[i * 8 + j]; + sd += diff; + sd2 += diff * diff; + sIg += gradData[i * 8 + j] * diff; + } + } + + vec2 dU = sIg - sd * gradSum / N; + float SSD = sd2 - sd * sd / N; + + flow -= H_inv * dU; + + if (SSD >= prevSSD) break; + prevSSD = SSD; + } + + vec2 wantOrigin = vec2(pix) + flow; + vec2 maxOrigin = vec2(denseSize) - patchSize; + bool clamped = any(lessThan(wantOrigin, vec2(-0.5))) + || any(greaterThan(wantOrigin, maxOrigin + 0.5)); + + bool unmatched = prevSSD * N_INV > DIS_MAX_MATCH_RMS * DIS_MAX_MATCH_RMS; + + if (any(isnan(flow)) || any(isinf(flow)) || clamped || unmatched || + length(flow - initialFlow) > patchSize) { + flow = initialFlow; + } + + flow *= invImageSize; + imageStore(sparseFlowMap, pixSparse, vec4(flow, -1.0, 1.0)); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/dis_luma.comp b/app/src/main/cpp/winlator/vk/shaders/dis_luma.comp new file mode 100644 index 000000000..70abe943f --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/dis_luma.comp @@ -0,0 +1,21 @@ +#version 450 + +precision highp float; +precision highp int; + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +layout(set = 0, binding = 0) uniform sampler2D colorMap; +layout(set = 0, binding = 5) writeonly uniform image2D lumaMap; + +float uluminance(vec3 c) { + return (0.299 * c.x + 0.587 * c.y + 0.114 * c.z) * 255.0; +} + +void main() { + ivec2 pix = ivec2(gl_GlobalInvocationID.xy); + ivec2 size = textureSize(colorMap, 0); + if (pix.x >= size.x || pix.y >= size.y) return; + + imageStore(lumaMap, pix, vec4(uluminance(texelFetch(colorMap, pix, 0).xyz), 0.0, 0.0, 1.0)); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/dis_luma_r16.comp b/app/src/main/cpp/winlator/vk/shaders/dis_luma_r16.comp new file mode 100644 index 000000000..cf39c1a06 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/dis_luma_r16.comp @@ -0,0 +1,21 @@ +#version 450 + +precision highp float; +precision highp int; + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +layout(set = 0, binding = 0) uniform sampler2D colorMap; +layout(set = 0, binding = 5, r16f) writeonly uniform image2D lumaMap; + +float uluminance(vec3 c) { + return (0.299 * c.x + 0.587 * c.y + 0.114 * c.z) * 255.0; +} + +void main() { + ivec2 pix = ivec2(gl_GlobalInvocationID.xy); + ivec2 size = textureSize(colorMap, 0); + if (pix.x >= size.x || pix.y >= size.y) return; + + imageStore(lumaMap, pix, vec4(uluminance(texelFetch(colorMap, pix, 0).xyz), 0.0, 0.0, 1.0)); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/dis_luma_r32.comp b/app/src/main/cpp/winlator/vk/shaders/dis_luma_r32.comp new file mode 100644 index 000000000..d2e2ca369 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/dis_luma_r32.comp @@ -0,0 +1,21 @@ +#version 450 + +precision highp float; +precision highp int; + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +layout(set = 0, binding = 0) uniform sampler2D colorMap; +layout(set = 0, binding = 5, r32f) writeonly uniform image2D lumaMap; + +float uluminance(vec3 c) { + return (0.299 * c.x + 0.587 * c.y + 0.114 * c.z) * 255.0; +} + +void main() { + ivec2 pix = ivec2(gl_GlobalInvocationID.xy); + ivec2 size = textureSize(colorMap, 0); + if (pix.x >= size.x || pix.y >= size.y) return; + + imageStore(lumaMap, pix, vec4(uluminance(texelFetch(colorMap, pix, 0).xyz), 0.0, 0.0, 1.0)); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/dis_propagate.comp b/app/src/main/cpp/winlator/vk/shaders/dis_propagate.comp index 3ad05e2ab..e299aa1f3 100644 --- a/app/src/main/cpp/winlator/vk/shaders/dis_propagate.comp +++ b/app/src/main/cpp/winlator/vk/shaders/dis_propagate.comp @@ -1,75 +1,92 @@ #version 450 -#extension GL_EXT_samplerless_texture_functions : require -// Spatial propagation (FS_PROP): each patch keeps its own flow or adopts a neighbour's flow at -// +offset, whichever has the lower mean-normalized patch SSD. +precision highp float; +precision highp int; layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; -#define PSZ 8 -#define N float(PSZ * PSZ) - -layout(set = 0, binding = 0) uniform texture2D u_I0; -layout(set = 0, binding = 1) uniform texture2D u_I1; -layout(set = 0, binding = 2) uniform texture2D u_grad; -layout(set = 0, binding = 3) uniform texture2D u_S; -layout(set = 0, binding = 4, rg32f) uniform image2D o_S; +layout(set = 0, binding = 0) uniform sampler2D lastLumaMap; +layout(set = 0, binding = 1) uniform sampler2D nextLumaMap; +layout(set = 0, binding = 2) uniform sampler2D flowIn; +layout(set = 0, binding = 5, rgba32f) uniform image2D flowOut; layout(push_constant) uniform PC { - vec2 u_size; - ivec2 u_off; - int u_stride; + int dist; } pc; -float sampleI1(vec2 pos) { - ivec2 sz = textureSize(u_I1, 0); - vec2 mx = vec2(sz - ivec2(1)); - vec2 q = clamp(pos, vec2(0.0), mx); - vec2 b = floor(q); - vec2 f = q - b; - ivec2 i0 = ivec2(b); - float a = texelFetch(u_I1, i0, 0).r; - float bb = texelFetch(u_I1, min(i0 + ivec2(1, 0), sz - 1), 0).r; - float c = texelFetch(u_I1, min(i0 + ivec2(0, 1), sz - 1), 0).r; - float d = texelFetch(u_I1, min(i0 + ivec2(1, 1), sz - 1), 0).r; - return mix(mix(a, bb, f.x), mix(c, d, f.x), f.y); -} +void main() { + ivec2 s = ivec2(gl_GlobalInvocationID.xy); + ivec2 mx = textureSize(flowIn, 0) - 1; + if (s.x > mx.x || s.y > mx.y) return; -vec2 clampPos(vec2 pos) { - return clamp(pos, vec2(1.0 - float(PSZ)), pc.u_size - 1.0); -} + vec4 ownIn = texelFetch(flowIn, s, 0); + vec2 own = ownIn.xy; + float ownSsd = ownIn.z; + bool needOwn = !(ownSsd >= 0.0); -float fetchI1(vec2 pos) { - return sampleI1(pos); -} + const ivec2 dirs[4] = + ivec2[4](ivec2(-1, 0), ivec2(1, 0), ivec2(0, -1), ivec2(0, 1)); + vec2 cand[4]; + int candCount = 0; + for (int i = 0; i < 4; i++) { + ivec2 q = clamp(s + dirs[i] * pc.dist, ivec2(0), mx); + if (q == s) continue; + cand[candCount++] = texelFetch(flowIn, q, 0).xy; + } + if (candCount == 0) { + imageStore(flowOut, s, vec4(own, ownSsd, 1.0)); + return; + } -float patchSSD(ivec2 org, vec2 flow) { - vec2 pos = clampPos(vec2(org) + flow); - float sd = 0.0, sd2 = 0.0; - for (int dy = 0; dy < PSZ; dy++) { - for (int dx = 0; dx < PSZ; dx++) { - float diff = fetchI1(pos + vec2(float(dx), float(dy))) - - texelFetch(u_I0, org + ivec2(dx, dy), 0).r; - sd += diff; - sd2 += diff * diff; + ivec2 org = s * 3; + ivec2 denseSize = textureSize(lastLumaMap, 0); + ivec2 denseMax = denseSize - 1; + vec2 invImageSize = 1.0 / vec2(denseSize); + + float refLum[64]; + for (int dy = 0; dy < 8; dy++) { + for (int dx = 0; dx < 8; dx++) { + ivec2 p = clamp(org + ivec2(dx, dy), ivec2(0), denseMax); + refLum[dy * 8 + dx] = texelFetch(lastLumaMap, p, 0).x; } } - return sd2 - sd * sd / N; -} -void main() { - ivec2 s = ivec2(gl_GlobalInvocationID.xy); - ivec2 sz = imageSize(o_S); - if (s.x >= sz.x || s.y >= sz.y) return; + float sd[5]; + float sd2[5]; + for (int i = 0; i < 5; i++) { + sd[i] = 0.0; + sd2[i] = 0.0; + } - ivec2 q = clamp(s + pc.u_off, ivec2(0), textureSize(u_S, 0) - 1); - vec2 own = texelFetch(u_S, s, 0).rg; - if (q == s) { - imageStore(o_S, s, vec4(own, 0.0, 1.0)); - return; + for (int dy = 0; dy < 8; dy++) { + for (int dx = 0; dx < 8; dx++) { + int i = dy * 8 + dx; + vec2 base = (vec2(org) + vec2(dx, dy) + 0.5) * invImageSize; + float r = refLum[i]; + + if (needOwn) { + float d0 = textureLod(nextLumaMap, base + own, 0.0).x - r; + sd[0] += d0; + sd2[0] += d0 * d0; + } + + for (int c = 0; c < candCount; c++) { + float dc = textureLod(nextLumaMap, base + cand[c], 0.0).x - r; + sd[c + 1] += dc; + sd2[c + 1] += dc * dc; + } + } + } + + vec2 best = own; + float bestSsd = needOwn ? (sd2[0] - sd[0] * sd[0] / 64.0) : ownSsd; + for (int c = 0; c < candCount; c++) { + float ssd = sd2[c + 1] - sd[c + 1] * sd[c + 1] / 64.0; + if (ssd < bestSsd) { + bestSsd = ssd; + best = cand[c]; + } } - vec2 cand = texelFetch(u_S, q, 0).rg; - ivec2 org = s * pc.u_stride; - imageStore(o_S, s, vec4(patchSSD(org, cand) < patchSSD(org, own) ? cand : own, 0.0, 1.0)); + imageStore(flowOut, s, vec4(best, bestSsd, 1.0)); } diff --git a/app/src/main/cpp/winlator/vk/shaders/dis_sobel.comp b/app/src/main/cpp/winlator/vk/shaders/dis_sobel.comp deleted file mode 100644 index 1ee852f1b..000000000 --- a/app/src/main/cpp/winlator/vk/shaders/dis_sobel.comp +++ /dev/null @@ -1,22 +0,0 @@ -#version 450 -#extension GL_EXT_samplerless_texture_functions : require - -// spatialGradient: Sobel 3x3, ksize=3, no normalization, BORDER_REPLICATE. - -layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; - -layout(set = 0, binding = 0) uniform texture2D src; -layout(set = 0, binding = 1, rg16f) uniform image2D dst; - -void main() { - ivec2 p = ivec2(gl_GlobalInvocationID.xy); - ivec2 sz = imageSize(dst); - if (p.x >= sz.x || p.y >= sz.y) return; - - ivec2 mx = textureSize(src, 0) - 1; -#define S(dx, dy) texelFetch(src, clamp(p + ivec2(dx, dy), ivec2(0), mx), 0).r - float ix = (S(1, -1) + 2.0 * S(1, 0) + S(1, 1)) - (S(-1, -1) + 2.0 * S(-1, 0) + S(-1, 1)); - float iy = (S(-1, 1) + 2.0 * S(0, 1) + S(1, 1)) - (S(-1, -1) + 2.0 * S(0, -1) + S(1, -1)); -#undef S - imageStore(dst, p, vec4(ix, iy, 0.0, 0.0)); -} diff --git a/app/src/main/cpp/winlator/vk/shaders/dis_structure.comp b/app/src/main/cpp/winlator/vk/shaders/dis_structure.comp deleted file mode 100644 index eccbd472a..000000000 --- a/app/src/main/cpp/winlator/vk/shaders/dis_structure.comp +++ /dev/null @@ -1,45 +0,0 @@ -#version 450 -#extension GL_EXT_samplerless_texture_functions : require - -// precomputeStructureTensor + initS in one pass. One invocation per patch of the sparse grid: -// accumulates Ix^2, Iy^2, IxIy, sum(Ix), sum(Iy) and seeds the patch flow from the coarser -// level's U at the patch centre. - -layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; - -#define PSZ 8 - -layout(set = 0, binding = 0) uniform texture2D u_grad; -layout(set = 0, binding = 1) uniform texture2D u_U; -layout(set = 0, binding = 2, rgba32f) uniform image2D o_ST; -layout(set = 0, binding = 3, r32f) uniform image2D o_ST2; -layout(set = 0, binding = 4, rg32f) uniform image2D o_S0; - -layout(push_constant) uniform PC { - ivec2 u_sparse; - int u_stride; - int u_psz2; -} pc; - -void main() { - ivec2 s = ivec2(gl_GlobalInvocationID.xy); - if (s.x >= pc.u_sparse.x || s.y >= pc.u_sparse.y) return; - - ivec2 base = s * pc.u_stride; - - float xx = 0.0, yy = 0.0, xy = 0.0, sx = 0.0, sy = 0.0; - for (int dy = 0; dy < PSZ; dy++) { - for (int dx = 0; dx < PSZ; dx++) { - vec2 g = texelFetch(u_grad, base + ivec2(dx, dy), 0).rg; - xx += g.x * g.x; - yy += g.y * g.y; - xy += g.x * g.y; - sx += g.x; - sy += g.y; - } - } - - imageStore(o_ST, s, vec4(xx, yy, xy, sx)); - imageStore(o_ST2, s, vec4(sy, 0.0, 0.0, 0.0)); - imageStore(o_S0, s, texelFetch(u_U, base + ivec2(pc.u_psz2), 0)); -} diff --git a/app/src/main/cpp/winlator/vk/shaders/dis_vr_add.comp b/app/src/main/cpp/winlator/vk/shaders/dis_vr_add.comp new file mode 100644 index 000000000..cba444082 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/dis_vr_add.comp @@ -0,0 +1,26 @@ +#version 450 + +precision highp float; +precision highp int; + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +layout(set = 0, binding = 0) uniform sampler2D flowDense; +layout(set = 0, binding = 1) uniform sampler2D dW; +layout(set = 0, binding = 8, rg32f) uniform image2D flowRefined; + +void main() { + ivec2 p = ivec2(gl_GlobalInvocationID.xy); + ivec2 sz = imageSize(flowRefined); + if (p.x >= sz.x || p.y >= sz.y) return; + vec2 uSize = vec2(sz); + vec2 invSize = 1.0 / uSize; + + vec2 W = texelFetch(flowDense, p, 0).xy * uSize; + vec2 f = W + texelFetch(dW, p, 0).xy; + float lim = 4.0 * uSize.x; + bvec2 ok = lessThan(abs(f), vec2(lim)); + vec2 fr = vec2(ok.x ? f.x : W.x, ok.y ? f.y : W.y); + + imageStore(flowRefined, p, vec4(fr * invSize, 0.0, 1.0)); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/dis_vr_add_resize.comp b/app/src/main/cpp/winlator/vk/shaders/dis_vr_add_resize.comp deleted file mode 100644 index c989fc97d..000000000 --- a/app/src/main/cpp/winlator/vk/shaders/dis_vr_add_resize.comp +++ /dev/null @@ -1,48 +0,0 @@ -#version 450 -#extension GL_EXT_samplerless_texture_functions : require - -// FS_VR_ADD + FS_RESIZE merged: the refined flow (W + dW) is bilinearly resampled straight into -// the next finer level's U (value scale 2) or into flow_full (value scale 2^finest). A bad -// cell is rejected back to W so it cannot spread through the pyramid. - -layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; - -layout(set = 0, binding = 0) uniform texture2D u_W; -layout(set = 0, binding = 1) uniform texture2D u_dW; -layout(set = 0, binding = 2, rg32f) uniform image2D o_dst; - -layout(push_constant) uniform PC { - float u_scale; -} pc; - -vec2 bilerp(texture2D t, vec2 pos) { - ivec2 sz = textureSize(t, 0); - vec2 mx = vec2(sz - ivec2(1)); - vec2 q = clamp(pos, vec2(0.0), mx); - vec2 b = floor(q); - vec2 f = q - b; - ivec2 i0 = ivec2(b); - vec2 a = texelFetch(t, i0, 0).rg; - vec2 bb = texelFetch(t, min(i0 + ivec2(1, 0), sz - 1), 0).rg; - vec2 c = texelFetch(t, min(i0 + ivec2(0, 1), sz - 1), 0).rg; - vec2 d = texelFetch(t, min(i0 + ivec2(1, 1), sz - 1), 0).rg; - return mix(mix(a, bb, f.x), mix(c, d, f.x), f.y); -} - -void main() { - ivec2 p = ivec2(gl_GlobalInvocationID.xy); - ivec2 dstSize = imageSize(o_dst); - if (p.x >= dstSize.x || p.y >= dstSize.y) return; - - ivec2 srcSize = textureSize(u_W, 0); - vec2 src = (vec2(p) + 0.5) * (vec2(srcSize) / vec2(dstSize)) - 0.5; - - vec2 W = bilerp(u_W, src); - vec2 dW = bilerp(u_dW, src); - vec2 f = W + dW; - - float lim = 4.0 * float(srcSize.x); - bvec2 ok = lessThan(abs(f), vec2(lim)); - vec2 r = vec2(ok.x ? f.x : W.x, ok.y ? f.y : W.y); - imageStore(o_dst, p, vec4(r * pc.u_scale, 0.0, 0.0)); -} diff --git a/app/src/main/cpp/winlator/vk/shaders/dis_vr_coef.comp b/app/src/main/cpp/winlator/vk/shaders/dis_vr_coef.comp new file mode 100644 index 000000000..f2a9e4b9d --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/dis_vr_coef.comp @@ -0,0 +1,77 @@ +#version 450 + +precision highp float; +precision highp int; + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +layout(set = 0, binding = 0) uniform sampler2D prep; +layout(set = 0, binding = 1) uniform sampler2D d1; +layout(set = 0, binding = 2) uniform sampler2D d2; +layout(set = 0, binding = 3) uniform sampler2D dW; +layout(set = 0, binding = 4) uniform sampler2D flowDense; +layout(set = 0, binding = 5) uniform sampler2D wt; +layout(set = 0, binding = 8, rgba32f) uniform image2D A; +layout(set = 0, binding = 9, rg32f) uniform image2D B; + +layout(push_constant) uniform PC { + float delta2; + float gamma2; + float zeta2; + float eps2; +} pc; + +void main() { + ivec2 p = ivec2(gl_GlobalInvocationID.xy); + ivec2 sz = imageSize(A); + if (p.x >= sz.x || p.y >= sz.y) return; + ivec2 mx = sz - 1; + vec2 uSize = vec2(sz); + + vec4 d1v = texelFetch(d1, p, 0); + vec4 d2v = texelFetch(d2, p, 0); + float Iz = texelFetch(prep, p, 0).y; + vec2 dWp = texelFetch(dW, p, 0).xy; + float Ix = d1v.x, Iy = d1v.y, Ixz = d1v.z, Iyz = d1v.w; + float Ixx = d2v.x, Ixy = d2v.y, Iyy = d2v.z; + float dU = dWp.x, dV = dWp.y; + + float dn = Ix * Ix + Iy * Iy + pc.zeta2; + float Ik1z = Iz + Ix * dU + Iy * dV; + float w = (pc.delta2 / sqrt(Ik1z * Ik1z / dn + pc.eps2)) / dn; + float a11 = w * Ix * Ix + pc.zeta2; + float a12 = w * Ix * Iy; + float a22 = w * Iy * Iy + pc.zeta2; + float b1 = -w * Iz * Ix; + float b2 = -w * Iz * Iy; + + float dnx = Ixx * Ixx + Ixy * Ixy + pc.zeta2; + float dny = Iyy * Iyy + Ixy * Ixy + pc.zeta2; + float Ik1zx = Ixz + Ixx * dU + Ixy * dV; + float Ik1zy = Iyz + Ixy * dU + Iyy * dV; + w = pc.gamma2 / sqrt(Ik1zx * Ik1zx / dnx + Ik1zy * Ik1zy / dny + pc.eps2); + a11 += w * (Ixx * Ixx / dnx + Ixy * Ixy / dny); + a12 += w * (Ixx * Ixy / dnx + Ixy * Iyy / dny); + a22 += w * (Ixy * Ixy / dnx + Iyy * Iyy / dny); + b1 -= w * (Ixx * Ixz / dnx + Ixy * Iyz / dny); + b2 -= w * (Ixy * Ixz / dnx + Iyy * Iyz / dny); + + float wc = texelFetch(wt, p, 0).r; + float wl = texelFetch(wt, ivec2(max(p.x - 1, 0), p.y), 0).r; + float wu = texelFetch(wt, ivec2(p.x, max(p.y - 1, 0)), 0).r; + vec2 W = texelFetch(flowDense, p, 0).xy * uSize; + vec2 Wr = texelFetch(flowDense, ivec2(min(p.x + 1, mx.x), p.y), 0).xy * uSize; + vec2 Wl = texelFetch(flowDense, ivec2(max(p.x - 1, 0), p.y), 0).xy * uSize; + vec2 Wd = texelFetch(flowDense, ivec2(p.x, min(p.y + 1, mx.y)), 0).xy * uSize; + vec2 Wu = texelFetch(flowDense, ivec2(p.x, max(p.y - 1, 0)), 0).xy * uSize; + + float addA = 0.0; + vec2 addB = vec2(0.0); + if (p.x < mx.x) { addA += wc; addB += wc * (Wr - W); } + if (p.x > 0) { addA += wl; addB -= wl * (W - Wl); } + if (p.y < mx.y) { addA += wc; addB += wc * (Wd - W); } + if (p.y > 0) { addA += wu; addB -= wu * (W - Wu); } + + imageStore(A, p, vec4(a11 + addA, a12, a22 + addA, 0.0)); + imageStore(B, p, vec4(vec2(b1, b2) + addB, 0.0, 0.0)); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/dis_vr_coeffs.comp b/app/src/main/cpp/winlator/vk/shaders/dis_vr_coeffs.comp deleted file mode 100644 index 12b0e5f8d..000000000 --- a/app/src/main/cpp/winlator/vk/shaders/dis_vr_coeffs.comp +++ /dev/null @@ -1,104 +0,0 @@ -#version 450 -#extension GL_EXT_samplerless_texture_functions : require - -// ComputeDataTerm + both ComputeSmoothnessTerm passes as a gather (FS_VR_COEF). The second -// derivatives (FS_VR_D2) and Iz are derived inline so no d2 / prep buffers are needed. - -layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; - -layout(set = 0, binding = 0) uniform texture2D u_d1; -layout(set = 0, binding = 1) uniform texture2D u_dW; -layout(set = 0, binding = 2) uniform texture2D u_W; -layout(set = 0, binding = 3) uniform texture2D u_wt; -layout(set = 0, binding = 4) uniform texture2D u_I0; -layout(set = 0, binding = 5) uniform texture2D u_I1; -layout(set = 0, binding = 6, rgba32f) uniform image2D o_A; -layout(set = 0, binding = 7, rg32f) uniform image2D o_B; - -layout(push_constant) uniform PC { - vec2 u_size; - float u_delta2; - float u_gamma2; - float u_zeta2; - float u_eps2; -} pc; - -float sampleI1(vec2 pos) { - ivec2 sz = textureSize(u_I1, 0); - vec2 mx = vec2(sz - ivec2(1)); - vec2 q = clamp(pos, vec2(0.0), mx); - vec2 b = floor(q); - vec2 f = q - b; - ivec2 i0 = ivec2(b); - float a = texelFetch(u_I1, i0, 0).r; - float bb = texelFetch(u_I1, min(i0 + ivec2(1, 0), sz - 1), 0).r; - float c = texelFetch(u_I1, min(i0 + ivec2(0, 1), sz - 1), 0).r; - float d = texelFetch(u_I1, min(i0 + ivec2(1, 1), sz - 1), 0).r; - return mix(mix(a, bb, f.x), mix(c, d, f.x), f.y); -} - -void main() { - ivec2 p = ivec2(gl_GlobalInvocationID.xy); - ivec2 sz = imageSize(o_A); - if (p.x >= sz.x || p.y >= sz.y) return; - ivec2 mx = sz - 1; - - vec4 d1 = texelFetch(u_d1, p, 0); - vec2 f = texelFetch(u_W, p, 0).rg; - float Iz = sampleI1(vec2(p) + f) - texelFetch(u_I0, p, 0).r; - - vec4 xp = texelFetch(u_d1, min(p + ivec2(1, 0), mx), 0); - vec4 xm = texelFetch(u_d1, max(p - ivec2(1, 0), ivec2(0)), 0); - vec4 yp = texelFetch(u_d1, min(p + ivec2(0, 1), mx), 0); - vec4 ym = texelFetch(u_d1, max(p - ivec2(0, 1), ivec2(0)), 0); - float Ixx = xp.x - xm.x; - float Ixy = yp.x - ym.x; - float Iyy = yp.y - ym.y; - - vec2 dW = texelFetch(u_dW, p, 0).rg; - float Ix = d1.x, Iy = d1.y, Ixz = d1.z, Iyz = d1.w; - float dU = dW.x, dV = dW.y; - - // Brightness constancy (Taylor expansion). - float dn = Ix * Ix + Iy * Iy + pc.u_zeta2; - float Ik1z = Iz + Ix * dU + Iy * dV; - float w = (pc.u_delta2 / sqrt(Ik1z * Ik1z / dn + pc.u_eps2)) / dn; - float a11 = w * Ix * Ix + pc.u_zeta2; - float a12 = w * Ix * Iy; - float a22 = w * Iy * Iy + pc.u_zeta2; - float b1 = -w * Iz * Ix; - float b2 = -w * Iz * Iy; - - // Gradient constancy. - float dnx = Ixx * Ixx + Ixy * Ixy + pc.u_zeta2; - float dny = Iyy * Iyy + Ixy * Ixy + pc.u_zeta2; - float Ik1zx = Ixz + Ixx * dU + Ixy * dV; - float Ik1zy = Iyz + Ixy * dU + Iyy * dV; - w = pc.u_gamma2 / sqrt(Ik1zx * Ik1zx / dnx + Ik1zy * Ik1zy / dny + pc.u_eps2); - a11 += w * (Ixx * Ixx / dnx + Ixy * Ixy / dny); - a12 += w * (Ixx * Ixy / dnx + Ixy * Iyy / dny); - a22 += w * (Ixy * Ixy / dnx + Iyy * Iyy / dny); - b1 -= w * (Ixx * Ixz / dnx + Ixy * Iyz / dny); - b2 -= w * (Ixy * Ixz / dnx + Iyy * Iyz / dny); - - // Smoothness: edges (p, p+x) and (p, p+y) carry wt(p), edges (p-x, p) and (p-y, p) carry - // the neighbours' weights. - float wc = texelFetch(u_wt, p, 0).r; - float wl = texelFetch(u_wt, ivec2(max(p.x - 1, 0), p.y), 0).r; - float wu = texelFetch(u_wt, ivec2(p.x, max(p.y - 1, 0)), 0).r; - vec2 W = texelFetch(u_W, p, 0).rg; - vec2 Wr = texelFetch(u_W, ivec2(min(p.x + 1, mx.x), p.y), 0).rg; - vec2 Wl = texelFetch(u_W, ivec2(max(p.x - 1, 0), p.y), 0).rg; - vec2 Wd = texelFetch(u_W, ivec2(p.x, min(p.y + 1, mx.y)), 0).rg; - vec2 Wu = texelFetch(u_W, ivec2(p.x, max(p.y - 1, 0)), 0).rg; - - float addA = 0.0; - vec2 addB = vec2(0.0); - if (p.x < mx.x) { addA += wc; addB += wc * (Wr - W); } - if (p.x > 0) { addA += wl; addB -= wl * (W - Wl); } - if (p.y < mx.y) { addA += wc; addB += wc * (Wd - W); } - if (p.y > 0) { addA += wu; addB -= wu * (W - Wu); } - - imageStore(o_A, p, vec4(a11 + addA, a12, a22 + addA, 0.0)); - imageStore(o_B, p, vec4(b1 + addB.x, b2 + addB.y, 0.0, 0.0)); -} diff --git a/app/src/main/cpp/winlator/vk/shaders/dis_vr_d1.comp b/app/src/main/cpp/winlator/vk/shaders/dis_vr_d1.comp new file mode 100644 index 000000000..bf66b5a6d --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/dis_vr_d1.comp @@ -0,0 +1,23 @@ +#version 450 + +precision highp float; +precision highp int; + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +layout(set = 0, binding = 0) uniform sampler2D prep; +layout(set = 0, binding = 8, rgba32f) uniform image2D d1; + +void main() { + ivec2 p = ivec2(gl_GlobalInvocationID.xy); + ivec2 sz = imageSize(d1); + if (p.x >= sz.x || p.y >= sz.y) return; + ivec2 mx = sz - 1; + + vec2 xp = texelFetch(prep, clamp(p + ivec2(1, 0), ivec2(0), mx), 0).xy; + vec2 xm = texelFetch(prep, clamp(p + ivec2(-1, 0), ivec2(0), mx), 0).xy; + vec2 yp = texelFetch(prep, clamp(p + ivec2(0, 1), ivec2(0), mx), 0).xy; + vec2 ym = texelFetch(prep, clamp(p + ivec2(0, -1), ivec2(0), mx), 0).xy; + + imageStore(d1, p, vec4(xp.x - xm.x, yp.x - ym.x, xp.y - xm.y, yp.y - ym.y)); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/dis_vr_d2.comp b/app/src/main/cpp/winlator/vk/shaders/dis_vr_d2.comp new file mode 100644 index 000000000..e1861a00f --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/dis_vr_d2.comp @@ -0,0 +1,23 @@ +#version 450 + +precision highp float; +precision highp int; + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +layout(set = 0, binding = 0) uniform sampler2D d1; +layout(set = 0, binding = 8, rgba32f) uniform image2D d2; + +void main() { + ivec2 p = ivec2(gl_GlobalInvocationID.xy); + ivec2 sz = imageSize(d2); + if (p.x >= sz.x || p.y >= sz.y) return; + ivec2 mx = sz - 1; + + vec2 xp = texelFetch(d1, clamp(p + ivec2(1, 0), ivec2(0), mx), 0).xy; + vec2 xm = texelFetch(d1, clamp(p + ivec2(-1, 0), ivec2(0), mx), 0).xy; + vec2 yp = texelFetch(d1, clamp(p + ivec2(0, 1), ivec2(0), mx), 0).xy; + vec2 ym = texelFetch(d1, clamp(p + ivec2(0, -1), ivec2(0), mx), 0).xy; + + imageStore(d2, p, vec4(xp.x - xm.x, yp.x - ym.x, yp.y - ym.y, 0.0)); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/dis_vr_prep.comp b/app/src/main/cpp/winlator/vk/shaders/dis_vr_prep.comp new file mode 100644 index 000000000..b5cfb6088 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/dis_vr_prep.comp @@ -0,0 +1,30 @@ +#version 450 + +precision highp float; +precision highp int; + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +layout(set = 0, binding = 0) uniform sampler2D prevColor; +layout(set = 0, binding = 1) uniform sampler2D nextColor; +layout(set = 0, binding = 2) uniform sampler2D flowDense; +layout(set = 0, binding = 8, rg32f) uniform image2D prep; +layout(set = 0, binding = 9, rg32f) uniform image2D dW; + +float uluminance(vec3 c) { + return (0.299 * c.x + 0.587 * c.y + 0.114 * c.z) * 255.0; +} + +void main() { + ivec2 p = ivec2(gl_GlobalInvocationID.xy); + ivec2 sz = imageSize(prep); + if (p.x >= sz.x || p.y >= sz.y) return; + + vec2 invSize = 1.0 / vec2(sz); + float i0 = uluminance(texelFetch(prevColor, p, 0).xyz); + vec2 fn = texelFetch(flowDense, p, 0).xy; + float w = uluminance(textureLod(nextColor, (vec2(p) + 0.5) * invSize + fn, 0.0).xyz); + + imageStore(prep, p, vec4(0.5 * (i0 + w), w - i0, 0.0, 0.0)); + imageStore(dW, p, vec4(0.0)); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/dis_vr_prepare.comp b/app/src/main/cpp/winlator/vk/shaders/dis_vr_prepare.comp deleted file mode 100644 index b73fc826f..000000000 --- a/app/src/main/cpp/winlator/vk/shaders/dis_vr_prepare.comp +++ /dev/null @@ -1,51 +0,0 @@ -#version 450 -#extension GL_EXT_samplerless_texture_functions : require - -// Variational refinement: warpImage + averageOp + subtractOp + Sobel ksize=1 (FS_VR_PREP + -// FS_VR_D1 merged). Writes only d1 = (Ix, Iy, Ixz, Iyz); Iz is derived on the fly in the -// coefficient pass. - -layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; - -layout(set = 0, binding = 0) uniform texture2D u_I0; -layout(set = 0, binding = 1) uniform texture2D u_I1; -layout(set = 0, binding = 2) uniform texture2D u_U; -layout(set = 0, binding = 3, rgba32f) uniform image2D o_d1; - -layout(push_constant) uniform PC { - vec2 u_size; -} pc; - -float sampleI1(vec2 pos) { - ivec2 sz = textureSize(u_I1, 0); - vec2 mx = vec2(sz - ivec2(1)); - vec2 q = clamp(pos, vec2(0.0), mx); - vec2 b = floor(q); - vec2 f = q - b; - ivec2 i0 = ivec2(b); - float a = texelFetch(u_I1, i0, 0).r; - float bb = texelFetch(u_I1, min(i0 + ivec2(1, 0), sz - 1), 0).r; - float c = texelFetch(u_I1, min(i0 + ivec2(0, 1), sz - 1), 0).r; - float d = texelFetch(u_I1, min(i0 + ivec2(1, 1), sz - 1), 0).r; - return mix(mix(a, bb, f.x), mix(c, d, f.x), f.y); -} - -vec2 prepAt(ivec2 p) { - float i0 = texelFetch(u_I0, p, 0).r; - float w = sampleI1(vec2(p) + texelFetch(u_U, p, 0).rg); - return vec2(0.5 * (i0 + w), w - i0); -} - -void main() { - ivec2 p = ivec2(gl_GlobalInvocationID.xy); - ivec2 sz = imageSize(o_d1); - if (p.x >= sz.x || p.y >= sz.y) return; - - ivec2 mx = sz - 1; - vec2 xp = prepAt(min(p + ivec2(1, 0), mx)); - vec2 xm = prepAt(max(p - ivec2(1, 0), ivec2(0))); - vec2 yp = prepAt(min(p + ivec2(0, 1), mx)); - vec2 ym = prepAt(max(p - ivec2(0, 1), ivec2(0))); - - imageStore(o_d1, p, vec4(xp.x - xm.x, yp.x - ym.x, xp.y - xm.y, yp.y - ym.y)); -} diff --git a/app/src/main/cpp/winlator/vk/shaders/dis_vr_sor.comp b/app/src/main/cpp/winlator/vk/shaders/dis_vr_sor.comp index 44755e408..b407df553 100644 --- a/app/src/main/cpp/winlator/vk/shaders/dis_vr_sor.comp +++ b/app/src/main/cpp/winlator/vk/shaders/dis_vr_sor.comp @@ -1,50 +1,49 @@ #version 450 -#extension GL_EXT_samplerless_texture_functions : require -// Red-black SOR (FS_VR_SOR). The edge weight is zeroed when the neighbour is missing, matching -// the four conditions used while assembling A, so diagonal dominance holds at the borders. +precision highp float; +precision highp int; layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; -layout(set = 0, binding = 0) uniform texture2D u_A; -layout(set = 0, binding = 1) uniform texture2D u_B; -layout(set = 0, binding = 2) uniform texture2D u_wt; -layout(set = 0, binding = 3) uniform texture2D u_dW; -layout(set = 0, binding = 4, rg32f) uniform image2D o_dW; +layout(set = 0, binding = 0) uniform sampler2D A; +layout(set = 0, binding = 1) uniform sampler2D B; +layout(set = 0, binding = 2) uniform sampler2D wt; +layout(set = 0, binding = 3) uniform sampler2D dWin; +layout(set = 0, binding = 8, rg32f) uniform image2D dWout; layout(push_constant) uniform PC { - float u_omega; - int u_parity; + float omega; + int parity; } pc; void main() { ivec2 p = ivec2(gl_GlobalInvocationID.xy); - ivec2 sz = imageSize(o_dW); + ivec2 sz = imageSize(dWout); if (p.x >= sz.x || p.y >= sz.y) return; ivec2 mx = sz - 1; - vec2 d = texelFetch(u_dW, p, 0).rg; - if (((p.x + p.y) & 1) != pc.u_parity) { - imageStore(o_dW, p, vec4(d, 0.0, 0.0)); + vec2 d = texelFetch(dWin, p, 0).xy; + if (((p.x + p.y) & 1) != pc.parity) { + imageStore(dWout, p, vec4(d, 0.0, 0.0)); return; } - float wc = texelFetch(u_wt, p, 0).r; - float wl = p.x > 0 ? texelFetch(u_wt, ivec2(p.x - 1, p.y), 0).r : 0.0; - float wu = p.y > 0 ? texelFetch(u_wt, ivec2(p.x, p.y - 1), 0).r : 0.0; + float wc = texelFetch(wt, p, 0).r; + float wl = p.x > 0 ? texelFetch(wt, ivec2(p.x - 1, p.y), 0).r : 0.0; + float wu = p.y > 0 ? texelFetch(wt, ivec2(p.x, p.y - 1), 0).r : 0.0; float wr = p.x < mx.x ? wc : 0.0; float wd = p.y < mx.y ? wc : 0.0; - vec2 dl = texelFetch(u_dW, ivec2(max(p.x - 1, 0), p.y), 0).rg; - vec2 dr = texelFetch(u_dW, ivec2(min(p.x + 1, mx.x), p.y), 0).rg; - vec2 du = texelFetch(u_dW, ivec2(p.x, max(p.y - 1, 0)), 0).rg; - vec2 dd = texelFetch(u_dW, ivec2(p.x, min(p.y + 1, mx.y)), 0).rg; + vec2 dl = texelFetch(dWin, ivec2(max(p.x - 1, 0), p.y), 0).xy; + vec2 dr = texelFetch(dWin, ivec2(min(p.x + 1, mx.x), p.y), 0).xy; + vec2 du = texelFetch(dWin, ivec2(p.x, max(p.y - 1, 0)), 0).xy; + vec2 dd = texelFetch(dWin, ivec2(p.x, min(p.y + 1, mx.y)), 0).xy; vec2 sigma = wl * dl + wr * dr + wu * du + wd * dd; - vec4 A = texelFetch(u_A, p, 0); - vec2 B = texelFetch(u_B, p, 0).rg; + vec4 A = texelFetch(A, p, 0); + vec2 Bv = texelFetch(B, p, 0).xy; - float nu = d.x + pc.u_omega * ((sigma.x + B.x - d.y * A.y) / A.x - d.x); - float nv = d.y + pc.u_omega * ((sigma.y + B.y - nu * A.y) / A.z - d.y); - imageStore(o_dW, p, vec4(nu, nv, 0.0, 0.0)); + float nu = d.x + pc.omega * ((sigma.x + Bv.x - d.y * A.y) / A.x - d.x); + float nv = d.y + pc.omega * ((sigma.y + Bv.y - nu * A.y) / A.z - d.y); + imageStore(dWout, p, vec4(nu, nv, 0.0, 0.0)); } diff --git a/app/src/main/cpp/winlator/vk/shaders/dis_vr_w.comp b/app/src/main/cpp/winlator/vk/shaders/dis_vr_w.comp new file mode 100644 index 000000000..685a1ede3 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/shaders/dis_vr_w.comp @@ -0,0 +1,32 @@ +#version 450 + +precision highp float; +precision highp int; + +layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; + +layout(set = 0, binding = 0) uniform sampler2D flowDense; +layout(set = 0, binding = 1) uniform sampler2D dW; +layout(set = 0, binding = 8, r32f) uniform image2D wt; + +layout(push_constant) uniform PC { + float alpha2; + float eps2; +} pc; + +void main() { + ivec2 p = ivec2(gl_GlobalInvocationID.xy); + ivec2 sz = imageSize(wt); + if (p.x >= sz.x || p.y >= sz.y) return; + ivec2 mx = sz - 1; + vec2 uSize = vec2(sz); + + vec2 c = texelFetch(flowDense, p, 0).xy * uSize + texelFetch(dW, p, 0).xy; + vec2 dx = texelFetch(flowDense, ivec2(min(p.x + 1, mx.x), p.y), 0).xy * uSize + + texelFetch(dW, ivec2(min(p.x + 1, mx.x), p.y), 0).xy - c; + vec2 dy = texelFetch(flowDense, ivec2(p.x, min(p.y + 1, mx.y)), 0).xy * uSize + + texelFetch(dW, ivec2(p.x, min(p.y + 1, mx.y)), 0).xy - c; + + float val = pc.alpha2 / sqrt(dot(dx, dx) + dot(dy, dy) + pc.eps2); + imageStore(wt, p, vec4(val, 0.0, 0.0, 0.0)); +} diff --git a/app/src/main/cpp/winlator/vk/shaders/dis_vr_weights.comp b/app/src/main/cpp/winlator/vk/shaders/dis_vr_weights.comp deleted file mode 100644 index f799d5cf2..000000000 --- a/app/src/main/cpp/winlator/vk/shaders/dis_vr_weights.comp +++ /dev/null @@ -1,32 +0,0 @@ -#version 450 -#extension GL_EXT_samplerless_texture_functions : require - -// Smoothness weights: alpha/2 / sqrt(|grad(W + dW)|^2 + eps^2) (FS_VR_W). - -layout(local_size_x = 8, local_size_y = 8, local_size_z = 1) in; - -layout(set = 0, binding = 0) uniform texture2D u_W; -layout(set = 0, binding = 1) uniform texture2D u_dW; -layout(set = 0, binding = 2, r32f) uniform image2D o_wt; - -layout(push_constant) uniform PC { - float u_alpha2; - float u_eps2; -} pc; - -vec2 TW(ivec2 q) { - return texelFetch(u_W, q, 0).rg + texelFetch(u_dW, q, 0).rg; -} - -void main() { - ivec2 p = ivec2(gl_GlobalInvocationID.xy); - ivec2 sz = imageSize(o_wt); - if (p.x >= sz.x || p.y >= sz.y) return; - - ivec2 mx = sz - 1; - vec2 c = TW(p); - vec2 dx = TW(min(p + ivec2(1, 0), mx)) - c; - vec2 dy = TW(min(p + ivec2(0, 1), mx)) - c; - float v = pc.u_alpha2 / sqrt(dot(dx, dx) + dot(dy, dy) + pc.u_eps2); - imageStore(o_wt, p, vec4(v, 0.0, 0.0, 0.0)); -} diff --git a/app/src/main/cpp/winlator/vk/vk_dispatch.c b/app/src/main/cpp/winlator/vk/vk_dispatch.c index 33b68cead..984c91e29 100644 --- a/app/src/main/cpp/winlator/vk/vk_dispatch.c +++ b/app/src/main/cpp/winlator/vk/vk_dispatch.c @@ -159,7 +159,6 @@ bool vkd_load_instance(VkInstance instance) { LOAD(CmdCopyBufferToImage); LOAD(CmdBlitImage); LOAD(CmdCopyImage); - LOAD(CmdClearColorImage); LOAD(CmdDispatch); // Queue diff --git a/app/src/main/cpp/winlator/vk/vk_dispatch.h b/app/src/main/cpp/winlator/vk/vk_dispatch.h index 993b341c0..c3a4284d3 100644 --- a/app/src/main/cpp/winlator/vk/vk_dispatch.h +++ b/app/src/main/cpp/winlator/vk/vk_dispatch.h @@ -135,7 +135,6 @@ typedef struct VkDispatch { PFN_vkCmdCopyBufferToImage CmdCopyBufferToImage; PFN_vkCmdBlitImage CmdBlitImage; PFN_vkCmdCopyImage CmdCopyImage; - PFN_vkCmdClearColorImage CmdClearColorImage; PFN_vkCmdDispatch CmdDispatch; // Queue diff --git a/app/src/main/cpp/winlator/vk/vk_renderer.c b/app/src/main/cpp/winlator/vk/vk_renderer.c index 124fce00b..9755849b5 100644 --- a/app/src/main/cpp/winlator/vk/vk_renderer.c +++ b/app/src/main/cpp/winlator/vk/vk_renderer.c @@ -396,17 +396,6 @@ static bool create_device(VkRenderer* r) { if (has_cubic) enable[enable_n++] = VK_EXT_FILTER_CUBIC_EXTENSION_NAME; (void)has_extmem_caps; - // DIS frame generation needs storage-image writes (untyped interp target) and the extended - // storage formats (R16F/RG16F/RG32F work images). Probe and enable them. - VkPhysicalDeviceFeatures2 feature_probe2 = {VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2}; - vkGetPhysicalDeviceFeatures2(r->physical_device, &feature_probe2); - VkPhysicalDeviceFeatures feature_probe = feature_probe2.features; - r->framegen_features_ok = feature_probe.shaderStorageImageWriteWithoutFormat && - feature_probe.shaderStorageImageExtendedFormats; - VK_LOGI("DIS features: storage_write_without_format=%d extended_formats=%d", - (int)feature_probe.shaderStorageImageWriteWithoutFormat, - (int)feature_probe.shaderStorageImageExtendedFormats); - VkPhysicalDeviceShaderFloat16Int8FeaturesKHR f16_feat = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES_KHR }; @@ -464,13 +453,6 @@ static bool create_device(VkRenderer* r) { dci.enabledExtensionCount = enable_n; dci.ppEnabledExtensionNames = enable; - VkPhysicalDeviceFeatures enabled_features = {0}; - enabled_features.shaderStorageImageWriteWithoutFormat = - feature_probe.shaderStorageImageWriteWithoutFormat; - enabled_features.shaderStorageImageExtendedFormats = - feature_probe.shaderStorageImageExtendedFormats; - dci.pEnabledFeatures = &enabled_features; - if (vkCreateDevice(r->physical_device, &dci, NULL, &r->device) != VK_SUCCESS) { VK_LOGE("vkCreateDevice failed"); return false; @@ -1949,33 +1931,17 @@ static void destroy_dis(VkRenderer* r) { r->framegen_timed_frames = 0; } -// The DIS presets pick the flow pyramid's minimum side (180/252/360 px) relative to 720p. The -// reworked chain scales a fixed fraction of the guest extent instead, so convert with 720p as -// the reference resolution. -static float dis_flow_scale(uint32_t min_side) { - if (min_side == 0) min_side = 180u; - float scale = (float)min_side / 720.0f; - if (scale < 0.25f) scale = 0.25f; - if (scale > 1.0f) scale = 1.0f; - return scale; -} - static void create_dis(VkRenderer* r) { if (r->dis || !r->device || !r->physical_device) return; - if (!r->framegen_features_ok) { - VK_LOGW("DIS frame generation requires storage write-without-format and extended formats"); - return; - } - r->dis = vkr_dis_create(r->device, r->physical_device); if (!r->dis) { VK_LOGW("DIS shaders unavailable; frame generation stays off"); return; } - vkr_dis_configure(r->dis, r->dis_target_fps, dis_flow_scale(r->dis_scale), - r->framegen_refresh_rate, 0.0f); - vkr_dis_set_debug_mode(r->dis, r->dis_debug_flow ? 1u : 0u); + vkr_dis_configure(r->dis, r->dis_scale ? r->dis_scale : 180u, r->dis_target_fps, + r->framegen_refresh_rate); + vkr_dis_set_debug_flow(r->dis, r->dis_debug_flow); } static uint32_t framegen_extra_images(const VkRenderer* r) { @@ -2279,24 +2245,20 @@ static void compose_xform_for_window(float out[6], const float scene_xform[6], out[5] = a[4]*scene_xform[1] + a[5]*scene_xform[3] + scene_xform[5]; } -// Content rectangle (letterboxed viewport) in swapchain pixels. Frame generation must only -// read, compute flow for and interpolate inside it: the black bars neither belong to the game -// image (they would pollute the pyramid and the temporal prior) nor should they be overwritten. -static VkRect2D compute_dis_content_rect(VkRenderer* r, const VkScene* s) { - VkRect2D full = {{0, 0}, {r->swapchain_extent.width, r->swapchain_extent.height}}; +static VkrDisContentRect compute_dis_content_rect(VkRenderer* r, const VkScene* s, + uint32_t composite_w, uint32_t composite_h) { + VkrDisContentRect full = {0, 0, composite_w, composite_h}; if (!s->viewport_set || s->viewport_w <= 0 || s->viewport_h <= 0) return full; VkPreRotatedRect vr = transform_rect_for_pretransform( s->viewport_x, s->viewport_y, s->viewport_w, s->viewport_h, r->swapchain_extent.width, r->swapchain_extent.height, r->swapchain_transform); - vr = clamp_rect_to_extent(vr, r->swapchain_extent.width, r->swapchain_extent.height); + vr = scale_rect_from_swapchain(r, vr, composite_w, composite_h); + vr = clamp_rect_to_extent(vr, composite_w, composite_h); if (vr.w <= 0 || vr.h <= 0) return full; - full.offset.x = vr.x; - full.offset.y = vr.y; - full.extent.width = (uint32_t)vr.w; - full.extent.height = (uint32_t)vr.h; - return full; + VkrDisContentRect out = {vr.x, vr.y, (uint32_t)vr.w, (uint32_t)vr.h}; + return out; } static void set_viewport_scissor(VkCommandBuffer cmd, VkRenderer* r, const VkScene* s, @@ -2486,6 +2448,39 @@ static VkExtent2D compute_sgsr1_source_extent(VkRenderer* r, const VkScene* s) { return source; } +static VkExtent2D compute_container_extent(VkRenderer* r, const VkScene* s) { + VkExtent2D out = r->swapchain_extent; + if (out.width == 0 || out.height == 0 || s->screen_width == 0 || s->screen_height == 0) { + return out; + } + + uint32_t w = s->screen_width; + uint32_t h = s->screen_height; + transformed_view_size(&w, &h, r->swapchain_transform); + if (w == 0 || h == 0) return out; + + if (s->viewport_set && s->viewport_w > 0 && s->viewport_h > 0 + && r->swapchain_extent.width > 0 && r->swapchain_extent.height > 0) { + VkPreRotatedRect vr = transform_rect_for_pretransform( + s->viewport_x, s->viewport_y, s->viewport_w, s->viewport_h, + r->swapchain_extent.width, r->swapchain_extent.height, r->swapchain_transform); + if (vr.w > 0 && vr.h > 0) { + const double fx = (double)vr.w / (double)r->swapchain_extent.width; + const double fy = (double)vr.h / (double)r->swapchain_extent.height; + if (fx > 0.0 && fx < 1.0) w = (uint32_t)((double)w / fx + 0.5); + if (fy > 0.0 && fy < 1.0) h = (uint32_t)((double)h / fy + 0.5); + } + } + + VkExtent2D e = { + w < out.width ? w : out.width, + h < out.height ? h : out.height, + }; + if (e.width < 1) e.width = 1; + if (e.height < 1) e.height = 1; + return e; +} + static uint64_t vkr_monotonic_ns(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); @@ -2621,9 +2616,6 @@ static bool record_and_submit_frame(VkRenderer* r) { bool via_composite = framegen_on && r->framegen_supported && r->swapchain_transfer_dst; - VkRect2D dis_content = {{0, 0}, {r->swapchain_extent.width, r->swapchain_extent.height}}; - const uint32_t dis_debug_scratch = (use_dis && r->dis_debug_flow) ? 1u : 0u; - VkExtent2D composite_extent = r->swapchain_extent; if (via_composite) { if (r->lsfg) { @@ -2632,10 +2624,10 @@ static bool record_and_submit_frame(VkRenderer* r) { } else if (use_dis) { if (snap.screen_width == 0 || snap.screen_height == 0) { via_composite = false; + } else if (wants_sgsr1) { + composite_extent = r->swapchain_extent; } else { - dis_content = compute_dis_content_rect(r, &snap); - vkr_dis_set_guest_extent(r->dis, dis_content.extent.width, - dis_content.extent.height); + composite_extent = compute_container_extent(r, &snap); } } } @@ -2643,31 +2635,28 @@ static bool record_and_submit_frame(VkRenderer* r) { uint32_t framegen_capacity = 0; if (via_composite && (r->lsfg || r->dis) && r->swapchain_image_count > 2) { framegen_capacity = r->swapchain_image_count - 2; - const uint32_t max_generations = - use_dis ? VKR_DIS_MAX_GENERATIONS : VKR_LSFG_MAX_GENERATIONS; - if (framegen_capacity > max_generations) { - framegen_capacity = max_generations; + if (framegen_capacity > VKR_LSFG_MAX_GENERATIONS) { + framegen_capacity = VKR_LSFG_MAX_GENERATIONS; } - // One extra target is reserved for the real-frame debug visualization when the flow - // overlay is active. - if (VK_FRAMES_IN_FLIGHT + framegen_capacity + dis_debug_scratch - > VK_MAX_COMPOSITE_TARGETS) { - framegen_capacity = VK_MAX_COMPOSITE_TARGETS - VK_FRAMES_IN_FLIGHT - - dis_debug_scratch; + if (VK_FRAMES_IN_FLIGHT + framegen_capacity > VK_MAX_COMPOSITE_TARGETS) { + framegen_capacity = VK_MAX_COMPOSITE_TARGETS - VK_FRAMES_IN_FLIGHT; } } if (via_composite) { - uint32_t composite_needed = VK_FRAMES_IN_FLIGHT + framegen_capacity + dis_debug_scratch; + uint32_t composite_needed = VK_FRAMES_IN_FLIGHT + framegen_capacity; bool composite_stale = !r->composite_built || r->composite_count != composite_needed || r->composite[0].width != composite_extent.width || r->composite[0].height != composite_extent.height; + VkrDisContentRect dis_content = compute_dis_content_rect( + r, &snap, composite_extent.width, composite_extent.height); bool chain_stale = (r->lsfg && vkr_lsfg_needs_rebuild(r->lsfg, r->swapchain_extent.width, r->swapchain_extent.height, r->swapchain_format)) || (r->dis && vkr_dis_needs_rebuild(r->dis, composite_extent.width, - composite_extent.height, r->swapchain_format)); + composite_extent.height, r->swapchain_format, + dis_content)); if (composite_stale || chain_stale) { if (r->dis) { vkDeviceWaitIdle(r->device); @@ -2689,7 +2678,8 @@ static bool record_and_submit_frame(VkRenderer* r) { } else if (r->dis) { vkr_dis_forget_targets(r->dis); if (!vkr_dis_prepare(r->dis, composite_extent.width, - composite_extent.height, r->swapchain_format)) { + composite_extent.height, r->swapchain_format, + dis_content)) { framegen_capacity = 0; } } @@ -2709,8 +2699,7 @@ static bool record_and_submit_frame(VkRenderer* r) { __atomic_load_n(&r->framegen_source_frames, __ATOMIC_RELAXED)); } else if (r->dis) { - vkr_dis_configure(r->dis, r->dis_target_fps, dis_flow_scale(r->dis_scale), - r->framegen_refresh_rate, 0.0f); + vkr_dis_configure(r->dis, r->dis_scale, r->dis_target_fps, r->framegen_refresh_rate); framegen_planned = vkr_dis_plan(r->dis, framegen_capacity, __atomic_load_n(&r->framegen_source_frames, __ATOMIC_RELAXED)); @@ -2897,11 +2886,7 @@ static bool record_and_submit_frame(VkRenderer* r) { if ((use_dis || r->lsfg) && framegen_capacity > 0) { if (use_dis) { vkr_dis_process(r->dis, f->cmd, composite->image, - composite->view, - r->composite[(r->frame_index + VK_FRAMES_IN_FLIGHT - 1) - % VK_FRAMES_IN_FLIGHT].view, - composite->width, composite->height, - dis_content, framegen_planned); + composite->width, composite->height, gen_count); } else { vkr_lsfg_process(r->lsfg, f->cmd, composite->image, r->swapchain_extent.width, r->swapchain_extent.height, gen_count); @@ -2914,7 +2899,8 @@ static bool record_and_submit_frame(VkRenderer* r) { vkr_dis_generate_into(r->dis, f->cmd, g, idx, r->swapchain_images[idx], r->swapchain_views[idx], r->swapchain_extent.width, - r->swapchain_extent.height); + r->swapchain_extent.height, + composite->image); } else { vkr_lsfg_generate_into(r->lsfg, f->cmd, g, idx, r->swapchain_images[idx], r->swapchain_views[idx], @@ -2932,7 +2918,8 @@ static bool record_and_submit_frame(VkRenderer* r) { if (use_dis) { vkr_dis_generate_into(r->dis, f->cmd, g, VK_FRAMES_IN_FLIGHT + g, gt->image, gt->view, - gt->width, gt->height); + gt->width, gt->height, + composite->image); } else { vkr_lsfg_generate_into(r->lsfg, f->cmd, g, VK_FRAMES_IN_FLIGHT + g, gt->image, gt->view, @@ -2968,37 +2955,14 @@ static bool record_and_submit_frame(VkRenderer* r) { } } - // The generated frames run through dis_interp (which draws the flow overlay), but the - // real frame is blitted straight from the composite, so with the debug view active it - // would blink between overlay and normal picture every other frame. Route the real - // frame through the same interp/debug pass so every presented frame shows the overlay. - const bool debug_present = use_dis && r->dis_debug_flow - && vkr_dis_debug_ready(r->dis) - && (r->swapchain_storage - || (r->composite_built - && VK_FRAMES_IN_FLIGHT + framegen_capacity - < r->composite_count)); - if (debug_present) { - const uint32_t debug_generation = VKR_DIS_MAX_TARGETS - 1; - if (r->swapchain_storage) { - vkr_dis_generate_into(r->dis, f->cmd, debug_generation, image_index, - r->swapchain_images[image_index], - r->swapchain_views[image_index], - r->swapchain_extent.width, r->swapchain_extent.height); - vkr_image_barrier(f->cmd, r->swapchain_images[image_index], - VK_IMAGE_LAYOUT_GENERAL, - VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, - VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, - VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, - VK_ACCESS_SHADER_WRITE_BIT, 0); - } else { - VkCompositeTarget* dt = &r->composite[VK_FRAMES_IN_FLIGHT + framegen_capacity]; - vkr_dis_generate_into(r->dis, f->cmd, debug_generation, - VK_FRAMES_IN_FLIGHT + framegen_capacity, - dt->image, dt->view, - r->swapchain_extent.width, r->swapchain_extent.height); - blit_composite_to_swapchain(r, f->cmd, dt, r->swapchain_images[image_index]); - } + if (use_dis && r->dis_debug_flow) { + vkr_dis_debug_into(r->dis, f->cmd, r->swapchain_images[image_index], + r->swapchain_extent.width, r->swapchain_extent.height); + vkr_image_barrier(f->cmd, r->swapchain_images[image_index], + VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + VK_PIPELINE_STAGE_TRANSFER_BIT, + VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT, 0); } else { blit_composite_to_swapchain(r, f->cmd, composite, r->swapchain_images[image_index]); } @@ -3081,8 +3045,7 @@ static bool record_and_submit_frame(VkRenderer* r) { wait_sems[wait_count] = f->image_available; wait_stages[wait_count] = - composite ? (VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT - | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT) + composite ? (VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT) : VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; wait_count++; signal_sems[signal_count++] = render_finished; @@ -3992,8 +3955,7 @@ JNIEXPORT void JNICALL JNI_FN(nativeSetDisFrameGenerationScale)(JNIEnv* env, jcl int side = scalePercent > 0 && scalePercent <= 100 ? scalePercent * 720 / 100 : scalePercent; r->dis_scale = side < 64 ? 64u : (uint32_t)(side > 1080 ? 1080 : side); if (r->dis) { - vkr_dis_configure(r->dis, r->dis_target_fps, dis_flow_scale(r->dis_scale), - r->framegen_refresh_rate, 0.0f); + vkr_dis_configure(r->dis, r->dis_scale, r->dis_target_fps, r->framegen_refresh_rate); } pthread_mutex_unlock(&r->render_mutex); } @@ -4007,8 +3969,7 @@ JNIEXPORT void JNICALL JNI_FN(nativeSetDisFrameGenerationTargetFps)(JNIEnv* env, pthread_mutex_lock(&r->render_mutex); r->dis_target_fps = targetFps < 0 ? 0u : (uint32_t)targetFps; if (r->dis) { - vkr_dis_configure(r->dis, r->dis_target_fps, dis_flow_scale(r->dis_scale), - r->framegen_refresh_rate, 0.0f); + vkr_dis_configure(r->dis, r->dis_scale, r->dis_target_fps, r->framegen_refresh_rate); } pthread_mutex_unlock(&r->render_mutex); } @@ -4021,7 +3982,7 @@ JNIEXPORT void JNICALL JNI_FN(nativeSetDisDebugFlow)(JNIEnv* env, jclass clazz, pthread_mutex_lock(&r->render_mutex); r->dis_debug_flow = (debugFlow == JNI_TRUE); - if (r->dis) vkr_dis_set_debug_mode(r->dis, r->dis_debug_flow ? 1u : 0u); + if (r->dis) vkr_dis_set_debug_flow(r->dis, r->dis_debug_flow); pthread_mutex_unlock(&r->render_mutex); } diff --git a/app/src/main/cpp/winlator/vk/vk_state.h b/app/src/main/cpp/winlator/vk/vk_state.h index a40b7a2f0..cf8f70cce 100644 --- a/app/src/main/cpp/winlator/vk/vk_state.h +++ b/app/src/main/cpp/winlator/vk/vk_state.h @@ -418,7 +418,6 @@ typedef struct VkRenderer { bool composite_built; bool framegen_supported; bool framegen_requested; - bool framegen_features_ok; bool swapchain_transfer_dst; bool swapchain_storage; uint64_t framegen_present_failures; From 83a237822fcca0099a4f2b95cc2a3449e3187b26 Mon Sep 17 00:00:00 2001 From: qwertypower Date: Sat, 12 Sep 2026 19:59:58 +0000 Subject: [PATCH 2/2] DIS: run the variational refinement on the whole pyramid The variational refinement ran once, at the finest level, after the coarse-to-fine loop had already finished. OpenCV's DIS runs it inside that loop, at every level, and the difference is not cosmetic: every level was handing the next finer one a raw densified patch flow as its initialisation instead of a refined, coherent one, so patch-search errors compounded all the way down the pyramid and the single pass at the end had to undo the lot at the most expensive resolution. Each refinement intermediate becomes a mip chain with a view per level, so the VR shaders keep deriving their extent from imageSize() and are unchanged. The dispatch block moves into the level loop as dis_vr_level(), and the patch search now initialises from view_flow_refined[l + 1] rather than view_dense[l + 1]. On a level that is not getting the full solve, prep and add still run: prep zeroes dW as a side effect, so add stores flow_refined = dense exactly. Two dispatches and seven taps a texel keep the descriptor wiring static - flow_refined is always the valid flow for its level - which is much cheaper than switching descriptor sets between refined and raw sources per frame. How many levels get the full solve is a tier on the generation count, beside the iteration counts already there. The flow chain runs once per SOURCE frame, so at x4 the guest sits at a quarter of the panel rate and the chain runs a quarter as often: half the load per second of the x2 case, which is the heaviest the system ever gets. x2 therefore keeps the refinement to the finest level and is bit-identical to before; x3 and x4 spend the headroom that is already there on the whole pyramid, and that is exactly where the flow is worst because the motion between two source frames is twice as large. Modelled per source frame at the Balance preset (448x252), against today's x2 load as the ceiling: x2 30 fps guest 33.0M taps +0.6% 0.99G/s 101% of ceiling x3 20 fps guest 45.3M taps +11.8% 0.91G/s 92% of ceiling x4 15 fps guest 48.2M taps +12.9% 0.72G/s 74% of ceiling Descriptor pool: the VR sets now exist once per level, 10 per level instead of 10 total. Pool and demand are both derived from the same constants and match exactly at 227 sets / 1375 samplers / 307 storage images (was 157 / 815 / 167). An undersized pool here fails with OUT_OF_POOL_MEMORY and frame generation silently stops existing, which is why it is computed rather than written out. Memory cost is the extra mips on nine scratch images: +33% of their level-0 footprint, about 3.4 MB at the Balance preset. Also points the DEVAR credit in the README at https://devar.ai/. --- README.md | 2 +- app/src/main/cpp/winlator/vk/dis/vkr_dis.c | 364 +++++++++++---------- 2 files changed, 192 insertions(+), 174 deletions(-) diff --git a/README.md b/README.md index 490a403be..26504c780 100644 --- a/README.md +++ b/README.md @@ -167,7 +167,7 @@ Please match the existing code style and ensure any AI-assisted code is thorough - **ARMSX2** by the [ARMSX2](https://github.com/ARMSX2/ARMSX2) team (GPL-3.0) — the PlayStation 2 core, a fork of **[PCSX2](https://github.com/pcsx2/pcsx2)** (GPL-3.0), built from source into `libemucore`. PS2 online play uses PCSX2's DEV9 network adapter - **lsfg-vk** by [PancakeTAS](https://github.com/PancakeTAS/lsfg-vk) (GPL-3.0-or-later) — the original Vulkan reimplementation of the Lossless Scaling frame generation chain - **LSFG frame generation** by **Camille LaVey** of the [Eden Emulator Project](https://git.eden-emu.dev/eden-emu/eden) (GPL-3.0-or-later) — the Vulkan port of that chain that WinNative's frame generation is derived from. See [Frame generation — what came from Camille LaVey's Eden port](#frame-generation--what-came-from-camille-laveys-eden-port) below -- **DIS optical flow frame generation** by **qwertypower** ([DEVAR Entertainment LLC](https://github.com/qwertypower)) (GPL-3.0) — a complete open-source implementation of a Dense Inverse Search frame generator, the second frame generation engine in WinNative and the one that needs no shaders from anywhere else. See [DIS frame generation — the fully open-source engine](#dis-frame-generation--the-fully-open-source-engine) below +- **DIS optical flow frame generation** by **qwertypower** ([DEVAR Entertainment LLC](https://devar.ai/)) (GPL-3.0) — a complete open-source implementation of a Dense Inverse Search frame generator, the second frame generation engine in WinNative and the one that needs no shaders from anywhere else. See [DIS frame generation — the fully open-source engine](#dis-frame-generation--the-fully-open-source-engine) below - **DIS optical flow** — the algorithm and its reference implementation come from [OpenCV](https://github.com/opencv/opencv) (`DISOpticalFlow`, [LICENSE](https://github.com/opencv/opencv/blob/5.x/LICENSE)), which adopted Till Kroeger's original [OF_DIS](https://github.com/tikroeger/OF_DIS) - **DXVK** by [Philip Rebohle and contributors](https://github.com/doitsujin/dxvk) (zlib/libpng) — the `dxbc` shader translator, vendored at `app/src/main/cpp/thirdparty/dxbc` to convert the frame generation shaders to SPIR-V - **DirectAudio** by [The412Banner](https://github.com/The412Banner/directaudio) (LGPL-2.1-or-later) — the native Wine → Android AAudio audio driver, and the only audio path in WinNative that carries a working microphone. See [DirectAudio — what came from The412Banner's driver](#directaudio--what-came-from-the412banners-driver) below diff --git a/app/src/main/cpp/winlator/vk/dis/vkr_dis.c b/app/src/main/cpp/winlator/vk/dis/vkr_dis.c index 66f72d3b6..4e518dd41 100644 --- a/app/src/main/cpp/winlator/vk/dis/vkr_dis.c +++ b/app/src/main/cpp/winlator/vk/dis/vkr_dis.c @@ -135,14 +135,14 @@ struct VkrDis { VkImageView view_sparse_b[DIS_MAX_LEVELS]; VkImageView view_dense[DIS_MAX_LEVELS]; VkImageView view_interp_out; - VkImageView view_vr_prep; - VkImageView view_vr_d1; - VkImageView view_vr_d2; - VkImageView view_vr_A; - VkImageView view_vr_B; - VkImageView view_vr_wt; - VkImageView view_vr_dw[2]; - VkImageView view_flow_refined; + VkImageView view_vr_prep[DIS_MAX_LEVELS]; + VkImageView view_vr_d1[DIS_MAX_LEVELS]; + VkImageView view_vr_d2[DIS_MAX_LEVELS]; + VkImageView view_vr_A[DIS_MAX_LEVELS]; + VkImageView view_vr_B[DIS_MAX_LEVELS]; + VkImageView view_vr_wt[DIS_MAX_LEVELS]; + VkImageView view_vr_dw[2][DIS_MAX_LEVELS]; + VkImageView view_flow_refined[DIS_MAX_LEVELS]; VkSampler sampler; @@ -159,14 +159,14 @@ struct VkrDis { VkDescriptorSetLayout vr_set_layout; VkPipelineLayout vr_pipeline_layout; - VkDescriptorSet vr_prep_sets[DIS_SLOTS]; - VkDescriptorSet vr_d1_set; - VkDescriptorSet vr_d2_set; - VkDescriptorSet vr_w_set; - VkDescriptorSet vr_coef_set; - VkDescriptorSet vr_sor_ab_set; - VkDescriptorSet vr_sor_ba_set; - VkDescriptorSet vr_add_set; + VkDescriptorSet vr_prep_sets[DIS_SLOTS][DIS_MAX_LEVELS]; + VkDescriptorSet vr_d1_set[DIS_MAX_LEVELS]; + VkDescriptorSet vr_d2_set[DIS_MAX_LEVELS]; + VkDescriptorSet vr_w_set[DIS_MAX_LEVELS]; + VkDescriptorSet vr_coef_set[DIS_MAX_LEVELS]; + VkDescriptorSet vr_sor_ab_set[DIS_MAX_LEVELS]; + VkDescriptorSet vr_sor_ba_set[DIS_MAX_LEVELS]; + VkDescriptorSet vr_add_set[DIS_MAX_LEVELS]; DisPass pass_luma; DisPass pass_gradient; @@ -509,8 +509,8 @@ static bool dis_create_pipelines(VkrDis* d) { const uint32_t shared_sets = DIS_SLOTS * DIS_MAX_LEVELS * DIS_SHARED_SETS_PER_LEVEL + DIS_SLOTS; - const uint32_t vr_sets = DIS_SLOTS - + DIS_VR_SHARED_SETS; + const uint32_t vr_sets = (DIS_SLOTS + + DIS_VR_SHARED_SETS) * DIS_MAX_LEVELS; const uint32_t total_sets = shared_sets + vr_sets; VkDescriptorPoolSize sizes[2]; @@ -663,18 +663,19 @@ typedef struct { uint32_t vr_fixed_point; uint32_t vr_sor; uint32_t prop_floor; + uint32_t vr_levels; } DisRefine; static DisRefine dis_refine_for(uint32_t generations) { if (generations >= 3u) { - const DisRefine r = {2u, 5u, 2u}; + const DisRefine r = {2u, 5u, 2u, DIS_MAX_LEVELS}; return r; } if (generations == 2u) { - const DisRefine r = {2u, 4u, 1u}; + const DisRefine r = {2u, 4u, 1u, DIS_MAX_LEVELS}; return r; } - const DisRefine r = {1u, 3u, 1u}; + const DisRefine r = {1u, 3u, 1u, 1u}; return r; } @@ -742,8 +743,8 @@ static void dis_write_all_descriptors(VkrDis* d) { dis_batch_sampled(d, &b, d->inverse_sets[s][l], 1, d->view_flow_luma[next][l], d->sampler); dis_batch_sampled(d, &b, d->inverse_sets[s][l], 2, d->view_grad[l], d->sampler); dis_batch_sampled(d, &b, d->inverse_sets[s][l], 3, - d->view_dense[l + 1 < L ? l + 1 : coarse], d->sampler); - dis_batch_sampled(d, &b, d->inverse_sets[s][l], 4, d->view_dense[coarse], d->sampler); + d->view_flow_refined[l + 1 < L ? l + 1 : coarse], d->sampler); + dis_batch_sampled(d, &b, d->inverse_sets[s][l], 4, d->view_flow_refined[coarse], d->sampler); dis_batch_storage(d, &b, d->inverse_sets[s][l], 5, d->view_sparse[l]); dis_batch_sampled(d, &b, d->prop_ab_sets[s][l], 0, d->view_flow_luma[prev][l], d->sampler); @@ -764,50 +765,54 @@ static void dis_write_all_descriptors(VkrDis* d) { dis_batch_sampled(d, &b, d->interp_sets[s], 0, d->view_color[prev], d->sampler); dis_batch_sampled(d, &b, d->interp_sets[s], 1, d->view_color[next], d->sampler); - dis_batch_sampled(d, &b, d->interp_sets[s], 2, d->view_flow_refined, d->sampler); + dis_batch_sampled(d, &b, d->interp_sets[s], 2, d->view_flow_refined[0], d->sampler); dis_batch_storage(d, &b, d->interp_sets[s], 5, d->view_interp_out); - dis_batch_sampled(d, &b, d->vr_prep_sets[s], 0, d->view_flow_color[prev][0], d->sampler); - dis_batch_sampled(d, &b, d->vr_prep_sets[s], 1, d->view_flow_color[next][0], d->sampler); - dis_batch_sampled(d, &b, d->vr_prep_sets[s], 2, d->view_dense[0], d->sampler); - dis_batch_storage(d, &b, d->vr_prep_sets[s], DIS_VR_FIRST_STORAGE, d->view_vr_prep); - dis_batch_storage(d, &b, d->vr_prep_sets[s], DIS_VR_FIRST_STORAGE + 1, d->view_vr_dw[0]); + for (uint32_t l = 0; l < L; l++) { + dis_batch_sampled(d, &b, d->vr_prep_sets[s][l], 0, d->view_flow_color[prev][l], d->sampler); + dis_batch_sampled(d, &b, d->vr_prep_sets[s][l], 1, d->view_flow_color[next][l], d->sampler); + dis_batch_sampled(d, &b, d->vr_prep_sets[s][l], 2, d->view_dense[l], d->sampler); + dis_batch_storage(d, &b, d->vr_prep_sets[s][l], DIS_VR_FIRST_STORAGE, d->view_vr_prep[l]); + dis_batch_storage(d, &b, d->vr_prep_sets[s][l], DIS_VR_FIRST_STORAGE + 1, d->view_vr_dw[0][l]); + } } - dis_batch_sampled(d, &b, d->vr_d1_set, 0, d->view_vr_prep, d->sampler); - dis_batch_storage(d, &b, d->vr_d1_set, DIS_VR_FIRST_STORAGE, d->view_vr_d1); - - dis_batch_sampled(d, &b, d->vr_d2_set, 0, d->view_vr_d1, d->sampler); - dis_batch_storage(d, &b, d->vr_d2_set, DIS_VR_FIRST_STORAGE, d->view_vr_d2); - - dis_batch_sampled(d, &b, d->vr_w_set, 0, d->view_dense[0], d->sampler); - dis_batch_sampled(d, &b, d->vr_w_set, 1, d->view_vr_dw[0], d->sampler); - dis_batch_storage(d, &b, d->vr_w_set, DIS_VR_FIRST_STORAGE, d->view_vr_wt); - - dis_batch_sampled(d, &b, d->vr_coef_set, 0, d->view_vr_prep, d->sampler); - dis_batch_sampled(d, &b, d->vr_coef_set, 1, d->view_vr_d1, d->sampler); - dis_batch_sampled(d, &b, d->vr_coef_set, 2, d->view_vr_d2, d->sampler); - dis_batch_sampled(d, &b, d->vr_coef_set, 3, d->view_vr_dw[0], d->sampler); - dis_batch_sampled(d, &b, d->vr_coef_set, 4, d->view_dense[0], d->sampler); - dis_batch_sampled(d, &b, d->vr_coef_set, 5, d->view_vr_wt, d->sampler); - dis_batch_storage(d, &b, d->vr_coef_set, DIS_VR_FIRST_STORAGE, d->view_vr_A); - dis_batch_storage(d, &b, d->vr_coef_set, DIS_VR_FIRST_STORAGE + 1, d->view_vr_B); - - dis_batch_sampled(d, &b, d->vr_sor_ab_set, 0, d->view_vr_A, d->sampler); - dis_batch_sampled(d, &b, d->vr_sor_ab_set, 1, d->view_vr_B, d->sampler); - dis_batch_sampled(d, &b, d->vr_sor_ab_set, 2, d->view_vr_wt, d->sampler); - dis_batch_sampled(d, &b, d->vr_sor_ab_set, 3, d->view_vr_dw[0], d->sampler); - dis_batch_storage(d, &b, d->vr_sor_ab_set, DIS_VR_FIRST_STORAGE, d->view_vr_dw[1]); - - dis_batch_sampled(d, &b, d->vr_sor_ba_set, 0, d->view_vr_A, d->sampler); - dis_batch_sampled(d, &b, d->vr_sor_ba_set, 1, d->view_vr_B, d->sampler); - dis_batch_sampled(d, &b, d->vr_sor_ba_set, 2, d->view_vr_wt, d->sampler); - dis_batch_sampled(d, &b, d->vr_sor_ba_set, 3, d->view_vr_dw[1], d->sampler); - dis_batch_storage(d, &b, d->vr_sor_ba_set, DIS_VR_FIRST_STORAGE, d->view_vr_dw[0]); - - dis_batch_sampled(d, &b, d->vr_add_set, 0, d->view_dense[0], d->sampler); - dis_batch_sampled(d, &b, d->vr_add_set, 1, d->view_vr_dw[0], d->sampler); - dis_batch_storage(d, &b, d->vr_add_set, DIS_VR_FIRST_STORAGE, d->view_flow_refined); + for (uint32_t l = 0; l < L; l++) { + dis_batch_sampled(d, &b, d->vr_d1_set[l], 0, d->view_vr_prep[l], d->sampler); + dis_batch_storage(d, &b, d->vr_d1_set[l], DIS_VR_FIRST_STORAGE, d->view_vr_d1[l]); + + dis_batch_sampled(d, &b, d->vr_d2_set[l], 0, d->view_vr_d1[l], d->sampler); + dis_batch_storage(d, &b, d->vr_d2_set[l], DIS_VR_FIRST_STORAGE, d->view_vr_d2[l]); + + dis_batch_sampled(d, &b, d->vr_w_set[l], 0, d->view_dense[l], d->sampler); + dis_batch_sampled(d, &b, d->vr_w_set[l], 1, d->view_vr_dw[0][l], d->sampler); + dis_batch_storage(d, &b, d->vr_w_set[l], DIS_VR_FIRST_STORAGE, d->view_vr_wt[l]); + + dis_batch_sampled(d, &b, d->vr_coef_set[l], 0, d->view_vr_prep[l], d->sampler); + dis_batch_sampled(d, &b, d->vr_coef_set[l], 1, d->view_vr_d1[l], d->sampler); + dis_batch_sampled(d, &b, d->vr_coef_set[l], 2, d->view_vr_d2[l], d->sampler); + dis_batch_sampled(d, &b, d->vr_coef_set[l], 3, d->view_vr_dw[0][l], d->sampler); + dis_batch_sampled(d, &b, d->vr_coef_set[l], 4, d->view_dense[l], d->sampler); + dis_batch_sampled(d, &b, d->vr_coef_set[l], 5, d->view_vr_wt[l], d->sampler); + dis_batch_storage(d, &b, d->vr_coef_set[l], DIS_VR_FIRST_STORAGE, d->view_vr_A[l]); + dis_batch_storage(d, &b, d->vr_coef_set[l], DIS_VR_FIRST_STORAGE + 1, d->view_vr_B[l]); + + dis_batch_sampled(d, &b, d->vr_sor_ab_set[l], 0, d->view_vr_A[l], d->sampler); + dis_batch_sampled(d, &b, d->vr_sor_ab_set[l], 1, d->view_vr_B[l], d->sampler); + dis_batch_sampled(d, &b, d->vr_sor_ab_set[l], 2, d->view_vr_wt[l], d->sampler); + dis_batch_sampled(d, &b, d->vr_sor_ab_set[l], 3, d->view_vr_dw[0][l], d->sampler); + dis_batch_storage(d, &b, d->vr_sor_ab_set[l], DIS_VR_FIRST_STORAGE, d->view_vr_dw[1][l]); + + dis_batch_sampled(d, &b, d->vr_sor_ba_set[l], 0, d->view_vr_A[l], d->sampler); + dis_batch_sampled(d, &b, d->vr_sor_ba_set[l], 1, d->view_vr_B[l], d->sampler); + dis_batch_sampled(d, &b, d->vr_sor_ba_set[l], 2, d->view_vr_wt[l], d->sampler); + dis_batch_sampled(d, &b, d->vr_sor_ba_set[l], 3, d->view_vr_dw[1][l], d->sampler); + dis_batch_storage(d, &b, d->vr_sor_ba_set[l], DIS_VR_FIRST_STORAGE, d->view_vr_dw[0][l]); + + dis_batch_sampled(d, &b, d->vr_add_set[l], 0, d->view_dense[l], d->sampler); + dis_batch_sampled(d, &b, d->vr_add_set[l], 1, d->view_vr_dw[0][l], d->sampler); + dis_batch_storage(d, &b, d->vr_add_set[l], DIS_VR_FIRST_STORAGE, d->view_flow_refined[l]); + } dis_batch_flush(d, &b); } @@ -815,16 +820,16 @@ static void dis_write_all_descriptors(VkrDis* d) { static void dis_destroy_views(VkrDis* d) { for (uint32_t s = 0; s < DIS_SLOTS; s++) dis_destroy_view(d, &d->view_color[s]); dis_destroy_view(d, &d->view_interp_out); - dis_destroy_view(d, &d->view_vr_prep); - dis_destroy_view(d, &d->view_vr_d1); - dis_destroy_view(d, &d->view_vr_d2); - dis_destroy_view(d, &d->view_vr_A); - dis_destroy_view(d, &d->view_vr_B); - dis_destroy_view(d, &d->view_vr_wt); - dis_destroy_view(d, &d->view_vr_dw[0]); - dis_destroy_view(d, &d->view_vr_dw[1]); - dis_destroy_view(d, &d->view_flow_refined); for (uint32_t l = 0; l < DIS_MAX_LEVELS; l++) { + dis_destroy_view(d, &d->view_vr_prep[l]); + dis_destroy_view(d, &d->view_vr_d1[l]); + dis_destroy_view(d, &d->view_vr_d2[l]); + dis_destroy_view(d, &d->view_vr_A[l]); + dis_destroy_view(d, &d->view_vr_B[l]); + dis_destroy_view(d, &d->view_vr_wt[l]); + dis_destroy_view(d, &d->view_vr_dw[0][l]); + dis_destroy_view(d, &d->view_vr_dw[1][l]); + dis_destroy_view(d, &d->view_flow_refined[l]); for (uint32_t s = 0; s < DIS_SLOTS; s++) { dis_destroy_view(d, &d->view_flow_color[s][l]); dis_destroy_view(d, &d->view_flow_luma[s][l]); @@ -894,23 +899,23 @@ static bool dis_create_resources(VkrDis* d, uint32_t w, uint32_t h, uint32_t ful return false; } - if (!dis_create_image(d, &d->vr_prep, w, h, VK_FORMAT_R32G32_SFLOAT, 1, + if (!dis_create_image(d, &d->vr_prep, w, h, VK_FORMAT_R32G32_SFLOAT, L, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT)) return false; - if (!dis_create_image(d, &d->vr_d1, w, h, VK_FORMAT_R32G32B32A32_SFLOAT, 1, + if (!dis_create_image(d, &d->vr_d1, w, h, VK_FORMAT_R32G32B32A32_SFLOAT, L, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT)) return false; - if (!dis_create_image(d, &d->vr_d2, w, h, VK_FORMAT_R32G32B32A32_SFLOAT, 1, + if (!dis_create_image(d, &d->vr_d2, w, h, VK_FORMAT_R32G32B32A32_SFLOAT, L, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT)) return false; - if (!dis_create_image(d, &d->vr_A, w, h, VK_FORMAT_R32G32B32A32_SFLOAT, 1, + if (!dis_create_image(d, &d->vr_A, w, h, VK_FORMAT_R32G32B32A32_SFLOAT, L, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT)) return false; - if (!dis_create_image(d, &d->vr_B, w, h, VK_FORMAT_R32G32_SFLOAT, 1, + if (!dis_create_image(d, &d->vr_B, w, h, VK_FORMAT_R32G32_SFLOAT, L, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT)) return false; - if (!dis_create_image(d, &d->vr_wt, w, h, VK_FORMAT_R32_SFLOAT, 1, + if (!dis_create_image(d, &d->vr_wt, w, h, VK_FORMAT_R32_SFLOAT, L, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT)) return false; - if (!dis_create_image(d, &d->vr_dw[0], w, h, VK_FORMAT_R32G32_SFLOAT, 1, + if (!dis_create_image(d, &d->vr_dw[0], w, h, VK_FORMAT_R32G32_SFLOAT, L, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT)) return false; - if (!dis_create_image(d, &d->vr_dw[1], w, h, VK_FORMAT_R32G32_SFLOAT, 1, + if (!dis_create_image(d, &d->vr_dw[1], w, h, VK_FORMAT_R32G32_SFLOAT, L, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT)) return false; - if (!dis_create_image(d, &d->flow_refined, w, h, VK_FORMAT_R32G32_SFLOAT, 1, + if (!dis_create_image(d, &d->flow_refined, w, h, VK_FORMAT_R32G32_SFLOAT, L, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_STORAGE_BIT)) return false; for (uint32_t s = 0; s < DIS_SLOTS; s++) { @@ -927,17 +932,17 @@ static bool dis_create_resources(VkrDis* d, uint32_t w, uint32_t h, uint32_t ful if (!dis_create_view(d, d->flow_sparse[l].image, VK_FORMAT_R32G32B32A32_SFLOAT, 0, 1, &d->view_sparse[l])) return false; if (!dis_create_view(d, d->flow_sparse_b[l].image, VK_FORMAT_R32G32B32A32_SFLOAT, 0, 1, &d->view_sparse_b[l])) return false; if (!dis_create_view(d, d->flow_dense.image, VK_FORMAT_R32G32_SFLOAT, l, 1, &d->view_dense[l])) return false; + if (!dis_create_view(d, d->vr_prep.image, VK_FORMAT_R32G32_SFLOAT, l, 1, &d->view_vr_prep[l])) return false; + if (!dis_create_view(d, d->vr_d1.image, VK_FORMAT_R32G32B32A32_SFLOAT, l, 1, &d->view_vr_d1[l])) return false; + if (!dis_create_view(d, d->vr_d2.image, VK_FORMAT_R32G32B32A32_SFLOAT, l, 1, &d->view_vr_d2[l])) return false; + if (!dis_create_view(d, d->vr_A.image, VK_FORMAT_R32G32B32A32_SFLOAT, l, 1, &d->view_vr_A[l])) return false; + if (!dis_create_view(d, d->vr_B.image, VK_FORMAT_R32G32_SFLOAT, l, 1, &d->view_vr_B[l])) return false; + if (!dis_create_view(d, d->vr_wt.image, VK_FORMAT_R32_SFLOAT, l, 1, &d->view_vr_wt[l])) return false; + if (!dis_create_view(d, d->vr_dw[0].image, VK_FORMAT_R32G32_SFLOAT, l, 1, &d->view_vr_dw[0][l])) return false; + if (!dis_create_view(d, d->vr_dw[1].image, VK_FORMAT_R32G32_SFLOAT, l, 1, &d->view_vr_dw[1][l])) return false; + if (!dis_create_view(d, d->flow_refined.image, VK_FORMAT_R32G32_SFLOAT, l, 1, &d->view_flow_refined[l])) return false; } if (!dis_create_view(d, d->interp_out.image, VK_FORMAT_R8G8B8A8_UNORM, 0, 1, &d->view_interp_out)) return false; - if (!dis_create_view(d, d->vr_prep.image, VK_FORMAT_R32G32_SFLOAT, 0, 1, &d->view_vr_prep)) return false; - if (!dis_create_view(d, d->vr_d1.image, VK_FORMAT_R32G32B32A32_SFLOAT, 0, 1, &d->view_vr_d1)) return false; - if (!dis_create_view(d, d->vr_d2.image, VK_FORMAT_R32G32B32A32_SFLOAT, 0, 1, &d->view_vr_d2)) return false; - if (!dis_create_view(d, d->vr_A.image, VK_FORMAT_R32G32B32A32_SFLOAT, 0, 1, &d->view_vr_A)) return false; - if (!dis_create_view(d, d->vr_B.image, VK_FORMAT_R32G32_SFLOAT, 0, 1, &d->view_vr_B)) return false; - if (!dis_create_view(d, d->vr_wt.image, VK_FORMAT_R32_SFLOAT, 0, 1, &d->view_vr_wt)) return false; - if (!dis_create_view(d, d->vr_dw[0].image, VK_FORMAT_R32G32_SFLOAT, 0, 1, &d->view_vr_dw[0])) return false; - if (!dis_create_view(d, d->vr_dw[1].image, VK_FORMAT_R32G32_SFLOAT, 0, 1, &d->view_vr_dw[1])) return false; - if (!dis_create_view(d, d->flow_refined.image, VK_FORMAT_R32G32_SFLOAT, 0, 1, &d->view_flow_refined)) return false; vkr_dis_reset(d); dis_write_all_descriptors(d); @@ -976,18 +981,22 @@ static bool dis_allocate_sets(VkrDis* d) { d->luma_sets[s][l] = sets[5]; } if (!dis_alloc(d, d->set_layout, 1, &d->interp_sets[s])) return false; - if (!dis_alloc(d, d->vr_set_layout, 1, &d->vr_prep_sets[s])) return false; + for (uint32_t l = 0; l < DIS_MAX_LEVELS; l++) { + if (!dis_alloc(d, d->vr_set_layout, 1, &d->vr_prep_sets[s][l])) return false; + } } - VkDescriptorSet vr_sets[7]; - if (!dis_alloc(d, d->vr_set_layout, 7, vr_sets)) return false; - d->vr_d1_set = vr_sets[0]; - d->vr_d2_set = vr_sets[1]; - d->vr_w_set = vr_sets[2]; - d->vr_coef_set = vr_sets[3]; - d->vr_sor_ab_set = vr_sets[4]; - d->vr_sor_ba_set = vr_sets[5]; - d->vr_add_set = vr_sets[6]; + for (uint32_t l = 0; l < DIS_MAX_LEVELS; l++) { + VkDescriptorSet vr_sets[DIS_VR_SHARED_SETS]; + if (!dis_alloc(d, d->vr_set_layout, DIS_VR_SHARED_SETS, vr_sets)) return false; + d->vr_d1_set[l] = vr_sets[0]; + d->vr_d2_set[l] = vr_sets[1]; + d->vr_w_set[l] = vr_sets[2]; + d->vr_coef_set[l] = vr_sets[3]; + d->vr_sor_ab_set[l] = vr_sets[4]; + d->vr_sor_ba_set[l] = vr_sets[5]; + d->vr_add_set[l] = vr_sets[6]; + } return true; } @@ -1421,6 +1430,87 @@ uint32_t vkr_dis_plan(VkrDis* d, uint32_t capacity, uint64_t source_frames) { return (uint32_t)d->planned_gen; } +static void dis_vr_level(VkrDis* d, VkCommandBuffer cmd, uint32_t slot, uint32_t l, + uint32_t lw, uint32_t lh, const DisRefine* refine, bool full) { + const uint32_t gw = (lw + DIS_LOCAL_SIZE - 1) / DIS_LOCAL_SIZE; + const uint32_t gh = (lh + DIS_LOCAL_SIZE - 1) / DIS_LOCAL_SIZE; + + vkd.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pass_vr_prep.pipeline); + vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->vr_pipeline_layout, 0, 1, + &d->vr_prep_sets[slot][l], 0, NULL); + vkd.CmdDispatch(cmd, gw, gh, 1); + dis_compute_barrier(cmd); + + if (full) { + vkd.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pass_vr_d1.pipeline); + vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->vr_pipeline_layout, 0, 1, + &d->vr_d1_set[l], 0, NULL); + vkd.CmdDispatch(cmd, gw, gh, 1); + dis_compute_barrier(cmd); + + vkd.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pass_vr_d2.pipeline); + vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->vr_pipeline_layout, 0, 1, + &d->vr_d2_set[l], 0, NULL); + vkd.CmdDispatch(cmd, gw, gh, 1); + dis_compute_barrier(cmd); + + for (uint32_t k = 0; k < refine->vr_fixed_point; k++) { + DisVrWPC wpc; + wpc.alpha2 = DIS_VR_ALPHA * 0.5f; + wpc.eps2 = DIS_VR_EPS * DIS_VR_EPS; + vkd.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pass_vr_w.pipeline); + vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->vr_pipeline_layout, + 0, 1, &d->vr_w_set[l], 0, NULL); + vkd.CmdPushConstants(cmd, d->vr_pipeline_layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, + sizeof(wpc), &wpc); + vkd.CmdDispatch(cmd, gw, gh, 1); + dis_compute_barrier(cmd); + + DisVrCoefPC cpc; + cpc.delta2 = DIS_VR_DELTA * 0.5f; + cpc.gamma2 = DIS_VR_GAMMA * 0.5f; + cpc.zeta2 = DIS_VR_ZETA * DIS_VR_ZETA; + cpc.eps2 = DIS_VR_EPS * DIS_VR_EPS; + vkd.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pass_vr_coef.pipeline); + vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->vr_pipeline_layout, + 0, 1, &d->vr_coef_set[l], 0, NULL); + vkd.CmdPushConstants(cmd, d->vr_pipeline_layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, + sizeof(cpc), &cpc); + vkd.CmdDispatch(cmd, gw, gh, 1); + dis_compute_barrier(cmd); + + for (uint32_t it = 0; it < refine->vr_sor; it++) { + DisVrSorPC spc; + spc.omega = DIS_VR_OMEGA; + spc.parity = 0; + vkd.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pass_vr_sor.pipeline); + vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, + d->vr_pipeline_layout, 0, 1, &d->vr_sor_ab_set[l], 0, + NULL); + vkd.CmdPushConstants(cmd, d->vr_pipeline_layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, + sizeof(spc), &spc); + vkd.CmdDispatch(cmd, gw, gh, 1); + dis_compute_barrier(cmd); + + spc.parity = 1; + vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, + d->vr_pipeline_layout, 0, 1, &d->vr_sor_ba_set[l], 0, + NULL); + vkd.CmdPushConstants(cmd, d->vr_pipeline_layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, + sizeof(spc), &spc); + vkd.CmdDispatch(cmd, gw, gh, 1); + dis_compute_barrier(cmd); + } + } + } + + vkd.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pass_vr_add.pipeline); + vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->vr_pipeline_layout, 0, 1, + &d->vr_add_set[l], 0, NULL); + vkd.CmdDispatch(cmd, gw, gh, 1); + dis_compute_barrier(cmd); +} + void vkr_dis_process(VkrDis* d, VkCommandBuffer cmd, VkImage source, uint32_t width, uint32_t height, uint32_t generations) { if (!d || !d->built || d->unavailable) return; @@ -1547,82 +1637,10 @@ void vkr_dis_process(VkrDis* d, VkCommandBuffer cmd, VkImage source, uint32_t wi dis_dispatch(d, cmd, d->pass_densify.pipeline, d->densify_sets[slot][l], lw, lh); dis_compute_barrier(cmd); - } - - const uint32_t gw = (w + DIS_LOCAL_SIZE - 1) / DIS_LOCAL_SIZE; - const uint32_t gh = (h + DIS_LOCAL_SIZE - 1) / DIS_LOCAL_SIZE; - - vkd.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pass_vr_prep.pipeline); - vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->vr_pipeline_layout, 0, 1, - &d->vr_prep_sets[slot], 0, NULL); - vkd.CmdDispatch(cmd, gw, gh, 1); - dis_compute_barrier(cmd); - vkd.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pass_vr_d1.pipeline); - vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->vr_pipeline_layout, 0, 1, - &d->vr_d1_set, 0, NULL); - vkd.CmdDispatch(cmd, gw, gh, 1); - dis_compute_barrier(cmd); - - vkd.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pass_vr_d2.pipeline); - vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->vr_pipeline_layout, 0, 1, - &d->vr_d2_set, 0, NULL); - vkd.CmdDispatch(cmd, gw, gh, 1); - dis_compute_barrier(cmd); - - for (uint32_t k = 0; k < refine.vr_fixed_point; k++) { - DisVrWPC wpc; - wpc.alpha2 = DIS_VR_ALPHA * 0.5f; - wpc.eps2 = DIS_VR_EPS * DIS_VR_EPS; - vkd.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pass_vr_w.pipeline); - vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->vr_pipeline_layout, 0, 1, - &d->vr_w_set, 0, NULL); - vkd.CmdPushConstants(cmd, d->vr_pipeline_layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, - sizeof(wpc), &wpc); - vkd.CmdDispatch(cmd, gw, gh, 1); - dis_compute_barrier(cmd); - - DisVrCoefPC cpc; - cpc.delta2 = DIS_VR_DELTA * 0.5f; - cpc.gamma2 = DIS_VR_GAMMA * 0.5f; - cpc.zeta2 = DIS_VR_ZETA * DIS_VR_ZETA; - cpc.eps2 = DIS_VR_EPS * DIS_VR_EPS; - vkd.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pass_vr_coef.pipeline); - vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->vr_pipeline_layout, 0, 1, - &d->vr_coef_set, 0, NULL); - vkd.CmdPushConstants(cmd, d->vr_pipeline_layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, - sizeof(cpc), &cpc); - vkd.CmdDispatch(cmd, gw, gh, 1); - dis_compute_barrier(cmd); - - for (uint32_t s = 0; s < refine.vr_sor; s++) { - DisVrSorPC spc; - spc.omega = DIS_VR_OMEGA; - spc.parity = 0; - vkd.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pass_vr_sor.pipeline); - vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->vr_pipeline_layout, 0, - 1, &d->vr_sor_ab_set, 0, NULL); - vkd.CmdPushConstants(cmd, d->vr_pipeline_layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, - sizeof(spc), &spc); - vkd.CmdDispatch(cmd, gw, gh, 1); - dis_compute_barrier(cmd); - - spc.parity = 1; - vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->vr_pipeline_layout, 0, - 1, &d->vr_sor_ba_set, 0, NULL); - vkd.CmdPushConstants(cmd, d->vr_pipeline_layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, - sizeof(spc), &spc); - vkd.CmdDispatch(cmd, gw, gh, 1); - dis_compute_barrier(cmd); - } + dis_vr_level(d, cmd, slot, l, lw, lh, &refine, l < refine.vr_levels); } - vkd.CmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->pass_vr_add.pipeline); - vkd.CmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, d->vr_pipeline_layout, 0, 1, - &d->vr_add_set, 0, NULL); - vkd.CmdDispatch(cmd, gw, gh, 1); - - dis_compute_barrier(cmd); } static void dis_render_into(VkrDis* d, VkCommandBuffer cmd, float t, int debug_mode,