diff --git a/src/graphics/guest_gpu/command_processor/commandProcessor.h b/src/graphics/guest_gpu/command_processor/commandProcessor.h index 844acf290..1eb1d0218 100644 --- a/src/graphics/guest_gpu/command_processor/commandProcessor.h +++ b/src/graphics/guest_gpu/command_processor/commandProcessor.h @@ -154,6 +154,8 @@ private: uint32_t interrupt_context_id); void ProcessPm4(Pm4Execution& execution); void SuspendPm4(); + uint32_t NumInstances(); + const void* IndexAddress(uint32_t first_index) const; CommandScheduler& GetScheduler() const { return m_renderer.GetCommandScheduler(); } CommandBuffer& CurrentBuffer() { return GetScheduler().Current(); } @@ -169,8 +171,10 @@ private: uint64_t m_index_base_addr = 0; uint64_t m_draw_indirect_args_base_addr = 0; uint64_t m_dispatch_indirect_args_base_addr = 0; - // Persistent draw state: indirect draws update it for subsequent draws. - uint32_t m_num_instances = 1; + // Persistent draw state. Indirect draws leave the address of their last instance count, + // read only when a later draw inherits it. + uint32_t m_num_instances = 1; + uint64_t m_num_instances_addr = 0; uint32_t m_de_count = 0; uint32_t m_ce_count = 0; diff --git a/src/graphics/guest_gpu/graphicsRun.cpp b/src/graphics/guest_gpu/graphicsRun.cpp index 19e270c6b..56e671a6f 100644 --- a/src/graphics/guest_gpu/graphicsRun.cpp +++ b/src/graphics/guest_gpu/graphicsRun.cpp @@ -39,21 +39,6 @@ static thread_local Pm4Execution* g_current_execution = nullptr; static thread_local bool g_gpu_thread = false; static thread_local GuestGpu* g_gpu_state = nullptr; -struct DrawIndirectArgs { - uint32_t vertex_count_per_instance; - uint32_t instance_count; - uint32_t start_vertex_location; - uint32_t start_instance_location; -}; - -struct DrawIndexedIndirectArgs { - uint32_t index_count_per_instance; - uint32_t instance_count; - uint32_t start_index_location; - uint32_t base_vertex_location; - uint32_t start_instance_location; -}; - static bool GraphicsRunDebugDumpEnabled() { return Config::GraphicsDebugDumpEnabled() && Config::GetPrintfDirection() != Config::LogDirection::Silent; @@ -206,6 +191,7 @@ void CommandProcessor::Reset() { m_index_buffer_size = 0; m_index_base_addr = 0; m_num_instances = 1; + m_num_instances_addr = 0; m_predicate_skip = false; m_user_data_marker = HW::UserSgprType::Unknown; m_draw_indirect_args_base_addr = 0; @@ -784,7 +770,16 @@ void CommandProcessor::SetNumInstances(uint32_t num_instances) { num_instances = 1; } - m_num_instances = num_instances; + m_num_instances = num_instances; + m_num_instances_addr = 0; +} + +uint32_t CommandProcessor::NumInstances() { + if (m_num_instances_addr != 0) { + m_num_instances = *reinterpret_cast(m_num_instances_addr); + m_num_instances_addr = 0; + } + return m_num_instances; } void CommandProcessor::SetPredication(uint32_t condition, uint32_t op, uint32_t wait_op, @@ -841,16 +836,12 @@ void CommandProcessor::SetPredication(uint32_t condition, uint32_t op, uint32_t void CommandProcessor::DrawIndex(DrawIndexArgs args) { args.index_type_and_size = m_index_type_and_size; if (args.instance_count == 0) { - args.instance_count = m_num_instances; - } - if (GraphicsRunDebugDumpEnabled() && (args.base_vertex != 0 || args.first_instance != 0)) { - LOGF("\t draw indexed offsets: base_vertex = %" PRId32 ", first_instance = %" PRIu32 "\n", - args.base_vertex, args.first_instance); + args.instance_count = NumInstances(); } m_renderer.GetRenderExecutor().DrawIndex(m_submit_id, CurrentBuffer(), args); } -void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_count) { +const void* CommandProcessor::IndexAddress(uint32_t first_index) const { uint64_t index_size = 0; switch (m_index_type_and_size) { case 0: index_size = 2; break; @@ -858,11 +849,11 @@ void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_cou case 2: index_size = 1; break; default: EXIT("unknown index_type_and_size: %u\n", m_index_type_and_size); } + return reinterpret_cast(m_index_base_addr + first_index * index_size); +} - auto* index_addr = reinterpret_cast( - m_index_base_addr + static_cast(index_offset) * index_size); - - DrawIndex({.index_count = index_count, .index_addr = index_addr}); +void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_count) { + DrawIndex({.index_count = index_count, .index_addr = IndexAddress(index_offset)}); } static void PatchIndirectDrawOffsets(CommandProcessor& cp, IndirectDrawRegisters registers, @@ -883,7 +874,8 @@ static void PatchIndirectDrawOffsets(CommandProcessor& cp, IndirectDrawRegisters void CommandProcessor::DrawIndirect(uint32_t data_offset, IndirectDrawRegisters registers, uint32_t draw_initiator, bool indexed) { - const auto args_size = indexed ? sizeof(DrawIndexedIndirectArgs) : sizeof(DrawIndirectArgs); + const auto args_size = + indexed ? sizeof(vk::DrawIndexedIndirectCommand) : sizeof(vk::DrawIndirectCommand); DrawIndirectMulti(data_offset, 1, nullptr, args_size, registers, draw_initiator, indexed); } @@ -893,77 +885,61 @@ void CommandProcessor::DrawIndirectMulti(uint32_t data_offset, uint32_t max_coun uint32_t draw_initiator, bool indexed) { 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) + : sizeof(vk::DrawIndirectCommand))); - uint32_t draw_count = max_count_or_count; - if (count_addr != nullptr) { - draw_count = *count_addr; - if (draw_count > max_count_or_count) { - draw_count = max_count_or_count; - } - } - + const auto draw_count = count_addr == nullptr + ? max_count_or_count + : std::min(uint32_t {*count_addr}, max_count_or_count); if (draw_count == 0) { return; } Common::DebugCounters::Add(Common::DebugCounters::Counter::IndirectDraws, draw_count); - const auto args_size = indexed ? sizeof(DrawIndexedIndirectArgs) : sizeof(DrawIndirectArgs); - EXIT_NOT_IMPLEMENTED(stride_in_bytes < args_size); - - uint64_t index_size = 0; - if (indexed) { - switch (m_index_type_and_size) { - case 0: index_size = 2; break; - case 1: index_size = 4; break; - case 2: index_size = 1; break; - default: EXIT("unknown index_type_and_size: %u\n", m_index_type_and_size); - } + auto& executor = m_renderer.GetRenderExecutor(); + const auto args_addr = m_draw_indirect_args_base_addr + data_offset; + // 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 && + 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})) { + return; } + const uint32_t max_indices = m_index_buffer_size != 0 ? m_index_buffer_size : UINT32_MAX; for (uint32_t i = 0; i < draw_count; i++) { - const auto args_addr = m_draw_indirect_args_base_addr + data_offset + - static_cast(i) * stride_in_bytes; - + const auto addr = args_addr + static_cast(i) * stride_in_bytes; if (!indexed) { - auto* args = reinterpret_cast(args_addr); - PatchIndirectDrawOffsets(*this, registers, args->start_vertex_location, - args->start_instance_location, 0); - m_num_instances = args->instance_count; - DrawIndexAuto({.vertex_count = args->vertex_count_per_instance, - .instance_count = args->instance_count, - .first_vertex = args->start_vertex_location, - .first_instance = args->start_instance_location, - .offset_source = DrawOffsetSource::IndirectArgs}); + const auto& args = *reinterpret_cast(addr); + PatchIndirectDrawOffsets(*this, registers, args.firstVertex, args.firstInstance, 0); + executor.DrawAuto(m_submit_id, CurrentBuffer(), + {.vertex_count = args.vertexCount, + .instance_count = args.instanceCount, + .first_vertex = args.firstVertex, + .first_instance = args.firstInstance, + .offset_source = DrawOffsetSource::IndirectArgs}); continue; } - auto* args = reinterpret_cast(args_addr); - PatchIndirectDrawOffsets(*this, registers, args->base_vertex_location, - args->start_instance_location, args->start_index_location); - - auto* index_addr = reinterpret_cast( - m_index_base_addr + static_cast(args->start_index_location) * index_size); - - const uint32_t index_count = - (m_index_buffer_size != 0 - ? std::min(args->index_count_per_instance, m_index_buffer_size) - : args->index_count_per_instance); - if (GraphicsRunDebugDumpEnabled() && index_count != args->index_count_per_instance) { - static std::atomic log_count {0}; - if (log_count.fetch_add(1, std::memory_order_relaxed) < 64) { - LOGF("\t DrawIndexIndirectMulti: clamped index_count from %" PRIu32 " to %" PRIu32 - " using INDEX_BUFFER_SIZE\n", - args->index_count_per_instance, index_count); - } - } - - m_num_instances = args->instance_count; - DrawIndex({.index_count = index_count, - .index_addr = index_addr, - .instance_count = args->instance_count, - .base_vertex = static_cast(args->base_vertex_location), - .first_instance = args->start_instance_location, - .offset_source = DrawOffsetSource::IndirectArgs}); + const auto& args = *reinterpret_cast(addr); + PatchIndirectDrawOffsets(*this, registers, static_cast(args.vertexOffset), + args.firstInstance, args.firstIndex); + executor.DrawIndex(m_submit_id, CurrentBuffer(), + {.index_count = std::min(args.indexCount, max_indices), + .index_addr = IndexAddress(args.firstIndex), + .instance_count = args.instanceCount, + .index_type_and_size = m_index_type_and_size, + .base_vertex = args.vertexOffset, + .first_instance = args.firstInstance, + .offset_source = DrawOffsetSource::IndirectArgs}); } } @@ -1012,7 +988,7 @@ void CommandProcessor::DispatchIndirect(uint64_t args_addr, uint32_t mode) { void CommandProcessor::DrawIndexAuto(DrawAutoArgs args) { if (args.instance_count == 0) { - args.instance_count = m_num_instances; + args.instance_count = NumInstances(); } m_renderer.GetRenderExecutor().DrawAuto(m_submit_id, CurrentBuffer(), args); } diff --git a/src/graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.cpp b/src/graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.cpp index 4a9508886..18d030b0f 100644 --- a/src/graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.cpp +++ b/src/graphics/host_gpu/renderer/pipeline/shaderResourceBarrier.cpp @@ -52,6 +52,7 @@ vk::MemoryBarrier MakeShaderWriteDependency() { barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite | vk::AccessFlagBits::eVertexAttributeRead | vk::AccessFlagBits::eIndexRead | vk::AccessFlagBits::eUniformRead | + vk::AccessFlagBits::eIndirectCommandRead | vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eTransferWrite | vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite; diff --git a/src/graphics/host_gpu/renderer/render.h b/src/graphics/host_gpu/renderer/render.h index d0bc8ba53..dc77af671 100644 --- a/src/graphics/host_gpu/renderer/render.h +++ b/src/graphics/host_gpu/renderer/render.h @@ -57,6 +57,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; }; struct DrawAutoArgs { @@ -163,7 +165,8 @@ public: std::span bindings); private: - void DrawIndex(uint64_t submit_id, CommandBuffer& buffer, const DrawIndexArgs& args); + // False, with nothing drawn, when the shaders cannot take indirect_args 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); struct GraphicsBindings { diff --git a/src/graphics/host_gpu/renderer/renderDraw.cpp b/src/graphics/host_gpu/renderer/renderDraw.cpp index 475711c8d..62af7a75b 100644 --- a/src/graphics/host_gpu/renderer/renderDraw.cpp +++ b/src/graphics/host_gpu/renderer/renderDraw.cpp @@ -635,6 +635,7 @@ struct DrawEmitInfo { int32_t vertex_offset = 0; uint32_t first_vertex = 0; uint32_t first_instance = 0; + uint64_t indirect_args = 0; }; struct DrawIndexBufferSource { @@ -1189,6 +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); + } if (draw.IsIndexed()) { LogDrawPhase(draw.Name(), "CreatePipeline"); } @@ -1275,6 +1282,8 @@ 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 { EmitDrawPrimitives(ucfg, vk_buffer, draw, emit); } @@ -1294,7 +1303,7 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff } } -void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer, +bool RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer, const DrawIndexArgs& args) { KYTY_PROFILER_FUNCTION(); @@ -1309,19 +1318,19 @@ void 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) { - return; + if (args.index_count == 0 || (args.instance_count == 0 && args.indirect_args == 0)) { + return true; } if (ConsumeMetadataColorOperation(buffer, args.render_target_slice_offset) || DepthStencilCopy(buffer) || ResolveColorTargets(buffer, args.render_target_slice_offset)) { ResetBindings(); - return; + return true; } if (!DrawHasValidVertexShader(sh_ctx)) { - return; + return true; } if (graphics_debug_dump_enabled()) { @@ -1347,7 +1356,7 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer, vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList; if (!GetDrawTopology(ucfg, topology)) { - return; + return true; } DrawIndexBufferSource index_source {}; @@ -1386,11 +1395,19 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer, DrawRenderState state {}; if (!PrepareDrawRenderState(buffer, draw, args.render_target_slice_offset, state)) { ResetBindings(); - return; + return true; } 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 && + (!state.vertex_info[0].fetch_embedded || + state.vertex_info[0].stage.program->stage == ShaderType::Mesh || + ucfg.GetPrimType() == Prospero::PrimitiveType::kQuadListLegacy)) { + ResetBindings(); + return false; + } const bool indirect = args.offset_source == DrawOffsetSource::IndirectArgs; const auto [vertex_offset, instance_offset] = @@ -1402,10 +1419,12 @@ void 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; ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source, primitive_restart); ResetBindings(); + return true; } // NOLINTNEXTLINE(readability-function-cognitive-complexity) diff --git a/src/graphics/presentation/window/vulkanWindow.cpp b/src/graphics/presentation/window/vulkanWindow.cpp index 4b698159f..022133464 100644 --- a/src/graphics/presentation/window/vulkanWindow.cpp +++ b/src/graphics/presentation/window/vulkanWindow.cpp @@ -328,6 +328,8 @@ static void VulkanFindPhysicalDevice(vk::Instance instance, vk::SurfaceKHR surfa check_feature(device_features2.features.independentBlend, "independentBlend"); check_feature(device_features2.features.tessellationShader, "tessellationShader"); + check_feature(device_features2.features.drawIndirectFirstInstance, + "drawIndirectFirstInstance"); if (!skip_device) { auto available_extensions = EnumerateVulkan( @@ -610,6 +612,7 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics, device_features.independentBlend = VK_TRUE; device_features.dualSrcBlend = VK_TRUE; device_features.tessellationShader = VK_TRUE; + device_features.drawIndirectFirstInstance = 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 c0088940a..d6961c928 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -399,9 +399,9 @@ struct TextureCacheTestAccess { }; struct RenderExecutorTestAccess { - static void DrawIndex(RenderExecutor &executor, CommandBuffer &command, + static bool DrawIndex(RenderExecutor &executor, CommandBuffer &command, const DrawIndexArgs &args) { - executor.DrawIndex(0, command, args); + return executor.DrawIndex(0, command, args); } static void DrawAuto(RenderExecutor &executor, CommandBuffer &command, @@ -18659,6 +18659,85 @@ 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. + { + constexpr uint64_t vertices_address = depth_address + 0x38000; + constexpr uint64_t args_address = vertices_address + 0x100; + std::array positions{}; + constexpr std::array triangle{-1, -1, 0, 1, 3, -1, 0, 1, -1, 3, 0, 1}; + 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)); + // 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{}; + Require("GpuIndirectDraw", "CPU backing", + LibKernel::Memory::TryWriteBacking(args_address, &zero_args, sizeof(zero_args)), + "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), + EncodeSmem1(0), EncodeVop2(0x01u, 0, Vgpr(8), 5), + EncodeMubuf0(0x03u, 0, true), EncodeMubuf1(9, 5, 0), + EncodeExp0(0x0c, 0xf), EncodeExp1(9, 10, 11, 12)}; + code.insert(code.end(), std::ranges::find(native_vertex, EncodeExp0(0x20, 0xf)), + native_vertex.end()); + return code; + }(); + static std::array(AgcDirectResourceType::Last) + 1> + fetch_offsets; + fetch_offsets.fill(AGC_ILLEGAL_DIRECT_OFFSET); + fetch_offsets[static_cast(AgcDirectResourceType::PtrVertexBufferTable)] = 0; + fetch_offsets[static_cast(AgcDirectResourceType::PtrVertexAttribDescTable)] = 2; + static ShaderUserData fetch_user_data{ + .direct_resource_offset = fetch_offsets.data(), + .direct_resource_count = static_cast(fetch_offsets.size())}; + static ShaderSemantic semantic{.hardware_mapping = 9, .size_in_elements = 4}; + static const uint32_t vertex_buffer[]{ + static_cast(vertices_address), + static_cast(vertices_address >> 32u) | (16u << 16u), 10, + DstSel(4, 5, 6, 7) | + (static_cast(Prospero::BufferFormat::k32_32_32_32Float) << 12u)}; + static const uint32_t attribute = + static_cast(Prospero::VertexAttribFormat::k32_32_32_32Float) << 5u; + const auto fetch_vs = reinterpret_cast(fetch_vertex.data()); + ShaderMapUserData(fetch_vs, + {.type = Prospero::ShaderBinaryType::kGs, .user_data = &fetch_user_data, + .input_semantics = &semantic, .num_input_semantics = 1, + .code_size_bytes = static_cast(fetch_vertex.size() * sizeof(u32))}); + const uint64_t tables[]{reinterpret_cast(vertex_buffer), + reinterpret_cast(&attribute)}; + shaders.SetGsShaderResource2({.user_sgpr = 4}); + for (u32 i = 0; i < 4; i++) { + shaders.SetGsUserSgpr(i, static_cast(tables[i / 2] >> (i % 2 * 32)), + HW::UserSgprType::Unknown); + } + const DrawIndexArgs indirect{ + .index_count = 3, .index_addr = reinterpret_cast(index_address), + .index_type_and_size = 1, .indirect_args = args_address}; + shaders.SetEsShaderBase(fetch_vs); + TextureCacheTestAccess::ClearImage(cache, scheduler.Current(), sparse_color.image_id, + {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}, {}); + Require("GpuIndirectDraw", "embedded fetch", + RenderExecutorTestAccess::DrawIndex(executor, scheduler.Current(), indirect), + "an embedded-fetch indirect draw fell back to CPU arguments"); + const auto pixels = ReadCachedTexel(name, context, sparse_color.image_id, + {}, {extent, extent, 1}); + 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"); + } + shaders.SetEsShaderBase(indirect_vs); + Require("GpuIndirectDraw", "CPU fallback", + !RenderExecutorTestAccess::DrawIndex(executor, scheduler.Current(), indirect), + "GPU-read arguments reached a shader without embedded fetch"); + } + // A fourth VS invocation reads beyond the descriptor instead of reconstructing the corner. const ShaderBufferResource rect_buffer{{ static_cast(rect_address), @@ -20693,6 +20772,7 @@ private: "vertex layer output is not supported"); m_rasterization_supported = available_features.fillModeNonSolid && available_features.tessellationShader && + available_features.drawIndirectFirstInstance && available_features.depthBounds && available_depth_clip.depthClipEnable && available_clip_control.depthClipControl && @@ -20785,6 +20865,7 @@ private: device_features.shaderFloat64 = available_features.shaderFloat64; device_features.fillModeNonSolid = m_rasterization_supported; device_features.tessellationShader = m_rasterization_supported; + device_features.drawIndirectFirstInstance = m_rasterization_supported; device_features.depthBounds = m_rasterization_supported; device_info.pEnabledFeatures = &device_features; std::vector device_extensions{