graphics: draw non-indexed and native-fetch indirect draws from GPU-read records

This commit is contained in:
nmzik
2026-10-22 15:11:39 +02:00
parent 949de19bb0
commit 86fed10b81
4 changed files with 118 additions and 99 deletions
+12 -8
View File
@@ -903,14 +903,18 @@ 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<uint64_t>(draw_count - 1) * stride_in_bytes +
offsetof(vk::DrawIndirectCommand, instanceCount);
// 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_addr, draw_count, stride_in_bytes}})) {
// With no SGPRs to patch, the GPU reads the records if the shaders allow. Shaders apply
// the records' offsets only through those SGPRs; Vulkan applies them, which matches
// while they are zero.
const DrawIndirectArgs records {args_addr, draw_count, stride_in_bytes};
if (registers.vertex_offset == Pm4::SH_NOP && registers.instance_offset == Pm4::SH_NOP &&
registers.index_offset == Pm4::SH_NOP && (!indexed || m_index_buffer_size != 0) &&
(indexed ? 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 = records})
: executor.DrawAuto(m_submit_id, CurrentBuffer(), {.indirect = records}))) {
return;
}
+4 -3
View File
@@ -48,7 +48,7 @@ enum class DrawOffsetSource : uint8_t {
IndirectArgs,
};
// Nonzero address: the GPU reads count VkDrawIndexedIndirectCommand records, stride bytes apart.
// Nonzero address: the GPU reads count Vulkan indirect draw records, stride bytes apart.
struct DrawIndirectArgs {
uint64_t address = 0;
uint32_t count = 0;
@@ -75,6 +75,7 @@ struct DrawAutoArgs {
uint32_t first_instance = 0;
DrawOffsetSource offset_source = DrawOffsetSource::DrawState;
uint32_t render_target_slice_offset = 0;
DrawIndirectArgs indirect;
};
struct SubmitInfo {
@@ -172,9 +173,9 @@ public:
std::span<PreparedBindings* const> bindings);
private:
// False, with nothing drawn, when the shaders cannot take indirect records from the GPU.
// False, with nothing drawn, when the draw's indirect records need CPU counts.
bool DrawIndex(uint64_t submit_id, CommandBuffer& buffer, const DrawIndexArgs& args);
void DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const DrawAutoArgs& args);
bool DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const DrawAutoArgs& args);
struct GraphicsBindings {
std::array<PreparedBindings, 3> vertex;
+63 -58
View File
@@ -631,9 +631,9 @@ bool RenderExecutor::ConsumeMetadataColorOperation(CommandBuffer& buffer,
mode == static_cast<uint8_t>(CbColorMode::DccDecompress);
}
// vertex_offset is the first vertex of non-indexed draws.
struct DrawEmitInfo {
int32_t vertex_offset = 0;
uint32_t first_vertex = 0;
uint32_t first_instance = 0;
DrawIndirectArgs indirect;
};
@@ -1044,8 +1044,8 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
vk_buffer.drawIndexed(draw.index_count, draw.instance_count, 0, emit.vertex_offset,
emit.first_instance);
} else {
vk_buffer.draw(draw.index_count, draw.instance_count, emit.first_vertex,
emit.first_instance);
vk_buffer.draw(draw.index_count, draw.instance_count,
static_cast<uint32_t>(emit.vertex_offset), emit.first_instance);
}
break;
case Prospero::PrimitiveType::kQuadListLegacy:
@@ -1055,7 +1055,8 @@ static void EmitDrawPrimitives(const HW::UserConfig& ucfg, vk::CommandBuffer vk_
vk_buffer.drawIndexed(4, draw.instance_count, i, emit.vertex_offset,
emit.first_instance);
} else {
vk_buffer.draw(4, draw.instance_count, i + emit.first_vertex,
vk_buffer.draw(4, draw.instance_count,
i + static_cast<uint32_t>(emit.vertex_offset),
emit.first_instance);
}
}
@@ -1076,7 +1077,7 @@ bool RenderExecutor::CopyDepthToColor(CommandBuffer& buffer, const DrawCallInfo&
EXIT_NOT_IMPLEMENTED(!m_context.GetGraphics().maintenance8_enabled ||
!Prospero::IsRectList(ucfg.GetPrimType()) ||
draw.IsIndexed() || draw.index_count != 3 || draw.instance_count != 1 ||
emit.first_vertex != 0 || emit.first_instance != 0 ||
emit.vertex_offset != 0 || emit.first_instance != 0 ||
state.color_count != 1 || color.target_slot != 0 || !depth.image_id ||
color.desc.info.samples != 1 ||
depth.desc.info.extent.width != color.Extent().width ||
@@ -1193,7 +1194,8 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
std::pair<Buffer*, uint64_t> args_buffer {};
if (emit.indirect.address != 0) {
const auto size = static_cast<uint64_t>(emit.indirect.count - 1) * emit.indirect.stride +
sizeof(vk::DrawIndexedIndirectCommand);
(draw.IsIndexed() ? sizeof(vk::DrawIndexedIndirectCommand)
: sizeof(vk::DrawIndirectCommand));
args_buffer = m_context.GetBufferCache().ObtainBuffer(emit.indirect.address, size, false);
EXIT_IF((args_buffer.second & 3u) != 0);
}
@@ -1233,12 +1235,12 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
if (draw_dwords != 0) {
std::array<uint32_t, PushData::MeshDrawDwordCount> draw_data {};
if (mesh_active) {
draw_data = {
draw.index_count,
draw.IsIndexed() ? static_cast<uint32_t>(emit.vertex_offset) : emit.first_vertex,
emit.first_instance, index_source.guest_element_size,
static_cast<uint32_t>(index_source.address),
static_cast<uint32_t>(index_source.address >> 32u)};
draw_data = {draw.index_count,
static_cast<uint32_t>(emit.vertex_offset),
emit.first_instance,
index_source.guest_element_size,
static_cast<uint32_t>(index_source.address),
static_cast<uint32_t>(index_source.address >> 32u)};
} else {
draw_data[0] = buffer.GetUserConfig().GetObjectId();
}
@@ -1283,11 +1285,14 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
}
if (mesh_active) {
vk_buffer.drawMeshTasksEXT(mesh_groups, draw.instance_count, 1);
} else if (args_buffer.first != nullptr) {
} else if (args_buffer.first == nullptr) {
EmitDrawPrimitives(ucfg, vk_buffer, draw, emit);
} else if (draw.IsIndexed()) {
vk_buffer.drawIndexedIndirect(args_buffer.first->Handle(), args_buffer.second,
emit.indirect.count, emit.indirect.stride);
} else {
EmitDrawPrimitives(ucfg, vk_buffer, draw, emit);
vk_buffer.drawIndirect(args_buffer.first->Handle(), args_buffer.second,
emit.indirect.count, emit.indirect.stride);
}
Common::DebugCounters::Add(Common::DebugCounters::Counter::Draws);
Common::DebugCounters::Add(Common::DebugCounters::Counter::DrawInstances, draw.instance_count);
@@ -1305,6 +1310,29 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
}
}
// Vulkan offsets for a prepared draw, or nullopt when its indirect records need CPU counts, as
// mesh and quad-list draws do. Indirect records carry their own offsets.
static std::optional<DrawEmitInfo> ResolveDrawEmit(const HW::UserConfig& ucfg,
const ShaderVertexInputInfo& vs,
DrawOffsetSource source,
int32_t first_vertex,
uint32_t first_instance,
const DrawIndirectArgs& indirect) {
if (indirect.address != 0) {
if (vs.stage.program->stage == ShaderType::Mesh ||
ucfg.GetPrimType() == Prospero::PrimitiveType::kQuadListLegacy) {
return std::nullopt;
}
return DrawEmitInfo {.indirect = indirect};
}
if (source == DrawOffsetSource::IndirectArgs) {
// Native fetches consume the patched SGPRs; rewritten fetches need Vulkan offsets.
return vs.fetch_embedded ? DrawEmitInfo {first_vertex, first_instance} : DrawEmitInfo {};
}
const auto [vertex_offset, instance_offset] = ResolveDrawOffsets(ucfg.GetIndexOffset(), vs);
return DrawEmitInfo {vertex_offset + first_vertex, instance_offset};
}
bool RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
const DrawIndexArgs& args) {
KYTY_PROFILER_FUNCTION();
@@ -1402,35 +1430,18 @@ 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.address != 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 auto emit = ResolveDrawEmit(ucfg, state.vertex_info[0], args.offset_source,
args.base_vertex, args.first_instance, args.indirect);
if (emit) {
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, *emit, index_source,
primitive_restart);
}
const bool indirect = args.offset_source == DrawOffsetSource::IndirectArgs;
const auto [vertex_offset, instance_offset] =
indirect ? std::pair<int32_t, uint32_t> {0, args.first_instance}
: ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vertex_info[0]);
DrawEmitInfo emit {};
// Native fetches consume the patched SGPRs; rewritten fetches need Vulkan offsets.
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.indirect;
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source,
primitive_restart);
ResetBindings();
return true;
return emit.has_value();
}
// NOLINTNEXTLINE(readability-function-cognitive-complexity)
void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const DrawAutoArgs& args) {
bool RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const DrawAutoArgs& args) {
KYTY_PROFILER_FUNCTION();
EXIT_IF(buffer.IsInvalid());
@@ -1444,19 +1455,19 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
args.first_instance);
Common::LockGuard lock(m_context.GetMutex());
if (args.vertex_count == 0 || args.instance_count == 0) {
return;
if (args.indirect.address == 0 && (args.vertex_count == 0 || args.instance_count == 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()) {
@@ -1482,12 +1493,12 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
if (!GetDrawTopology(ucfg, topology)) {
ResetBindings();
return;
return true;
}
DrawRenderState state {};
if (!PrepareDrawRenderState(buffer, draw, args.render_target_slice_offset, state)) {
ResetBindings();
return;
return true;
}
const bool rect_list = Prospero::IsRectList(ucfg.GetPrimType());
@@ -1501,24 +1512,18 @@ void RenderExecutor::DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const D
sh_ctx.GetVs().es_regs.data_addr, sh_ctx.GetVs().gs_regs.data_addr);
}
ResetBindings();
return;
return true;
}
LogDrawStateIfNeeded(buffer, draw, state, 0, nullptr);
const bool indirect = args.offset_source == DrawOffsetSource::IndirectArgs;
const auto [vertex_offset, instance_offset] =
indirect ? std::pair<int32_t, uint32_t> {0, args.first_instance}
: ResolveDrawOffsets(ucfg.GetIndexOffset(), state.vertex_info[0]);
DrawEmitInfo emit {};
const bool native_indirect = indirect && !state.vertex_info[0].fetch_embedded;
emit.first_vertex = native_indirect ? 0 :
static_cast<uint32_t>(vertex_offset + static_cast<int32_t>(args.first_vertex));
emit.first_instance = native_indirect ? 0 : instance_offset;
DrawIndexBufferSource index_source {};
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source, false);
const auto emit = ResolveDrawEmit(ucfg, state.vertex_info[0], args.offset_source,
static_cast<int32_t>(args.first_vertex), args.first_instance,
args.indirect);
if (emit) {
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, *emit, {}, false);
}
ResetBindings();
return emit.has_value();
}
bool RenderExecutor::ResolveColorTargets(CommandBuffer& buffer, uint32_t render_target_slice_offset) {