graphics: draw single indexed indirect draws from GPU-read arguments

This commit is contained in:
nmzik
2026-10-22 13:36:24 +02:00
parent b54057942d
commit c513a4a13a
7 changed files with 185 additions and 98 deletions
@@ -154,6 +154,8 @@ private:
uint32_t interrupt_context_id); uint32_t interrupt_context_id);
void ProcessPm4(Pm4Execution& execution); void ProcessPm4(Pm4Execution& execution);
void SuspendPm4(); void SuspendPm4();
uint32_t NumInstances();
const void* IndexAddress(uint32_t first_index) const;
CommandScheduler& GetScheduler() const { return m_renderer.GetCommandScheduler(); } CommandScheduler& GetScheduler() const { return m_renderer.GetCommandScheduler(); }
CommandBuffer& CurrentBuffer() { return GetScheduler().Current(); } CommandBuffer& CurrentBuffer() { return GetScheduler().Current(); }
@@ -169,8 +171,10 @@ private:
uint64_t m_index_base_addr = 0; uint64_t m_index_base_addr = 0;
uint64_t m_draw_indirect_args_base_addr = 0; uint64_t m_draw_indirect_args_base_addr = 0;
uint64_t m_dispatch_indirect_args_base_addr = 0; uint64_t m_dispatch_indirect_args_base_addr = 0;
// Persistent draw state: indirect draws update it for subsequent draws. // Persistent draw state. Indirect draws leave the address of their last instance count,
uint32_t m_num_instances = 1; // 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_de_count = 0;
uint32_t m_ce_count = 0; uint32_t m_ce_count = 0;
+62 -86
View File
@@ -39,21 +39,6 @@ static thread_local Pm4Execution* g_current_execution = nullptr;
static thread_local bool g_gpu_thread = false; static thread_local bool g_gpu_thread = false;
static thread_local GuestGpu* g_gpu_state = nullptr; 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() { static bool GraphicsRunDebugDumpEnabled() {
return Config::GraphicsDebugDumpEnabled() && return Config::GraphicsDebugDumpEnabled() &&
Config::GetPrintfDirection() != Config::LogDirection::Silent; Config::GetPrintfDirection() != Config::LogDirection::Silent;
@@ -206,6 +191,7 @@ void CommandProcessor::Reset() {
m_index_buffer_size = 0; m_index_buffer_size = 0;
m_index_base_addr = 0; m_index_base_addr = 0;
m_num_instances = 1; m_num_instances = 1;
m_num_instances_addr = 0;
m_predicate_skip = false; m_predicate_skip = false;
m_user_data_marker = HW::UserSgprType::Unknown; m_user_data_marker = HW::UserSgprType::Unknown;
m_draw_indirect_args_base_addr = 0; m_draw_indirect_args_base_addr = 0;
@@ -784,7 +770,16 @@ void CommandProcessor::SetNumInstances(uint32_t num_instances) {
num_instances = 1; 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<const uint32_t*>(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, 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) { void CommandProcessor::DrawIndex(DrawIndexArgs args) {
args.index_type_and_size = m_index_type_and_size; args.index_type_and_size = m_index_type_and_size;
if (args.instance_count == 0) { if (args.instance_count == 0) {
args.instance_count = m_num_instances; args.instance_count = NumInstances();
}
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);
} }
m_renderer.GetRenderExecutor().DrawIndex(m_submit_id, CurrentBuffer(), args); 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; uint64_t index_size = 0;
switch (m_index_type_and_size) { switch (m_index_type_and_size) {
case 0: index_size = 2; break; 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; case 2: index_size = 1; break;
default: EXIT("unknown index_type_and_size: %u\n", m_index_type_and_size); default: EXIT("unknown index_type_and_size: %u\n", m_index_type_and_size);
} }
return reinterpret_cast<const void*>(m_index_base_addr + first_index * index_size);
}
auto* index_addr = reinterpret_cast<const void*>( void CommandProcessor::DrawIndexOffset(uint32_t index_offset, uint32_t index_count) {
m_index_base_addr + static_cast<uint64_t>(index_offset) * index_size); DrawIndex({.index_count = index_count, .index_addr = IndexAddress(index_offset)});
DrawIndex({.index_count = index_count, .index_addr = index_addr});
} }
static void PatchIndirectDrawOffsets(CommandProcessor& cp, IndirectDrawRegisters registers, 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, void CommandProcessor::DrawIndirect(uint32_t data_offset, IndirectDrawRegisters registers,
uint32_t draw_initiator, bool indexed) { 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); 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) { uint32_t draw_initiator, bool indexed) {
EXIT_NOT_IMPLEMENTED((draw_initiator & ~0x20u) != (indexed ? 0u : 2u)); EXIT_NOT_IMPLEMENTED((draw_initiator & ~0x20u) != (indexed ? 0u : 2u));
EXIT_NOT_IMPLEMENTED(m_draw_indirect_args_base_addr == 0); 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; const auto draw_count = count_addr == nullptr
if (count_addr != nullptr) { ? max_count_or_count
draw_count = *count_addr; : std::min(uint32_t {*count_addr}, max_count_or_count);
if (draw_count > max_count_or_count) {
draw_count = max_count_or_count;
}
}
if (draw_count == 0) { if (draw_count == 0) {
return; return;
} }
Common::DebugCounters::Add(Common::DebugCounters::Counter::IndirectDraws, draw_count); Common::DebugCounters::Add(Common::DebugCounters::Counter::IndirectDraws, draw_count);
const auto args_size = indexed ? sizeof(DrawIndexedIndirectArgs) : sizeof(DrawIndirectArgs); auto& executor = m_renderer.GetRenderExecutor();
EXIT_NOT_IMPLEMENTED(stride_in_bytes < args_size); const auto args_addr = m_draw_indirect_args_base_addr + data_offset;
// NUM_INSTANCES keeps the last draw's instance count.
uint64_t index_size = 0; m_num_instances_addr = args_addr + static_cast<uint64_t>(draw_count - 1) * stride_in_bytes +
if (indexed) { offsetof(vk::DrawIndirectCommand, instanceCount);
switch (m_index_type_and_size) { // With no SGPRs to patch, the GPU reads a single indexed draw's arguments if the shaders allow.
case 0: index_size = 2; break; if (indexed && draw_count == 1 && m_index_buffer_size != 0 &&
case 1: index_size = 4; break; registers.vertex_offset == Pm4::SH_NOP && registers.instance_offset == Pm4::SH_NOP &&
case 2: index_size = 1; break; registers.index_offset == Pm4::SH_NOP &&
default: EXIT("unknown index_type_and_size: %u\n", m_index_type_and_size); 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++) { for (uint32_t i = 0; i < draw_count; i++) {
const auto args_addr = m_draw_indirect_args_base_addr + data_offset + const auto addr = args_addr + static_cast<uint64_t>(i) * stride_in_bytes;
static_cast<uint64_t>(i) * stride_in_bytes;
if (!indexed) { if (!indexed) {
auto* args = reinterpret_cast<const DrawIndirectArgs*>(args_addr); const auto& args = *reinterpret_cast<const vk::DrawIndirectCommand*>(addr);
PatchIndirectDrawOffsets(*this, registers, args->start_vertex_location, PatchIndirectDrawOffsets(*this, registers, args.firstVertex, args.firstInstance, 0);
args->start_instance_location, 0); executor.DrawAuto(m_submit_id, CurrentBuffer(),
m_num_instances = args->instance_count; {.vertex_count = args.vertexCount,
DrawIndexAuto({.vertex_count = args->vertex_count_per_instance, .instance_count = args.instanceCount,
.instance_count = args->instance_count, .first_vertex = args.firstVertex,
.first_vertex = args->start_vertex_location, .first_instance = args.firstInstance,
.first_instance = args->start_instance_location, .offset_source = DrawOffsetSource::IndirectArgs});
.offset_source = DrawOffsetSource::IndirectArgs});
continue; continue;
} }
auto* args = reinterpret_cast<const DrawIndexedIndirectArgs*>(args_addr); const auto& args = *reinterpret_cast<const vk::DrawIndexedIndirectCommand*>(addr);
PatchIndirectDrawOffsets(*this, registers, args->base_vertex_location, PatchIndirectDrawOffsets(*this, registers, static_cast<uint32_t>(args.vertexOffset),
args->start_instance_location, args->start_index_location); args.firstInstance, args.firstIndex);
executor.DrawIndex(m_submit_id, CurrentBuffer(),
auto* index_addr = reinterpret_cast<const void*>( {.index_count = std::min(args.indexCount, max_indices),
m_index_base_addr + static_cast<uint64_t>(args->start_index_location) * index_size); .index_addr = IndexAddress(args.firstIndex),
.instance_count = args.instanceCount,
const uint32_t index_count = .index_type_and_size = m_index_type_and_size,
(m_index_buffer_size != 0 .base_vertex = args.vertexOffset,
? std::min(args->index_count_per_instance, m_index_buffer_size) .first_instance = args.firstInstance,
: args->index_count_per_instance); .offset_source = DrawOffsetSource::IndirectArgs});
if (GraphicsRunDebugDumpEnabled() && index_count != args->index_count_per_instance) {
static std::atomic<uint32_t> 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<int32_t>(args->base_vertex_location),
.first_instance = args->start_instance_location,
.offset_source = DrawOffsetSource::IndirectArgs});
} }
} }
@@ -1012,7 +988,7 @@ void CommandProcessor::DispatchIndirect(uint64_t args_addr, uint32_t mode) {
void CommandProcessor::DrawIndexAuto(DrawAutoArgs args) { void CommandProcessor::DrawIndexAuto(DrawAutoArgs args) {
if (args.instance_count == 0) { if (args.instance_count == 0) {
args.instance_count = m_num_instances; args.instance_count = NumInstances();
} }
m_renderer.GetRenderExecutor().DrawAuto(m_submit_id, CurrentBuffer(), args); m_renderer.GetRenderExecutor().DrawAuto(m_submit_id, CurrentBuffer(), args);
} }
@@ -52,6 +52,7 @@ vk::MemoryBarrier MakeShaderWriteDependency() {
barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite | barrier.dstAccessMask = vk::AccessFlagBits::eShaderRead | vk::AccessFlagBits::eShaderWrite |
vk::AccessFlagBits::eVertexAttributeRead | vk::AccessFlagBits::eVertexAttributeRead |
vk::AccessFlagBits::eIndexRead | vk::AccessFlagBits::eUniformRead | vk::AccessFlagBits::eIndexRead | vk::AccessFlagBits::eUniformRead |
vk::AccessFlagBits::eIndirectCommandRead |
vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eTransferWrite | vk::AccessFlagBits::eTransferRead | vk::AccessFlagBits::eTransferWrite |
vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentRead |
vk::AccessFlagBits::eColorAttachmentWrite; vk::AccessFlagBits::eColorAttachmentWrite;
+4 -1
View File
@@ -57,6 +57,8 @@ struct DrawIndexArgs {
uint32_t first_instance = 0; uint32_t first_instance = 0;
DrawOffsetSource offset_source = DrawOffsetSource::DrawState; DrawOffsetSource offset_source = DrawOffsetSource::DrawState;
uint32_t render_target_slice_offset = 0; 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 { struct DrawAutoArgs {
@@ -163,7 +165,8 @@ public:
std::span<PreparedBindings* const> bindings); std::span<PreparedBindings* const> bindings);
private: 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); void DrawAuto(uint64_t submit_id, CommandBuffer& buffer, const DrawAutoArgs& args);
struct GraphicsBindings { struct GraphicsBindings {
+26 -7
View File
@@ -635,6 +635,7 @@ struct DrawEmitInfo {
int32_t vertex_offset = 0; int32_t vertex_offset = 0;
uint32_t first_vertex = 0; uint32_t first_vertex = 0;
uint32_t first_instance = 0; uint32_t first_instance = 0;
uint64_t indirect_args = 0;
}; };
struct DrawIndexBufferSource { struct DrawIndexBufferSource {
@@ -1189,6 +1190,12 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
vertex_bindings = AcquireVertexBuffers(buffer, state.vertex_info[0]); vertex_bindings = AcquireVertexBuffers(buffer, state.vertex_info[0]);
index_binding = PrepareIndexBuffer(buffer, index_source); index_binding = PrepareIndexBuffer(buffer, index_source);
} }
std::pair<Buffer*, uint64_t> 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()) { if (draw.IsIndexed()) {
LogDrawPhase(draw.Name(), "CreatePipeline"); LogDrawPhase(draw.Name(), "CreatePipeline");
} }
@@ -1275,6 +1282,8 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff
} }
if (mesh_active) { if (mesh_active) {
vk_buffer.drawMeshTasksEXT(mesh_groups, draw.instance_count, 1); 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 { } else {
EmitDrawPrimitives(ucfg, vk_buffer, draw, emit); 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) { const DrawIndexArgs& args) {
KYTY_PROFILER_FUNCTION(); KYTY_PROFILER_FUNCTION();
@@ -1309,19 +1318,19 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
reinterpret_cast<uint64_t>(args.index_addr)); reinterpret_cast<uint64_t>(args.index_addr));
Common::LockGuard lock(m_context.GetMutex()); Common::LockGuard lock(m_context.GetMutex());
if (args.index_count == 0 || args.instance_count == 0) { if (args.index_count == 0 || (args.instance_count == 0 && args.indirect_args == 0)) {
return; return true;
} }
if (ConsumeMetadataColorOperation(buffer, args.render_target_slice_offset) || if (ConsumeMetadataColorOperation(buffer, args.render_target_slice_offset) ||
DepthStencilCopy(buffer) || DepthStencilCopy(buffer) ||
ResolveColorTargets(buffer, args.render_target_slice_offset)) { ResolveColorTargets(buffer, args.render_target_slice_offset)) {
ResetBindings(); ResetBindings();
return; return true;
} }
if (!DrawHasValidVertexShader(sh_ctx)) { if (!DrawHasValidVertexShader(sh_ctx)) {
return; return true;
} }
if (graphics_debug_dump_enabled()) { if (graphics_debug_dump_enabled()) {
@@ -1347,7 +1356,7 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList; vk::PrimitiveTopology topology = vk::PrimitiveTopology::ePointList;
if (!GetDrawTopology(ucfg, topology)) { if (!GetDrawTopology(ucfg, topology)) {
return; return true;
} }
DrawIndexBufferSource index_source {}; DrawIndexBufferSource index_source {};
@@ -1386,11 +1395,19 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer,
DrawRenderState state {}; DrawRenderState state {};
if (!PrepareDrawRenderState(buffer, draw, args.render_target_slice_offset, state)) { if (!PrepareDrawRenderState(buffer, draw, args.render_target_slice_offset, state)) {
ResetBindings(); ResetBindings();
return; return true;
} }
LogDrawStateIfNeeded(buffer, draw, state, args.index_type_and_size, LogDrawStateIfNeeded(buffer, draw, state, args.index_type_and_size,
args.index_addr); 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 bool indirect = args.offset_source == DrawOffsetSource::IndirectArgs;
const auto [vertex_offset, instance_offset] = 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; const bool native_indirect = indirect && !state.vertex_info[0].fetch_embedded;
emit.vertex_offset = native_indirect ? 0 : vertex_offset + args.base_vertex; emit.vertex_offset = native_indirect ? 0 : vertex_offset + args.base_vertex;
emit.first_instance = native_indirect ? 0 : instance_offset; emit.first_instance = native_indirect ? 0 : instance_offset;
emit.indirect_args = args.indirect_args;
ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source, ExecutePreparedDraw(submit_id, buffer, draw, state, topology, emit, index_source,
primitive_restart); primitive_restart);
ResetBindings(); ResetBindings();
return true;
} }
// NOLINTNEXTLINE(readability-function-cognitive-complexity) // NOLINTNEXTLINE(readability-function-cognitive-complexity)
@@ -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.independentBlend, "independentBlend");
check_feature(device_features2.features.tessellationShader, "tessellationShader"); check_feature(device_features2.features.tessellationShader, "tessellationShader");
check_feature(device_features2.features.drawIndirectFirstInstance,
"drawIndirectFirstInstance");
if (!skip_device) { if (!skip_device) {
auto available_extensions = EnumerateVulkan<vk::ExtensionProperties>( auto available_extensions = EnumerateVulkan<vk::ExtensionProperties>(
@@ -610,6 +612,7 @@ static vk::Device VulkanCreateDevice(GraphicContext& graphics,
device_features.independentBlend = VK_TRUE; device_features.independentBlend = VK_TRUE;
device_features.dualSrcBlend = VK_TRUE; device_features.dualSrcBlend = VK_TRUE;
device_features.tessellationShader = VK_TRUE; device_features.tessellationShader = VK_TRUE;
device_features.drawIndirectFirstInstance = VK_TRUE;
device_features.sampleRateShading = VK_TRUE; device_features.sampleRateShading = VK_TRUE;
device_features.depthBiasClamp = VK_TRUE; device_features.depthBiasClamp = VK_TRUE;
device_features.shaderClipDistance = VK_TRUE; device_features.shaderClipDistance = VK_TRUE;
+83 -2
View File
@@ -399,9 +399,9 @@ struct TextureCacheTestAccess {
}; };
struct RenderExecutorTestAccess { struct RenderExecutorTestAccess {
static void DrawIndex(RenderExecutor &executor, CommandBuffer &command, static bool DrawIndex(RenderExecutor &executor, CommandBuffer &command,
const DrawIndexArgs &args) { const DrawIndexArgs &args) {
executor.DrawIndex(0, command, args); return executor.DrawIndex(0, command, args);
} }
static void DrawAuto(RenderExecutor &executor, CommandBuffer &command, 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<float, 40> positions{};
constexpr std::array<float, 12> triangle{-1, -1, 0, 1, 3, -1, 0, 1, -1, 3, 0, 1};
std::ranges::copy(triangle, positions.begin() + 28);
std::memcpy(reinterpret_cast<void *>(vertices_address), positions.data(),
sizeof(positions));
const vk::DrawIndexedIndirectCommand args{3, 1, 0, 7, 5};
std::memcpy(reinterpret_cast<void *>(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<u32> 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<uint16_t, static_cast<size_t>(AgcDirectResourceType::Last) + 1>
fetch_offsets;
fetch_offsets.fill(AGC_ILLEGAL_DIRECT_OFFSET);
fetch_offsets[static_cast<size_t>(AgcDirectResourceType::PtrVertexBufferTable)] = 0;
fetch_offsets[static_cast<size_t>(AgcDirectResourceType::PtrVertexAttribDescTable)] = 2;
static ShaderUserData fetch_user_data{
.direct_resource_offset = fetch_offsets.data(),
.direct_resource_count = static_cast<uint16_t>(fetch_offsets.size())};
static ShaderSemantic semantic{.hardware_mapping = 9, .size_in_elements = 4};
static const uint32_t vertex_buffer[]{
static_cast<u32>(vertices_address),
static_cast<u32>(vertices_address >> 32u) | (16u << 16u), 10,
DstSel(4, 5, 6, 7) |
(static_cast<u32>(Prospero::BufferFormat::k32_32_32_32Float) << 12u)};
static const uint32_t attribute =
static_cast<u32>(Prospero::VertexAttribFormat::k32_32_32_32Float) << 5u;
const auto fetch_vs = reinterpret_cast<uint64_t>(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<uint32_t>(fetch_vertex.size() * sizeof(u32))});
const uint64_t tables[]{reinterpret_cast<uint64_t>(vertex_buffer),
reinterpret_cast<uint64_t>(&attribute)};
shaders.SetGsShaderResource2({.user_sgpr = 4});
for (u32 i = 0; i < 4; i++) {
shaders.SetGsUserSgpr(i, static_cast<u32>(tables[i / 2] >> (i % 2 * 32)),
HW::UserSgprType::Unknown);
}
const DrawIndexArgs indirect{
.index_count = 3, .index_addr = reinterpret_cast<const void *>(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. // A fourth VS invocation reads beyond the descriptor instead of reconstructing the corner.
const ShaderBufferResource rect_buffer{{ const ShaderBufferResource rect_buffer{{
static_cast<u32>(rect_address), static_cast<u32>(rect_address),
@@ -20693,6 +20772,7 @@ private:
"vertex layer output is not supported"); "vertex layer output is not supported");
m_rasterization_supported = available_features.fillModeNonSolid && m_rasterization_supported = available_features.fillModeNonSolid &&
available_features.tessellationShader && available_features.tessellationShader &&
available_features.drawIndirectFirstInstance &&
available_features.depthBounds && available_features.depthBounds &&
available_depth_clip.depthClipEnable && available_depth_clip.depthClipEnable &&
available_clip_control.depthClipControl && available_clip_control.depthClipControl &&
@@ -20785,6 +20865,7 @@ private:
device_features.shaderFloat64 = available_features.shaderFloat64; device_features.shaderFloat64 = available_features.shaderFloat64;
device_features.fillModeNonSolid = m_rasterization_supported; device_features.fillModeNonSolid = m_rasterization_supported;
device_features.tessellationShader = m_rasterization_supported; device_features.tessellationShader = m_rasterization_supported;
device_features.drawIndirectFirstInstance = m_rasterization_supported;
device_features.depthBounds = m_rasterization_supported; device_features.depthBounds = m_rasterization_supported;
device_info.pEnabledFeatures = &device_features; device_info.pEnabledFeatures = &device_features;
std::vector<const char *> device_extensions{ std::vector<const char *> device_extensions{