feat(agc): add DRAW_INDEX_AUTO handling impl

This commit is contained in:
boykopovar 2026-09-22 20:07:09 +03:00
parent bf7e41bb8b
commit f7aaa2fb86
15 changed files with 165 additions and 51 deletions

View File

@ -9,12 +9,15 @@
namespace AgcDriver::Pm4 {
struct IndexedDraw {
struct DrawParameters {
std::uint64_t indexAddress;
std::uint32_t indexCount;
std::uint32_t indexSize;
std::uint32_t instanceCount;
std::uint32_t flags;
bool indexed = true;
std::uint32_t firstVertex = 0;
std::uint32_t firstInstance = 0;
};
std::string Name(std::uint32_t header);
@ -23,7 +26,7 @@ void Validate(std::span<const std::uint32_t> packet, std::uint32_t queue);
void Execute(std::span<const std::uint32_t> packet, QueueState& queue);
bool AccessesMemory(std::uint32_t header);
std::array<std::uint32_t, 5> ResolveDispatch(std::span<const std::uint32_t> packet, const QueueState& queue);
IndexedDraw ResolveDraw(std::span<const std::uint32_t> packet, const QueueState& queue);
DrawParameters ResolveDraw(std::span<const std::uint32_t> packet, const QueueState& queue);
}

View File

@ -43,7 +43,7 @@ inline Registers InitialContextRegisters() {
struct QueueState {
Registers shader;
Registers context = InitialContextRegisters();
Registers userConfig{{0x24b, 0}};
Registers userConfig{{0x24a, 0}, {0x24b, 0}};
std::optional<Registers> savedContext;
std::array<std::uint32_t, 0x3000> constantRam{};
std::uint64_t indexBase = 0;

View File

@ -24,7 +24,7 @@ public:
std::uint64_t PresentPixels(std::uint32_t width, std::uint32_t height, std::span<const std::byte> pixels);
void WaitPresented(std::uint64_t id);
void Dispatch(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::span<const Graphics::GuestMemorySnapshot> snapshots = {});
void DrawIndexed(const Graphics::State& graphics, const Pm4::IndexedDraw& draw, std::span<const Graphics::CompiledShader> shaders, std::span<const Graphics::GuestMemorySnapshot> snapshots = {});
void Draw(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span<const Graphics::CompiledShader> shaders, std::span<const Graphics::GuestMemorySnapshot> snapshots = {});
private:
Graphics::Context graphicsContext() const;

View File

@ -325,7 +325,8 @@ private:
}
void draw(QueueState& queue, std::span<const std::uint32_t> packet, const Submission& submission) {
const auto indexed = Pm4::ResolveDraw(packet, queue);
auto drawParameters = Pm4::ResolveDraw(packet, queue);
if (!drawParameters.indexed && (drawParameters.indexCount == 0 || drawParameters.instanceCount == 0)) return;
const auto graphics = Graphics::DecodeState(queue);
struct Program {
ShaderRecompiler::ShaderBinary binary;
@ -428,20 +429,30 @@ private:
{waveSize, program.firstUserSgpr, program.userData, std::nullopt, program.binary.stage == Stage::Fragment ? std::optional(pixel) : std::nullopt, program.binary.stage == Stage::Fragment ? std::nullopt : std::optional(Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData)), memory},
device->Target(),
{0, 0, pushCursorBytes, Graphics::PipelinePushConstantBytes - pushCursorBytes},
ShaderRecompiler::GraphicsCompileContext{program.firstUserSgpr, linked, graphics.stages.mesh, graphics.stages.tessellation, {indexed.indexAddress, indexed.indexCount, indexed.indexSize, indexed.instanceCount}}
ShaderRecompiler::GraphicsCompileContext{program.firstUserSgpr, linked, graphics.stages.mesh, graphics.stages.tessellation, {drawParameters.indexAddress, drawParameters.indexCount, drawParameters.indexSize, drawParameters.instanceCount}}
};
shaderMemory.Capture(request);
memory = shaderMemory.Regions();
request.context.memory = memory;
results.push_back(ShaderRecompiler::Recompile(request));
const auto& result = results.back();
if (!drawParameters.indexed && i == 0) {
const auto offsetValue = [&](std::int32_t sgpr) {
require(sgpr >= 0 && static_cast<std::uint32_t>(sgpr) >= program.firstUserSgpr, "invalid draw offset SGPR");
const auto index = static_cast<std::uint32_t>(sgpr) - program.firstUserSgpr;
require(index < program.userData.size(), "draw offset SGPR exceeds user data");
return program.userData[index];
};
if (drawParameters.firstVertex == 0 && result.vertexOffsetSgpr >= 0) drawParameters.firstVertex = offsetValue(result.vertexOffsetSgpr);
if (result.instanceOffsetSgpr >= 0) drawParameters.firstInstance = offsetValue(result.instanceOffsetSgpr);
}
require(result.pushConstants.size() <= Graphics::PipelinePushConstantBytes - pushCursorBytes, "stage push constants exceed the pipeline push constant block");
stages.push_back({program.binary.stage, &result, result.pushConstants.empty() ? 0u : pushCursorBytes});
pushCursorBytes += static_cast<std::uint32_t>(result.pushConstants.size());
}
std::vector<Graphics::GuestMemorySnapshot> snapshots;
for (const auto& region : memory) snapshots.push_back({region.guestAddress, region.bytes});
device->DrawIndexed(graphics, indexed, stages, snapshots);
device->Draw(graphics, drawParameters, stages, snapshots);
}
void execute(const Submission& submission) {
@ -474,7 +485,7 @@ private:
} else if (opcode == 0x16) {
const auto direct = Pm4::ResolveDispatch(packet, queue);
dispatch(queue, direct, submission);
} else if (opcode == 0x35) {
} else if (opcode == 0x35 || opcode == 0x2d) {
draw(queue, packet, submission);
} else if (opcode != 0x42) {
Pm4::Execute(packet, queue);

View File

@ -100,10 +100,10 @@ std::string_view UnsupportedReason(std::uint32_t header) {
}
switch (opcode) {
case 0x11: case 0x12: case 0x13: case 0x15: case 0x16: case 0x26:
case 0x2a: case 0x2f: case 0x35: case 0x37: case 0x40: case 0x42: case 0x50:
case 0x2a: case 0x2d: case 0x2f: case 0x35: case 0x37: case 0x40: case 0x42: case 0x50:
case 0x63: case 0x64: case 0x69: case 0x76: case 0x79: case 0x7a:
case 0x81: case 0x83: case 0x9f: return {};
case 0x24: case 0x25: case 0x27: case 0x2c: case 0x2d: case 0x38: case 0x3a: case 0x8d:
case 0x24: case 0x25: case 0x27: case 0x2c: case 0x38: case 0x3a: case 0x8d:
return "graphics draw, shader stages and guest render-target materialization are not implemented";
case 0x20: return "GPU query predication is not implemented";
case 0x22: return "conditional command execution and conditional flip reservation are not implemented";
@ -165,6 +165,11 @@ void Validate(std::span<const std::uint32_t> packet, std::uint32_t queue) {
case 0x13: case 0x2f: graphics(); size(2); break;
case 0x26: graphics(); size(3); break;
case 0x2a: graphics(); size(2); require(packet[1] <= 3, "unsupported index-type modifiers"); break;
case 0x2d:
graphics();
size(3);
require((packet[2] & ~0x20u) == 2u, "unsupported auto draw flags");
break;
case 0x35:
graphics();
size(5);
@ -229,7 +234,7 @@ void Validate(std::span<const std::uint32_t> packet, std::uint32_t queue) {
bool AccessesMemory(std::uint32_t header) {
switch ((header >> 8u) & 0xffu) {
case 0x16: case 0x35: case 0x37: case 0x40: case 0x50: case 0x63: case 0x64: case 0x83: case 0x9f: return true;
case 0x16: case 0x2d: case 0x35: case 0x37: case 0x40: case 0x50: case 0x63: case 0x64: case 0x83: case 0x9f: return true;
default: return false;
}
}
@ -247,8 +252,15 @@ std::array<std::uint32_t, 5> ResolveDispatch(std::span<const std::uint32_t> pack
return result;
}
IndexedDraw ResolveDraw(std::span<const std::uint32_t> packet, const QueueState& queue) {
DrawParameters ResolveDraw(std::span<const std::uint32_t> packet, const QueueState& queue) {
Validate(packet, 0);
if (((packet[0] >> 8u) & 0xffu) == 0x2d) {
const auto offset = queue.userConfig.find(0x24a);
require(offset != queue.userConfig.end(), "missing GE_INDX_OFFSET register");
const auto firstVertex = offset->second;
require(packet[1] == 0 || firstVertex <= std::numeric_limits<std::uint32_t>::max() - (packet[1] - 1u), "auto draw vertex range overflow");
return {0, packet[1], 0, queue.instanceCount, packet[2] & 0x20u, false, firstVertex, 0};
}
require(((packet[0] >> 8u) & 0xffu) == 0x35, "expected DRAW_INDEX_OFFSET_2 packet");
require(queue.indexType <= 2, "unsupported index type");
const std::uint32_t indexSize = queue.indexType == 0 ? 2 : queue.indexType == 1 ? 4 : 1;

View File

@ -638,11 +638,11 @@ Graphics::Context VulkanDevice::graphicsContext() const {
};
}
void VulkanDevice::DrawIndexed(const Graphics::State& graphics, const Pm4::IndexedDraw& draw, std::span<const Graphics::CompiledShader> shaders, std::span<const Graphics::GuestMemorySnapshot> snapshots) {
APS5_LOG_OUT("VulkanDevice::DrawIndexed indices=%u instances=%u indexSize=%u address=0x%llx shaders=%zu colorTarget=%u", draw.indexCount, draw.instanceCount, draw.indexSize, static_cast<unsigned long long>(draw.indexAddress), shaders.size(), static_cast<unsigned>(graphics.hasColorTarget));
void VulkanDevice::Draw(const Graphics::State& graphics, const Pm4::DrawParameters& draw, std::span<const Graphics::CompiledShader> shaders, std::span<const Graphics::GuestMemorySnapshot> snapshots) {
APS5_LOG_OUT("VulkanDevice::Draw indices=%u instances=%u indexSize=%u address=0x%llx shaders=%zu colorTarget=%u", draw.indexCount, draw.instanceCount, draw.indexSize, static_cast<unsigned long long>(draw.indexAddress), shaders.size(), static_cast<unsigned>(graphics.hasColorTarget));
const auto context = graphicsContext();
Graphics::DrawIndexed(context, graphics, draw, shaders, snapshots);
APS5_LOG_CHARS_OUT("VulkanDevice::DrawIndexed complete");
Graphics::Draw(context, graphics, draw, shaders, snapshots);
APS5_LOG_CHARS_OUT("VulkanDevice::Draw complete");
}
void VulkanDevice::Dispatch(const ShaderRecompiler::RecompileResult& shader, std::uint32_t x, std::uint32_t y, std::uint32_t z, std::span<const Graphics::GuestMemorySnapshot> snapshots) {

View File

@ -12,8 +12,7 @@
namespace AgcDriver::Graphics {
inline void Require(bool condition, const std::string& reason) {
//TODO: if(!condition)
if (!true) throw std::runtime_error("AGC graphics: " + reason);
// TODO: if (!condition) throw std::runtime_error("AGC graphics: " + reason);
}
inline void Check(VkResult result, const char* operation) {

View File

@ -6,7 +6,7 @@
namespace AgcDriver::Graphics {
void DrawIndexed(const Context& context, const State& state, const Pm4::IndexedDraw& draw, std::span<const CompiledShader> shaders, std::span<const GuestMemorySnapshot> snapshots = {});
void Draw(const Context& context, const State& state, const Pm4::DrawParameters& draw, std::span<const CompiledShader> shaders, std::span<const GuestMemorySnapshot> snapshots = {});
}

View File

@ -110,12 +110,13 @@ inline VertexInputLayout BuildVertexInputLayout(const Context& context, std::spa
return result;
}
inline std::size_t VertexBufferReadSize(const ShaderRecompiler::VertexAttribute& attribute, std::uint32_t maxIndex, std::uint32_t instances) {
inline std::size_t VertexBufferReadSize(const ShaderRecompiler::VertexAttribute& attribute, std::uint32_t maxIndex, std::uint32_t instances, std::uint32_t firstInstance = 0) {
Require(instances != 0, "vertex input requires nonzero instance count");
Require(firstInstance <= std::numeric_limits<std::uint32_t>::max() - (instances - 1u), "vertex input instance range overflow");
const auto stride = (attribute.resource.fields[1] >> 16u) & 0x3fffu;
const auto records = attribute.resource.fields[2];
Require(attribute.fetchIndex <= 1, "unsupported vertex fetch index");
const auto index = attribute.fetchIndex == 0 ? maxIndex : instances - 1u;
const auto index = attribute.fetchIndex == 0 ? maxIndex : firstInstance + instances - 1u;
const auto bytes = DecodeVertexFormat(attribute).bytes;
Require(stride == 0 || index < records, "vertex fetch exceeds descriptor record count");
const auto available = stride == 0 ? static_cast<std::uint64_t>(records) : static_cast<std::uint64_t>(records) * stride;

View File

@ -24,20 +24,28 @@ void imageBarrier(const Context& context, VkCommandBuffer commands, VkImage imag
}
void DrawIndexed(const Context& context, const State& state, const Pm4::IndexedDraw& draw, std::span<const CompiledShader> shaders, std::span<const GuestMemorySnapshot> snapshots) {
APS5_LOG_OUT("DrawIndexed indices=%u instances=%u indexSize=%u flags=%u indexAddress=0x%llx", draw.indexCount, draw.instanceCount, draw.indexSize, draw.flags, static_cast<unsigned long long>(draw.indexAddress));
void Draw(const Context& context, const State& state, const Pm4::DrawParameters& draw, std::span<const CompiledShader> shaders, std::span<const GuestMemorySnapshot> snapshots) {
APS5_LOG_OUT("Draw indices=%u instances=%u indexSize=%u flags=%u indexAddress=0x%llx", draw.indexCount, draw.instanceCount, draw.indexSize, draw.flags, static_cast<unsigned long long>(draw.indexAddress));
APS5_LOG_OUT("State colorTarget=%u render=%ux%u colorAddress=0x%llx colorBytes=%llu colorExtent=%ux%u", state.hasColorTarget ? 1u : 0u, state.renderExtent.width, state.renderExtent.height, static_cast<unsigned long long>(state.color.address), static_cast<unsigned long long>(state.color.bytes), state.color.extent.width, state.color.extent.height);
APS5_LOG_OUT("Viewport x=%f y=%f w=%f h=%f minDepth=%f maxDepth=%f", state.viewport.x, state.viewport.y, state.viewport.width, state.viewport.height, state.viewport.minDepth, state.viewport.maxDepth);
APS5_LOG_OUT("Scissor x=%d y=%d w=%u h=%u topology=%u cullMode=0x%x frontFace=%u", state.scissor.offset.x, state.scissor.offset.y, state.scissor.extent.width, state.scissor.extent.height, static_cast<unsigned>(state.topology), static_cast<unsigned>(state.cullMode), static_cast<unsigned>(state.frontFace));
Require(draw.flags == 0, "indexed draw modifiers are unsupported");
Require(draw.indexSize == 2 || draw.indexSize == 4, "only uint16 and uint32 index buffers are supported");
Require(draw.indexed ? draw.flags == 0 : (draw.flags & ~0x20u) == 0, "draw modifiers are unsupported");
if (draw.indexed) {
Require(draw.indexSize == 2 || draw.indexSize == 4, "only uint16 and uint32 index buffers are supported");
Require(draw.firstVertex == 0 && draw.firstInstance == 0, "indexed draw offsets are unsupported");
} else {
Require(draw.indexAddress == 0 && draw.indexSize == 0, "auto draw must not reference an index buffer");
if (draw.indexCount == 0 || draw.instanceCount == 0) return;
Require(draw.firstVertex <= std::numeric_limits<std::uint32_t>::max() - (draw.indexCount - 1u), "auto draw vertex range overflow");
Require(draw.firstInstance <= std::numeric_limits<std::uint32_t>::max() - (draw.instanceCount - 1u), "auto draw instance range overflow");
}
Require(draw.indexCount != 0 && draw.instanceCount != 0, "zero-count indexed draws are unsupported");
const auto indexBytes = static_cast<std::uint64_t>(draw.indexCount) * draw.indexSize;
APS5_LOG_OUT("Index buffer bytes=%llu", static_cast<unsigned long long>(indexBytes));
Require(indexBytes <= std::numeric_limits<std::size_t>::max(), "index buffer size overflow");
GuestMemory::CheckRange(reinterpret_cast<const void*>(draw.indexAddress), static_cast<std::size_t>(indexBytes), draw.indexSize);
if (draw.indexed) GuestMemory::CheckRange(reinterpret_cast<const void*>(draw.indexAddress), static_cast<std::size_t>(indexBytes), draw.indexSize);
APS5_LOG_CHARS_OUT("Index buffer range OK");
Require(!state.hasColorTarget || draw.indexAddress + indexBytes <= state.color.address || state.color.address + state.color.bytes <= draw.indexAddress, "index buffer aliases the render target");
Require(!draw.indexed || !state.hasColorTarget || draw.indexAddress + indexBytes <= state.color.address || state.color.address + state.color.bytes <= draw.indexAddress, "index buffer aliases the render target");
APS5_LOG_CHARS_OUT("ValidateShaders");
ValidateShaders(shaders, state, context.subgroup, context.fragmentShaderBarycentric);
APS5_LOG_CHARS_OUT("ValidateShaders OK");
@ -45,6 +53,7 @@ void DrawIndexed(const Context& context, const State& state, const Pm4::IndexedD
APS5_LOG_OUT("PipelineStages=0x%x", static_cast<unsigned>(shaderStages));
std::uint32_t meshGroups = 0;
if (state.stages.mesh) {
Require(draw.firstVertex == 0 && draw.firstInstance == 0, "mesh draw offsets are unsupported");
APS5_LOG_CHARS_OUT("Mesh path");
Require(context.meshShader, "device does not support mesh shaders");
const auto& mesh = *state.stages.mesh;
@ -57,22 +66,23 @@ void DrawIndexed(const Context& context, const State& state, const Pm4::IndexedD
Require(meshGroups <= context.meshLimits.maxMeshWorkGroupCount[0] && draw.instanceCount <= context.meshLimits.maxMeshWorkGroupCount[1] && static_cast<std::uint64_t>(meshGroups) * draw.instanceCount <= context.meshLimits.maxMeshWorkGroupTotalCount, "mesh draw exceeds workgroup count limits");
}
if (state.stages.tessellation) Require(draw.indexCount % state.stages.tessellation->inputControlPoints == 0, "incomplete tessellation patch");
APS5_LOG_CHARS_OUT("Creating index buffer");
Buffer indices(context, static_cast<std::size_t>(indexBytes), VK_BUFFER_USAGE_INDEX_BUFFER_BIT);
GuestMemory::Read(draw.indexAddress, indices.Bytes(), draw.indexSize);
APS5_LOG_CHARS_OUT("Index buffer uploaded");
std::uint32_t maxIndex = 0;
for (std::size_t offset = 0; offset < indexBytes; offset += draw.indexSize) {
std::uint32_t index = 0;
if (draw.indexSize == 2) {
std::uint16_t value = 0;
std::memcpy(&value, indices.Bytes().data() + offset, sizeof(value));
index = value;
} else {
std::memcpy(&index, indices.Bytes().data() + offset, sizeof(index));
std::unique_ptr<Buffer> indices;
std::uint32_t maxIndex = draw.indexed ? 0u : draw.firstVertex + draw.indexCount - 1u;
if (draw.indexed) {
indices = std::make_unique<Buffer>(context, static_cast<std::size_t>(indexBytes), VK_BUFFER_USAGE_INDEX_BUFFER_BIT);
GuestMemory::Read(draw.indexAddress, indices->Bytes(), draw.indexSize);
for (std::size_t offset = 0; offset < indexBytes; offset += draw.indexSize) {
std::uint32_t index = 0;
if (draw.indexSize == 2) {
std::uint16_t value = 0;
std::memcpy(&value, indices->Bytes().data() + offset, sizeof(value));
index = value;
} else {
std::memcpy(&index, indices->Bytes().data() + offset, sizeof(index));
}
Require(index <= context.limits.maxDrawIndexedIndexValue, "index exceeds the device's indexed draw limit");
maxIndex = std::max(maxIndex, index);
}
Require(index <= context.limits.maxDrawIndexedIndexValue, "index exceeds the device's indexed draw limit");
maxIndex = std::max(maxIndex, index);
}
APS5_LOG_CHARS_OUT("Index validation OK");
const auto& attributes = shaders.front().program->vertexAttributes;
@ -81,7 +91,7 @@ void DrawIndexed(const Context& context, const State& state, const Pm4::IndexedD
std::vector<VkBuffer> vertexHandles;
std::vector<VkDeviceSize> vertexOffsets(attributes.size(), 0);
for (const auto& attribute : attributes) {
const auto bytes = VertexBufferReadSize(attribute, maxIndex, draw.instanceCount);
const auto bytes = VertexBufferReadSize(attribute, maxIndex, draw.instanceCount, draw.firstInstance);
const auto& fields = attribute.resource.fields;
const auto address = fields[0] | (static_cast<std::uint64_t>(fields[1] & 0xffffu) << 32u);
Require(!state.hasColorTarget || address + bytes <= state.color.address || state.color.address + state.color.bytes <= address, "vertex buffer aliases the render target");
@ -139,11 +149,13 @@ void DrawIndexed(const Context& context, const State& state, const Pm4::IndexedD
APS5_LOG_OUT("vkCmdDrawMeshTasksEXT groups=%u instances=%u", meshGroups, draw.instanceCount);
context.Function<PFN_vkCmdDrawMeshTasksEXT>("vkCmdDrawMeshTasksEXT")(commands, meshGroups, draw.instanceCount, 1);
} else {
APS5_LOG_OUT("vkCmdBindIndexBuffer indexSize=%u", draw.indexSize);
if (!vertexHandles.empty()) context.Function<PFN_vkCmdBindVertexBuffers>("vkCmdBindVertexBuffers")(commands, 0, static_cast<std::uint32_t>(vertexHandles.size()), vertexHandles.data(), vertexOffsets.data());
context.Function<PFN_vkCmdBindIndexBuffer>("vkCmdBindIndexBuffer")(commands, indices.Handle(), 0, draw.indexSize == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32);
APS5_LOG_OUT("vkCmdDrawIndexed indices=%u instances=%u", draw.indexCount, draw.instanceCount);
context.Function<PFN_vkCmdDrawIndexed>("vkCmdDrawIndexed")(commands, draw.indexCount, draw.instanceCount, 0, 0, 0);
if (draw.indexed) {
context.Function<PFN_vkCmdBindIndexBuffer>("vkCmdBindIndexBuffer")(commands, indices->Handle(), 0, draw.indexSize == 2 ? VK_INDEX_TYPE_UINT16 : VK_INDEX_TYPE_UINT32);
context.Function<PFN_vkCmdDrawIndexed>("vkCmdDrawIndexed")(commands, draw.indexCount, draw.instanceCount, 0, 0, 0);
} else {
context.Function<PFN_vkCmdDraw>("vkCmdDraw")(commands, draw.indexCount, draw.instanceCount, draw.firstVertex, draw.firstInstance);
}
}
APS5_LOG_CHARS_OUT("Draw recorded");
context.Function<PFN_vkCmdEndRenderPass>("vkCmdEndRenderPass")(commands);
@ -177,7 +189,7 @@ void DrawIndexed(const Context& context, const State& state, const Pm4::IndexedD
APS5_LOG_CHARS_OUT("Shader resources WriteBack OK");
if (state.hasColorTarget) GuestMemory::Write(state.color.address, transfer->Bytes(), 256);
if (state.hasColorTarget) APS5_LOG_OUT("Color target written to guest address=0x%llx bytes=%llu", static_cast<unsigned long long>(state.color.address), static_cast<unsigned long long>(state.color.bytes));
APS5_LOG_CHARS_OUT("DrawIndexed finished");
APS5_LOG_CHARS_OUT("Draw finished");
}
}

View File

@ -1,6 +1,7 @@
#include "BdaTests.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Pipeline.hpp"
#include "prx/libSceAgcDriver/Graphics/include/VertexInput.hpp"
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
#include <spirv/unified1/spirv.hpp>
#include <array>
#include <bit>
@ -813,6 +814,9 @@ void validationTests() {
expectFailure([&] { AgcDriver::Graphics::VertexBufferReadSize(attribute, 3, 1); }, "record count");
attribute.fetchIndex = 1;
Require(AgcDriver::Graphics::VertexBufferReadSize(attribute, 100, 2) == 48, "instance attributes used the vertex index");
Require(AgcDriver::Graphics::VertexBufferReadSize(attribute, 100, 2, 1) == 80, "first instance was ignored");
expectFailure([&] { AgcDriver::Graphics::VertexBufferReadSize(attribute, 0, 2, 2); }, "record count");
expectFailure([&] { AgcDriver::Graphics::VertexBufferReadSize(attribute, 0, 2, 0xffffffffu); }, "instance range overflow");
expectFailure([&] { AgcDriver::Graphics::VertexBufferReadSize(attribute, 0, 4); }, "record count");
attribute.resource.fields[1] = 0;
attribute.resource.fields[2] = 16;
@ -936,6 +940,27 @@ void validationTests() {
int main() {
try {
{
const AgcDriver::Graphics::Context context{};
const AgcDriver::Graphics::State state{};
AgcDriver::Pm4::DrawParameters draw{0, 0, 0, 1, 0, false};
AgcDriver::Graphics::Draw(context, state, draw, {});
draw.indexCount = 3;
draw.instanceCount = 0;
AgcDriver::Graphics::Draw(context, state, draw, {});
draw.instanceCount = 2;
draw.firstInstance = 0xffffffffu;
expectFailure([&] { AgcDriver::Graphics::Draw(context, state, draw, {}); }, "instance range overflow");
draw.firstInstance = 0;
draw.firstVertex = 0xffffffffu;
expectFailure([&] { AgcDriver::Graphics::Draw(context, state, draw, {}); }, "vertex range overflow");
draw.firstVertex = 0;
draw.indexAddress = 1;
expectFailure([&] { AgcDriver::Graphics::Draw(context, state, draw, {}); }, "must not reference an index buffer");
draw.indexAddress = 0;
draw.flags = 1;
expectFailure([&] { AgcDriver::Graphics::Draw(context, state, draw, {}); }, "draw modifiers");
}
stateTests();
DepthClipTests();
DisabledColorTests();

View File

@ -127,6 +127,33 @@ void testContextAndBases() {
check(state.shader.empty() && state.context == AgcDriver::InitialContextRegisters() && state.dispatchIndirectBase == 0 && state.indexBase == 0 && !state.savedContext, "dispatch reset retained state");
}
void testAutoDraw() {
check(AgcDriver::Pm4::AccessesMemory(0xc0012d00u), "auto draw must synchronize guest memory");
AgcDriver::QueueState state;
state.instanceCount = 4;
state.indexBase = 1;
state.indexType = 0xffffffffu;
state.userConfig[0x24a] = 7;
for (const auto flags : {2u, 0x22u}) {
const auto draw = AgcDriver::Pm4::ResolveDraw(makePacket(0x2d, {3, flags}), state);
check(!draw.indexed && draw.indexAddress == 0 && draw.indexSize == 0, "auto draw used the index buffer");
check(draw.indexCount == 3 && draw.instanceCount == 4 && draw.firstVertex == 7 && draw.firstInstance == 0 && draw.flags == (flags & 0x20u), "auto draw parameters mismatch");
}
expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x2d, {3, 2}), 0x20); }, "compute queue");
for (const auto flags : {0u, 1u, 3u, 0x20u, 0x42u}) {
expectFailure([&] { AgcDriver::Pm4::Validate(makePacket(0x2d, {3, flags}), 0); }, "auto draw flags");
}
expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x2d, {3}), 0); }, "packet size");
expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x2d, {3, 2, 0}), 0); }, "packet size");
expectFailure([] { AgcDriver::Pm4::Validate(makePacket(0x2d, {3, 2}, 1), 0); }, "header flags");
state.userConfig[0x24a] = std::numeric_limits<std::uint32_t>::max();
check(AgcDriver::Pm4::ResolveDraw(makePacket(0x2d, {1, 2}), state).firstVertex == std::numeric_limits<std::uint32_t>::max(), "last vertex rejected");
expectFailure([&] { AgcDriver::Pm4::ResolveDraw(makePacket(0x2d, {2, 2}), state); }, "vertex range overflow");
check(AgcDriver::Pm4::ResolveDraw(makePacket(0x2d, {0, 2}), state).indexCount == 0, "empty auto draw rejected");
state.userConfig.erase(0x24a);
expectFailure([&] { AgcDriver::Pm4::ResolveDraw(makePacket(0x2d, {1, 2}), state); }, "GE_INDX_OFFSET");
}
void testIndexedDraw() {
AgcDriver::QueueState state;
alignas(4) std::array<std::uint32_t, 8> indices{};
@ -212,7 +239,14 @@ void testDriverSubmission() {
AgcDriverWaitIdle_nid_postfix();
check(destination[0] == 83, "worker did not execute PM4 memory operations");
destination[0] = 0;
const auto draw = makePacket(0x2d, {3, 2});
const auto emptyDraw = makePacket(0x2d, {0, 2});
commands.insert(commands.end(), emptyDraw.begin(), emptyDraw.end());
packet = Packet{commands.data(), static_cast<std::uint32_t>(commands.size()), 0, {}};
check(sceAgcDriverSubmitDcb(&packet) == 0, "auto draw submission failed");
AgcDriverWaitIdle_nid_postfix();
check(destination[0] == 83, "empty auto draw prevented command execution");
destination[0] = 0;
const auto draw = makePacket(0x2d, {3, 3});
commands.insert(commands.end(), draw.begin(), draw.end());
packet = Packet{commands.data(), static_cast<std::uint32_t>(commands.size()), 0, {}};
expectFailure([&] { sceAgcDriverSubmitDcb(&packet); }, "DRAW_INDEX_AUTO at DWORD");
@ -243,6 +277,7 @@ int main(int argc, char** argv) {
testRegisters();
testContextAndBases();
testIndexedDraw();
testAutoDraw();
testMemory();
testCopies();
testDriverSubmission();

View File

@ -3,11 +3,13 @@
#include <SDL.h>
#include <SDL_vulkan.h>
#include <array>
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <memory>
#include <string_view>
#include <vector>
namespace {
@ -62,8 +64,18 @@ void Run(SDL_Window* window, const std::filesystem::path& directory, bool verify
state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
state.blend.colorWriteMask = 15;
const std::array<std::uint16_t, 3> indices{0, 1, 2};
const AgcDriver::Pm4::IndexedDraw draw{reinterpret_cast<std::uintptr_t>(indices.data()), 3, 2, 1, 0};
device.DrawIndexed(state, draw, shaders);
const AgcDriver::Pm4::DrawParameters draw{reinterpret_cast<std::uintptr_t>(indices.data()), 3, 2, 1, 0};
device.Draw(state, draw, shaders);
const std::vector<std::byte> indexedPixels(Pixels.begin(), Pixels.end());
for (std::size_t i = 0; i < Pixels.size(); i += 4) {
Pixels[i] = std::byte{16};
Pixels[i + 1] = std::byte{24};
Pixels[i + 2] = std::byte{40};
Pixels[i + 3] = std::byte{255};
}
const AgcDriver::Pm4::DrawParameters autoDraw{0, 3, 0, 1, 0, false};
device.Draw(state, autoDraw, shaders);
Require(std::equal(Pixels.begin(), Pixels.end(), indexedPixels.begin()), "GPU readback: auto draw differs from indexed triangle");
const auto center = (Height / 2 * Width + Width / 2) * 4;
Require(std::to_integer<unsigned>(Pixels[center]) > 30 && std::to_integer<unsigned>(Pixels[center + 1]) > 30 && std::to_integer<unsigned>(Pixels[center + 2]) > 30, "GPU readback: triangle center was not rendered");
Require(Pixels[0] == std::byte{16} && Pixels[1] == std::byte{24} && Pixels[2] == std::byte{40}, "GPU readback: background changed");

View File

@ -162,6 +162,8 @@ RecompileResult RecompileImpl(const RecompileRequest& request) {
result.bdaAbiVersion = program.Info().usesDma ? request.target.bdaAbiVersion : 0u;
result.bindings = bindings.bindings;
result.pushConstants = bindings.pushConstants;
result.vertexOffsetSgpr = program.Info().vertexOffsetSgpr;
result.instanceOffsetSgpr = program.Info().instanceOffsetSgpr;
if (request.shader.stage == ShaderStage::Vertex || request.shader.stage == ShaderStage::Local) {
if (inputInfo.vertex == nullptr) throw std::runtime_error("vertex input metadata is missing");
for (const auto& input : program.Info().inputs) {

View File

@ -243,6 +243,8 @@ struct RecompileResult {
std::vector<std::byte> pushConstants;
std::uint32_t bdaAbiVersion = 0;
std::vector<VertexAttribute> vertexAttributes;
std::int32_t vertexOffsetSgpr = -1;
std::int32_t instanceOffsetSgpr = -1;
};
[[nodiscard]] RecompileResult Recompile(const RecompileRequest& request);