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/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..4e518dd41 --- /dev/null +++ b/app/src/main/cpp/winlator/vk/dis/vkr_dis.c @@ -0,0 +1,1769 @@ +#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[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; + + 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][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; + 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) * DIS_MAX_LEVELS; + 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; + uint32_t vr_levels; +} DisRefine; + +static DisRefine dis_refine_for(uint32_t generations) { + if (generations >= 3u) { + const DisRefine r = {2u, 5u, 2u, DIS_MAX_LEVELS}; + return r; + } + if (generations == 2u) { + const DisRefine r = {2u, 4u, 1u, DIS_MAX_LEVELS}; + return r; + } + const DisRefine r = {1u, 3u, 1u, 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_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); + 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[0], d->sampler); + dis_batch_storage(d, &b, d->interp_sets[s], 5, d->view_interp_out); + + 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]); + } + } + + 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); +} + +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); + 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]); + } + 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, 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, 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, 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, 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, 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, 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, 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, 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, L, + 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->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; + + 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; + 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; + } + } + + 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; +} + +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; +} + +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; + + 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); + + dis_vr_level(d, cmd, slot, l, lw, lh, &refine, l < refine.vr_levels); + } + +} + +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;