fix(agc): bind a null index buffer to non-indexed mesh draws
A non-indexed mesh draw (DRAW_INDEX_AUTO, or a non-indexed indirect record) gave the mesh program's hidden index buffer V# (user words 4..7) a 4-byte range at the program's own code address, so the descriptor had a guest address although the prologue never loads through it (the load is predicated on the draw's index size). That range is bound from live guest memory. When the program also reads guest memory by address, the address build captures the program's code as an immutable snapshot, and the two overlapped: the draw failed with "writable guest memory overlaps an immutable snapshot", or, when the live bytes at the code address had changed since the capture, "guest snapshot differs from the writable memory it overlaps". MeshIndexBufferDescriptor now returns a null V# (base and size 0, same format word) for a non-indexed draw, which ShaderResources binds to its empty placeholder buffer, and takes no unread address any more. PPSA14632 (LEGO Horizon Adventures) hit this on its layered mesh-path draws. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
e79c971f99
commit
5822c26b59
|
|
@ -91,7 +91,7 @@ DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> packe
|
|||
if (!graphics.stages.mesh) return;
|
||||
auto& words = programs.front().userData;
|
||||
require(programs.front().firstUserSgpr == 0 && words.size() >= ShaderRecompiler::MeshIndexBufferUserWord + 4, "mesh program lacks the hidden user words");
|
||||
const auto descriptor = Graphics::MeshIndexBufferDescriptor(parameters, programs.front().binary.codeAddress);
|
||||
const auto descriptor = Graphics::MeshIndexBufferDescriptor(parameters);
|
||||
std::copy(descriptor.begin(), descriptor.end(), words.begin() + ShaderRecompiler::MeshIndexBufferUserWord);
|
||||
};
|
||||
if (!drawParameters.indirect) setMeshIndexBuffer(drawParameters);
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ struct DrawInputCopy {
|
|||
DrawInputCopy CopyDrawInput(const Context& context, Recorder* recorder, std::uint64_t address, std::size_t bytes, std::size_t alignment, Recorder::SnapshotUse use);
|
||||
void KeepDrawInput(Recorder* recorder, std::uint64_t address, const DrawInputCopy& copy, Recorder::SnapshotUse use, std::uint32_t derived);
|
||||
|
||||
std::array<std::uint32_t, 4> MeshIndexBufferDescriptor(const Pm4::DrawParameters& draw, std::uint64_t unreadAddress);
|
||||
std::array<std::uint32_t, 4> MeshIndexBufferDescriptor(const Pm4::DrawParameters& draw);
|
||||
|
||||
struct MeshArguments {
|
||||
std::uint32_t groups;
|
||||
|
|
|
|||
|
|
@ -601,11 +601,12 @@ void CheckBufferAliases(std::span<const CompiledShader> shaders, const ColorTarg
|
|||
|
||||
}
|
||||
|
||||
std::array<std::uint32_t, 4> MeshIndexBufferDescriptor(const Pm4::DrawParameters& draw, std::uint64_t unreadAddress) {
|
||||
const auto address = draw.indexed ? draw.indexAddress : unreadAddress;
|
||||
const auto bytes = draw.indexed ? (static_cast<std::uint64_t>(draw.indexCount) * draw.indexSize + 3u) & ~std::uint64_t{3} : 4u;
|
||||
Require(address != 0 && bytes != 0 && bytes <= 0xffffffffu && (address >> 48u) == 0, "invalid mesh index buffer range");
|
||||
std::array<std::uint32_t, 4> MeshIndexBufferDescriptor(const Pm4::DrawParameters& draw) {
|
||||
constexpr std::uint32_t RawWord3 = 0x31016facu;
|
||||
if (!draw.indexed) return {0u, 0u, 0u, RawWord3};
|
||||
const auto address = draw.indexAddress;
|
||||
const auto bytes = (static_cast<std::uint64_t>(draw.indexCount) * draw.indexSize + 3u) & ~std::uint64_t{3};
|
||||
Require(address != 0 && bytes != 0 && bytes <= 0xffffffffu && (address >> 48u) == 0, "invalid mesh index buffer range");
|
||||
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>(address >> 32u) & 0xffffu, static_cast<std::uint32_t>(bytes), RawWord3};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1977,6 +1977,16 @@ bool recompilesDebugBranch(std::uint32_t opcode) {
|
|||
return !ShaderRecompiler::Recompile(request).spirv.Words().empty();
|
||||
}
|
||||
|
||||
void meshIndexBufferTests() {
|
||||
const AgcDriver::Pm4::DrawParameters automatic{0, 96, 0, 1, 0, false};
|
||||
const auto unindexed = AgcDriver::Graphics::MeshIndexBufferDescriptor(automatic);
|
||||
Require(unindexed[0] == 0 && unindexed[1] == 0 && unindexed[2] == 0 && unindexed[3] == 0x31016facu, "a non-indexed mesh draw did not get a null index buffer V#");
|
||||
const AgcDriver::Pm4::DrawParameters indexed{0x123456789a00ull, 5, 2, 1, 0, true};
|
||||
const auto words = AgcDriver::Graphics::MeshIndexBufferDescriptor(indexed);
|
||||
Require(words[0] == 0x56789a00u && words[1] == 0x1234u && words[2] == 12u && words[3] == 0x31016facu, "an indexed mesh draw's index buffer V# changed");
|
||||
expectFailure([] { static_cast<void>(AgcDriver::Graphics::MeshIndexBufferDescriptor(AgcDriver::Pm4::DrawParameters{0, 3, 2, 1, 0, true})); }, "invalid mesh index buffer range");
|
||||
}
|
||||
|
||||
void meshArgumentTests() {
|
||||
using AgcDriver::Graphics::MeshArguments;
|
||||
using AgcDriver::Graphics::ResolveMeshArguments;
|
||||
|
|
@ -2105,6 +2115,7 @@ int main() {
|
|||
misalignedShaderDataTests();
|
||||
debugBranchTests();
|
||||
meshArgumentTests();
|
||||
meshIndexBufferTests();
|
||||
validationTests();
|
||||
vertexCopyTests();
|
||||
pixelParameterSlotTests();
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ struct MeshDraw {
|
|||
void DrawMesh(AgcDriver::VulkanDevice& device, const MeshDraw& setup) {
|
||||
const auto target = device.Target();
|
||||
std::vector<std::uint32_t> userData(12, 0u);
|
||||
const auto index = AgcDriver::Graphics::MeshIndexBufferDescriptor(setup.draw, reinterpret_cast<std::uintptr_t>(GeometryCode.data()));
|
||||
const auto index = AgcDriver::Graphics::MeshIndexBufferDescriptor(setup.draw);
|
||||
std::copy(index.begin(), index.end(), userData.begin() + ShaderRecompiler::MeshIndexBufferUserWord);
|
||||
std::copy(setup.vertexBuffer.begin(), setup.vertexBuffer.end(), userData.begin() + 8);
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> geometryMemory{{{reinterpret_cast<std::uintptr_t>(GeometryCode.data()), std::as_bytes(std::span(GeometryCode))}}};
|
||||
|
|
|
|||
Loading…
Reference in New Issue