From 2097ebb41497e70339c861e1262caf7c17022ec3 Mon Sep 17 00:00:00 2001 From: nmzik Date: Mon, 7 Sep 2026 11:10:04 +0400 Subject: [PATCH] render: support overlapping fragment depth feedback --- src/graphics/host_gpu/graphicContext.h | 1 + .../host_gpu/renderer/image/image.cpp | 3 + .../renderer/pipeline/descriptors.cpp | 5 +- .../host_gpu/renderer/pipeline/shaders.cpp | 21 +- src/graphics/host_gpu/renderer/render.h | 3 +- src/graphics/host_gpu/renderer/renderDraw.cpp | 36 ++- .../presentation/window/vulkanWindow.cpp | 25 +- tests/ShaderRecompilerComputeTests.cpp | 276 +++++++++++++----- 8 files changed, 286 insertions(+), 84 deletions(-) diff --git a/src/graphics/host_gpu/graphicContext.h b/src/graphics/host_gpu/graphicContext.h index 6631d3cfc..21be1c7f7 100644 --- a/src/graphics/host_gpu/graphicContext.h +++ b/src/graphics/host_gpu/graphicContext.h @@ -32,6 +32,7 @@ struct GraphicContext { bool rt_extensions_enabled = false; bool compute_subgroup_size_control_enabled = false; bool sample_rate_shading_enabled = false; + bool attachment_feedback_loop_enabled = false; bool mesh_shader_enabled = false; vk::PhysicalDeviceMeshShaderPropertiesEXT mesh_shader_properties = {}; uint32_t subgroup_size = 0; diff --git a/src/graphics/host_gpu/renderer/image/image.cpp b/src/graphics/host_gpu/renderer/image/image.cpp index 7485e2eca..c3d947123 100644 --- a/src/graphics/host_gpu/renderer/image/image.cpp +++ b/src/graphics/host_gpu/renderer/image/image.cpp @@ -54,6 +54,9 @@ namespace { } if (DepthAspectTransferFormat(info.pixel_format) != vk::Format::eUndefined) { usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment; + if (graphics.attachment_feedback_loop_enabled && (usage & vk::ImageUsageFlagBits::eSampled)) { + usage |= vk::ImageUsageFlagBits::eAttachmentFeedbackLoopEXT; + } return usage; } if (HasFormatFeature(properties, vk::FormatFeatureFlagBits::eColorAttachment)) { diff --git a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp index 4e4d60a43..4b72e7725 100644 --- a/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/descriptors.cpp @@ -1004,8 +1004,11 @@ void RenderExecutor::CommitBindings(CommandBuffer& buffer, std::ranges::find(image.views, binding.image_view, &CachedImageView::view); EXIT_IF(storage || host_view == image.views.end()); const auto aspect = host_view->info.aspect; + const bool depth_feedback = + layout == vk::ImageLayout::eAttachmentFeedbackLoopOptimalEXT && + program.stage == ShaderType::Pixel; const bool depth_read = - layout == vk::ImageLayout::eDepthReadOnlyOptimal || + depth_feedback || layout == vk::ImageLayout::eDepthReadOnlyOptimal || layout == vk::ImageLayout::eDepthStencilReadOnlyOptimal || layout == vk::ImageLayout::eDepthReadOnlyStencilAttachmentOptimal; const bool stencil_read = diff --git a/src/graphics/host_gpu/renderer/pipeline/shaders.cpp b/src/graphics/host_gpu/renderer/pipeline/shaders.cpp index 12ff42ae2..f662b1b8d 100644 --- a/src/graphics/host_gpu/renderer/pipeline/shaders.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/shaders.cpp @@ -702,7 +702,7 @@ void CreatePipelineInternal( depth_stencil_info.minDepthBounds = static_params.depth_min_bounds; depth_stencil_info.maxDepthBounds = static_params.depth_max_bounds; - const vk::DynamicState dynamic_states[] = { + std::vector dynamic_states { vk::DynamicState::eViewportWithCount, vk::DynamicState::eScissorWithCount, vk::DynamicState::eLineWidth, @@ -715,22 +715,19 @@ void CreatePipelineInternal( vk::DynamicState::eStencilReference, vk::DynamicState::eStencilWriteMask, vk::DynamicState::eBlendConstants, -#if !defined(__APPLE__) - // Keep last so depth-only pipelines can omit this dynamic state. - vk::DynamicState::eColorWriteEnableEXT, // unsupported by MoltenVK; static mask instead -#endif }; - auto dynamic_states_count = - static_cast(sizeof(dynamic_states) / sizeof(dynamic_states[0])); #if !defined(__APPLE__) - if (rendering.color_count == 0) { - dynamic_states_count--; + if (rendering.color_count != 0) { + dynamic_states.push_back(vk::DynamicState::eColorWriteEnableEXT); } #endif + if (graphics.attachment_feedback_loop_enabled) { + dynamic_states.push_back(vk::DynamicState::eAttachmentFeedbackLoopEnableEXT); + } vk::PipelineDynamicStateCreateInfo dynamic_state {}; - dynamic_state.dynamicStateCount = dynamic_states_count; - dynamic_state.pDynamicStates = dynamic_states; + dynamic_state.dynamicStateCount = static_cast(dynamic_states.size()); + dynamic_state.pDynamicStates = dynamic_states.data(); vk::GraphicsPipelineCreateInfo pipeline_info {}; vk::PipelineRenderingCreateInfo rendering_info {}; @@ -764,7 +761,7 @@ void CreatePipelineInternal( vertex_program.id, ps_active ? pixel_program.id : 0, static_cast(static_params.topology), static_params.color_mask[0], (with_depth ? "true" : "false"), (static_params.blend_enable[0] ? "true" : "false"), - dynamic_states_count); + dynamic_state.dynamicStateCount); } result = graphics.device.createGraphicsPipelines(driver_cache, 1, &pipeline_info, nullptr, &pipeline.pipeline); diff --git a/src/graphics/host_gpu/renderer/render.h b/src/graphics/host_gpu/renderer/render.h index 92d196a6f..1feede639 100644 --- a/src/graphics/host_gpu/renderer/render.h +++ b/src/graphics/host_gpu/renderer/render.h @@ -195,7 +195,8 @@ private: bool primitive_restart_enable, bool log_pipeline_phase, bool set_bind_debug, bool set_auto_debug); [[nodiscard]] RenderState AcquireRenderTargets(CommandBuffer& buffer, RenderColorInfo* colors, - uint32_t color_count, RenderDepthInfo& depth); + uint32_t color_count, RenderDepthInfo& depth, + const std::optional& pixel = std::nullopt); [[nodiscard]] bool ResolveColorTargets(uint64_t submit_id, CommandBuffer& buffer, uint32_t render_target_slice_offset); void BindImage(ImageId id, bool storage); diff --git a/src/graphics/host_gpu/renderer/renderDraw.cpp b/src/graphics/host_gpu/renderer/renderDraw.cpp index 9138d080d..044102c74 100644 --- a/src/graphics/host_gpu/renderer/renderDraw.cpp +++ b/src/graphics/host_gpu/renderer/renderDraw.cpp @@ -475,7 +475,8 @@ struct DrawCallInfo { }; RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderColorInfo* colors, - uint32_t color_count, RenderDepthInfo& depth) { + uint32_t color_count, RenderDepthInfo& depth, + const std::optional& pixel) { EXIT_IF(colors == nullptr || color_count > RENDER_COLOR_ATTACHMENTS_MAX); auto& cache = m_context.GetTextureCache(); RenderState state {}; @@ -567,7 +568,28 @@ RenderState RenderExecutor::AcquireRenderTargets(CommandBuffer& buffer, RenderCo EXIT("mixed color/depth sample counts are unsupported: %u and %u\n", attachment_samples, depth.desc.info.samples); } - const auto layout = depth_attachment_layout(depth); + const bool feedback = depth.depth_write_enable && pixel && + std::ranges::any_of(pixel->images, [&](const TextureBinding& binding) { + if (binding.image_id != depth.image_id || + binding.desc.type != TextureCache::BindingType::Texture) { + return false; + } + const auto native = + std::ranges::find(image.views, binding.image_view, &CachedImageView::view); + EXIT_IF(native == image.views.end()); + const auto& sampled = native->info; + const auto& target = depth.desc.view_info; + return (sampled.aspect & vk::ImageAspectFlagBits::eDepth) && + ImageRangeOverlaps(sampled.base_level, sampled.level_count, + target.base_level, target.level_count) && + ImageRangeOverlaps(sampled.base_layer, sampled.layer_count, + target.base_layer, target.layer_count); + }); + if (feedback && !m_context.GetGraphics().attachment_feedback_loop_enabled) { + EXIT("depth attachment feedback loop is not supported by the host\n"); + } + const auto layout = feedback ? vk::ImageLayout::eAttachmentFeedbackLoopOptimalEXT + : depth_attachment_layout(depth); // The attachment store writes even when guest depth/stencil tests do not. const auto access = vk::AccessFlagBits2::eDepthStencilAttachmentRead | vk::AccessFlagBits2::eDepthStencilAttachmentWrite; @@ -1089,7 +1111,8 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff index_binding = PrepareIndexBuffer(buffer, index_source); } const auto rendering = - AcquireRenderTargets(buffer, state.color_info, state.color_count, state.depth_info); + AcquireRenderTargets(buffer, state.color_info, state.color_count, state.depth_info, + bindings.pixel); if (log_pipeline_phase) { LogDrawPhase(draw.name, "CreatePipeline"); @@ -1142,6 +1165,13 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff SetGraphicsDynamicParams(buffer, vk_buffer, state.vs_input_info, state.color_info, state.color_count, state.depth_info); + if (m_context.GetGraphics().attachment_feedback_loop_enabled) { + vk_buffer.setAttachmentFeedbackLoopEnableEXT( + rendering.depth_stencil_attachment.image_layout == + vk::ImageLayout::eAttachmentFeedbackLoopOptimalEXT + ? vk::ImageAspectFlags {vk::ImageAspectFlagBits::eDepth} + : vk::ImageAspectFlags {}); + } LogDrawPhase(draw.name, "BeginRendering"); if (set_auto_debug) { diff --git a/src/graphics/presentation/window/vulkanWindow.cpp b/src/graphics/presentation/window/vulkanWindow.cpp index 96707c49f..713bc2aea 100644 --- a/src/graphics/presentation/window/vulkanWindow.cpp +++ b/src/graphics/presentation/window/vulkanWindow.cpp @@ -623,7 +623,22 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V supported_features2.sType = vk::StructureType::ePhysicalDeviceFeatures2; supported_features2.pNext = mesh_extension ? static_cast(&supported_mesh) : static_cast(&supported_features13); + const bool feedback_extensions = + HasExtension(device_extensions, VK_EXT_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_EXTENSION_NAME) && + HasExtension(device_extensions, VK_EXT_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_EXTENSION_NAME); + vk::PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT feedback_layout {}; + vk::PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT feedback_dynamic {}; + if (feedback_extensions) { + feedback_dynamic.pNext = supported_features2.pNext; + feedback_layout.pNext = &feedback_dynamic; + supported_features2.pNext = &feedback_layout; + } physical_device.getFeatures2(&supported_features2); + graphics.attachment_feedback_loop_enabled = + feedback_extensions && feedback_layout.attachmentFeedbackLoopLayout && + feedback_dynamic.attachmentFeedbackLoopDynamicState; + LOGF("Vulkan depth feedback support: %s\n", + graphics.attachment_feedback_loop_enabled ? "true" : "false"); graphics.mesh_shader_enabled = mesh_extension && supported_mesh.meshShader; if (graphics.mesh_shader_enabled) { vk::PhysicalDeviceProperties2 properties {}; @@ -729,8 +744,11 @@ static vk::Device VulkanCreateDevice(vk::PhysicalDevice physical_device, const V mesh_features.pNext = &features13; mesh_features.meshShader = graphics.mesh_shader_enabled; create_info.sType = vk::StructureType::eDeviceCreateInfo; - create_info.pNext = + feedback_dynamic.pNext = mesh_extension ? static_cast(&mesh_features) : static_cast(&features13); + create_info.pNext = graphics.attachment_feedback_loop_enabled + ? static_cast(&feedback_layout) + : feedback_dynamic.pNext; create_info.flags = {}; create_info.pQueueCreateInfos = &queue_create_info; create_info.queueCreateInfoCount = 1; @@ -1117,6 +1135,11 @@ void WindowContext::CreateVulkan() { device_extensions.push_back(extension); } } + if (HasExtension(available_extensions, VK_EXT_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_EXTENSION_NAME) && + HasExtension(available_extensions, VK_EXT_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_EXTENSION_NAME)) { + device_extensions.push_back(VK_EXT_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_EXTENSION_NAME); + device_extensions.push_back(VK_EXT_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_EXTENSION_NAME); + } } VulkanInitSubgroupSizeControl(graphic_ctx.physical_device, graphic_ctx); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 4907ca3d9..ebaae3982 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -478,6 +478,14 @@ struct RenderExecutorTestAccess { return pipeline; } + static void CommitBindings(RenderExecutor &executor, CommandBuffer &buffer, + const PipelineCache::Pipeline &pipeline, + PreparedBindings &vertex, PreparedBindings &pixel) { + std::array stages{&vertex, &pixel}; + executor.CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline, + stages); + } + static void DestroyDescriptorPipelines( RenderExecutor &executor, std::span pipelines) { @@ -511,8 +519,9 @@ struct RenderExecutorTestAccess { CommandBuffer &buffer, RenderColorInfo *colors, uint32_t color_count, - RenderDepthInfo &depth) { - return executor.AcquireRenderTargets(buffer, colors, color_count, depth); + RenderDepthInfo &depth, + const std::optional &pixel = std::nullopt) { + return executor.AcquireRenderTargets(buffer, colors, color_count, depth, pixel); } static void ResetBindings(RenderExecutor &executor) { @@ -1190,6 +1199,7 @@ struct GraphicsCase { vk::SampleCountFlagBits samples = vk::SampleCountFlagBits::e1; bool pixel_position_w = false; float vertex_clip_w = 1.0f; + bool pixel_depth_export = false; }; struct CompiledShader { @@ -1534,6 +1544,7 @@ CompiledShader CompileFragmentCase(const GraphicsCase &test) { pixel_info.ps_ancillary = test.pixel_ancillary; pixel_info.ps_front_face = test.pixel_front_face; pixel_info.ps_pos_w = test.pixel_position_w; + pixel_info.ps_depth_export_enable = test.pixel_depth_export; pixel_info.ps_system_input_base = 2; for (u32 i = 0; i < std::size(pixel_info.interpolator_settings); i++) { pixel_info.interpolator_settings[i] = i; @@ -8097,19 +8108,23 @@ public: } std::vector ReadCachedTexel(const char *name, RenderContext &context, - ImageId id, vk::Offset3D offset = {}) { + ImageId id, vk::Offset3D offset = {}, + vk::Extent3D extent = {1, 1, 1}) { auto &scheduler = context.GetCommandScheduler(); auto &image = context.GetTextureCache().GetImage(id); - auto probe = CreateHostBuffer(name, image.info.bytes_per_block, + const auto bytes = image.info.bytes_per_block * extent.width * + extent.height * extent.depth; + auto probe = CreateHostBuffer(name, bytes, vk::BufferUsageFlagBits::eTransferDst, {}); scheduler.Current().EndRendering(); image.Transit(vk::ImageLayout::eTransferSrcOptimal, vk::AccessFlagBits2::eTransferRead, {}, scheduler.Current().Handle()); vk::BufferImageCopy copy{}; - copy.imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1}; + copy.imageSubresource = {image.info.IsDepth() ? vk::ImageAspectFlagBits::eDepth + : vk::ImageAspectFlagBits::eColor, 0, 0, 1}; copy.imageOffset = offset; - copy.imageExtent = vk::Extent3D{1, 1, 1}; + copy.imageExtent = extent; scheduler.Current().Handle().copyImageToBuffer( image.backing.image, vk::ImageLayout::eTransferSrcOptimal, probe.buffer, 1, ©); @@ -8125,7 +8140,7 @@ public: vk::PipelineStageFlagBits::eHost, {}, 0, nullptr, 1, &barrier, 0, nullptr); scheduler.Finish(); - auto result = ReadBuffer(name, probe, image.info.bytes_per_block / 4); + auto result = ReadBuffer(name, probe, bytes / 4); DestroyBuffer(&probe); return result; } @@ -9859,7 +9874,7 @@ public: auto bounds_bindings = RenderExecutorTestAccess::PrepareGraphicsBindings( executor, bounds_vertex, bounds_pixel, true); const auto bounds_rendering = RenderExecutorTestAccess::AcquireRenderTargets( - executor, scheduler.Current(), &no_color, 0, bounds_depth); + executor, scheduler.Current(), &no_color, 0, bounds_depth, bounds_bindings.pixel); descriptor_pipelines.push_back(RenderExecutorTestAccess::CommitBindings( executor, scheduler.Current(), bounds_bindings.vertex, *bounds_bindings.pixel)); const auto &vertex_depth = bounds_bindings.vertex.images[0]; @@ -9946,7 +9961,7 @@ public: executor, shared_depth_vertex, shared_depth_pixel, true); const auto shared_rendering = RenderExecutorTestAccess::AcquireRenderTargets( - executor, scheduler.Current(), &no_color, 0, shared_depth); + executor, scheduler.Current(), &no_color, 0, shared_depth, shared_bindings.pixel); descriptor_pipelines.push_back(RenderExecutorTestAccess::CommitBindings( executor, scheduler.Current(), shared_bindings.vertex, *shared_bindings.pixel)); @@ -11375,9 +11390,12 @@ public: m_device.destroyShaderModule(module, nullptr); } - void CheckPolygonModeRasterization() { - constexpr const char *name = "PolygonModeRasterization"; - constexpr uint32_t extent = 32; + void CheckRasterization(bool depth_feedback) { + const char *name = depth_feedback ? "DepthAttachmentFeedback" + : "PolygonModeRasterization"; + const uint32_t extent = depth_feedback ? 8 : 32; + constexpr uintptr_t depth_address = 0x0000000204400000ull; + constexpr uint64_t allocation_size = 0x20000; EnsureRuntimeContext(); RenderContext context(m_runtime_context); auto &scheduler = context.GetCommandScheduler(); @@ -11386,10 +11404,79 @@ public: HW::Shader shaders{}; registers.SetRenderTargetMask(0xf); scheduler.Begin(registers, user_config, shaders); + auto &resources = context.GetGpuResources(); + auto &cache = context.GetTextureCache(); + auto &executor = context.GetRenderExecutor(); + int64_t direct_offset = -1; + RenderDepthInfo depth{}; + Require(name, "direct allocation", + Libs::LibKernel::Memory::KernelAllocateDirectMemory( + 0, Libs::LibKernel::Memory::KernelGetDirectMemorySize(), + allocation_size, allocation_size, 0, &direct_offset) == 0, + "depth allocation failed"); + void *mapped = reinterpret_cast(depth_address); + Require(name, "direct mapping", + Libs::LibKernel::Memory::KernelMapDirectMemory( + &mapped, allocation_size, 0x3, 0x10, direct_offset, + allocation_size) == 0 && + mapped == reinterpret_cast(depth_address), + "depth mapping failed"); + std::memset(mapped, 0, allocation_size); + const auto initial_depth = [](uint32_t x, uint32_t y) { + return 0.125f + static_cast(x + 2 * y) / 128.0f; + }; + for (uint32_t y = 0; y < extent; y++) { + for (uint32_t x = 0; x < extent; x++) { + static_cast(mapped)[y * 64 + x] = initial_depth(x, y); + } + } + resources.MapMemory(depth_address, allocation_size); + if (depth_feedback) { + depth.desc.type = BindingType::DepthTarget; + depth.desc.info.data = {depth_address, 256 * extent}; + depth.desc.info.pixel_format = vk::Format::eD32Sfloat; + depth.desc.info.guest_format = Prospero::BufferFormat::k32Float; + depth.desc.info.extent = {extent, extent, 1}; + depth.desc.info.pitch = 64; + depth.desc.info.bytes_per_block = 4; + depth.desc.info.tile_mode = Prospero::TileMode::kLinear; + depth.desc.info.mip_layout[0] = {0, 256 * extent, 64, extent}; + depth.desc.view_info.format = depth.desc.info.pixel_format; + depth.desc.view_info.aspect = vk::ImageAspectFlagBits::eDepth; + depth.desc.view_info.usage = vk::ImageUsageFlagBits::eDepthStencilAttachment; + depth.depth_test_enable = depth.depth_write_enable = true; + depth.depth_compare_op = vk::CompareOp::eAlways; + depth.image_id = cache.FindImage(depth.desc); + } GraphicsCase test; test.name = name; - AppendVMovLiteral(&test.fragment_code, 0, 0x3f800000u); + test.pixel_depth_export = depth_feedback; + if (depth_feedback) { + const ShaderTextureResource descriptor{{ + static_cast(depth_address >> 8u), + (static_cast(Prospero::BufferFormat::k32Float) << 20u) | + (((extent - 1u) & 3u) << 30u), + ((extent - 1u) >> 2u) | ((extent - 1u) << 14u), + DstSel(4, 4, 4, 4) | + (static_cast(Prospero::ImageType::kColor2D) << 28u), + 0, 0, 0, 0}}; + test.has_user_data = true; + std::copy_n(descriptor.fields, 8, test.user_data.begin()); + // Each fragment samples its own texel through interpolated screen UVs. + for (uint32_t component = 0; component < 2; component++) { + test.fragment_code.push_back(EncodeVintrp(0, 20 + component, 0, component, 0)); + test.fragment_code.push_back(EncodeVintrp(1, 20 + component, 0, component, 1)); + } + test.fragment_code.push_back(EncodeMimg0(0x27, 1)); + test.fragment_code.push_back(EncodeMimg1(0, 20, 0, 2)); + AppendVMovLiteral(&test.fragment_code, 1, 0x3e000000u); + test.fragment_code.push_back(EncodeVop2(0x03, 1, Vgpr(0), 1)); + test.fragment_code.push_back(EncodeExp0(0x08, 0x1, false)); + test.fragment_code.push_back(EncodeExp1(1, 1, 1, 1)); + } else { + AppendVMovLiteral(&test.fragment_code, 0, 0x3f800000u); + } test.fragment_code.push_back(EncodeExp0(0x00, 0xf)); test.fragment_code.push_back(EncodeExp1(0, 0, 0, 0)); AppendEnd(&test.fragment_code); @@ -11422,8 +11509,12 @@ public: } ShaderPixelInputInfo pixel{}; pixel.stage.program = &pixel_program; + pixel.stage.resources = std::move(fragment.resources); RenderColorInfo color{}; + color.desc.type = BindingType::RenderTarget; + color.desc.info.data = {depth_address + 0x10000, extent * extent * 16}; + color.desc.info.mip_layout[0] = {0, color.desc.info.data.size, extent, extent}; color.desc.info.pixel_format = vk::Format::eR32G32B32A32Sfloat; color.desc.info.guest_format = Prospero::BufferFormat::k32_32_32_32Float; color.desc.info.extent = {extent, extent, 1}; @@ -11431,17 +11522,13 @@ public: color.desc.info.bytes_per_block = 16; color.desc.view_info.format = color.desc.info.pixel_format; color.desc.view_info.usage = vk::ImageUsageFlagBits::eColorAttachment; - auto &cache = context.GetTextureCache(); - color.image_id = TextureCacheTestAccess::InsertImage(cache, color.desc.info); - auto &native = cache.GetImage(color.image_id); - Image target{}; - target.image = native.backing.image; - target.view = native.FindView(color.desc.view_info); - target.width = target.height = extent; - target.dwords_per_pixel = 4; - const std::array vertices{ + color.image_id = cache.FindImage(color.desc); + std::array vertices{ -0.75f, -0.75f, 1, 1, 1, 1, 0.75f, -0.75f, 1, 1, 1, 1, 0.0f, 0.75f, 1, 1, 1, 1}; + if (depth_feedback) { + vertices = {-1, -1, 0, 0, 0, 1, 3, -1, 2, 0, 0, 1, -1, 3, 0, 2, 0, 1}; + } std::vector vertex_words(vertices.size()); std::memcpy(vertex_words.data(), vertices.data(), sizeof(vertices)); auto buffer = CreateHostBuffer(name, sizeof(vertices), vk::BufferUsageFlagBits::eVertexBuffer, @@ -11453,64 +11540,104 @@ public: mode.polymode_back_ptype = back; registers.SetModeControl(mode); return context.GetPipelineCache().CreateGraphicsPipeline( - std::span{&color, 1u}, {}, vertex, scheduler.Current(), &pixel, + std::span{&color, 1u}, depth, vertex, scheduler.Current(), &pixel, vk::PrimitiveTopology::eTriangleList, false, vertex_shader, pixel_shader); }; auto &filled = pipeline(true, 2, 2); - auto &wireframe = pipeline(true, 1, 1); - Require(name, "pipeline cache", filled.pipeline != wireframe.pipeline && - pipeline(true, 2, 2).pipeline == filled.pipeline && - pipeline(false, 1, 0).pipeline == filled.pipeline, - "polygon mode was lost from the cache key or dormant face modes were applied"); const auto draw = [&](const PipelineCache::Pipeline &selected) { - auto cmd = BeginCommands(name, "rasterization"); - AddImageBarrier(cmd, target.image, target.layout, vk::ImageLayout::eGeneral, - vk::PipelineStageFlagBits::eAllCommands, - vk::PipelineStageFlagBits::eColorAttachmentOutput, - vk::AccessFlagBits::eMemoryRead | vk::AccessFlagBits::eMemoryWrite, - vk::AccessFlagBits::eColorAttachmentWrite); - vk::RenderingAttachmentInfo attachment{}; - attachment.imageView = target.view; - attachment.imageLayout = vk::ImageLayout::eGeneral; - attachment.loadOp = vk::AttachmentLoadOp::eClear; - attachment.storeOp = vk::AttachmentStoreOp::eStore; - vk::RenderingInfo rendering{}; - rendering.renderArea.extent = {extent, extent}; - rendering.layerCount = 1; - rendering.colorAttachmentCount = 1; - rendering.pColorAttachments = &attachment; - cmd.beginRendering(rendering); + RenderExecutorTestAccess::BindRenderTarget(executor, color.image_id); + if (depth.image_id) { + RenderExecutorTestAccess::BindRenderTarget(executor, depth.image_id); + } + auto bindings = RenderExecutorTestAccess::PrepareGraphicsBindings( + executor, vertex.stage, pixel.stage, true); + Require(name, "sampled attachment identity", + !depth_feedback || bindings.pixel->images[0].image_id == depth.image_id, + "the fragment must sample the depth attachment's native image"); + auto &command = scheduler.Current(); + auto rendering = RenderExecutorTestAccess::AcquireRenderTargets( + executor, command, &color, 1, depth, bindings.pixel); + const bool feedback_enabled = rendering.depth_stencil_attachment.image_layout == + vk::ImageLayout::eAttachmentFeedbackLoopOptimalEXT; + Require(name, "per-draw depth feedback", + feedback_enabled == (depth_feedback && depth.depth_write_enable), + "feedback was not selected only for an overlapping fragment depth read/write"); + RenderExecutorTestAccess::CommitBindings( + executor, command, selected, bindings.vertex, *bindings.pixel); + rendering.color_attachments[0].is_clear = true; + command.BeginRendering(rendering); + auto cmd = command.Handle(); cmd.bindPipeline(vk::PipelineBindPoint::eGraphics, selected.pipeline); - const vk::Viewport viewport{0, 0, extent, extent, 0, 1}; + const vk::Viewport viewport{0, 0, static_cast(extent), + static_cast(extent), 0, 1}; const vk::Rect2D scissor{{0, 0}, {extent, extent}}; cmd.setViewportWithCount(1, &viewport); cmd.setScissorWithCount(1, &scissor); cmd.setLineWidth(1); - cmd.setDepthTestEnable(false); - cmd.setDepthWriteEnable(false); + cmd.setDepthTestEnable(depth.depth_test_enable); + cmd.setDepthWriteEnable(depth.depth_write_enable); cmd.setDepthCompareOp(vk::CompareOp::eAlways); cmd.setDepthBiasEnable(false); const vk::Bool32 write = true; cmd.setColorWriteEnableEXT(1, &write); + cmd.setAttachmentFeedbackLoopEnableEXT( + feedback_enabled ? vk::ImageAspectFlags{vk::ImageAspectFlagBits::eDepth} + : vk::ImageAspectFlags{}); const vk::DeviceSize offset = 0; cmd.bindVertexBuffers(0, 1, &buffer.buffer, &offset); cmd.draw(3, 1, 0, 0); - cmd.endRendering(); - EndSubmitAndFree(name, "rasterization", cmd); - target.layout = vk::ImageLayout::eGeneral; - return ReadImage(name, &target); + command.EndRendering(); + RenderExecutorTestAccess::ResetBindings(executor); }; - const auto solid_pixels = draw(filled); - const auto line_pixels = draw(wireframe); - const auto interior = 4 * (16 * extent + 16); - Require(name, "GPU coverage", solid_pixels[interior] == 0x3f800000u && - line_pixels[interior] == 0 && - std::ranges::any_of(line_pixels, [](u32 value) { return value == 0x3f800000u; }), - "wireframe did not preserve triangle edges while leaving its interior empty"); + const auto read_color = [&] { + return ReadCachedTexel(name, context, color.image_id, {}, {extent, extent, 1}); + }; + draw(filled); + const auto solid_pixels = read_color(); + if (depth_feedback) { + // Consecutive draws in the same submission must see each earlier depth write. + draw(filled); + draw(filled); + depth.depth_write_enable = false; + draw(filled); // Reset feedback in the same command buffer and preserve the depth writes. + const auto repeated_pixels = read_color(); + const auto stored_depth = ReadCachedTexel( + name, context, depth.image_id, {}, {extent, extent, 1}); + for (uint32_t y = 0; y < extent; y++) { + for (uint32_t x = 0; x < extent; x++) { + const auto index = y * extent + x; + Require(name, "sampled and stored depth", + solid_pixels[index * 4] == std::bit_cast(initial_depth(x, y)) && + repeated_pixels[index * 4] == + std::bit_cast(initial_depth(x, y) + 0.375f) && + stored_depth[index] == repeated_pixels[index * 4], + "depth feedback lost a prior write or the read-only draw changed depth"); + } + } + } else { + auto &wireframe = pipeline(true, 1, 1); + Require(name, "pipeline cache", filled.pipeline != wireframe.pipeline && + pipeline(true, 2, 2).pipeline == filled.pipeline && + pipeline(false, 1, 0).pipeline == filled.pipeline, + "polygon mode was lost from the cache key or dormant face modes were applied"); + draw(wireframe); + const auto line_pixels = read_color(); + const auto interior = 4 * (16 * extent + 16); + Require(name, "GPU coverage", solid_pixels[interior] == 0x3f800000u && + line_pixels[interior] == 0 && + std::ranges::any_of(line_pixels, [](u32 value) { return value == 0x3f800000u; }), + "wireframe did not preserve triangle edges while leaving its interior empty"); + } + resources.UnmapMemory(depth_address, allocation_size); scheduler.Finish(); DestroyBuffer(&buffer); m_device.destroyShaderModule(pixel_shader.module); m_device.destroyShaderModule(vertex_shader.module); + Require(name, "release depth memory", + Libs::LibKernel::Memory::KernelMunmap(depth_address, allocation_size) == 0 && + Libs::LibKernel::Memory::KernelReleaseDirectMemory( + direct_offset, allocation_size) == 0, + "depth mapping or allocation release failed"); std::printf("[gpu] %-32s ok\n", name); } @@ -12810,6 +12937,7 @@ private: m_runtime_context.physical_device_memory_properties = m_memory_properties; m_runtime_context.queue_family = m_queue_family; m_runtime_context.queue = m_queue; + m_runtime_context.attachment_feedback_loop_enabled = true; VmaVulkanFunctions functions{}; functions.vkGetInstanceProcAddr = @@ -12918,9 +13046,13 @@ private: available_clip_control.pNext = &available_depth_clip; vk::PhysicalDeviceColorWriteEnableFeaturesEXT available_color_write{}; available_color_write.pNext = &available_clip_control; + vk::PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT available_feedback_layout{}; + available_feedback_layout.pNext = &available_color_write; + vk::PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT available_feedback_dynamic{}; + available_feedback_dynamic.pNext = &available_feedback_layout; vk::PhysicalDeviceFeatures2 available_features2{}; available_features2.sType = vk::StructureType::ePhysicalDeviceFeatures2; - available_features2.pNext = &available_color_write; + available_features2.pNext = &available_feedback_dynamic; m_physical_device.getFeatures2(&available_features2); Require("VulkanHarness", "dispatch", available_features.shaderStorageImageWriteWithoutFormat == true, @@ -12950,7 +13082,9 @@ private: "vertex layer output is not supported"); Require("VulkanHarness", "graphics", available_features.fillModeNonSolid && available_depth_clip.depthClipEnable && available_clip_control.depthClipControl && - available_color_write.colorWriteEnable, + available_color_write.colorWriteEnable && + available_feedback_layout.attachmentFeedbackLoopLayout && + available_feedback_dynamic.attachmentFeedbackLoopDynamicState, "production rasterization features are not supported"); float priority = 1.0f; @@ -12992,7 +13126,13 @@ private: vk::PhysicalDeviceColorWriteEnableFeaturesEXT color_write{}; color_write.pNext = &clip_control; color_write.colorWriteEnable = true; - device_info.pNext = &color_write; + vk::PhysicalDeviceAttachmentFeedbackLoopLayoutFeaturesEXT feedback_layout{}; + feedback_layout.pNext = &color_write; + feedback_layout.attachmentFeedbackLoopLayout = true; + vk::PhysicalDeviceAttachmentFeedbackLoopDynamicStateFeaturesEXT feedback_dynamic{}; + feedback_dynamic.pNext = &feedback_layout; + feedback_dynamic.attachmentFeedbackLoopDynamicState = true; + device_info.pNext = &feedback_dynamic; vk::PhysicalDeviceFeatures device_features{}; device_features.shaderStorageImageWriteWithoutFormat = true; device_features.shaderImageGatherExtended = true; @@ -13006,7 +13146,9 @@ private: VK_KHR_FRAGMENT_SHADER_BARYCENTRIC_EXTENSION_NAME, VK_EXT_DEPTH_CLIP_ENABLE_EXTENSION_NAME, VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME, - VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME}; + VK_EXT_COLOR_WRITE_ENABLE_EXTENSION_NAME, + VK_EXT_ATTACHMENT_FEEDBACK_LOOP_LAYOUT_EXTENSION_NAME, + VK_EXT_ATTACHMENT_FEEDBACK_LOOP_DYNAMIC_STATE_EXTENSION_NAME}; device_info.enabledExtensionCount = std::size(device_extensions); device_info.ppEnabledExtensionNames = device_extensions; RequireVk("VulkanHarness", "dispatch", @@ -28094,7 +28236,7 @@ int main(int argc, char **argv) { CheckDepthAttachmentWrites(); CheckDynamicRenderingState(); VulkanHarness vulkan; - vulkan.CheckPolygonModeRasterization(); + vulkan.CheckRasterization(false); vulkan.CheckRenderExecutorColor1DDiscovery(); vulkan.CheckRenderExecutorColorVolumeDiscovery(); vulkan.CheckRenderExecutorDccFixedClearFloat(); @@ -28107,7 +28249,7 @@ int main(int argc, char **argv) { } if (argc == 2 && std::strcmp(argv[1], "--polygon-mode-only") == 0) { VulkanHarness vulkan; - vulkan.CheckPolygonModeRasterization(); + vulkan.CheckRasterization(false); return 0; } if (argc == 2 && std::strcmp(argv[1], "--htile-clear-only") == 0) { @@ -28159,6 +28301,7 @@ int main(int argc, char **argv) { CheckSampledDepthResource(); CheckDepthTextureEncoding(); vulkan.CheckComparisonDepthTexture(); + vulkan.CheckRasterization(true); RunCase(nullptr, ImageSampleA16CompareBiasRdna2AddressOrder()); return 0; } @@ -28239,6 +28382,7 @@ int main(int argc, char **argv) { CheckSampledDepthResource(); CheckDepthTextureEncoding(); vulkan.CheckComparisonDepthTexture(); + vulkan.CheckRasterization(true); CheckBasicStorageTextureDescriptor(); CheckStorageTextureLinearUploadLayout(); CheckStorageTextureDepthTileUploadLayout(); @@ -28307,7 +28451,7 @@ int main(int argc, char **argv) { vulkan.CheckRenderExecutorStencilBindingDiscovery(); vulkan.CheckUnifiedTextureCacheFlow(); vulkan.CheckBgra16Readback(); - vulkan.CheckPolygonModeRasterization(); + vulkan.CheckRasterization(false); vulkan.CheckBufferCacheDirtyGarbageCollection(); #endif vulkan.CheckUnifiedImageViewCache();