Merge pull request #143 from SamG-Coder/codex/shader-geometry-validation
fix(shader): preserve NGG passthrough and wave32 launch inputs
This commit is contained in:
commit
de49f81697
|
|
@ -135,7 +135,12 @@ DrawVerdict Driver::draw(QueueState& queue, std::span<const std::uint32_t> packe
|
|||
const auto& program = programs[i];
|
||||
if (program.binary.stage == Stage::Fragment || roles[i] == Role::GeometryBack) return;
|
||||
decodeReads[i].clear();
|
||||
vertexInfos[i] = Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, program.userData, &decodeReads[i]);
|
||||
std::span<const std::uint32_t> vertexUserData = program.userData;
|
||||
if (program.binary.stage == Stage::Mesh) {
|
||||
require(vertexUserData.size() >= 8u, "mesh vertex metadata requires eight hidden user words");
|
||||
vertexUserData = vertexUserData.subspan(8u);
|
||||
}
|
||||
vertexInfos[i] = Graphics::DecodeVertexStageInfo(program.binary.header, program.binary.headerAddress, vertexUserData, &decodeReads[i]);
|
||||
};
|
||||
if (!registerKey) {
|
||||
for (std::size_t i = 0; i < programs.size(); ++i) decodeVertexInfo(i);
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ void Driver::lookupDraw(const Submission& submission, const std::shared_ptr<Vulk
|
|||
if (graphics.stages.mesh) {
|
||||
const auto& mesh = *graphics.stages.mesh;
|
||||
for (const auto value : {mesh.inputPrimitive, mesh.primitivesPerGroup, mesh.verticesPerGroup, mesh.maxVertices, mesh.maxPrimitives, mesh.threadsPerGroup, mesh.ldsSizeDwords, mesh.provokingVertex, mesh.esgsItemSize}) mix(value);
|
||||
mix(mesh.passthrough);
|
||||
}
|
||||
mix(graphics.stages.tessellation.has_value());
|
||||
if (graphics.stages.tessellation) {
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ bool Driver::sameDecode(const DrawDecode& a, const DrawDecode& b) {
|
|||
const auto sameMesh = [](const std::optional<ShaderRecompiler::MeshConfiguration>& x, const std::optional<ShaderRecompiler::MeshConfiguration>& y) {
|
||||
if (x.has_value() != y.has_value()) return false;
|
||||
if (!x) return true;
|
||||
return x->inputPrimitive == y->inputPrimitive && x->primitivesPerGroup == y->primitivesPerGroup && x->verticesPerGroup == y->verticesPerGroup && x->maxVertices == y->maxVertices && x->maxPrimitives == y->maxPrimitives && x->threadsPerGroup == y->threadsPerGroup && x->ldsSizeDwords == y->ldsSizeDwords && x->provokingVertex == y->provokingVertex && x->esgsItemSize == y->esgsItemSize;
|
||||
return x->inputPrimitive == y->inputPrimitive && x->primitivesPerGroup == y->primitivesPerGroup && x->verticesPerGroup == y->verticesPerGroup && x->maxVertices == y->maxVertices && x->maxPrimitives == y->maxPrimitives && x->threadsPerGroup == y->threadsPerGroup && x->ldsSizeDwords == y->ldsSizeDwords && x->provokingVertex == y->provokingVertex && x->esgsItemSize == y->esgsItemSize && x->passthrough == y->passthrough;
|
||||
};
|
||||
const auto sameTess = [](const std::optional<ShaderRecompiler::TessellationConfiguration>& x, const std::optional<ShaderRecompiler::TessellationConfiguration>& y) {
|
||||
if (x.has_value() != y.has_value()) return false;
|
||||
|
|
|
|||
|
|
@ -467,6 +467,8 @@ bool ValidationKey(const Context& context, std::span<const CompiledShader> shade
|
|||
add(mesh.threadsPerGroup);
|
||||
add(mesh.ldsSizeDwords);
|
||||
add(mesh.provokingVertex);
|
||||
add(mesh.esgsItemSize);
|
||||
add(mesh.passthrough);
|
||||
}
|
||||
add(state.stages.tessellation.has_value());
|
||||
if (state.stages.tessellation) {
|
||||
|
|
|
|||
|
|
@ -362,6 +362,8 @@ std::vector<std::byte> pipelineKey(const Context& context, const State& state, c
|
|||
append(key, mesh.threadsPerGroup);
|
||||
append(key, mesh.ldsSizeDwords);
|
||||
append(key, mesh.provokingVertex);
|
||||
append(key, mesh.esgsItemSize);
|
||||
append(key, mesh.passthrough);
|
||||
}
|
||||
append(key, state.stages.tessellation.has_value());
|
||||
if (state.stages.tessellation) {
|
||||
|
|
|
|||
|
|
@ -341,7 +341,9 @@ ShaderStages DecodeShaderStages(const QueueState& queue) {
|
|||
validate((value & 3u) != 3u && ((value >> 3u) & 3u) != 3u && ((value >> 6u) & 3u) != 3u, "reserved LS_EN, ES_EN or VS_EN encoding");
|
||||
const auto primitive = read(queue.userConfig, 0x242, RegisterBank::UserConfig);
|
||||
const bool tessellation = primitive == 9;
|
||||
const bool geometry = (value & 0x20u) != 0;
|
||||
const bool passthrough = (value & 0x02000000u) != 0;
|
||||
const bool geometry = (value & 0x20u) != 0 || passthrough;
|
||||
validate(!passthrough || (value & 0x2000u) != 0, "passthrough routing without PRIMGEN_EN is unsupported");
|
||||
validate(tessellation == ((value & 4u) != 0), "Patch topology and HS_EN disagree");
|
||||
validate(!tessellation || !geometry, "combined tessellation and geometry is unsupported by the reference path");
|
||||
const auto path = tessellation ? ShaderPath::Tessellation : geometry ? ShaderPath::Geometry : ShaderPath::Vertex;
|
||||
|
|
@ -360,17 +362,17 @@ ShaderStages DecodeShaderStages(const QueueState& queue) {
|
|||
validate(tess.domain == 1 && tess.partitioning == 2 && tess.outputTopology == 2, "only triangular, fractional-odd, clockwise tessellation is supported by the reference path");
|
||||
result.tessellation = tess;
|
||||
} else {
|
||||
validate((value & ~0x0047ec30u) == 0, "unsupported geometry routing, fast launch or wave-ID state");
|
||||
validate((value & ~(passthrough ? 0x02402010u : 0x0047ec30u)) == 0, "unsupported geometry routing, fast launch or wave-ID state");
|
||||
const auto group = read(queue.userConfig, 0x25b, RegisterBank::UserConfig);
|
||||
const auto vertices = (group >> 9u) & 0x1ffu;
|
||||
const auto primitives = group & 0x1ffu;
|
||||
const auto maxVertices = read(queue.context, 0x1ff);
|
||||
const auto verticesPerPrimitive = read(queue.context, 0x2ce);
|
||||
validate((primitive == 1 || primitive == 2 || primitive == 4 || primitive == 6) && read(queue.context, 0x29b) == 2 && verticesPerPrimitive >= 3, "unsupported geometry input or output assembly");
|
||||
const auto verticesPerPrimitive = passthrough ? 3u : read(queue.context, 0x2ce);
|
||||
validate(passthrough ? (primitive == 4 || primitive == 6) : ((primitive == 1 || primitive == 2 || primitive == 4 || primitive == 6) && read(queue.context, 0x29b) == 2 && verticesPerPrimitive >= 3), "unsupported geometry input or output assembly");
|
||||
const auto inputSize = primitive == 1 ? 1u : primitive == 2 ? 2u : 3u;
|
||||
validate(vertices >= inputSize && maxVertices != 0 && maxVertices <= 256 && verticesPerPrimitive <= 256, "invalid geometry subgroup output");
|
||||
const auto inputStep = primitive == 6 ? 1u : inputSize;
|
||||
const auto groupPrimitives = std::min({primitives, (vertices - inputSize) / inputStep + 1u, maxVertices / verticesPerPrimitive});
|
||||
const auto groupPrimitives = std::min({primitives, (vertices - inputSize) / inputStep + 1u, passthrough ? (maxVertices >= inputSize ? (maxVertices - inputSize) / inputStep + 1u : 0u) : maxVertices / verticesPerPrimitive});
|
||||
validate(groupPrimitives != 0, "geometry subgroup contains no primitives");
|
||||
const auto resources = read(queue.shader, 0x8b, RegisterBank::Shader);
|
||||
validate(((read(queue.shader, 0x8a, RegisterBank::Shader) >> 29u) & 3u) == 3 && ((resources >> 16u) & 3u) == 3, "unsupported geometry VGPR allocation");
|
||||
|
|
@ -378,6 +380,7 @@ ShaderStages DecodeShaderStages(const QueueState& queue) {
|
|||
validate(esgsItemSize != 0 && esgsItemSize * vertices <= 0xffffu, "invalid VGT_ESGS_RING_ITEMSIZE");
|
||||
const auto threads = std::max({(groupPrimitives - 1u) * inputStep + inputSize, primitives, maxVertices, primitives * (verticesPerPrimitive - 2u)});
|
||||
result.mesh = ShaderRecompiler::MeshConfiguration{primitive, groupPrimitives, (groupPrimitives - 1u) * inputStep + inputSize, maxVertices, primitives * (verticesPerPrimitive - 2u), ((threads + result.vertexWaveSize - 1u) / result.vertexWaveSize) * result.vertexWaveSize, ((resources >> 19u) & 0xffu) * 128u, 0, esgsItemSize};
|
||||
result.mesh->passthrough = passthrough;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ void hardwareScreenOffsetTests() {
|
|||
|
||||
void ShaderStageTests() {
|
||||
auto queue = makeState();
|
||||
for (const auto routing : {0x2000u, 0x2010u, 0x02002000u, 0x02002010u}) {
|
||||
for (const auto routing : {0x2000u, 0x2010u}) {
|
||||
for (const auto vertexWave32 : {false, true}) {
|
||||
for (const auto fragmentWave32 : {false, true}) {
|
||||
queue.context[0x2d5] = routing | (vertexWave32 ? 0x00400000u : 0u);
|
||||
|
|
@ -210,6 +210,17 @@ void ShaderStageTests() {
|
|||
auto stages = AgcDriver::Graphics::DecodeState(queue).stages;
|
||||
Require(stages.path == AgcDriver::Graphics::ShaderPath::Geometry && stages.mesh && stages.mesh->primitivesPerGroup == 21 && stages.mesh->verticesPerGroup == 63, "geometry assembly changed");
|
||||
Require(stages.mesh->maxVertices == 64 && stages.mesh->maxPrimitives == 21 && stages.mesh->threadsPerGroup == 64 && stages.mesh->esgsItemSize == 4, "geometry subgroup outputs changed");
|
||||
for (const auto routing : {0x02002000u, 0x02002010u, 0x02402000u, 0x02402010u}) {
|
||||
queue.context[0x2d5] = routing;
|
||||
queue.context[0x2ce] = 0;
|
||||
queue.context[0x29b] = 0;
|
||||
const auto pass = AgcDriver::Graphics::DecodeState(queue).stages;
|
||||
Require(pass.path == AgcDriver::Graphics::ShaderPath::Geometry && pass.mesh && pass.mesh->passthrough && pass.mesh->verticesPerGroup == 63 && pass.mesh->primitivesPerGroup == 21, "passthrough subgroup assembly changed");
|
||||
Require(pass.vertexWaveSize == ((routing & 0x00400000u) ? 32u : 64u), "passthrough wave size changed");
|
||||
}
|
||||
queue.context[0x2d5] = 0x2020;
|
||||
queue.context[0x2ce] = 3;
|
||||
queue.context[0x29b] = 2;
|
||||
queue.context[0x2ab] = 0;
|
||||
expectFailure([&] { AgcDriver::Graphics::DecodeState(queue); }, "invalid VGT_ESGS_RING_ITEMSIZE");
|
||||
queue.context[0x2ab] = 4;
|
||||
|
|
|
|||
|
|
@ -180,7 +180,11 @@ void CheckTriangles(const char* what) {
|
|||
|
||||
int main() {
|
||||
try {
|
||||
#ifdef _WIN32
|
||||
_putenv_s("APS5_NO_SHADER_DISK_CACHE", "1");
|
||||
#else
|
||||
setenv("APS5_NO_SHADER_DISK_CACHE", "1", 1);
|
||||
#endif
|
||||
for (std::uint32_t triangle = 0; triangle < Triangles; ++triangle) {
|
||||
const auto vertices = TriangleVertices(triangle);
|
||||
for (std::uint32_t k = 0; k < 3; ++k) {
|
||||
|
|
|
|||
|
|
@ -449,6 +449,13 @@ void verifyMeshConfiguration() {
|
|||
other.graphics = GraphicsCompileContext{0u, {}, otherMesh, std::nullopt, {}};
|
||||
RecompileCacheKey::Build(other, key);
|
||||
require(key != first && RecompileCacheKey::ContextHash(request) != RecompileCacheKey::ContextHash(other), "the cache keys ignore the mesh configuration");
|
||||
otherMesh = mesh;
|
||||
otherMesh.passthrough = true;
|
||||
other.graphics = GraphicsCompileContext{0u, {}, otherMesh, std::nullopt, {}};
|
||||
RecompileCacheKey::Build(other, key);
|
||||
require(key != first && RecompileCacheKey::ContextHash(request) != RecompileCacheKey::ContextHash(other), "the cache keys ignore passthrough input layout");
|
||||
const auto passReplay = RequestSerializer{}.Deserialize(RequestSerializer{}.Serialize(other));
|
||||
require(passReplay.request.graphics->mesh->passthrough && !replay.request.graphics->mesh->passthrough, "passthrough input layout was lost in serialization");
|
||||
}
|
||||
|
||||
ShaderRecompiler::ShaderPixelStageInfo twoParameterPixel() {
|
||||
|
|
|
|||
|
|
@ -78,6 +78,7 @@ private:
|
|||
const auto* mesh = request.graphics && request.graphics->mesh ? &*request.graphics->mesh : nullptr;
|
||||
append(key, mesh != nullptr);
|
||||
if (mesh == nullptr) return;
|
||||
append(key, mesh->passthrough);
|
||||
for (const auto value : {mesh->inputPrimitive, mesh->primitivesPerGroup, mesh->verticesPerGroup, mesh->maxVertices, mesh->maxPrimitives, mesh->threadsPerGroup, mesh->ldsSizeDwords, mesh->provokingVertex, mesh->esgsItemSize}) append(key, value);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -442,6 +442,7 @@ void writeMeshConfiguration(Writer& writer, const MeshConfiguration& configurati
|
|||
writer.WriteU32(configuration.ldsSizeDwords);
|
||||
writer.WriteU32(configuration.provokingVertex);
|
||||
writer.WriteU32(configuration.esgsItemSize);
|
||||
writer.WriteBool(configuration.passthrough);
|
||||
}
|
||||
|
||||
MeshConfiguration readMeshConfiguration(Reader& reader, std::uint32_t version) {
|
||||
|
|
@ -455,6 +456,7 @@ MeshConfiguration readMeshConfiguration(Reader& reader, std::uint32_t version) {
|
|||
configuration.ldsSizeDwords = reader.ReadU32();
|
||||
configuration.provokingVertex = reader.ReadU32();
|
||||
configuration.esgsItemSize = version >= 4u ? reader.ReadU32() : 4u;
|
||||
configuration.passthrough = version >= 6u ? reader.ReadBool() : false;
|
||||
return configuration;
|
||||
}
|
||||
|
||||
|
|
@ -677,7 +679,7 @@ std::string RequestSerializer::Serialize(const RecompileRequest& request) const
|
|||
std::string buffer;
|
||||
Writer writer(buffer);
|
||||
writer.WriteU32(0x41505335u);
|
||||
writer.WriteU32(5u);
|
||||
writer.WriteU32(6u);
|
||||
writeShaderBinary(writer, request.shader);
|
||||
writeGuestContext(writer, request.context);
|
||||
writeSpirvTarget(writer, request.target);
|
||||
|
|
@ -700,7 +702,7 @@ DeserializedRequest RequestSerializer::Deserialize(std::string_view text) const
|
|||
Reader reader(decoded);
|
||||
if (reader.ReadU32() != 0x41505335u) throw std::runtime_error("invalid recompile request signature");
|
||||
const auto version = reader.ReadU32();
|
||||
if (version < 1u || version > 5u) throw std::runtime_error("unsupported recompile request serialization version");
|
||||
if (version < 1u || version > 6u) throw std::runtime_error("unsupported recompile request serialization version");
|
||||
DeserializedRequest result{};
|
||||
result.request.shader = readShaderBinary(reader, result.shaderCode, result.shaderHeader);
|
||||
result.request.context = readGuestContext(reader, result, version);
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@ struct ShaderMeshInputInfo: ShaderWorkgroupInputInfo {
|
|||
std::uint32_t maxPrimitives = 0;
|
||||
std::uint32_t provokingVertex = 0;
|
||||
std::uint32_t esgsItemSize = 0;
|
||||
bool passthrough = false;
|
||||
|
||||
[[nodiscard]] std::uint32_t InputPrimitiveSize() const {
|
||||
return inputPrimitive == 1u ? 1u : inputPrimitive == 2u ? 2u : 3u;
|
||||
|
|
|
|||
|
|
@ -225,6 +225,7 @@ struct MeshConfiguration {
|
|||
std::uint32_t ldsSizeDwords;
|
||||
std::uint32_t provokingVertex;
|
||||
std::uint32_t esgsItemSize = 0;
|
||||
bool passthrough = false;
|
||||
};
|
||||
|
||||
struct TessellationConfiguration {
|
||||
|
|
|
|||
|
|
@ -237,7 +237,11 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO
|
|||
const auto& mesh = options.inputInfo.vertex->mesh;
|
||||
const std::uint32_t size = mesh.InputPrimitiveSize();
|
||||
const std::uint32_t stepCount = mesh.InputPrimitiveStep();
|
||||
if (options.waveSize != 64u || mesh.primitivesPerGroup == 0u || mesh.verticesPerGroup != mesh.InputVertexCount(mesh.primitivesPerGroup) || mesh.verticesPerGroup > totalThreads || mesh.primitivesPerGroup > totalThreads || totalThreads % 64u != 0u || totalThreads > 15u * 64u || mesh.esgsItemSize == 0u || mesh.esgsItemSize * mesh.verticesPerGroup > 0xffffu) {
|
||||
const std::uint32_t waveSize = options.waveSize;
|
||||
if (mesh.passthrough && mesh.inputPrimitive != 4u && mesh.inputPrimitive != 6u) {
|
||||
throw std::runtime_error("passthrough mesh translation requires triangle list or strip topology");
|
||||
}
|
||||
if (mesh.primitivesPerGroup == 0u || mesh.verticesPerGroup != mesh.InputVertexCount(mesh.primitivesPerGroup) || mesh.verticesPerGroup > totalThreads || mesh.primitivesPerGroup > totalThreads || totalThreads % waveSize != 0u || totalThreads > 15u * waveSize || mesh.esgsItemSize == 0u || mesh.esgsItemSize * mesh.verticesPerGroup > 0xffffu) {
|
||||
throw std::runtime_error("mesh shader translation configuration is not supported (wave " + std::to_string(options.waveSize) + ", primitives per group " + std::to_string(mesh.primitivesPerGroup) + ", vertices per group " + std::to_string(mesh.verticesPerGroup) + ", threads " + std::to_string(totalThreads) + ", ESGS item size " + std::to_string(mesh.esgsItemSize) + ")");
|
||||
}
|
||||
constexpr std::uint32_t kTriStripPrimitiveType = 6u;
|
||||
|
|
@ -260,11 +264,11 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO
|
|||
IrValue& vertices = minimum(subtractSaturate(draw(0u), firstVertex), u32(mesh.verticesPerGroup));
|
||||
IrValue& primitives = entryIr.Select(entryIr.ULessThan(vertices, u32(size)), u32(0u), entryIr.IAdd(entryIr.Emit(IrOpcode::UDiv32, IrOpcodeType(IrOpcode::UDiv32), {&subtractSaturate(vertices, u32(size)), &step}), u32(1u)));
|
||||
entryIr.SetScalarReg(static_cast<ScalarReg>(2), entryIr.BitwiseOr(entryIr.ShiftLeftLogical(vertices, u32(12u)), entryIr.ShiftLeftLogical(primitives, u32(22u))));
|
||||
IrValue& wave = entryIr.ShiftRightLogical(local, u32(6u));
|
||||
IrValue& waveBase = entryIr.BitwiseAnd(local, u32(~63u));
|
||||
IrValue& vertexCount = minimum(subtractSaturate(vertices, waveBase), u32(64u));
|
||||
IrValue& primitiveCount = minimum(subtractSaturate(primitives, waveBase), u32(64u));
|
||||
IrValue& waveInfo = entryIr.BitwiseOr(entryIr.ShiftLeftLogical(wave, u32(24u)), u32((totalThreads / 64u) << 28u));
|
||||
IrValue& wave = entryIr.ShiftRightLogical(local, u32(waveSize == 32u ? 5u : 6u));
|
||||
IrValue& waveBase = entryIr.BitwiseAnd(local, u32(~(waveSize - 1u)));
|
||||
IrValue& vertexCount = minimum(subtractSaturate(vertices, waveBase), u32(waveSize));
|
||||
IrValue& primitiveCount = minimum(subtractSaturate(primitives, waveBase), u32(waveSize));
|
||||
IrValue& waveInfo = entryIr.BitwiseOr(entryIr.ShiftLeftLogical(wave, u32(24u)), u32((totalThreads / 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);
|
||||
IrValue& vertex = entryIr.IMul(local, step);
|
||||
|
|
@ -274,6 +278,13 @@ void emitEntryPrologue(IrProgram& program, IrBlock& entryBlock, const TranslateO
|
|||
IrValue& third = size == 3u ? 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)));
|
||||
if (mesh.passthrough) {
|
||||
IrValue& firstIndex = entryIr.IAdd(vertex, parity);
|
||||
IrValue& secondIndex = entryIr.ISub(entryIr.IAdd(vertex, u32(1u)), parity);
|
||||
IrValue& thirdIndex = entryIr.IAdd(vertex, u32(2u));
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(0), entryIr.BitwiseOr(firstIndex, entryIr.BitwiseOr(entryIr.ShiftLeftLogical(secondIndex, u32(10u)), entryIr.ShiftLeftLogical(thirdIndex, u32(20u)))));
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(1), u32(0u));
|
||||
}
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(2), entryIr.IAdd(firstPrimitive, local));
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(3), u32(0u));
|
||||
entryIr.SetVectorReg(static_cast<VectorReg>(4), u32(0u));
|
||||
|
|
|
|||
|
|
@ -184,6 +184,7 @@ ShaderStageInputInfo BuildShaderStageInputInfo(ShaderStageKind stage, const Gues
|
|||
target.maxPrimitives = mesh->maxPrimitives;
|
||||
target.provokingVertex = mesh->provokingVertex;
|
||||
target.esgsItemSize = mesh->esgsItemSize;
|
||||
target.passthrough = mesh->passthrough;
|
||||
if (target.threadsNum[0] == 0u || target.maxVertices == 0u || target.maxPrimitives == 0u || target.provokingVertex > 2u) throw std::runtime_error("ShaderInputInfoBuilder: invalid mesh configuration");
|
||||
}
|
||||
ShaderStageInputInfo result;
|
||||
|
|
|
|||
Loading…
Reference in New Issue