diff --git a/src/graphics/host_gpu/renderer/image/imageInfo.h b/src/graphics/host_gpu/renderer/image/imageInfo.h index 7919cb555..b7886acda 100644 --- a/src/graphics/host_gpu/renderer/image/imageInfo.h +++ b/src/graphics/host_gpu/renderer/image/imageInfo.h @@ -159,6 +159,7 @@ struct ImageViewInfo { vk::ImageAspectFlags aspect = vk::ImageAspectFlagBits::eColor; uint32_t base_level = 0; uint32_t level_count = 1; + uint32_t min_lod = 0; // U4.8 clamp relative to base_level. uint32_t base_layer = 0; uint32_t layer_count = 1; vk::ComponentMapping mapping = {}; @@ -167,9 +168,10 @@ struct ImageViewInfo { [[nodiscard]] bool operator==(const ImageViewInfo& rhs) const noexcept { return format == rhs.format && type == rhs.type && aspect == rhs.aspect && base_level == rhs.base_level && level_count == rhs.level_count && - base_layer == rhs.base_layer && layer_count == rhs.layer_count && - mapping.r == rhs.mapping.r && mapping.g == rhs.mapping.g && - mapping.b == rhs.mapping.b && mapping.a == rhs.mapping.a && usage == rhs.usage; + min_lod == rhs.min_lod && base_layer == rhs.base_layer && + layer_count == rhs.layer_count && mapping.r == rhs.mapping.r && + mapping.g == rhs.mapping.g && mapping.b == rhs.mapping.b && + mapping.a == rhs.mapping.a && usage == rhs.usage; } }; diff --git a/src/graphics/host_gpu/renderer/image/imageView.cpp b/src/graphics/host_gpu/renderer/image/imageView.cpp index 2ad0d46b5..fa6f972ef 100644 --- a/src/graphics/host_gpu/renderer/image/imageView.cpp +++ b/src/graphics/host_gpu/renderer/image/imageView.cpp @@ -362,6 +362,12 @@ vk::ImageView Image::FindView(const ImageViewInfo& view_info) { if (!is_storage) { usage.usage &= ~vk::ImageUsageFlagBits::eStorage; } + vk::ImageViewMinLodCreateInfoEXT min_lod {}; + if (normalized.min_lod != 0) { + min_lod.minLod = static_cast(normalized.base_level) + + static_cast(normalized.min_lod) / 256.0f; + usage.pNext = &min_lod; + } vk::ImageViewCreateInfo create {}; create.pNext = &usage; create.image = image.image; diff --git a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp index 4bf58663e..0b6705f8a 100644 --- a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp @@ -452,6 +452,14 @@ static ImageViewInfo TextureViewInfo(const ShaderRecompiler::IR::ImageResource& view.aspect = vk::ImageAspectFlagBits::eColor; view.base_level = descriptor.BaseLevel(); view.level_count = view_levels; + if (descriptor.MinLod() > descriptor.LastLevel() * 256u) { + EXIT("texture minimum LOD exceeds last mip level: min_lod=%u last_level=%u\n", + descriptor.MinLod(), descriptor.LastLevel()); + } + const auto base_lod = view.base_level * 256u; + if (descriptor.MinLod() > base_lod) { + view.min_lod = descriptor.MinLod() - base_lod; + } view.usage = storage ? vk::ImageUsageFlagBits::eStorage : vk::ImageUsageFlagBits::eSampled; view.mapping = storage || surface_format.conversion_format != Prospero::BufferFormat::kInvalid diff --git a/src/graphics/presentation/window/vulkanWindow.cpp b/src/graphics/presentation/window/vulkanWindow.cpp index 9cdcc7d55..b5adbc83b 100644 --- a/src/graphics/presentation/window/vulkanWindow.cpp +++ b/src/graphics/presentation/window/vulkanWindow.cpp @@ -201,6 +201,8 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa vk::PhysicalDeviceVulkan13Features features13 {}; vk::PhysicalDeviceColorWriteEnableFeaturesEXT color_write_ext {}; + vk::PhysicalDeviceImageViewMinLodFeaturesEXT image_view_min_lod {}; + color_write_ext.pNext = &image_view_min_lod; vk::PhysicalDeviceDepthClipEnableFeaturesEXT depth_clip_enable {}; depth_clip_enable.pNext = &color_write_ext; @@ -235,6 +237,10 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa skip_device = true; #endif } + if (image_view_min_lod.minLod != VK_TRUE) { + LOGF("image view minLod is not supported\n"); + skip_device = true; + } if (depth_clip_control.depthClipControl != VK_TRUE) { LOGF("depthClipControl is not supported\n"); @@ -514,11 +520,14 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics, depth_clip_enable.depthClipEnable = VK_TRUE; vk::PhysicalDeviceDepthClipControlFeaturesEXT depth_clip_control {}; + vk::PhysicalDeviceImageViewMinLodFeaturesEXT image_view_min_lod {}; + image_view_min_lod.minLod = VK_TRUE; + depth_clip_control.pNext = &image_view_min_lod; // MoltenVK lacks VK_EXT_depth_clip_enable and VK_EXT_color_write_enable, so drop those // feature structs from the chain on macOS (the renderer falls back to default depth // clipping and static color-write masks). #if !defined(__APPLE__) - depth_clip_control.pNext = &depth_clip_enable; + image_view_min_lod.pNext = &depth_clip_enable; #endif depth_clip_control.depthClipControl = VK_TRUE; @@ -984,7 +993,8 @@ void WindowContext::CreateVulkan() { std::vector device_extensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME, - VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME, "VK_KHR_maintenance1"}; + VK_EXT_IMAGE_VIEW_MIN_LOD_EXTENSION_NAME, VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME, + "VK_KHR_maintenance1"}; #if defined(__APPLE__) // MoltenVK lacks VK_EXT_depth_clip_enable and VK_EXT_color_write_enable; the renderer diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index c758a1a45..de0fa8181 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -10044,6 +10044,113 @@ public: sampled_overwide_binding.mip_views.empty() && sampled_overwide_binding.image_view != nullptr, "sampled view lost addressable mips in the allocated tail"); + // Streaming T# clamps apply after S# max LOD, without changing the view + // base. + { + constexpr uint64_t lod_address = base + 0x170000; + auto lod_descriptor = mipped_storage_descriptor; + lod_descriptor.dwords[0] = static_cast(lod_address >> 8u); + lod_descriptor.dwords[1] = + static_cast(lod_address >> 40u) | + (static_cast(Prospero::BufferFormat::k32Float) << 20u) | + (3u << 30u); + lod_descriptor.dwords[3] &= ~(0xfu << 12u); + ShaderSamplerResource lod_sampler{ + {0, 0, + static_cast(Prospero::SamplerMipFilter::kLinear) << 26u, + 0}}; + TestCase lod_test; + lod_test.name = "TextureMinLodAfterSamplerClamp"; + lod_test.has_user_data = true; + lod_test.image_descriptor_swizzle = mipped_storage.DstSelXYZW(); + std::copy_n(lod_descriptor.dwords.begin(), 8, + lod_test.user_data.begin()); + std::copy_n(lod_sampler.fields, 4, lod_test.user_data.begin() + 8); + lod_test.user_data[50] = sizeof(uint32_t); + lod_test.opcodes = {ShaderOpcode::V_MOV_B32, ShaderOpcode::IMAGE_SAMPLE, + ShaderOpcode::BUFFER_STORE_DWORD, + ShaderOpcode::S_ENDPGM}; + lod_test.required_spirv = {"OpImageSampleExplicitLod"}; + AppendVMovLiteral(&lod_test.code, 20, std::bit_cast(0.5f)); + AppendVMovLiteral(&lod_test.code, 21, std::bit_cast(0.5f)); + lod_test.code.push_back(EncodeMimg0(0x27, 1)); + lod_test.code.push_back(EncodeMimg1(0, 20, 0, 2)); + AppendStoreVgpr(&lod_test.code, 0, 0); + AppendEnd(&lod_test.code); + const auto lod_program = CompileCase(lod_test, SubgroupSize()); +#if KYTY_PLATFORM != KYTY_PLATFORM_WINDOWS + ExpectFatal("TextureMinLodBeyondView", [&] { + auto invalid_lod = lod_descriptor; + invalid_lod.dwords[1] |= 1024u << 8u; + (void)RenderExecutorTestAccess::ResolveTexture( + executor, lod_program.program.info.images[0], invalid_lod); + }); +#endif + const auto lod_binding = RenderExecutorTestAccess::ResolveTexture( + executor, lod_program.program.info.images[0], lod_descriptor); + for (uint32_t mip = 0; mip < 4; ++mip) { + vk::ClearValue clear{}; + clear.color.float32[0] = static_cast(1u << mip); + TextureCacheTestAccess::ClearImage( + texture_cache, scheduler.Current(), lod_binding.image_id, + {vk::ImageAspectFlagBits::eColor, mip, 1, 0, 1}, clear); + } + const auto sampler = context.GetSamplerCache().GetSampler(lod_sampler); + auto output = CreateStorageBuffer(lod_test.name, {}, 1); + struct LodCase { + uint32_t base_level; + uint32_t min_lod; + float expected; + float integer_min_lod_expected; + }; + constexpr std::array lod_cases{ + LodCase{0, 0, 1.0f, 1.0f}, LodCase{0, 256, 2.0f, 2.0f}, + LodCase{0, 384, 3.0f, 2.0f}, LodCase{1, 384, 3.0f, 2.0f}, + LodCase{1, 512, 4.0f, 4.0f}, LodCase{1, 0, 2.0f, 2.0f}, + LodCase{0, 256, 2.0f, 2.0f}}; + std::array views{}; + for (size_t index = 0; index < lod_cases.size(); ++index) { + const auto &test = lod_cases[index]; + lod_descriptor.dwords[1] = + (lod_descriptor.dwords[1] & ~0xfff00u) | (test.min_lod << 8u); + lod_descriptor.dwords[3] = + (lod_descriptor.dwords[3] & ~(0xfu << 12u)) | + (test.base_level << 12u); + std::copy_n(lod_descriptor.dwords.begin(), 8, + lod_test.user_data.begin()); + const auto binding = RenderExecutorTestAccess::ResolveTexture( + executor, lod_program.program.info.images[0], lod_descriptor); + views[index] = + texture_cache.FindTexture(binding.image_id, binding.desc); + auto &image = texture_cache.GetImage(binding.image_id); + image.Transit(vk::ImageLayout::eShaderReadOnlyOptimal, + vk::AccessFlagBits2::eShaderRead, {}, + scheduler.Current().Handle()); + Image sampled; + sampled.view = views[index]; + sampled.layout = image.backing.state.layout; + scheduler.Finish(); + Dispatch(lod_test, lod_program, output, nullptr, &sampled, nullptr, + nullptr, sampler); + const auto result = ReadBuffer(lod_test.name, output, 1)[0]; + // Vulkan permits flooring imageViewMinLod instead of retaining its + // fraction. + Require(lod_test.name, "sampled mip", + result == std::bit_cast(test.expected) || + result == std::bit_cast( + test.integer_min_lod_expected), + "texture clamp was lost, applied before sampler max LOD, or " + "rebased incorrectly"); + } + Require(lod_test.name, "view identity", + views[0] != views[1] && views[1] != views[2] && + views[2] != views[3] && views[1] == views.back(), + "different texture clamps aliased or identical clamped views " + "were not reused"); + DestroyBuffer(&output); + RenderExecutorTestAccess::ResetBindings(executor); + } + auto srgb_storage = storage; constexpr auto srgb_format = static_cast(Prospero::BufferFormat::k8_8_8_8Srgb); @@ -14815,9 +14922,11 @@ private: available_feedback_dynamic.pNext = &available_feedback_layout; vk::PhysicalDeviceProvokingVertexFeaturesEXT available_provoking_vertex{}; available_provoking_vertex.pNext = &available_feedback_dynamic; + vk::PhysicalDeviceImageViewMinLodFeaturesEXT available_min_lod{}; + available_min_lod.pNext = &available_provoking_vertex; vk::PhysicalDeviceFeatures2 available_features2{}; available_features2.sType = vk::StructureType::ePhysicalDeviceFeatures2; - available_features2.pNext = &available_provoking_vertex; + available_features2.pNext = &available_min_lod; m_physical_device.getFeatures2(&available_features2); Require("VulkanHarness", "dispatch", available_features.shaderStorageImageWriteWithoutFormat == true, @@ -14843,6 +14952,8 @@ private: Require("VulkanHarness", "dispatch", available_features12.bufferDeviceAddress == true, "bufferDeviceAddress is not supported"); + Require("VulkanHarness", "dispatch", available_min_lod.minLod == true, + "image view minimum LOD is not supported"); Require("VulkanHarness", "graphics", available_features12.shaderOutputLayer == true, "vertex layer output is not supported"); Require("VulkanHarness", "graphics", available_features.fillModeNonSolid && @@ -14901,7 +15012,10 @@ private: vk::PhysicalDeviceProvokingVertexFeaturesEXT provoking_vertex{}; provoking_vertex.pNext = &feedback_dynamic; provoking_vertex.provokingVertexLast = available_provoking_vertex.provokingVertexLast; - device_info.pNext = &provoking_vertex; + vk::PhysicalDeviceImageViewMinLodFeaturesEXT min_lod{}; + min_lod.pNext = &provoking_vertex; + min_lod.minLod = true; + device_info.pNext = &min_lod; vk::PhysicalDeviceFeatures device_features{}; device_features.shaderStorageImageWriteWithoutFormat = true; device_features.shaderImageGatherExtended = true; @@ -14918,7 +15032,8 @@ private: VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME, VK_EXT_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_EXTENSION_NAME, VK_EXT_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_EXTENSION_NAME, - VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME}; + VK_EXT_PROVOKING_VERTEX_EXTENSION_NAME, + VK_EXT_IMAGE_VIEW_MIN_LOD_EXTENSION_NAME}; device_info.enabledExtensionCount = std::size(device_extensions); device_info.ppEnabledExtensionNames = device_extensions; RequireVk("VulkanHarness", "dispatch",