fix(agc): honour primitive restart in mesh-path triangle strips

A geometry (NGG) strip draw on the mesh path assigned every primitive the next index positions without looking for the restart index: a triangle spanning a restart fetched vertex 0xFFFF and was drawn to wherever that read landed, and the strip parity never reset, so after an odd-length strip every triangle's winding flipped and face culling dropped it.

For an indexed strip draw with restart enabled and the restart index present, the draw now records each position's strip start (one past the last restart at or before it) and passes the table after the mesh arguments, flagged in draw word 3. The mesh prologue reads the start of its primitive's last vertex: a primitive whose start lies past its first vertex spans a restart and is made degenerate, and the others take their parity from their strip's start (or the draw's first index). The restart vertex itself fetches vertex 0. The table is built from the index bytes the draw already reads (its copy, or the guest memory an in-place import reads), next to #1968's restart-aware highest-index scan.

The new Mesh.cpp test draws quads as four-index strips each followed by a restart (16- and 32-bit indices, and an indirect draw starting mid-buffer) with front faces culled: every quad must render whole and the gaps between them stay clear. Without the table the first check fails ("a triangle crossed the restart after quad 0").

AI-assisted (Claude Code).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Scott Schuster 2026-10-09 23:11:00 -05:00
parent cf8b129e27
commit 8c06af0ad9
10 changed files with 177 additions and 22 deletions

View File

@ -246,6 +246,31 @@ inline std::optional<std::uint32_t> HighestDrawIndex(std::span<const std::byte>
return highest;
}
inline std::vector<std::uint32_t> MeshRestartTable(std::span<const std::byte> indices, std::uint32_t indexSize) {
Require(indexSize == 2 || indexSize == 4, "unsupported index size");
const auto restartIndex = indexSize == 2 ? 0xffffu : 0xffffffffu;
std::vector<std::uint32_t> starts(indices.size() / indexSize);
std::uint32_t stripStart = 0;
bool restarted = false;
for (std::size_t position = 0; position < starts.size(); ++position) {
std::uint32_t index = 0;
if (indexSize == 2) {
std::uint16_t value = 0;
std::memcpy(&value, indices.data() + position * 2u, sizeof(value));
index = value;
} else {
std::memcpy(&index, indices.data() + position * 4u, sizeof(index));
}
if (index == restartIndex) {
restarted = true;
stripStart = static_cast<std::uint32_t>(position) + 1u;
}
starts[position] = stripStart;
}
if (!restarted) starts.clear();
return starts;
}
inline std::vector<std::size_t> SoloZeroPaddedFetchIndices(const std::vector<VertexFetch>& fetches, const std::vector<std::size_t>& fetchValid) {
Require(fetchValid.size() == fetches.size(), "fetch validity does not match the fetch count");
std::vector<std::size_t> solo;

View File

@ -961,6 +961,7 @@ struct DrawInputs {
std::set<std::uint32_t> fragmentOutputs;
VkPipelineStageFlags shaderStages = 0;
std::uint32_t meshGroups = 0;
std::vector<std::uint32_t> restartTable;
};
std::shared_ptr<Buffer> zeroVertexBuffer(const Context& context, const ShaderRecompiler::VertexAttribute& attribute) {
@ -1062,6 +1063,10 @@ DrawInputs prepareDrawInputs(const Context& context, const State& state, const P
Require(*highest <= context.limits.maxDrawIndexedIndexValue, "index exceeds the device's indexed draw limit");
Require(!fanGeometry || !state.primitiveRestart || *highest != (draw.indexSize == 2 ? 0xffffu : 0xffffffffu), "primitive restart in a triangle fan geometry draw is unsupported");
inputs.maxIndex = *highest;
if (skipRestart && state.stages.mesh && state.stages.mesh->inputPrimitive == 6) {
const auto indexSpan = import != nullptr ? std::span<const std::byte>(reinterpret_cast<const std::byte*>(draw.indexAddress), static_cast<std::size_t>(indexBytes)) : copy.buffer->Bytes().first(static_cast<std::size_t>(indexBytes));
inputs.restartTable = MeshRestartTable(indexSpan, draw.indexSize);
}
if (import != nullptr) {
inputs.indexHandle = import->buffer;
inputs.indexOffset = draw.indexAddress - import->base;
@ -1363,10 +1368,30 @@ bool recordIndirectArguments(const Context& context, VkCommandBuffer commands, R
return true;
}
std::shared_ptr<Buffer> recordMeshArguments(const Context& context, VkCommandBuffer commands, Recorder* recorder, bool recorded, const State& state, const Pm4::DrawParameters& draw, const IndirectRecord& indirect, const std::function<void(std::uint32_t)>& countBarrier) {
std::shared_ptr<Buffer> meshArgumentBuffer(const Context& context, std::span<const std::uint32_t> restartTable) {
const auto tableBytes = restartTable.empty() ? 0u : sizeof(std::uint32_t) * (restartTable.size() + 1u);
auto arguments = std::make_shared<Buffer>(context, ShaderRecompiler::MeshArgumentBytes + tableBytes, VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
if (!restartTable.empty()) {
Require(restartTable.size() <= std::numeric_limits<std::uint32_t>::max(), "mesh restart table is too large");
const auto length = static_cast<std::uint32_t>(restartTable.size());
auto* bytes = arguments->Bytes().data() + ShaderRecompiler::MeshArgumentBytes;
std::memcpy(bytes, &length, sizeof(length));
std::memcpy(bytes + sizeof(length), restartTable.data(), restartTable.size_bytes());
}
return arguments;
}
std::shared_ptr<Buffer> directMeshArguments(const Context& context, const Pm4::DrawParameters& draw, std::span<const std::uint32_t> restartTable) {
auto arguments = meshArgumentBuffer(context, restartTable);
const MeshArguments resolved{0u, 0u, 0u, draw.indexCount, 0u};
std::memcpy(arguments->Bytes().data(), &resolved, sizeof(resolved));
return arguments;
}
std::shared_ptr<Buffer> recordMeshArguments(const Context& context, VkCommandBuffer commands, Recorder* recorder, bool recorded, const State& state, const Pm4::DrawParameters& draw, const IndirectRecord& indirect, const std::function<void(std::uint32_t)>& countBarrier, std::span<const std::uint32_t> restartTable) {
const auto* args = indirect.args;
const auto rules = MeshArgumentRulesFor(context, *state.stages.mesh, draw.indexCount);
auto arguments = std::make_shared<Buffer>(context, ShaderRecompiler::MeshArgumentBytes, VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT);
auto arguments = meshArgumentBuffer(context, restartTable);
if (indirect.path == IndirectDrawPath::Gpu && recorder != nullptr && indirect.argumentImport->address != 0) {
const std::array<std::uint32_t, 7> words{rules.indexCount, rules.inputSize, rules.step, rules.primitivesPerGroup, rules.maxGroups, rules.maxInstances, rules.maxTotal};
if (recorder->RecordMeshArguments(commands, indirect.argumentImport->address + (args->arguments - indirect.argumentImport->base), arguments->DeviceAddress(), words)) {
@ -1481,7 +1506,7 @@ struct RecordedDraw {
VkShaderStageFlags pushStages = 0;
};
void pushDrawConstants(const Pipeline& pipeline, VkCommandBuffer commands, const State& state, const Pm4::DrawParameters& draw, std::span<const CompiledShader> shaders, const ShaderResources& resources, const std::array<std::byte, PipelinePushConstantBytes>* bytes, VkShaderStageFlags stages, VkDeviceAddress meshArguments = 0) {
void pushDrawConstants(const Pipeline& pipeline, VkCommandBuffer commands, const State& state, const Pm4::DrawParameters& draw, std::span<const CompiledShader> shaders, const ShaderResources& resources, const std::array<std::byte, PipelinePushConstantBytes>* bytes, VkShaderStageFlags stages, VkDeviceAddress meshArguments = 0, bool restartTable = false) {
auto block = bytes != nullptr ? *bytes : AssemblePushConstants(shaders);
if (bytes == nullptr) {
resources.PatchPushConstants(block);
@ -1493,7 +1518,7 @@ void pushDrawConstants(const Pipeline& pipeline, VkCommandBuffer commands, const
}
const auto firstVertex = draw.indirect ? draw.indirect->vertexConstant : draw.firstVertex;
const auto firstInstance = draw.indirect ? draw.indirect->instanceConstant : draw.firstInstance;
const std::array<std::uint32_t, ShaderRecompiler::MeshDrawPushBytes / 4> words{draw.indexCount, firstVertex, firstInstance, draw.indexed ? draw.indexSize : 0u, static_cast<std::uint32_t>(meshArguments), static_cast<std::uint32_t>(meshArguments >> 32u)};
const std::array<std::uint32_t, ShaderRecompiler::MeshDrawPushBytes / 4> words{draw.indexCount, firstVertex, firstInstance, draw.indexed ? draw.indexSize | (restartTable ? ShaderRecompiler::MeshIndexRestartTable : 0u) : 0u, static_cast<std::uint32_t>(meshArguments), static_cast<std::uint32_t>(meshArguments >> 32u)};
static_assert(ShaderRecompiler::MeshDrawPushOffsetBytes + ShaderRecompiler::MeshDrawPushBytes == PipelinePushSlotBytes);
std::memcpy(block.data() + ShaderRecompiler::MeshDrawPushOffsetBytes, words.data(), sizeof(words));
pipeline.PushConstants(commands, stages | VK_SHADER_STAGE_MESH_BIT_EXT, block);
@ -1651,6 +1676,7 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam
VkBuffer argumentBuffer = VK_NULL_HANDLE;
VkDeviceSize argumentOffset = 0;
bool rewritten = false;
if (!meshIndirect && !inputs.restartTable.empty()) meshArguments = directMeshArguments(context, draw, inputs.restartTable);
if (continued) {
record.pipeline->Continue(commands, state);
} else {
@ -1665,7 +1691,7 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam
}
APS5_LOG_CHARS_OUT_DEBUG("Upload barrier recorded");
if (meshIndirect) {
meshArguments = recordMeshArguments(context, commands, recorder, true, state, draw, *record.indirect, countBarrier);
meshArguments = recordMeshArguments(context, commands, recorder, true, state, draw, *record.indirect, countBarrier, inputs.restartTable);
argumentBuffer = meshArguments->Handle();
} else if (gpuIndirect) {
rewritten = recordIndirectArguments(context, commands, recorder, true, *record.indirect, scratch, argumentBuffer, argumentOffset, countBarrier);
@ -1683,7 +1709,7 @@ void recordDraw(const Context& context, const State& state, const Pm4::DrawParam
resources.Bind(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, record.pipeline->Layout());
}
APS5_LOG_CHARS_OUT_DEBUG("Resources bound");
pushDrawConstants(*record.pipeline, commands, state, draw, shaders, resources, record.pushBytes, record.pushStages, meshArguments != nullptr ? meshArguments->DeviceAddress() : 0);
pushDrawConstants(*record.pipeline, commands, state, draw, shaders, resources, record.pushBytes, record.pushStages, meshArguments != nullptr ? meshArguments->DeviceAddress() : 0, !inputs.restartTable.empty());
APS5_LOG_CHARS_OUT_DEBUG("Push constants recorded");
recordDrawCommands(context, commands, state, draw, inputs, record.indirect, argumentBuffer, argumentOffset);
if (record.pipeline->SplitsFaces()) {
@ -2117,8 +2143,10 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters&
APS5_LOG_CHARS_OUT_DEBUG("Upload barrier recorded");
std::shared_ptr<Buffer> meshArguments;
if (state.stages.mesh && args != nullptr) {
meshArguments = recordMeshArguments(context, commands, recorder, recorded, state, draw, indirect, countBarrier);
meshArguments = recordMeshArguments(context, commands, recorder, recorded, state, draw, indirect, countBarrier, inputs.restartTable);
argumentBuffer = meshArguments->Handle();
} else if (!inputs.restartTable.empty()) {
meshArguments = directMeshArguments(context, draw, inputs.restartTable);
} else if (gpuIndirect) {
rewritten = recordIndirectArguments(context, commands, recorder, recorded, indirect, scratch, argumentBuffer, argumentOffset, countBarrier);
}
@ -2153,7 +2181,7 @@ void Draw(const Context& context, const State& state, const Pm4::DrawParameters&
APS5_LOG_CHARS_OUT_DEBUG("Pipeline Begin OK");
resources->Bind(commands, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->Layout());
APS5_LOG_CHARS_OUT_DEBUG("Resources bound");
pushDrawConstants(*pipeline, commands, state, draw, shaders, *resources, nullptr, 0, meshArguments != nullptr ? meshArguments->DeviceAddress() : 0);
pushDrawConstants(*pipeline, commands, state, draw, shaders, *resources, nullptr, 0, meshArguments != nullptr ? meshArguments->DeviceAddress() : 0, !inputs.restartTable.empty());
APS5_LOG_CHARS_OUT_DEBUG("Push constants recorded");
recordDrawCommands(context, commands, state, draw, inputs, args != nullptr ? &indirect : nullptr, argumentBuffer, argumentOffset);
if (pipeline->SplitsFaces()) {

View File

@ -95,6 +95,9 @@ alignas(256) std::array<Vertex, FanRim + 1> Fan{};
alignas(256) std::array<Vertex, FanRim + 1> FanScrambled{};
alignas(256) std::array<std::uint16_t, FanRim + 1> FanIndices{};
alignas(256) std::array<std::uint16_t, FanRim + 1> FanRestart{};
alignas(256) std::array<Vertex, 4 * Columns> Quads{};
alignas(256) std::array<std::uint16_t, 5 * Columns> QuadStrips{};
alignas(256) std::array<std::uint32_t, 5 * Columns> QuadStrips32{};
std::array<std::uint32_t, 4> VertexBufferDescriptor(const void* vertices, std::uint32_t count) {
const auto address = reinterpret_cast<std::uintptr_t>(vertices);
@ -127,6 +130,7 @@ struct MeshDraw {
std::uint32_t geometryPushBytes = ShaderRecompiler::MeshDrawPushOffsetBytes;
bool restart = false;
std::uint32_t waveSize = 64u;
VkCullModeFlags cull = VK_CULL_MODE_NONE;
};
void DrawMesh(AgcDriver::VulkanDevice& device, const MeshDraw& setup) {
@ -178,7 +182,7 @@ void DrawMesh(AgcDriver::VulkanDevice& device, const MeshDraw& setup) {
state.viewport = {0, static_cast<float>(Height), static_cast<float>(Width), -static_cast<float>(Height), 0, 1};
state.negativeOneToOne = false;
state.scissor = {{0, 0}, {Width, Height}};
state.cullMode = VK_CULL_MODE_NONE;
state.cullMode = setup.cull;
state.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
state.blend.colorWriteMask = 15;
state.blends = {state.blend};
@ -205,6 +209,24 @@ void CheckFan(const char* what) {
Require(PixelIs(0, Background) && PixelIs(Pixels.size() - 4u, Background), std::string(what) + ": the corners changed");
}
std::size_t QuadPixel(std::uint32_t quad, float across) {
const auto x = static_cast<std::uint32_t>((static_cast<float>(quad) + across * 0.8f) * Width / Columns);
return (static_cast<std::size_t>(Height * 3u / 4u) * Width + x) * 4u;
}
void CheckQuads(const char* what, std::uint32_t first, std::uint32_t count) {
for (std::uint32_t quad = 0; quad < Columns; ++quad) {
const bool drawn = quad >= first && quad < first + count;
for (const float across : {0.25f, 0.75f}) {
const auto offset = QuadPixel(quad, across);
Require(PixelIs(offset, drawn ? TriangleColor(quad) : Background), std::string(what) + ": quad " + std::to_string(quad) + (across < 0.5f ? " left" : " right") + " half pixel " + PixelText(offset));
}
const auto gap = QuadPixel(quad, 1.12f);
Require(PixelIs(gap, Background), std::string(what) + ": a triangle crossed the restart after quad " + std::to_string(quad) + ", pixel " + PixelText(gap));
}
Require(PixelIs((static_cast<std::size_t>(Height / 4u) * Width + Width / 2u) * 4u, Background), std::string(what) + ": the upper half changed");
}
void CheckTriangles(const char* what) {
for (std::uint32_t triangle = 0; triangle < Triangles; ++triangle) {
const auto offset = CellPixel(triangle);
@ -249,6 +271,22 @@ int main() {
FanRestart[k] = FanIndices[k];
}
FanRestart[FanRim] = 0xffffu;
for (std::uint32_t quad = 0; quad < Columns; ++quad) {
const auto color = TriangleColor(quad);
const std::array<float, 4> rgba{color[0] / 255.0f, color[1] / 255.0f, color[2] / 255.0f, 1.0f};
const float left = -1.0f + 2.0f * static_cast<float>(quad) / Columns;
const float right = left + 1.6f / Columns;
Quads[4 * quad] = {{left, -1.0f, 0.5f, 1.0f}, rgba};
Quads[4 * quad + 1] = {{left, 0.0f, 0.5f, 1.0f}, rgba};
Quads[4 * quad + 2] = {{right, -1.0f, 0.5f, 1.0f}, rgba};
Quads[4 * quad + 3] = {{right, 0.0f, 0.5f, 1.0f}, rgba};
for (std::uint32_t k = 0; k < 4; ++k) {
QuadStrips[5 * quad + k] = static_cast<std::uint16_t>(4 * quad + k);
QuadStrips32[5 * quad + k] = 4 * quad + k;
}
QuadStrips[5 * quad + 4] = 0xffffu;
QuadStrips32[5 * quad + 4] = 0xffffffffu;
}
const auto testDevice = OpenVulkanTestDevice();
if (!testDevice) return VulkanTestSkipped;
@ -332,6 +370,21 @@ int main() {
}
Require(PixelIs((static_cast<std::size_t>(Height / 4u) * Width + Width / 2u) * 4u, Background), "triangle strip: the upper half changed");
const auto quadBuffer = VertexBufferDescriptor(Quads.data(), static_cast<std::uint32_t>(Quads.size()));
ClearPixels();
DrawMesh(device, {strip, {reinterpret_cast<std::uintptr_t>(QuadStrips.data()), static_cast<std::uint32_t>(QuadStrips.size()), 2, 1, 0, true}, quadBuffer, ShaderRecompiler::MeshDrawPushOffsetBytes, true, 64u, VK_CULL_MODE_FRONT_BIT});
CheckQuads("16-bit triangle strips with restarts", 0, Columns);
ClearPixels();
DrawMesh(device, {strip, {reinterpret_cast<std::uintptr_t>(QuadStrips32.data()), static_cast<std::uint32_t>(QuadStrips32.size()), 4, 1, 0, true}, quadBuffer, ShaderRecompiler::MeshDrawPushOffsetBytes, true, 64u, VK_CULL_MODE_FRONT_BIT});
CheckQuads("32-bit triangle strips with restarts", 0, Columns);
const std::array<std::uint32_t, 5> quadWords{15u, 1u, 5u, 0u, 0u};
std::copy(quadWords.begin(), quadWords.end(), record);
AgcDriver::Pm4::DrawParameters quadDraw{reinterpret_cast<std::uintptr_t>(QuadStrips.data()), static_cast<std::uint32_t>(QuadStrips.size()), 2, 1, 0, true};
quadDraw.indirect = AgcDriver::Pm4::DrawParameters::IndirectDraw{reinterpret_cast<std::uintptr_t>(record), 0x25u, 20u, 20u, 1u, false, 0u, 0x280u, 0x280u, 0x280u, false, 0u};
ClearPixels();
DrawMesh(device, {strip, quadDraw, quadBuffer, ShaderRecompiler::MeshDrawPushOffsetBytes, true, 64u, VK_CULL_MODE_FRONT_BIT});
CheckQuads("indirect triangle strips with restarts", 1, 3);
const ShaderRecompiler::MeshConfiguration fan{5u, 3u, 5u, 9u, 3u, 64u, 1024u, 0u, 4u};
ClearPixels();
DrawMesh(device, {fan, {0, static_cast<std::uint32_t>(Fan.size()), 0, 1, 0, false}, VertexBufferDescriptor(Fan.data(), static_cast<std::uint32_t>(Fan.size()))});

View File

@ -79,6 +79,7 @@ enum class IrOpcode : std::uint16_t {
RealtimeClock,
MeshDrawParameter,
MeshArgument,
MeshRestartStart,
MeshAllocate,
TessellationBase,
GetTessellationAttribute,

View File

@ -120,6 +120,7 @@ constexpr std::array<OpcodeMeta, static_cast<std::size_t>(IrOpcode::Count)> Meta
makeMeta("RealtimeClock", U64),
makeMeta("MeshDrawParameter", U32, U32),
makeMeta("MeshArgument", U32, U32),
makeMeta("MeshRestartStart", U32, U32, U1),
makeMeta("MeshAllocate", Void, U32),
makeMeta("TessellationBase", U32, U32),
makeMeta("GetTessellationAttribute", U32, U32, U32, U1),

View File

@ -278,6 +278,9 @@ inline constexpr std::uint32_t MeshArgumentAddressDword = 4;
inline constexpr std::uint32_t MeshArgumentIndexCountDword = 3;
inline constexpr std::uint32_t MeshArgumentFirstIndexDword = 4;
inline constexpr std::uint32_t MeshArgumentBytes = 20;
inline constexpr std::uint32_t MeshIndexRestartTable = 0x100;
inline constexpr std::uint32_t MeshRestartLengthDword = MeshArgumentBytes / 4;
inline constexpr std::uint32_t MeshRestartTableDword = MeshRestartLengthDword + 1;
inline constexpr std::uint32_t MeshIndexBufferUserWord = 4;
inline constexpr std::uint32_t WorkgroupMemoryDescriptorSet = 1;

View File

@ -62,6 +62,7 @@ void EmitVoid(SpirvValueEmitContext& context);
void EmitBarrier(SpirvEmitterState& state);
std::uint32_t EmitMeshDrawParameter(SpirvValueEmitContext& ctx, const IrValue& inst);
std::uint32_t EmitMeshArgument(SpirvValueEmitContext& ctx, const IrValue& inst);
std::uint32_t EmitMeshRestartStart(SpirvValueEmitContext& ctx, const IrValue& inst);
std::uint32_t EmitGetTessellationAttribute(SpirvValueEmitContext& ctx, const IrValue& inst);
void EmitSetTessellationAttribute(SpirvValueEmitContext& ctx, const IrValue& inst);
std::uint32_t EmitGetUserData(SpirvEmitterState& state, ScalarReg reg);

View File

@ -294,6 +294,7 @@ void EmitDirectInstruction(SpirvValueEmitContext& ctx, const IrValue& inst) {
case IrOpcode::RealtimeClock: return Invoke(EmitRealtimeClock, ctx, inst);
case IrOpcode::MeshDrawParameter: return Invoke(EmitMeshDrawParameter, ctx, inst);
case IrOpcode::MeshArgument: return Invoke(EmitMeshArgument, ctx, inst);
case IrOpcode::MeshRestartStart: return Invoke(EmitMeshRestartStart, ctx, inst);
case IrOpcode::MeshAllocate: return Invoke(EmitMeshAllocate, ctx, inst);
case IrOpcode::TessellationBase: return Invoke(EmitTessellationBase, ctx, inst);
case IrOpcode::GetTessellationAttribute: return Invoke(EmitGetTessellationAttribute, ctx, inst);

View File

@ -1053,12 +1053,9 @@ std::uint32_t EmitMeshDrawParameter(SpirvValueEmitContext& ctx, const IrValue& i
return result;
}
std::uint32_t EmitMeshArgument(SpirvValueEmitContext& ctx, const IrValue& inst) {
auto& state = ctx.state;
const auto index = inst.Argument(0)->ImmediateU32();
if (state.program.Resources().stage != IrShaderStage::Mesh || index >= MeshArgumentBytes / 4u) {
ctx.Fail(inst, "invalid mesh argument");
}
namespace {
std::uint32_t MeshArgumentAddress(SpirvEmitterState& state) {
const auto push = [&](std::uint32_t dword) {
const auto pointer = state.module.AllocateId();
state.module.AddFunction(spv::OpAccessChain, TypePushConstantElementPointer(state), pointer, state.pushConstantVariable, ConstantU32(state, 0u), ConstantU32(state, MeshDrawPushOffsetBytes / 4u + dword));
@ -1068,7 +1065,42 @@ std::uint32_t EmitMeshArgument(SpirvValueEmitContext& ctx, const IrValue& inst)
};
const auto low = push(MeshArgumentAddressDword);
const auto high = push(MeshArgumentAddressDword + 1u);
const auto address = Binary(state, spv::OpBitwiseOr, TypeScalarU64(state), low, Binary(state, spv::OpShiftLeftLogical, TypeScalarU64(state), high, ConstantU32(state, 32u)));
return Binary(state, spv::OpBitwiseOr, TypeScalarU64(state), low, Binary(state, spv::OpShiftLeftLogical, TypeScalarU64(state), high, ConstantU32(state, 32u)));
}
std::uint32_t LoadMeshArgumentWord(SpirvEmitterState& state, std::uint32_t address, std::uint32_t dword) {
const auto element = Binary(state, spv::OpIAdd, TypeScalarU64(state), address, Binary(state, spv::OpShiftLeftLogical, TypeScalarU64(state), Unary(state, spv::OpUConvert, TypeScalarU64(state), dword), ConstantU32(state, 2u)));
const auto pointer = state.module.AllocateId();
state.module.AddFunction(spv::OpConvertUToPtr, TypePointer(state, spv::StorageClassPhysicalStorageBuffer, TypeU32(state)), pointer, element);
const auto loaded = state.module.AllocateId();
state.module.AddFunction(spv::OpLoad, TypeU32(state), loaded, pointer, spv::MemoryAccessAlignedMask, 4u);
return loaded;
}
}
std::uint32_t EmitMeshRestartStart(SpirvValueEmitContext& ctx, const IrValue& inst) {
auto& state = ctx.state;
if (state.program.Resources().stage != IrShaderStage::Mesh) {
ctx.Fail(inst, "mesh restart table read outside a mesh shader");
}
return EmitValueOrZeroIfCondition(state, ctx.Arg(inst, 1), [&]() {
const auto address = MeshArgumentAddress(state);
const auto length = LoadMeshArgumentWord(state, address, ConstantU32(state, MeshRestartLengthDword));
const auto last = Binary(state, spv::OpISub, TypeU32(state), length, ConstantU32(state, 1u));
const auto position = state.module.AllocateId();
state.module.AddFunction(spv::OpExtInst, TypeU32(state), position, GlslStd450(state), GLSLstd450UMin, ctx.Arg(inst, 0), last);
return LoadMeshArgumentWord(state, address, Binary(state, spv::OpIAdd, TypeU32(state), position, ConstantU32(state, MeshRestartTableDword)));
});
}
std::uint32_t EmitMeshArgument(SpirvValueEmitContext& ctx, const IrValue& inst) {
auto& state = ctx.state;
const auto index = inst.Argument(0)->ImmediateU32();
if (state.program.Resources().stage != IrShaderStage::Mesh || index >= MeshArgumentBytes / 4u) {
ctx.Fail(inst, "invalid mesh argument");
}
const auto address = MeshArgumentAddress(state);
const auto present = Binary(state, spv::OpINotEqual, TypeBool(state), address, BdaConstant(state, 0u));
const auto before = state.currentLabel;
const auto loadLabel = state.module.AllocateId();

View File

@ -262,12 +262,20 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO
IrValue& primitiveCount = minimum(subtractSaturate(primitives, waveBase), u32(options.waveSize));
IrValue& waveInfo = entryIr.BitwiseOr(entryIr.ShiftLeftLogical(wave, u32(24u)), u32((totalThreads / options.waveSize) << 28u));
entryIr.SetScalarReg(static_cast<ScalarReg>(3), entryIr.BitwiseOr(waveInfo, entryIr.BitwiseOr(entryIr.ShiftLeftLogical(primitiveCount, u32(8u)), vertexCount)));
IrValue& parity = mesh.inputPrimitive == kTriStripPrimitiveType ? entryIr.BitwiseAnd(entryIr.IAdd(firstPrimitive, local), u32(1u)) : u32(0u);
const bool strip = mesh.inputPrimitive == kTriStripPrimitiveType;
IrValue& indexWord = draw(3u);
IrValue& stripPosition = entryIr.IAdd(entryIr.IAdd(firstPrimitive, local), firstIndex);
IrValue& restartTable = entryIr.INotEqual(entryIr.BitwiseAnd(indexWord, u32(MeshIndexRestartTable)), u32(0u));
IrValue& stripStart = strip ? entryIr.Emit(IrOpcode::MeshRestartStart, IrOpcodeType(IrOpcode::MeshRestartStart), {&entryIr.IAdd(stripPosition, u32(2u)), &restartTable}) : u32(0u);
IrValue& parity = strip ? entryIr.BitwiseAnd(entryIr.ISub(stripPosition, entryIr.Emit(IrOpcode::UMax32, IrOpcodeType(IrOpcode::UMax32), {&stripStart, &firstIndex})), u32(1u)) : u32(0u);
IrValue& vertex = entryIr.IMul(local, step);
IrValue& item = u32(mesh.esgsItemSize);
IrValue& first = fan ? entryIr.IMul(entryIr.IAdd(vertex, u32(1u)), item) : entryIr.IMul(entryIr.IAdd(vertex, parity), item);
IrValue& second = fan ? entryIr.IMul(entryIr.IAdd(vertex, u32(2u)), item) : size >= 2u ? entryIr.IMul(entryIr.ISub(entryIr.IAdd(vertex, u32(1u)), parity), item) : u32(0u);
IrValue& third = fan ? u32(0u) : size == 3u ? entryIr.IMul(entryIr.IAdd(vertex, u32(2u)), item) : u32(0u);
const auto unlessRestarted = [&](IrValue& offset) -> IrValue& {
return strip ? entryIr.Select(entryIr.ULessThan(stripPosition, stripStart), entryIr.IMul(vertex, item), offset) : offset;
};
IrValue& first = fan ? entryIr.IMul(entryIr.IAdd(vertex, u32(1u)), item) : unlessRestarted(entryIr.IMul(entryIr.IAdd(vertex, parity), item));
IrValue& second = fan ? entryIr.IMul(entryIr.IAdd(vertex, u32(2u)), item) : size >= 2u ? unlessRestarted(entryIr.IMul(entryIr.ISub(entryIr.IAdd(vertex, u32(1u)), parity), item)) : u32(0u);
IrValue& third = fan ? u32(0u) : size == 3u ? unlessRestarted(entryIr.IMul(entryIr.IAdd(vertex, u32(2u)), item)) : u32(0u);
entryIr.SetVectorReg(static_cast<VectorReg>(0), entryIr.BitwiseOr(entryIr.BitwiseAnd(first, u32(0xffffu)), entryIr.ShiftLeftLogical(second, u32(16u))));
entryIr.SetVectorReg(static_cast<VectorReg>(1), entryIr.BitwiseAnd(third, u32(0xffffu)));
entryIr.SetVectorReg(static_cast<VectorReg>(2), entryIr.IAdd(firstPrimitive, local));
@ -277,7 +285,7 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO
throw std::runtime_error("mesh shader translation requires the merged program's eight hidden user words");
}
IrValue& inputVertex = fan ? entryIr.Select(entryIr.IEqual(local, u32(0u)), u32(0u), entryIr.IAdd(firstVertex, local)) : entryIr.IAdd(firstVertex, local);
IrValue& indexBytes = draw(3u);
IrValue& indexBytes = entryIr.BitwiseAnd(indexWord, u32(0xffu));
IrValue& indexed = entryIr.INotEqual(indexBytes, u32(0u));
IrValue& byteOffset = entryIr.IMul(entryIr.IAdd(inputVertex, firstIndex), indexBytes);
IrValue& indexResource = entryIr.Emit(IrOpcode::GetBufferResource, IrOpcodeType(IrOpcode::GetBufferResource), {&entryIr.GetUserData(static_cast<ScalarReg>(4)), &entryIr.GetUserData(static_cast<ScalarReg>(5)), &entryIr.GetUserData(static_cast<ScalarReg>(6)), &entryIr.GetUserData(static_cast<ScalarReg>(7))});
@ -288,7 +296,9 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO
for (std::uint32_t reg = 4u; reg < 8u; reg++) {
entryIr.SetScalarReg(static_cast<ScalarReg>(reg), u32(0u));
}
entryIr.SetVectorReg(static_cast<VectorReg>(5), entryIr.IAdd(draw(1u), entryIr.Select(indexed, index, inputVertex)));
IrValue& restartIndex = entryIr.Select(entryIr.IEqual(indexBytes, u32(2u)), u32(0xffffu), u32(0xffffffffu));
IrValue& fetchedIndex = entryIr.Select(entryIr.LogicalAnd(restartTable, entryIr.IEqual(index, restartIndex)), u32(0u), index);
entryIr.SetVectorReg(static_cast<VectorReg>(5), entryIr.IAdd(draw(1u), entryIr.Select(indexed, fetchedIndex, inputVertex)));
entryIr.SetVectorReg(static_cast<VectorReg>(6), u32(0u));
entryIr.SetVectorReg(static_cast<VectorReg>(7), u32(0u));
entryIr.SetVectorReg(static_cast<VectorReg>(8), entryIr.IAdd(draw(2u), builtin(StageInputKind::WorkgroupId, 1u)));