diff --git a/src/graphics/guest_gpu/graphicsRun.cpp b/src/graphics/guest_gpu/graphicsRun.cpp index 56e671a6f..75b980f51 100644 --- a/src/graphics/guest_gpu/graphicsRun.cpp +++ b/src/graphics/guest_gpu/graphicsRun.cpp @@ -886,7 +886,8 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun EXIT_NOT_IMPLEMENTED((draw_initiator & ~0x20u) != (indexed ? 0u : 2u)); EXIT_NOT_IMPLEMENTED(m_draw_indirect_args_base_addr == 0); // The guest records match Vulkan's indirect commands byte for byte. - EXIT_NOT_IMPLEMENTED(stride_in_bytes < (indexed ? sizeof(vk::DrawIndexedIndirectCommand) + EXIT_NOT_IMPLEMENTED((stride_in_bytes & 3u) != 0 || + stride_in_bytes < (indexed ? sizeof(vk::DrawIndexedIndirectCommand) : sizeof(vk::DrawIndirectCommand))); const auto draw_count = count_addr == nullptr @@ -902,15 +903,14 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun // NUM_INSTANCES keeps the last draw's instance count. m_num_instances_addr = args_addr + static_cast(draw_count - 1) * stride_in_bytes + offsetof(vk::DrawIndirectCommand, instanceCount); - // With no SGPRs to patch, the GPU reads a single indexed draw's arguments if the shaders allow. - if (indexed && draw_count == 1 && m_index_buffer_size != 0 && - registers.vertex_offset == Pm4::SH_NOP && registers.instance_offset == Pm4::SH_NOP && - registers.index_offset == Pm4::SH_NOP && + // With no SGPRs to patch, the GPU reads indexed draws' arguments if the shaders allow. + if (indexed && m_index_buffer_size != 0 && registers.vertex_offset == Pm4::SH_NOP && + registers.instance_offset == Pm4::SH_NOP && registers.index_offset == Pm4::SH_NOP && executor.DrawIndex(m_submit_id, CurrentBuffer(), {.index_count = m_index_buffer_size, .index_addr = IndexAddress(0), .index_type_and_size = m_index_type_and_size, - .indirect_args = args_addr})) { + .indirect = {args_addr, draw_count, stride_in_bytes}})) { return; } diff --git a/src/graphics/host_gpu/renderer/render.h b/src/graphics/host_gpu/renderer/render.h index dc77af671..dbd1a2637 100644 --- a/src/graphics/host_gpu/renderer/render.h +++ b/src/graphics/host_gpu/renderer/render.h @@ -48,6 +48,13 @@ enum class DrawOffsetSource : uint8_t { IndirectArgs, }; +// Nonzero address: the GPU reads count VkDrawIndexedIndirectCommand records, stride bytes apart. +struct DrawIndirectArgs { + uint64_t address = 0; + uint32_t count = 0; + uint32_t stride = 0; +}; + struct DrawIndexArgs { uint32_t index_count = 0; const void* index_addr = nullptr; @@ -57,8 +64,8 @@ struct DrawIndexArgs { uint32_t first_instance = 0; DrawOffsetSource offset_source = DrawOffsetSource::DrawState; uint32_t render_target_slice_offset = 0; - // Nonzero: the GPU reads this VkDrawIndexedIndirectCommand; index_count is INDEX_BUFFER_SIZE. - uint64_t indirect_args = 0; + // With indirect records, index_count is INDEX_BUFFER_SIZE. + DrawIndirectArgs indirect; }; struct DrawAutoArgs { @@ -165,7 +172,7 @@ public: std::span bindings); private: - // False, with nothing drawn, when the shaders cannot take indirect_args from the GPU. + // False, with nothing drawn, when the shaders cannot take indirect records from the GPU. bool DrawIndex(uint64_t submit_id, CommandBuffer& buffer, const DrawIndexArgs& args); void DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const DrawAutoArgs& args); diff --git a/src/graphics/host_gpu/renderer/renderDraw.cpp b/src/graphics/host_gpu/renderer/renderDraw.cpp index 62af7a75b..1843e00fe 100644 --- a/src/graphics/host_gpu/renderer/renderDraw.cpp +++ b/src/graphics/host_gpu/renderer/renderDraw.cpp @@ -632,10 +632,10 @@ bool RenderExecutor::ConsumeMetadataColorOperation(CommandBuffer& buffer, } struct DrawEmitInfo { - int32_t vertex_offset = 0; - uint32_t first_vertex = 0; - uint32_t first_instance = 0; - uint64_t indirect_args = 0; + int32_t vertex_offset = 0; + uint32_t first_vertex = 0; + uint32_t first_instance = 0; + DrawIndirectArgs indirect; }; struct DrawIndexBufferSource { @@ -1190,11 +1190,12 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff vertex_bindings = AcquireVertexBuffers(buffer, state.vertex_info[0]); index_binding = PrepareIndexBuffer(buffer, index_source); } - std::pair indirect {}; - if (emit.indirect_args != 0) { - indirect = m_context.GetBufferCache().ObtainBuffer( - emit.indirect_args, sizeof(vk::DrawIndexedIndirectCommand), false); - EXIT_IF((indirect.second & 3u) != 0); + std::pair args_buffer {}; + if (emit.indirect.address != 0) { + const auto size = static_cast(emit.indirect.count - 1) * emit.indirect.stride + + sizeof(vk::DrawIndexedIndirectCommand); + args_buffer = m_context.GetBufferCache().ObtainBuffer(emit.indirect.address, size, false); + EXIT_IF((args_buffer.second & 3u) != 0); } if (draw.IsIndexed()) { LogDrawPhase(draw.Name(), "CreatePipeline"); @@ -1282,8 +1283,9 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff } if (mesh_active) { vk_buffer.drawMeshTasksEXT(mesh_groups, draw.instance_count, 1); - } else if (indirect.first != nullptr) { - vk_buffer.drawIndexedIndirect(indirect.first->Handle(), indirect.second, 1, 0); + } else if (args_buffer.first != nullptr) { + vk_buffer.drawIndexedIndirect(args_buffer.first->Handle(), args_buffer.second, + emit.indirect.count, emit.indirect.stride); } else { EmitDrawPrimitives(ucfg, vk_buffer, draw, emit); } @@ -1318,7 +1320,7 @@ bool RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer, reinterpret_cast(args.index_addr)); Common::LockGuard lock(m_context.GetMutex()); - if (args.index_count == 0 || (args.instance_count == 0 && args.indirect_args == 0)) { + if (args.index_count == 0 || (args.instance_count == 0 && args.indirect.address == 0)) { return true; } @@ -1401,7 +1403,7 @@ bool RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer, LogDrawStateIfNeeded(buffer, draw, state, args.index_type_and_size, args.index_addr); // Only embedded fetches apply Vulkan's offsets; mesh and quad-list draws need CPU counts. - if (args.indirect_args != 0 && + if (args.indirect.address != 0 && (!state.vertex_info[0].fetch_embedded || state.vertex_info[0].stage.program->stage == ShaderType::Mesh || ucfg.GetPrimType() == Prospero::PrimitiveType::kQuadListLegacy)) { @@ -1419,7 +1421,7 @@ bool RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer, const bool native_indirect = indirect && !state.vertex_info[0].fetch_embedded; emit.vertex_offset = native_indirect ? 0 : vertex_offset + args.base_vertex; emit.first_instance = native_indirect ? 0 : instance_offset; - emit.indirect_args = args.indirect_args; + emit.indirect = args.indirect; ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source, primitive_restart); diff --git a/src/graphics/presentation/window/vulkanWindow.cpp b/src/graphics/presentation/window/vulkanWindow.cpp index 022133464..14f19c0e3 100644 --- a/src/graphics/presentation/window/vulkanWindow.cpp +++ b/src/graphics/presentation/window/vulkanWindow.cpp @@ -330,6 +330,7 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa check_feature(device_features2.features.tessellationShader, "tessellationShader"); check_feature(device_features2.features.drawIndirectFirstInstance, "drawIndirectFirstInstance"); + check_feature(device_features2.features.multiDrawIndirect, "multiDrawIndirect"); if (!skip_device) { auto available_extensions = EnumerateVulkan( @@ -613,6 +614,7 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics, device_features.dualSrcBlend = VK_TRUE; device_features.tessellationShader = VK_TRUE; device_features.drawIndirectFirstInstance = VK_TRUE; + device_features.multiDrawIndirect = VK_TRUE; device_features.sampleRateShading = VK_TRUE; device_features.depthBiasClamp = VK_TRUE; device_features.shaderClipDistance = VK_TRUE; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index d6961c928..9908bce1c 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -18659,9 +18659,9 @@ public: } } - // Embedded fetches take indirect arguments from the GPU copy, whose vertex offset 7 - // selects the covering triangle; the CPU backing holds zeros. Other vertex shaders read - // the arguments on the CPU. + // Embedded fetches take indirect records from the GPU copy: the first draws degenerate + // vertices 0-2, the second's vertex offset 7 selects the covering triangle. The CPU + // backing holds zeros. Other vertex shaders read the records on the CPU. { constexpr uint64_t vertices_address = depth_address + 0x38000; constexpr uint64_t args_address = vertices_address + 0x100; @@ -18670,14 +18670,17 @@ public: std::ranges::copy(triangle, positions.begin() + 28); std::memcpy(reinterpret_cast(vertices_address), positions.data(), sizeof(positions)); - const vk::DrawIndexedIndirectCommand args{3, 1, 0, 7, 5}; - std::memcpy(reinterpret_cast(args_address), &args, sizeof(args)); + constexpr uint32_t stride = 32; + const vk::DrawIndexedIndirectCommand degenerate{3, 1, 0, 0, 5}; + const vk::DrawIndexedIndirectCommand covering{3, 1, 0, 7, 5}; + std::memcpy(reinterpret_cast(args_address), °enerate, sizeof(degenerate)); + std::memcpy(reinterpret_cast(args_address + stride), &covering, sizeof(covering)); // Larger than a caching page, so the arguments are cached instead of streamed. (void)context.GetBufferCache().ObtainBuffer( vertices_address, BufferCache::CACHING_PAGESIZE * 2, false); - const vk::DrawIndexedIndirectCommand zero_args{}; + const std::array zeros{}; Require("GpuIndirectDraw", "CPU backing", - LibKernel::Memory::TryWriteBacking(args_address, &zero_args, sizeof(zero_args)), + LibKernel::Memory::TryWriteBacking(args_address, zeros.data(), sizeof(zeros)), "the CPU copy of the arguments could not be cleared"); static const auto fetch_vertex = [] { std::vector code{EncodeSMovB32(0, InlineU32(0)), EncodeSmem0(0x02u, 20, 4), @@ -18718,7 +18721,7 @@ public: } const DrawIndexArgs indirect{ .index_count = 3, .index_addr = reinterpret_cast(index_address), - .index_type_and_size = 1, .indirect_args = args_address}; + .index_type_and_size = 1, .indirect = {args_address, 2, stride}}; shaders.SetEsShaderBase(fetch_vs); TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), sparse_color.image_id, {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}, {}); @@ -18730,7 +18733,7 @@ public: for (size_t component = 0; component < pixels.size(); component++) { Require("GpuIndirectDraw", "vertex offset", pixels[component] == (component % 4 == 3 ? 0x3f800000u : 0x3e800000u), - "the draw did not take its arguments, vertex offset included, from the GPU"); + "the draw did not take every record, vertex offset included, from the GPU"); } shaders.SetEsShaderBase(indirect_vs); Require("GpuIndirectDraw", "CPU fallback", @@ -20773,6 +20776,7 @@ private: m_rasterization_supported = available_features.fillModeNonSolid && available_features.tessellationShader && available_features.drawIndirectFirstInstance && + available_features.multiDrawIndirect && available_features.depthBounds && available_depth_clip.depthClipEnable && available_clip_control.depthClipControl && @@ -20866,6 +20870,7 @@ private: device_features.fillModeNonSolid = m_rasterization_supported; device_features.tessellationShader = m_rasterization_supported; device_features.drawIndirectFirstInstance = m_rasterization_supported; + device_features.multiDrawIndirect = m_rasterization_supported; device_features.depthBounds = m_rasterization_supported; device_info.pEnabledFeatures = &device_features; std::vector device_extensions{