summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEllie Hermaszewska <ellieh@nvidia.com>2022-11-16 09:49:06 +0800
committerGitHub <noreply@github.com>2022-11-16 09:49:06 +0800
commit1643471da0d6239177d11b0301c26d1adf95c0fb (patch)
tree9b8fddf92a5f817541e055a2657f9b0a00f90069
parent4917d71100aafcc38a81cd99d2a44a4cb3a89a0c (diff)
Mesh shader support (#2464)
* Add gdb generated files to .gitignore * Switch to c++17 TODO: Ellie update coding style doc * WIP mesh shaders * Add MeshOutputType and mesh output decorations * Lift array type layout creation out of _createTypeLayout in preparation for sharing it elsewhere * Initial pass at GLSL legalization for mesh shaders * Create output types for builtin mesh outputs This should be rendered as an out paramter block * Handle writes to member fields in mesh shader output * Per primitive output from mesh shaders * Add mesh shader tests * Redeclare mesh output builtins * Remove unused instruction * Emit explicit mesh output max max size * Add unimplemented warning for array members in mesh output * Implement mesh output splitting for GLSL in terms of getSubscriptVal * Allow HLSL syntax for mesh output modifiers * Improve error messages for mesh output * Add test for HLSL style mesh output syntax * Emit explicit mesh output indices max size * HLSL generation support for mesh shaders * Better errors for mesh shader misuse * Neaten comments * Regenerate vs2019 project files * Fix build on vs2019 * Retreat on c++17 Will make the change in a separate PR * slang-glslang binary dep 11.10.0 -> 11.12.0-32 * Fixes for msvc compiler * Update msvc project
-rw-r--r--.gitignore3
-rw-r--r--build/visual-studio/slang/slang.vcxproj2
-rw-r--r--build/visual-studio/slang/slang.vcxproj.filters6
-rw-r--r--deps/target-deps.json14
-rw-r--r--slang.h4
-rw-r--r--source/slang/core.meta.slang38
-rw-r--r--source/slang/hlsl.meta.slang3
-rw-r--r--source/slang/slang-ast-builder.cpp29
-rw-r--r--source/slang/slang-ast-builder.h5
-rw-r--r--source/slang/slang-ast-modifier.h22
-rw-r--r--source/slang/slang-ast-type.h26
-rw-r--r--source/slang/slang-check-decl.cpp58
-rw-r--r--source/slang/slang-diagnostic-defs.h6
-rw-r--r--source/slang/slang-emit-c-like.cpp20
-rw-r--r--source/slang/slang-emit-c-like.h5
-rw-r--r--source/slang/slang-emit-glsl.cpp142
-rw-r--r--source/slang/slang-emit-glsl.h3
-rw-r--r--source/slang/slang-emit-hlsl.cpp38
-rw-r--r--source/slang/slang-emit-hlsl.h1
-rw-r--r--source/slang/slang-emit-spirv.cpp2
-rw-r--r--source/slang/slang-emit.cpp10
-rw-r--r--source/slang/slang-ir-glsl-legalize.cpp677
-rw-r--r--source/slang/slang-ir-glsl-legalize.h3
-rw-r--r--source/slang/slang-ir-inst-defs.h15
-rw-r--r--source/slang/slang-ir-insts.h61
-rw-r--r--source/slang/slang-ir-legalize-mesh-outputs.cpp37
-rw-r--r--source/slang/slang-ir-legalize-mesh-outputs.h12
-rw-r--r--source/slang/slang-ir-specialize-resources.cpp2
-rw-r--r--source/slang/slang-ir-spirv-legalize.cpp5
-rw-r--r--source/slang/slang-ir-spirv-legalize.h2
-rw-r--r--source/slang/slang-ir.cpp47
-rw-r--r--source/slang/slang-ir.h141
-rw-r--r--source/slang/slang-lower-to-ir.cpp29
-rw-r--r--source/slang/slang-options.cpp2
-rw-r--r--source/slang/slang-parameter-binding.cpp27
-rw-r--r--source/slang/slang-parser.cpp5
-rw-r--r--source/slang/slang-profile-defs.h8
-rw-r--r--source/slang/slang-reflection-api.cpp4
-rw-r--r--source/slang/slang-syntax.cpp16
-rw-r--r--source/slang/slang-type-layout.cpp319
-rw-r--r--source/slang/slang.natvis6
-rw-r--r--tests/pipeline/rasterization/mesh/component-write.slang49
-rw-r--r--tests/pipeline/rasterization/mesh/component-write.slang.glsl39
-rw-r--r--tests/pipeline/rasterization/mesh/hello.slang49
-rw-r--r--tests/pipeline/rasterization/mesh/hello.slang.glsl46
-rw-r--r--tests/pipeline/rasterization/mesh/hello.slang.hlsl36
-rw-r--r--tests/pipeline/rasterization/mesh/hlsl-syntax.slang54
-rw-r--r--tests/pipeline/rasterization/mesh/hlsl-syntax.slang.glsl51
-rw-r--r--tests/pipeline/rasterization/mesh/nested-component-write.slang51
-rw-r--r--tests/pipeline/rasterization/mesh/nested-component-write.slang.glsl37
-rw-r--r--tests/pipeline/rasterization/mesh/passing-outputs.slang114
-rw-r--r--tests/pipeline/rasterization/mesh/passing-outputs.slang.glsl172
-rw-r--r--tests/pipeline/rasterization/mesh/primitive-output.slang57
-rw-r--r--tests/pipeline/rasterization/mesh/primitive-output.slang.glsl66
-rw-r--r--tests/pipeline/rasterization/mesh/swizzled-store.slang32
-rw-r--r--tests/pipeline/rasterization/mesh/swizzled-store.slang.glsl35
56 files changed, 2515 insertions, 228 deletions
diff --git a/.gitignore b/.gitignore
index 0f7cdb6b9..8cf116d3b 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,6 +12,7 @@
*.zip
*.ini
.clang-format
+.gdb_history
bin/
intermediate/
@@ -74,4 +75,4 @@ build/**/*.lastbuildstate
build/**/*.recipe
build/**/*.log
*.dll
-*.dxil \ No newline at end of file
+*.dxil
diff --git a/build/visual-studio/slang/slang.vcxproj b/build/visual-studio/slang/slang.vcxproj
index 020bacb0e..42eb3c26c 100644
--- a/build/visual-studio/slang/slang.vcxproj
+++ b/build/visual-studio/slang/slang.vcxproj
@@ -365,6 +365,7 @@ IF EXIST ..\..\..\external\slang-glslang\bin\windows-aarch64\release\slang-glsla
<ClInclude Include="..\..\..\source\slang\slang-ir-insts.h" />
<ClInclude Include="..\..\..\source\slang\slang-ir-layout.h" />
<ClInclude Include="..\..\..\source\slang\slang-ir-legalize-array-return-type.h" />
+ <ClInclude Include="..\..\..\source\slang\slang-ir-legalize-mesh-outputs.h" />
<ClInclude Include="..\..\..\source\slang\slang-ir-legalize-varying-params.h" />
<ClInclude Include="..\..\..\source\slang\slang-ir-link.h" />
<ClInclude Include="..\..\..\source\slang\slang-ir-liveness.h" />
@@ -533,6 +534,7 @@ IF EXIST ..\..\..\external\slang-glslang\bin\windows-aarch64\release\slang-glsla
<ClCompile Include="..\..\..\source\slang\slang-ir-inline.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-ir-layout.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-ir-legalize-array-return-type.cpp" />
+ <ClCompile Include="..\..\..\source\slang\slang-ir-legalize-mesh-outputs.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-ir-legalize-types.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-ir-legalize-varying-params.cpp" />
<ClCompile Include="..\..\..\source\slang\slang-ir-link.cpp" />
diff --git a/build/visual-studio/slang/slang.vcxproj.filters b/build/visual-studio/slang/slang.vcxproj.filters
index 9e7d0db5e..45eb4d755 100644
--- a/build/visual-studio/slang/slang.vcxproj.filters
+++ b/build/visual-studio/slang/slang.vcxproj.filters
@@ -228,6 +228,9 @@
<ClInclude Include="..\..\..\source\slang\slang-ir-legalize-array-return-type.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="..\..\..\source\slang\slang-ir-legalize-mesh-outputs.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
<ClInclude Include="..\..\..\source\slang\slang-ir-legalize-varying-params.h">
<Filter>Header Files</Filter>
</ClInclude>
@@ -728,6 +731,9 @@
<ClCompile Include="..\..\..\source\slang\slang-ir-legalize-array-return-type.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="..\..\..\source\slang\slang-ir-legalize-mesh-outputs.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
<ClCompile Include="..\..\..\source\slang\slang-ir-legalize-types.cpp">
<Filter>Source Files</Filter>
</ClCompile>
diff --git a/deps/target-deps.json b/deps/target-deps.json
index e195d41db..8ac59f335 100644
--- a/deps/target-deps.json
+++ b/deps/target-deps.json
@@ -16,16 +16,16 @@
},
{
"name" : "slang-glslang",
- "baseUrl" : "https://github.com/shader-slang/slang-glslang/releases/download/v11.10.0/",
+ "baseUrl" : "https://github.com/shader-slang/slang-glslang/releases/download/v11.12.0-32/",
"optional" : true,
"packages" :
{
- "windows-x86_64" : { "type" : "url", "path" : "slang-glslang-11.10.0-windows-x64-release.zip" },
- "windows-x86" : { "type": "url", "path" : "slang-glslang-11.10.0-windows-win32-release.zip" },
- "linux-x86_64" : { "type": "url", "path" : "slang-glslang-11.10.0-linux-x64-release.zip" },
- "linux-x86" : { "type": "url", "path" : "slang-glslang-11.10.0-linux-x86-release.zip" },
- "macosx-x86_64" : { "type": "url", "path" : "slang-glslang-11.10.0-macosx-x86_64-release.zip" },
- "windows-aarch64" : { "type" : "url", "path" : "slang-glslang-11.10.0-windows-aarch64-release.zip" }
+ "windows-x86_64" : { "type" : "url", "path" : "slang-glslang-11.12.0-32-windows-x64-release.zip" },
+ "windows-x86" : { "type": "url", "path" : "slang-glslang-11.12.0-32-windows-win32-release.zip" },
+ "linux-x86_64" : { "type": "url", "path" : "slang-glslang-11.12.0-32-linux-x64-release.zip" },
+ "linux-x86" : { "type": "url", "path" : "slang-glslang-11.12.0-32-linux-x86-release.zip" },
+ "macosx-x86_64" : { "type": "url", "path" : "slang-glslang-11.12.0-32-macosx-x86_64-release.zip" },
+ "windows-aarch64" : { "type" : "url", "path" : "slang-glslang-11.12.0-32-windows-aarch64-release.zip" }
}
},
{
diff --git a/slang.h b/slang.h
index 9c85a2f74..6589de67d 100644
--- a/slang.h
+++ b/slang.h
@@ -1868,6 +1868,7 @@ extern "C"
SLANG_TYPE_KIND_GENERIC_TYPE_PARAMETER,
SLANG_TYPE_KIND_INTERFACE,
SLANG_TYPE_KIND_OUTPUT_STREAM,
+ SLANG_TYPE_KIND_MESH_OUTPUT,
SLANG_TYPE_KIND_SPECIALIZED,
SLANG_TYPE_KIND_FEEDBACK,
SLANG_TYPE_KIND_COUNT,
@@ -1959,6 +1960,9 @@ extern "C"
// HLSL register `space`, Vulkan GLSL `set`
SLANG_PARAMETER_CATEGORY_REGISTER_SPACE,
+ // TODO: Ellie, Both APIs treat mesh outputs as more or less varying output,
+ // Does it deserve to be represented here??
+
// A parameter whose type is to be specialized by a global generic type argument
SLANG_PARAMETER_CATEGORY_GENERIC,
diff --git a/source/slang/core.meta.slang b/source/slang/core.meta.slang
index 0a6278ec1..d1328b72a 100644
--- a/source/slang/core.meta.slang
+++ b/source/slang/core.meta.slang
@@ -849,6 +849,44 @@ __intrinsic_type($(kIROp_ParameterBlockType))
__magic_type(ParameterBlockType)
struct ParameterBlock {}
+__generic<T, let MAX_VERTS : uint>
+__magic_type(VerticesType)
+__intrinsic_type($(kIROp_VerticesType))
+struct Vertices
+{
+ __subscript(uint index) -> T
+ {
+ // TODO: Ellie make sure these remains write only
+ __intrinsic_op($(kIROp_getElementPtr))
+ ref;
+ }
+};
+
+__generic<T, let MAX_PRIMITIVES : uint>
+__magic_type(IndicesType)
+__intrinsic_type($(kIROp_IndicesType))
+struct Indices
+{
+ __subscript(uint index) -> T
+ {
+ // TODO: Ellie: It's illegal to not write out the whole primitive at once, should we use set over ref?
+ __intrinsic_op($(kIROp_getElementPtr))
+ ref;
+ }
+};
+
+__generic<T, let MAX_PRIMITIVES : uint>
+__magic_type(PrimitivesType)
+__intrinsic_type($(kIROp_PrimitivesType))
+struct Primitives
+{
+ __subscript(uint index) -> T
+ {
+ __intrinsic_op($(kIROp_getElementPtr))
+ ref;
+ }
+};
+
//@ hidden:
// Need to add constructors to the types above
diff --git a/source/slang/hlsl.meta.slang b/source/slang/hlsl.meta.slang
index 81df2b5e4..aa8ebf847 100644
--- a/source/slang/hlsl.meta.slang
+++ b/source/slang/hlsl.meta.slang
@@ -5033,6 +5033,9 @@ float dot2add(float2 left, float2 right, float acc);
//
// Set the number of output vertices and primitives for a mesh shader invocation.
+__target_intrinsic(glsl, "SetMeshOutputsEXT")
+__glsl_extension(GL_EXT_mesh_shader)
+__glsl_version(450)
void SetMeshOutputCounts(uint vertexCount, uint primitiveCount);
// Specify the number of downstream mesh shader thread groups to invoke from an amplification shader,
diff --git a/source/slang/slang-ast-builder.cpp b/source/slang/slang-ast-builder.cpp
index 6249d7825..819fe7d25 100644
--- a/source/slang/slang-ast-builder.cpp
+++ b/source/slang/slang-ast-builder.cpp
@@ -345,6 +345,35 @@ bool ASTBuilder::isDifferentiableInterfaceAvailable()
return (m_sharedASTBuilder->tryFindMagicDecl("DifferentiableType") != nullptr);
}
+MeshOutputType* ASTBuilder::getMeshOutputTypeFromModifier(
+ HLSLMeshShaderOutputModifier* modifier,
+ Type* elementType,
+ IntVal* maxElementCount)
+{
+ SLANG_ASSERT(modifier);
+ SLANG_ASSERT(elementType);
+ SLANG_ASSERT(maxElementCount);
+
+ const char* declName
+ = as<HLSLVerticesModifier>(modifier) ? "VerticesType"
+ : as<HLSLIndicesModifier>(modifier) ? "IndicesType"
+ : as<HLSLPrimitivesModifier>(modifier) ? "PrimitivesType"
+ : (SLANG_UNEXPECTED("Unhandled mesh output modifier"), nullptr);
+ auto genericDecl = dynamicCast<GenericDecl>(m_sharedASTBuilder->findMagicDecl(declName));
+
+ auto typeDecl = genericDecl->inner;
+
+ auto substitutions = getOrCreate<GenericSubstitution>(
+ genericDecl,
+ elementType,
+ maxElementCount);
+
+ auto declRef = DeclRef<Decl>(typeDecl, substitutions);
+ auto rsType = DeclRefType::create(this, declRef);
+
+ return as<MeshOutputType>(rsType);
+}
+
DeclRef<Decl> ASTBuilder::getBuiltinDeclRef(const char* builtinMagicTypeName, Val* genericArg)
{
DeclRef<Decl> declRef;
diff --git a/source/slang/slang-ast-builder.h b/source/slang/slang-ast-builder.h
index 190e3727d..6c0e1af36 100644
--- a/source/slang/slang-ast-builder.h
+++ b/source/slang/slang-ast-builder.h
@@ -345,6 +345,11 @@ public:
bool isDifferentiableInterfaceAvailable();
+ MeshOutputType* getMeshOutputTypeFromModifier(
+ HLSLMeshShaderOutputModifier* modifier,
+ Type* elementType,
+ IntVal* maxElementCount);
+
DeclRef<Decl> getBuiltinDeclRef(const char* builtinMagicTypeName, Val* genericArg);
Type* getAndType(Type* left, Type* right);
diff --git a/source/slang/slang-ast-modifier.h b/source/slang/slang-ast-modifier.h
index 9146c21d9..24f614019 100644
--- a/source/slang/slang-ast-modifier.h
+++ b/source/slang/slang-ast-modifier.h
@@ -902,6 +902,28 @@ class HLSLTriangleAdjModifier : public HLSLGeometryShaderInputPrimitiveTypeModif
SLANG_AST_CLASS(HLSLTriangleAdjModifier)
};
+// Mesh shader paramters
+
+class HLSLMeshShaderOutputModifier : public Modifier
+{
+ SLANG_AST_CLASS(HLSLMeshShaderOutputModifier)
+};
+
+class HLSLVerticesModifier : public HLSLMeshShaderOutputModifier
+{
+ SLANG_AST_CLASS(HLSLVerticesModifier)
+};
+
+class HLSLIndicesModifier : public HLSLMeshShaderOutputModifier
+{
+ SLANG_AST_CLASS(HLSLIndicesModifier)
+};
+
+class HLSLPrimitivesModifier : public HLSLMeshShaderOutputModifier
+{
+ SLANG_AST_CLASS(HLSLPrimitivesModifier)
+};
+
// A modifier to indicate that a constructor/initializer can be used
// to perform implicit type conversion, and to specify the cost of
// the conversion, if applied.
diff --git a/source/slang/slang-ast-type.h b/source/slang/slang-ast-type.h
index d9829c4ca..d85391d58 100644
--- a/source/slang/slang-ast-type.h
+++ b/source/slang/slang-ast-type.h
@@ -354,6 +354,32 @@ class HLSLTriangleStreamType : public HLSLStreamOutputType
SLANG_AST_CLASS(HLSLTriangleStreamType)
};
+// mesh shader output types
+
+class MeshOutputType : public BuiltinGenericType
+{
+ SLANG_AST_CLASS(MeshOutputType)
+
+ Type* getElementType();
+
+ IntVal* getMaxElementCount();
+};
+
+class VerticesType : public MeshOutputType
+{
+ SLANG_AST_CLASS(VerticesType)
+};
+
+class IndicesType : public MeshOutputType
+{
+ SLANG_AST_CLASS(IndicesType)
+};
+
+class PrimitivesType : public MeshOutputType
+{
+ SLANG_AST_CLASS(PrimitivesType)
+};
+
//
class GLSLInputAttachmentType : public BuiltinType
diff --git a/source/slang/slang-check-decl.cpp b/source/slang/slang-check-decl.cpp
index b33c33e7a..aae741770 100644
--- a/source/slang/slang-check-decl.cpp
+++ b/source/slang/slang-check-decl.cpp
@@ -47,6 +47,7 @@ namespace Slang
void checkDerivativeMemberAttribute(VarDeclBase* varDecl, DerivativeMemberAttribute* attr);
void checkExtensionExternVarAttribute(VarDeclBase* varDecl, ExtensionExternVarModifier* m);
+ void checkMeshOutputDecl(VarDeclBase* varDecl);
void checkVarDeclCommon(VarDeclBase* varDecl);
@@ -1178,6 +1179,8 @@ namespace Slang
validateArraySizeForVariable(varDecl);
}
+ checkMeshOutputDecl(varDecl);
+
// The NVAPI library allows user code to express extended operations
// (not supported natively by D3D HLSL) by communicating with
// a specially identified shader parameter called `g_NvidiaExt`.
@@ -5398,7 +5401,62 @@ namespace Slang
{
typeExpr = CheckUsableType(typeExpr);
paramDecl->type = typeExpr;
+ checkMeshOutputDecl(paramDecl);
+ }
+ }
+
+ // This checks that the declaration is marked as "out" and changes the hlsl
+ // modifier based syntax into a proper type.
+ void SemanticsDeclHeaderVisitor::checkMeshOutputDecl(VarDeclBase* varDecl)
+ {
+ auto modifier = varDecl->findModifier<HLSLMeshShaderOutputModifier>();
+ auto meshOutputType = as<MeshOutputType>(varDecl->type.type);
+ bool isMeshOutput = modifier || meshOutputType;
+
+ if(!isMeshOutput)
+ {
+ return;
+ }
+ if(!varDecl->findModifier<OutModifier>())
+ {
+ getSink()->diagnose(varDecl, Diagnostics::meshOutputMustBeOut);
+ }
+
+ //
+ // If necessary, convert to our typed representation
+ //
+ if(!modifier)
+ {
+ return;
}
+ if(meshOutputType)
+ {
+ getSink()->diagnose(modifier, Diagnostics::unnecessaryHLSLMeshOutputModifier);
+ varDecl->type.type = m_astBuilder->getErrorType();
+ return;
+ }
+ auto indexExpr = as<IndexExpr>(varDecl->type.exp);
+ if(!indexExpr)
+ {
+ getSink()->diagnose(varDecl, Diagnostics::meshOutputMustBeArray);
+ varDecl->type.type = m_astBuilder->getErrorType();
+ return;
+ }
+ if(indexExpr->indexExprs.getCount() != 1)
+ {
+ getSink()->diagnose(varDecl, Diagnostics::meshOutputArrayMustHaveSize);
+ varDecl->type.type = m_astBuilder->getErrorType();
+ return;
+ }
+ auto base = ExpectAType(indexExpr->baseExpression);
+ auto index = CheckIntegerConstantExpression(
+ indexExpr->indexExprs[0],
+ IntegerConstantExpressionCoercionType::AnyInteger,
+ nullptr,
+ getSink());
+
+ Type* d = m_astBuilder->getMeshOutputTypeFromModifier(modifier, base, index);
+ varDecl->type.type = d;
}
void SemanticsDeclBodyVisitor::visitParamDecl(ParamDecl* paramDecl)
diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h
index 5263ac39b..0dfd06923 100644
--- a/source/slang/slang-diagnostic-defs.h
+++ b/source/slang/slang-diagnostic-defs.h
@@ -624,6 +624,12 @@ DIAGNOSTIC(52007, Error, typeCannotBeUsedInDynamicDispatch, "failed to generate
DIAGNOSTIC(52008, Error, dynamicDispatchOnSpecializeOnlyInterface, "type '$0' is marked for specialization only, but dynamic dispatch is needed for the call.")
DIAGNOSTIC(53001,Error, invalidTypeMarshallingForImportedDLLSymbol, "invalid type marshalling in imported func $0.")
+DIAGNOSTIC(54001, Error, meshOutputMustBeOut, "Mesh shader outputs must be declared with 'out'.")
+DIAGNOSTIC(54002, Error, meshOutputMustBeArray, "HLSL style mesh shader outputs must be arrays")
+DIAGNOSTIC(54003, Error, meshOutputArrayMustHaveSize, "HLSL style mesh shader output arrays must have a length specified")
+DIAGNOSTIC(54004, Warning, unnecessaryHLSLMeshOutputModifier, "Unnecessary HLSL style mesh shader output modifier")
+
+
//
// 8xxxx - Issues specific to a particular library/technology/platform/etc.
//
diff --git a/source/slang/slang-emit-c-like.cpp b/source/slang/slang-emit-c-like.cpp
index e872bffb1..5b2370665 100644
--- a/source/slang/slang-emit-c-like.cpp
+++ b/source/slang/slang-emit-c-like.cpp
@@ -1224,6 +1224,10 @@ bool CLikeSourceEmitter::shouldFoldInstIntoUseSites(IRInst* inst)
{
return true;
}
+ else if(as<IRMeshOutputType>(type))
+ {
+ return true;
+ }
}
@@ -2699,6 +2703,7 @@ void CLikeSourceEmitter::emitSimpleFuncParamImpl(IRParam* param)
|| layout->usesResourceKind(LayoutResourceKind::VaryingOutput))
{
emitInterpolationModifiers(param, paramType, layout);
+ emitMeshOutputModifiers(param);
}
}
@@ -2922,6 +2927,13 @@ void CLikeSourceEmitter::emitStruct(IRStructType* structType)
emitPostKeywordTypeAttributes(structType);
m_writer->emit(getName(structType));
+
+ emitStructDeclarationsBlock(structType);
+ m_writer->emit(";\n\n");
+}
+
+void CLikeSourceEmitter::emitStructDeclarationsBlock(IRStructType* structType)
+{
m_writer->emit("\n{\n");
m_writer->indent();
@@ -2947,7 +2959,7 @@ void CLikeSourceEmitter::emitStruct(IRStructType* structType)
}
m_writer->dedent();
- m_writer->emit("};\n\n");
+ m_writer->emit("}");
}
void CLikeSourceEmitter::emitClass(IRClassType* classType)
@@ -3059,6 +3071,11 @@ void CLikeSourceEmitter::emitInterpolationModifiers(IRInst* varInst, IRType* val
emitInterpolationModifiersImpl(varInst, valueType, layout);
}
+void CLikeSourceEmitter::emitMeshOutputModifiers(IRInst* varInst)
+{
+ emitMeshOutputModifiersImpl(varInst);
+}
+
/// Emit modifiers that should apply even for a declaration of an SSA temporary.
void CLikeSourceEmitter::emitTempModifiers(IRInst* temp)
{
@@ -3087,6 +3104,7 @@ void CLikeSourceEmitter::emitVarModifiers(IRVarLayout* layout, IRInst* varDecl,
|| layout->usesResourceKind(LayoutResourceKind::VaryingOutput))
{
emitInterpolationModifiers(varDecl, varType, layout);
+ emitMeshOutputModifiers(varDecl);
}
// Output target specific qualifiers
diff --git a/source/slang/slang-emit-c-like.h b/source/slang/slang-emit-c-like.h
index e702dbf01..ff229c38b 100644
--- a/source/slang/slang-emit-c-like.h
+++ b/source/slang/slang-emit-c-like.h
@@ -401,12 +401,16 @@ public:
void emitFuncDecorations(IRFunc* func) { emitFuncDecorationsImpl(func); }
void emitStruct(IRStructType* structType);
+ // This is used independently of `emitStruct` by some GLSL parameter group
+ // output functionality
+ void emitStructDeclarationsBlock(IRStructType* structType);
void emitClass(IRClassType* structType);
/// Emit type attributes that should appear after, e.g., a `struct` keyword
void emitPostKeywordTypeAttributes(IRInst* inst) { emitPostKeywordTypeAttributesImpl(inst); }
void emitInterpolationModifiers(IRInst* varInst, IRType* valueType, IRVarLayout* layout);
+ void emitMeshOutputModifiers(IRInst* varInst);
@@ -492,6 +496,7 @@ public:
virtual void emitSimpleFuncParamImpl(IRParam* param);
virtual void emitSimpleFuncParamsImpl(IRFunc* func);
virtual void emitInterpolationModifiersImpl(IRInst* varInst, IRType* valueType, IRVarLayout* layout) { SLANG_UNUSED(varInst); SLANG_UNUSED(valueType); SLANG_UNUSED(layout); }
+ virtual void emitMeshOutputModifiersImpl(IRInst* varInst) { SLANG_UNUSED(varInst) }
virtual void emitSimpleTypeImpl(IRType* type) = 0;
virtual void emitVarDecorationsImpl(IRInst* varDecl) { SLANG_UNUSED(varDecl); }
virtual void emitMatrixLayoutModifiersImpl(IRVarLayout* layout) { SLANG_UNUSED(layout); }
diff --git a/source/slang/slang-emit-glsl.cpp b/source/slang/slang-emit-glsl.cpp
index e187f5e59..8a074d057 100644
--- a/source/slang/slang-emit-glsl.cpp
+++ b/source/slang/slang-emit-glsl.cpp
@@ -41,6 +41,12 @@ SlangResult GLSLSourceEmitter::init()
_requireRayTracing();
break;
}
+ case Stage::Mesh:
+ case Stage::Amplification:
+ {
+ _requireGLSLExtension(UnownedStringSlice::fromLiteral("GL_EXT_mesh_shader"));
+ break;
+ }
default: break;
}
@@ -732,6 +738,66 @@ void GLSLSourceEmitter::_emitGLSLTypePrefix(IRType* type, bool promoteHalfToFloa
}
}
+void GLSLSourceEmitter::_maybeEmitGLSLBuiltin(IRGlobalParam* var, UnownedStringSlice name)
+{
+ // It's important for us to redeclare these mesh output builtins with an
+ // explicit array size to allow indexing into them with a variable
+ // according to the rules of GLSL.
+ if(name == "gl_MeshPrimitivesEXT" || name == "gl_MeshVerticesEXT")
+ {
+ // GLSL doesn't allow us to specify the struct outside the block
+ // declaration, so we snoop the underlying struct type here and emit
+ // that inline.
+
+ auto paramGroupType = as<IRGLSLOutputParameterGroupType>(var->getFullType());
+ SLANG_ASSERT(paramGroupType && "Mesh shader builtin output was not a paramter group");
+ auto arrayType = as<IRArrayTypeBase>(paramGroupType->getOperand(0));
+ SLANG_ASSERT(paramGroupType && "Mesh shader builtin output was not an array");
+ auto elementType = as<IRStructType>(arrayType->getElementType());
+ SLANG_ASSERT(paramGroupType && "Mesh shader builtin output was not an array of structs");
+ auto elementTypeNameOp = composeGetters<IRStringLit>(
+ elementType,
+ &IRInst::findDecoration<IRTargetIntrinsicDecoration>,
+ &IRTargetIntrinsicDecoration::getDefinitionOperand);
+ SLANG_ASSERT(elementTypeNameOp && "Mesh shader builtin output element type wasn't named");
+ auto elementTypeName = elementTypeNameOp->getStringSlice();
+
+ // // It would be nice to use emitVarModifiers here, however with
+ // // LRK::BuiltinVaryingOutput this is going to add an illegal location
+ // // layout qualifier.
+ // auto layout = getVarLayout(var);
+ // SLANG_ASSERT(layout && "Mesh shader builtin output has no layout");
+ // SLANG_ASSERT(layout->usesResourceKind(LayoutResourceKind::VaryingOutput));
+ // emitVarModifiers(layout, var, arrayType);
+ emitMeshOutputModifiers(var);
+ m_writer->emit("out");
+ m_writer->emit(" ");
+ m_writer->emit(elementTypeName);
+ emitStructDeclarationsBlock(elementType);
+ m_writer->emit(" ");
+ m_writer->emit(name);
+ emitArrayBrackets(arrayType);
+ m_writer->emit(";\n\n");
+ }
+ else if(name == "gl_PrimitivePointIndicesEXT"
+ || name == "gl_PrimitiveLineIndicesEXT"
+ || name == "gl_PrimitiveTriangleIndicesEXT")
+ {
+ // GLSL has some specific requirements about how these are declared,
+ // Do it manually here to avoid `emitGlobalParam` emitting
+ // decorations/layout we are not allowed to output.
+ auto varType = composeGetters<IRType>(
+ var,
+ &IRGlobalParam::getDataType,
+ &IROutTypeBase::getValueType);
+ SLANG_ASSERT(varType && "Indices mesh output dind't have an 'out' type");
+
+ m_writer->emit("out ");
+ emitType(varType, getName(var));
+ m_writer->emit(";\n\n");
+ }
+}
+
void GLSLSourceEmitter::_requireBaseType(BaseType baseType)
{
m_glslExtensionTracker->requireBaseTypeExtension(baseType);
@@ -922,24 +988,29 @@ void GLSLSourceEmitter::emitEntryPointAttributesImpl(IRFunc* irFunc, IREntryPoin
auto profile = entryPointDecor->getProfile();
auto stage = profile.getStage();
+ auto emitLocalSizeLayout = [&]()
+ {
+ Int sizeAlongAxis[kThreadGroupAxisCount];
+ getComputeThreadGroupSize(irFunc, sizeAlongAxis);
+
+ m_writer->emit("layout(");
+ char const* axes[] = { "x", "y", "z" };
+ for (int ii = 0; ii < kThreadGroupAxisCount; ++ii)
+ {
+ if (ii != 0) m_writer->emit(", ");
+ m_writer->emit("local_size_");
+ m_writer->emit(axes[ii]);
+ m_writer->emit(" = ");
+ m_writer->emit(sizeAlongAxis[ii]);
+ }
+ m_writer->emit(") in;\n");
+ };
+
switch (stage)
{
case Stage::Compute:
{
- Int sizeAlongAxis[kThreadGroupAxisCount];
- getComputeThreadGroupSize(irFunc, sizeAlongAxis);
-
- m_writer->emit("layout(");
- char const* axes[] = { "x", "y", "z" };
- for (int ii = 0; ii < kThreadGroupAxisCount; ++ii)
- {
- if (ii != 0) m_writer->emit(", ");
- m_writer->emit("local_size_");
- m_writer->emit(axes[ii]);
- m_writer->emit(" = ");
- m_writer->emit(sizeAlongAxis[ii]);
- }
- m_writer->emit(") in;\n");
+ emitLocalSizeLayout();
}
break;
case Stage::Geometry:
@@ -1001,6 +1072,32 @@ void GLSLSourceEmitter::emitEntryPointAttributesImpl(IRFunc* irFunc, IREntryPoin
}
break;
}
+ case Stage::Mesh:
+ {
+ emitLocalSizeLayout();
+ if (auto decor = irFunc->findDecoration<IRVerticesDecoration>())
+ {
+ m_writer->emit("layout(max_vertices = ");
+ m_writer->emit(decor->getMaxSize()->getValue());
+ m_writer->emit(") out;\n");
+ }
+ if (auto decor = irFunc->findDecoration<IRPrimitivesDecoration>())
+ {
+ m_writer->emit("layout(max_primitives = ");
+ m_writer->emit(decor->getMaxSize()->getValue());
+ m_writer->emit(") out;\n");
+ }
+ if (auto decor = irFunc->findDecoration<IROutputTopologyDecoration>())
+ {
+ // TODO: Ellie validate here/elsewhere, what's allowed here is
+ // different from the tesselator
+ // The naming here is plural, so add an 's'
+ m_writer->emit("layout(");
+ m_writer->emit(decor->getTopology()->getStringSlice());
+ m_writer->emit("s) out;\n");
+ }
+ }
+ break;
// TODO: There are other stages that will need this kind of handling.
default:
break;
@@ -1096,12 +1193,10 @@ bool GLSLSourceEmitter::tryEmitGlobalParamImpl(IRGlobalParam* varDecl, IRType* v
//
if (auto linkageDecoration = varDecl->findDecoration<IRLinkageDecoration>())
{
- if (linkageDecoration->getMangledName().startsWith("gl_"))
+ auto name = linkageDecoration->getMangledName();
+ if (name.startsWith("gl_"))
{
- // The variable represents an OpenGL system value,
- // so we will assume that it doesn't need to be declared.
- //
- // TODO: handle case where we *should* declare the variable.
+ _maybeEmitGLSLBuiltin(varDecl, name);
return true;
}
}
@@ -2233,6 +2328,15 @@ void GLSLSourceEmitter::emitInterpolationModifiersImpl(IRInst* varInst, IRType*
}
}
+void GLSLSourceEmitter::emitMeshOutputModifiersImpl(IRInst* varInst)
+{
+ if(varInst->findDecoration<IRGLSLPrimitivesRateDecoration>())
+ {
+ m_writer->emit("perprimitiveEXT");
+ m_writer->emit(" ");
+ }
+}
+
void GLSLSourceEmitter::emitVarDecorationsImpl(IRInst* varDecl)
{
// Deal with Vulkan raytracing layout stuff *before* we
diff --git a/source/slang/slang-emit-glsl.h b/source/slang/slang-emit-glsl.h
index 3edf7bec7..6a18fe035 100644
--- a/source/slang/slang-emit-glsl.h
+++ b/source/slang/slang-emit-glsl.h
@@ -33,6 +33,7 @@ protected:
virtual void emitRateQualifiersImpl(IRRate* rate) SLANG_OVERRIDE;
virtual void emitInterpolationModifiersImpl(IRInst* varInst, IRType* valueType, IRVarLayout* layout) SLANG_OVERRIDE;
+ virtual void emitMeshOutputModifiersImpl(IRInst* varInst) SLANG_OVERRIDE;
virtual void emitSimpleTypeImpl(IRType* type) SLANG_OVERRIDE;
virtual void emitVectorTypeNameImpl(IRType* elementType, IRIntegerValue elementCount) SLANG_OVERRIDE;
virtual void emitVarDecorationsImpl(IRInst* varDecl) SLANG_OVERRIDE;
@@ -64,6 +65,8 @@ protected:
void _emitGLSLTypePrefix(IRType* type, bool promoteHalfToFloat = false);
+ void _maybeEmitGLSLBuiltin(IRGlobalParam* var, UnownedStringSlice name);
+
void _requireGLSLExtension(const UnownedStringSlice& name);
void _requireGLSLVersion(ProfileVersion version);
diff --git a/source/slang/slang-emit-hlsl.cpp b/source/slang/slang-emit-hlsl.cpp
index 187115232..8891f06a6 100644
--- a/source/slang/slang-emit-hlsl.cpp
+++ b/source/slang/slang-emit-hlsl.cpp
@@ -313,9 +313,7 @@ void HLSLSourceEmitter::emitEntryPointAttributesImpl(IRFunc* irFunc, IREntryPoin
}
}
- switch (stage)
- {
- case Stage::Compute:
+ auto emitNumThreadsAttribute = [&]()
{
Int sizeAlongAxis[kThreadGroupAxisCount];
getComputeThreadGroupSize(irFunc, sizeAlongAxis);
@@ -327,6 +325,13 @@ void HLSLSourceEmitter::emitEntryPointAttributesImpl(IRFunc* irFunc, IREntryPoin
m_writer->emit(sizeAlongAxis[ii]);
}
m_writer->emit(")]\n");
+ };
+
+ switch (stage)
+ {
+ case Stage::Compute:
+ {
+ emitNumThreadsAttribute();
}
break;
case Stage::Geometry:
@@ -406,6 +411,18 @@ void HLSLSourceEmitter::emitEntryPointAttributesImpl(IRFunc* irFunc, IREntryPoin
}
break;
}
+ case Stage::Mesh:
+ {
+ emitNumThreadsAttribute();
+ if (auto decor = irFunc->findDecoration<IROutputTopologyDecoration>())
+ {
+ // TODO: Ellie validate here/elsewhere, what's allowed here is
+ // different from the tesselator
+ // The naming here is plural, so add an 's'
+ _emitHLSLDecorationSingleString("outputtopology", irFunc, decor->getTopology());
+ }
+ break;
+ }
// TODO: There are other stages that will need this kind of handling.
default:
break;
@@ -1052,6 +1069,7 @@ void HLSLSourceEmitter::_emitPrefixTypeAttr(IRAttr* attr)
void HLSLSourceEmitter::emitSimpleFuncParamImpl(IRParam* param)
{
emitRateQualifiers(param);
+ emitMeshOutputModifiers(param);
if (auto decor = param->findDecoration<IRGeometryInputPrimitiveTypeDecoration>())
{
@@ -1104,6 +1122,20 @@ void HLSLSourceEmitter::emitInterpolationModifiersImpl(IRInst* varInst, IRType*
}
}
+void HLSLSourceEmitter::emitMeshOutputModifiersImpl(IRInst* varInst)
+{
+ if(auto modifier = varInst->findDecoration<IRMeshOutputDecoration>())
+ {
+ const char* s =
+ as<IRVerticesDecoration>(modifier) ? "vertices "
+ : as<IRIndicesDecoration>(modifier) ? "indices "
+ : as<IRPrimitivesDecoration>(modifier) ? "primitives "
+ : nullptr;
+ SLANG_EXPECT(s, "Unhandled type of mesh output decoration");
+ m_writer->emit(s);
+ }
+}
+
void HLSLSourceEmitter::emitVarDecorationsImpl(IRInst* varDecl)
{
if (varDecl->findDecoration<IRGloballyCoherentDecoration>())
diff --git a/source/slang/slang-emit-hlsl.h b/source/slang/slang-emit-hlsl.h
index 763b63277..0a4e7fde8 100644
--- a/source/slang/slang-emit-hlsl.h
+++ b/source/slang/slang-emit-hlsl.h
@@ -39,6 +39,7 @@ protected:
virtual void emitSemanticsImpl(IRInst* inst) SLANG_OVERRIDE;
virtual void emitSimpleFuncParamImpl(IRParam* param) SLANG_OVERRIDE;
virtual void emitInterpolationModifiersImpl(IRInst* varInst, IRType* valueType, IRVarLayout* layout) SLANG_OVERRIDE;
+ virtual void emitMeshOutputModifiersImpl(IRInst* varInst) SLANG_OVERRIDE;
virtual void emitSimpleTypeImpl(IRType* type) SLANG_OVERRIDE;
virtual void emitVectorTypeNameImpl(IRType* elementType, IRIntegerValue elementCount) SLANG_OVERRIDE;
virtual void emitVarDecorationsImpl(IRInst* varDecl) SLANG_OVERRIDE;
diff --git a/source/slang/slang-emit-spirv.cpp b/source/slang/slang-emit-spirv.cpp
index 96ba89c55..85699ad95 100644
--- a/source/slang/slang-emit-spirv.cpp
+++ b/source/slang/slang-emit-spirv.cpp
@@ -2894,7 +2894,7 @@ SlangResult emitSPIRVFromIR(
auto sink = codeGenContext->getSink();
SPIRVEmitContext context(irModule, targetRequest, sink);
- legalizeIRForSPIRV(&context, irModule, irEntryPoints, sink);
+ legalizeIRForSPIRV(&context, irModule, irEntryPoints, codeGenContext);
context.emitFrontMatter();
for (auto irEntryPoint : irEntryPoints)
diff --git a/source/slang/slang-emit.cpp b/source/slang/slang-emit.cpp
index 2478eccc2..cd5f58925 100644
--- a/source/slang/slang-emit.cpp
+++ b/source/slang/slang-emit.cpp
@@ -24,6 +24,7 @@
#include "slang-ir-insts.h"
#include "slang-ir-inline.h"
#include "slang-ir-legalize-array-return-type.h"
+#include "slang-ir-legalize-mesh-outputs.h"
#include "slang-ir-legalize-varying-params.h"
#include "slang-ir-link.h"
#include "slang-ir-com-interface.h"
@@ -680,7 +681,7 @@ Result linkAndOptimizeIR(
session,
irModule,
irEntryPoints,
- codeGenContext->getSink(),
+ codeGenContext,
glslExtensionTracker);
#if 0
@@ -774,6 +775,13 @@ Result linkAndOptimizeIR(
cleanUpVoidType(irModule);
+ // For some small improvement in type safety we represent these as opaque
+ // structs instead of regular arrays.
+ //
+ // If any have survived this far, change them back to regular (decorated)
+ // arrays that the emitters can deal with.
+ legalizeMeshOutputTypes(irModule);
+
// Lower all bit_cast operations on complex types into leaf-level
// bit_cast on basic types.
lowerBitCast(targetRequest, irModule);
diff --git a/source/slang/slang-ir-glsl-legalize.cpp b/source/slang/slang-ir-glsl-legalize.cpp
index 76c73796f..fd256c68c 100644
--- a/source/slang/slang-ir-glsl-legalize.cpp
+++ b/source/slang/slang-ir-glsl-legalize.cpp
@@ -1,8 +1,12 @@
// slang-ir-glsl-legalize.cpp
#include "slang-ir-glsl-legalize.h"
+#include <functional>
+
#include "slang-ir.h"
#include "slang-ir-insts.h"
+#include "slang-ir-inst-pass-base.h"
+#include "slang-ir-specialize-function-call.h"
#include "slang-glsl-extension-tracker.h"
@@ -175,9 +179,10 @@ IRType* getFieldType(
if(ff->getKey() == fieldKey)
return ff->getFieldType();
}
+ SLANG_UNEXPECTED("no such field");
+ UNREACHABLE_RETURN(nullptr);
}
-
- SLANG_UNEXPECTED("no such field");
+ SLANG_UNEXPECTED("not a struct");
UNREACHABLE_RETURN(nullptr);
}
@@ -252,6 +257,8 @@ struct ScalarizedVal
return result;
}
+ List<IRInst*> leafAddresses();
+
Flavor flavor = Flavor::none;
IRInst* irValue = nullptr;
RefPtr<ScalarizedValImpl> impl;
@@ -284,6 +291,9 @@ struct GlobalVaryingDeclarator
enum class Flavor
{
array,
+ meshOutputVertices,
+ meshOutputIndices,
+ meshOutputPrimitives,
};
Flavor flavor;
@@ -304,6 +314,44 @@ struct GLSLSystemValueInfo
IRType* requiredType;
};
+static void leafAddressesImpl(List<IRInst*>& ret, const ScalarizedVal& v)
+{
+ switch(v.flavor)
+ {
+ case ScalarizedVal::Flavor::none:
+ case ScalarizedVal::Flavor::value:
+ break;
+
+ case ScalarizedVal::Flavor::address:
+ {
+ ret.add(v.irValue);
+ }
+ break;
+ case ScalarizedVal::Flavor::tuple:
+ {
+ auto tupleVal = as<ScalarizedTupleValImpl>(v.impl);
+ for(auto e : tupleVal->elements)
+ {
+ leafAddressesImpl(ret, e.val);
+ }
+ }
+ break;
+ case ScalarizedVal::Flavor::typeAdapter:
+ {
+ auto typeAdapterVal = as<ScalarizedTypeAdapterValImpl>(v.impl);
+ leafAddressesImpl(ret, typeAdapterVal->val);
+ }
+ break;
+ }
+}
+
+List<IRInst*> ScalarizedVal::leafAddresses()
+{
+ List<IRInst*> ret;
+ leafAddressesImpl(ret, *this);
+ return ret;
+}
+
struct GLSLLegalizationContext
{
Session* session;
@@ -340,13 +388,88 @@ struct GLSLLegalizationContext
IRBuilder* getBuilder() { return builder; }
};
+// This examines the passed type and determines the GLSL mesh shader indices
+// builtin name and type
+GLSLSystemValueInfo* getMeshOutputIndicesSystemValueInfo(
+ GLSLLegalizationContext* context,
+ LayoutResourceKind kind,
+ Stage stage,
+ IRType* type,
+ GlobalVaryingDeclarator* declarator,
+ GLSLSystemValueInfo* inStorage)
+{
+ IRBuilder* builder = context->builder;
+ if(stage != Stage::Mesh)
+ {
+ return nullptr;
+ }
+ if(kind != LayoutResourceKind::VaryingOutput)
+ {
+ return nullptr;
+ }
+ if(!declarator || declarator->flavor != GlobalVaryingDeclarator::Flavor::meshOutputIndices)
+ {
+ return nullptr;
+ }
+
+ inStorage->outerArrayName = nullptr;
+
+ // Points
+ if(isIntegralType(type))
+ {
+ inStorage->name = "gl_PrimitivePointIndicesEXT";
+ inStorage->requiredType = builder->getUIntType();
+ return inStorage;
+ }
+
+ auto vectorCount = composeGetters<IRIntLit>(type, &IRVectorType::getElementCount);
+ auto elemType = composeGetters<IRType>(type, &IRVectorType::getElementType);
+
+ // Lines
+ if(vectorCount->getValue() == 2 && isIntegralType(elemType))
+ {
+ inStorage->name = "gl_PrimitiveLineIndicesEXT";
+ inStorage->requiredType =
+ builder->getVectorType(
+ builder->getUIntType(),
+ builder->getIntValue(builder->getIntType(), 2));
+ return inStorage;
+ }
+
+ // Triangles
+ if(vectorCount->getValue() == 3 && isIntegralType(elemType))
+ {
+ inStorage->name = "gl_PrimitiveTriangleIndicesEXT";
+ inStorage->requiredType =
+ builder->getVectorType(
+ builder->getUIntType(),
+ builder->getIntValue(builder->getIntType(), 3));
+ return inStorage;
+ }
+
+ SLANG_UNREACHABLE("Unhandled mesh output indices type");
+}
+
GLSLSystemValueInfo* getGLSLSystemValueInfo(
GLSLLegalizationContext* context,
IRVarLayout* varLayout,
LayoutResourceKind kind,
Stage stage,
+ IRType* type,
+ GlobalVaryingDeclarator* declarator,
GLSLSystemValueInfo* inStorage)
{
+ if(auto indicesSemantic = getMeshOutputIndicesSystemValueInfo(
+ context,
+ kind,
+ stage,
+ type,
+ declarator,
+ inStorage))
+ {
+ return indicesSemantic;
+ }
+
char const* name = nullptr;
char const* outerArrayName = nullptr;
@@ -732,6 +855,14 @@ GLSLSystemValueInfo* getGLSLSystemValueInfo(
// we ought to use if the `noperspective` modifier has been
// applied to this varying input.
}
+ else if (semanticName == "sv_cullprimitive")
+ {
+ name = "gl_CullPrimitiveEXT";
+ }
+ else if (semanticName == "sv_shadingrate")
+ {
+ name = "gl_PrimitiveShadingRateEXT";
+ }
if( name )
{
@@ -765,6 +896,8 @@ ScalarizedVal createSimpleGLSLGlobalVarying(
inVarLayout,
kind,
stage,
+ inType,
+ declarator,
&systemValueInfoStorage);
IRType* type = inType;
@@ -781,28 +914,58 @@ ScalarizedVal createSimpleGLSLGlobalVarying(
IRTypeLayout* typeLayout = inTypeLayout;
for( auto dd = declarator; dd; dd = dd->next )
{
- // We only have one declarator case right now...
- SLANG_ASSERT(dd->flavor == GlobalVaryingDeclarator::Flavor::array);
+ switch(dd->flavor)
+ {
+ case GlobalVaryingDeclarator::Flavor::array:
+ {
+ auto arrayType = builder->getArrayType(
+ type,
+ dd->elementCount);
- auto arrayType = builder->getArrayType(
- type,
- dd->elementCount);
+ IRArrayTypeLayout::Builder arrayTypeLayoutBuilder(builder, typeLayout);
+ if( auto resInfo = inTypeLayout->findSizeAttr(kind) )
+ {
+ // TODO: it is kind of gross to be re-running some
+ // of the type layout logic here.
- IRArrayTypeLayout::Builder arrayTypeLayoutBuilder(builder, typeLayout);
- if( auto resInfo = inTypeLayout->findSizeAttr(kind) )
- {
- // TODO: it is kind of gross to be re-running some
- // of the type layout logic here.
+ UInt elementCount = (UInt) getIntVal(dd->elementCount);
+ arrayTypeLayoutBuilder.addResourceUsage(
+ kind,
+ resInfo->getSize() * elementCount);
+ }
+ auto arrayTypeLayout = arrayTypeLayoutBuilder.build();
- UInt elementCount = (UInt) getIntVal(dd->elementCount);
- arrayTypeLayoutBuilder.addResourceUsage(
- kind,
- resInfo->getSize() * elementCount);
+ type = arrayType;
+ typeLayout = arrayTypeLayout;
}
- auto arrayTypeLayout = arrayTypeLayoutBuilder.build();
+ break;
+ case GlobalVaryingDeclarator::Flavor::meshOutputVertices:
+ case GlobalVaryingDeclarator::Flavor::meshOutputIndices:
+ case GlobalVaryingDeclarator::Flavor::meshOutputPrimitives:
+ {
+ // It's legal to declare these as unsized arrays, but by sizing
+ // them by the (max) max size GLSL allows us to index into them
+ // with variable index.
+ SLANG_EXPECT(dd->elementCount, "Mesh output declarator didn't specify element count");
+ auto arrayType = builder->getArrayType(type, dd->elementCount);
+
+ IRArrayTypeLayout::Builder arrayTypeLayoutBuilder(builder, typeLayout);
+ if( auto resInfo = inTypeLayout->findSizeAttr(kind) )
+ {
+ // Although these are arrays, they consume slots as though
+ // they're scalar parameters, so don't multiply the usage by the
+ // (runtime) array size.
+ arrayTypeLayoutBuilder.addResourceUsage(
+ kind,
+ resInfo->getSize());
+ }
+ auto arrayTypeLayout = arrayTypeLayoutBuilder.build();
- type = arrayType;
- typeLayout = arrayTypeLayout;
+ type = arrayType;
+ typeLayout = arrayTypeLayout;
+ }
+ break;
+ }
}
// We need to construct a fresh layout for the variable, even
@@ -869,6 +1032,11 @@ ScalarizedVal createSimpleGLSLGlobalVarying(
}
}
+ if(declarator && declarator->flavor == GlobalVaryingDeclarator::Flavor::meshOutputPrimitives)
+ {
+ builder->addDecoration(globalParam, kIROp_GLSLPrimitivesRateDecoration);
+ }
+
builder->addLayoutDecoration(globalParam, varLayout);
return val;
@@ -938,6 +1106,48 @@ ScalarizedVal createGLSLGlobalVaryingsImpl(
&arrayDeclarator,
leafVar);
}
+ else if( auto meshOutputType = as<IRMeshOutputType>(type))
+ {
+ // We will need to SOA-ize any nested types.
+ // TODO: Ellie, deduplicate with the above case?
+
+ auto elementType = meshOutputType->getElementType();
+ auto arrayLayout = as<IRArrayTypeLayout>(typeLayout);
+ SLANG_ASSERT(arrayLayout);
+ auto elementTypeLayout = arrayLayout->getElementTypeLayout();
+
+ GlobalVaryingDeclarator arrayDeclarator;
+ switch(type->getOp())
+ {
+ using F = GlobalVaryingDeclarator::Flavor;
+ case kIROp_VerticesType:
+ arrayDeclarator.flavor = F::meshOutputVertices;
+ break;
+ case kIROp_IndicesType:
+ arrayDeclarator.flavor = F::meshOutputIndices;
+ break;
+ case kIROp_PrimitivesType:
+ arrayDeclarator.flavor = F::meshOutputPrimitives;
+ break;
+ default:
+ SLANG_UNEXPECTED("Unhandled mesh output type");
+ }
+ arrayDeclarator.elementCount = meshOutputType->getMaxElementCount();
+ arrayDeclarator.next = declarator;
+
+ return createGLSLGlobalVaryingsImpl(
+ context,
+ builder,
+ elementType,
+ varLayout,
+ elementTypeLayout,
+ kind,
+ stage,
+ bindingIndex,
+ bindingSpace,
+ &arrayDeclarator,
+ leafVar);
+ }
else if( auto streamType = as<IRHLSLStreamOutputType>(type))
{
auto elementType = streamType->getElementType();
@@ -972,10 +1182,19 @@ ScalarizedVal createGLSLGlobalVaryingsImpl(
IRType* fullType = type;
for( auto dd = declarator; dd; dd = dd->next )
{
- SLANG_ASSERT(dd->flavor == GlobalVaryingDeclarator::Flavor::array);
- fullType = builder->getArrayType(
- fullType,
- dd->elementCount);
+ switch(dd->flavor)
+ {
+ case GlobalVaryingDeclarator::Flavor::meshOutputVertices:
+ case GlobalVaryingDeclarator::Flavor::meshOutputIndices:
+ case GlobalVaryingDeclarator::Flavor::meshOutputPrimitives:
+ case GlobalVaryingDeclarator::Flavor::array:
+ {
+ fullType = builder->getArrayType(
+ fullType,
+ dd->elementCount);
+ }
+ break;
+ }
}
tupleValImpl->type = fullType;
@@ -1052,6 +1271,7 @@ ScalarizedVal createGLSLGlobalVaryings(
ScalarizedVal extractField(
IRBuilder* builder,
ScalarizedVal const& val,
+ // Pass ~0 in to search for the index via the key
UInt fieldIndex,
IRStructKey* fieldKey)
{
@@ -1080,7 +1300,22 @@ ScalarizedVal extractField(
case ScalarizedVal::Flavor::tuple:
{
auto tupleVal = as<ScalarizedTupleValImpl>(val.impl);
- return tupleVal->elements[fieldIndex].val;
+ const auto& es = tupleVal->elements;
+ if(fieldIndex == kMaxUInt)
+ {
+ for(fieldIndex = 0; fieldIndex < (UInt)es.getCount(); ++fieldIndex)
+ {
+ if(es[fieldIndex].key == fieldKey)
+ {
+ break;
+ }
+ }
+ if(fieldIndex >= (UInt)es.getCount())
+ {
+ SLANG_UNEXPECTED("Unable to find field index from struct key");
+ }
+ }
+ return es[fieldIndex].val;
}
default:
@@ -1131,7 +1366,9 @@ ScalarizedVal adaptType(
void assign(
IRBuilder* builder,
ScalarizedVal const& left,
- ScalarizedVal const& right)
+ ScalarizedVal const& right,
+ // Pass nullptr for an unindexed write (for everything but mesh shaders)
+ IRInst* index = nullptr)
{
switch( left.flavor )
{
@@ -1140,7 +1377,12 @@ void assign(
{
case ScalarizedVal::Flavor::value:
{
- builder->emitStore(left.irValue, right.irValue);
+ auto address = left.irValue;
+ if(index)
+ {
+ address = builder->emitElementAddress(right.irValue->getFullType(), left.irValue, index);
+ }
+ builder->emitStore(address, right.irValue);
}
break;
@@ -1167,7 +1409,7 @@ void assign(
left,
ee,
rightElement.key);
- assign(builder, leftElementVal, rightElement.val);
+ assign(builder, leftElementVal, rightElement.val, index);
}
}
break;
@@ -1192,7 +1434,7 @@ void assign(
right,
ee,
leftTupleVal->elements[ee].key);
- assign(builder, leftTupleVal->elements[ee].val, rightElementVal);
+ assign(builder, leftTupleVal->elements[ee].val, rightElementVal, index);
}
}
break;
@@ -1206,7 +1448,7 @@ void assign(
// from the "pretend" type that it had in the IR before.
auto typeAdapter = as<ScalarizedTypeAdapterValImpl>(left.impl);
auto adaptedRight = adaptType(builder, right, typeAdapter->actualType, typeAdapter->pretendType);
- assign(builder, typeAdapter->val, adaptedRight);
+ assign(builder, typeAdapter->val, adaptedRight, index);
}
break;
@@ -1272,6 +1514,21 @@ ScalarizedVal getSubscriptVal(
return ScalarizedVal::tuple(resultTuple);
}
+ case ScalarizedVal::Flavor::typeAdapter:
+ {
+ auto inputAdapter = val.impl.as<ScalarizedTypeAdapterValImpl>();
+ RefPtr<ScalarizedTypeAdapterValImpl> resultAdapter = new ScalarizedTypeAdapterValImpl();
+
+ resultAdapter->pretendType = inputAdapter->pretendType;
+ resultAdapter->actualType = inputAdapter->actualType;
+
+ resultAdapter->val = getSubscriptVal(
+ builder,
+ elementType,
+ inputAdapter->val,
+ indexVal);
+ return ScalarizedVal::typeAdapter(resultAdapter);
+ }
default:
SLANG_UNEXPECTED("unimplemented");
@@ -1454,8 +1711,338 @@ void legalizeRayTracingEntryPointParameterForGLSL(
builder->addDependsOnDecoration(func, globalParam);
}
+void legalizeMeshOutputParam(
+ GLSLLegalizationContext* context,
+ CodeGenContext* codeGenContext,
+ IRFunc* func,
+ IRParam* pp,
+ IRVarLayout* paramLayout,
+ IRMeshOutputType* meshOutputType)
+{
+ auto builder = context->getBuilder();
+ auto stage = context->getStage();
+ SLANG_EXPECT(stage == Stage::Mesh, "legalizing mesh output, but we're not a mesh shader");
+ IRBuilderInsertLocScope locScope{builder};
+ builder->setInsertInto(func);
+
+ auto globalOutputVal = createGLSLGlobalVaryings(
+ context,
+ builder,
+ meshOutputType,
+ paramLayout,
+ LayoutResourceKind::VaryingOutput,
+ stage,
+ pp);
+
+ switch ( globalOutputVal.flavor )
+ {
+ case ScalarizedVal::Flavor::tuple:
+ {
+ auto v = as<ScalarizedTupleValImpl>(globalOutputVal.impl);
+
+ Index elementCount = v->elements.getCount();
+ for( Index ee = 0; ee < elementCount; ++ee )
+ {
+ auto e = v->elements[ee];
+ auto leftElementVal = extractField(
+ builder,
+ globalOutputVal,
+ ee,
+ e.key);
+ }
+ }
+ break;
+ case ScalarizedVal::Flavor::value:
+ case ScalarizedVal::Flavor::address:
+ case ScalarizedVal::Flavor::typeAdapter:
+ break;
+ }
+
+ //
+ // Introduce a global parameter to drive the specialization machinery
+ //
+ // It would potentially be nicer to orthogonalize the SOA-ization
+ // and the entry point parameter legalization, however this isn't
+ // possible in the general case as we need to know which members
+ // map to builtin values and which to user defined varyings;
+ // information which is only readily available given the entry
+ // point.
+ // The AOS handling of builtins is a quirk specific to GLSL and
+ // once we've moved to a SPIR-V direct backend we can neaten this
+ // up.
+ //
+ auto g = addGlobalParam(builder->getModule(), pp->getFullType());
+ moveValueBefore(g, builder->getFunc());
+ builder->addNameHintDecoration(g, pp->findDecoration<IRNameHintDecoration>()->getName());
+ pp->replaceUsesWith(g);
+ struct MeshOutputSpecializationCondition : FunctionCallSpecializeCondition
+ {
+ bool doesParamWantSpecialization(IRParam*, IRInst* arg)
+ {
+ return arg == g;
+ }
+ IRInst* g;
+ } condition;
+ condition.g = g;
+ specializeFunctionCalls(codeGenContext, builder->getModule(), &condition);
+
+ //
+ // Remove this global by making all writes actually write to the
+ // newly introduced out variables.
+ //
+ // Sadly it's not as simple as just using this file's `assign` function as
+ // the writes may only be writing to parts of the output struct, or may not
+ // be writes at all (i.e. being passed as an out paramter).
+ //
+ traverseUses(g, [&](IRInst* u)
+ {
+ auto l = as<IRLoad>(u);
+ SLANG_EXPECT(l, "Mesh Output sentinel parameter wasn't used in a load");
+
+ std::function<void(ScalarizedVal&, IRInst*)> assignUses =
+ [&](ScalarizedVal& d, IRInst* a)
+ {
+ // If we're just writing to an address, we can seamlessly
+ // replace it with the address to the SOA representation.
+ // GLSL's `out` function parameters have copy-out semantics, so
+ // this is all above board.
+ if(d.flavor == ScalarizedVal::Flavor::address)
+ {
+ IRBuilderInsertLocScope locScope{builder};
+ builder->setInsertBefore(a);
+ a->replaceUsesWith(d.irValue);
+ return;
+ }
+ // Otherwise, go through the uses one by one and see what we can do
+ traverseUses(a, [&](IRInst* s)
+ {
+ IRBuilderInsertLocScope locScope{builder};
+ builder->setInsertBefore(s);
+ if(auto m = as<IRFieldAddress>(s))
+ {
+ auto key = as<IRStructKey>(m->getField());
+ SLANG_EXPECT(key, "Result of getField wasn't a struct key");
+
+ auto d_ = extractField(builder, d, kMaxUInt, key);
+ assignUses(d_, m);
+ }
+ else if(auto g = as<IRGetElementPtr>(s))
+ {
+ // Writing to something like `struct Vertex{ Foo foo[10]; }`
+ // This case is also what's taken in the initial
+ // traversal, as every mesh output is an array.
+ auto elemType = composeGetters<IRType>(
+ g,
+ &IRInst::getFullType,
+ &IRPtrTypeBase::getValueType);
+ auto d_ = getSubscriptVal(builder, elemType, d, g->getIndex());
+ assignUses(d_, g);
+ }
+ else if(auto store = as<IRStore>(s))
+ {
+ // Store using the SOA representation
+
+ assign(
+ builder,
+ d,
+ ScalarizedVal::value(store->getVal()));
+
+ // Stores aren't used, safe to remove here without checking
+ store->removeAndDeallocate();
+ }
+ else if(auto c = as<IRCall>(s))
+ {
+ // Translate
+ // foo(vertices[n])
+ // to
+ // tmp
+ // foo(tmp)
+ // vertices[n] = tmp;
+ //
+ // This has copy-out semantics, which is really the
+ // best we can hope for without going and
+ // specializing foo.
+ auto ptr = as<IRPtrTypeBase>(a->getFullType());
+ SLANG_EXPECT(ptr, "Mesh output parameter was passed by value");
+ auto t = ptr->getValueType();
+ auto tmp = builder->emitVar(t);
+ for(UInt i = 0; i < c->getOperandCount(); i++)
+ {
+ if(c->getOperand(i) == a)
+ {
+ c->setOperand(i, tmp);
+ }
+ }
+ builder->setInsertAfter(c);
+ assign(builder, d,
+ ScalarizedVal::value(builder->emitLoad(tmp)));
+ }
+ else if(auto swiz = as<IRSwizzledStore>(s))
+ {
+ SLANG_UNEXPECTED("Swizzled store to a non-address ScalarizedVal");
+ }
+ else
+ {
+ SLANG_UNEXPECTED("Unhandled use of mesh output parameter during GLSL legalization");
+ }
+ });
+ };
+ assignUses(globalOutputVal, l);
+ });
+
+ //
+ // GLSL requires that builtins are written to a block named
+ // gl_MeshVerticesEXT or gl_MeshPrimitivesEXT. Once we've done the
+ // specialization above, we can fix this.
+ //
+ // This part is split into a separate step so as not to infect
+ // ScalarizedVal and also so it can be excised easily when moving
+ // to a SPIR-V direct.
+ //
+ // It's tempting to move this into a separate IR pass which looks for
+ // global mesh output params and coalesces them, however the precise
+ // definitions of gl_MeshPerVertexEXT can differ depending on the entry
+ // point, consider sizing the gl_ClipDistance array for different number of
+ // clip planes used by different entry points.
+ //
+ // We are allowed to redeclare these with just the necessary subset of
+ // members.
+ //
+ // out gl_MeshPerVertexEXT {
+ // vec4 gl_Position;
+ // float gl_PointSize;
+ // float gl_ClipDistance[];
+ // float gl_CullDistance[];
+ // } gl_MeshVerticesEXT[];
+ //
+ // perprimitiveEXT out gl_MeshPerPrimitiveEXT {
+ // int gl_PrimitiveID;
+ // int gl_Layer;
+ // int gl_ViewportIndex;
+ // bool gl_CullPrimitiveEXT;
+ // int gl_PrimitiveShadingRateEXT;
+ // } gl_MeshPrimitivesEXT[];
+ //
+
+ // First, collect the subset of outputs being used
+ auto isMeshOutputBuiltin = [](IRInst* g)
+ {
+ if(const auto s = composeGetters<IRStringLit>(
+ g,
+ &IRInst::findDecoration<IRImportDecoration>,
+ &IRImportDecoration::getMangledNameOperand
+ ))
+ {
+ const auto n = s->getStringSlice();
+ if (n == "gl_Position" ||
+ n == "gl_PointSize" ||
+ n == "gl_ClipDistance" ||
+ n == "gl_CullDistance" ||
+ n == "gl_PrimitiveID" ||
+ n == "gl_Layer" ||
+ n == "gl_ViewportIndex" ||
+ n == "gl_CullPrimitiveEXT" ||
+ n == "gl_PrimitiveShadingRateEXT")
+ {
+ return s;
+ }
+ }
+ return (IRStringLit*)nullptr;
+ };
+ auto leaves = globalOutputVal.leafAddresses();
+ struct BuiltinOutputInfo
+ {
+ IRInst* param;
+ IRStringLit* nameDecoration;
+ IRType* type;
+ IRStructKey* key;
+ };
+ List<BuiltinOutputInfo> builtins;
+ for(auto leaf : leaves)
+ {
+ if(auto decoration = isMeshOutputBuiltin(leaf))
+ {
+ builtins.add({leaf, decoration, nullptr, nullptr});
+ }
+ }
+ if(builtins.getCount() == 0)
+ {
+ return;
+ }
+ const auto _locScope = IRBuilderInsertLocScope{builder};
+ builder->setInsertBefore(func);
+ auto meshOutputBlockType = builder->createStructType();
+ {
+ const auto _locScope2 = IRBuilderInsertLocScope{builder};
+ builder->setInsertInto(meshOutputBlockType);
+ for(auto& builtin : builtins)
+ {
+ auto t = composeGetters<IRType>(
+ builtin.param,
+ &IRInst::getFullType,
+ &IROutTypeBase::getValueType,
+ &IRArrayTypeBase::getElementType);
+ auto key = builder->createStructKey();
+ auto n = builtin.nameDecoration->getStringSlice();
+ builder->addImportDecoration(key, n);
+ builder->createStructField(meshOutputBlockType, key, t);
+ builtin.type = t;
+ builtin.key = key;
+ }
+ }
+
+ // No emitter actually handles GLSLOutputParameterGroupTypes, this isn't a
+ // problem as it's used as an intrinsic.
+ // GLSL does permit redeclaring these particular ones, so it might be nice to
+ // add a linkage decoration instead of it being an intrinsic in the event
+ // that we start outputting the linkage decoration instead of it being an
+ // intrinsic in the event that we start outputting these.
+ auto blockParamType = builder->getGLSLOutputParameterGroupType(
+ builder->getArrayType(meshOutputBlockType, meshOutputType->getMaxElementCount()));
+ auto blockParam = builder->createGlobalParam(blockParamType);
+ bool isPerPrimitive = as<IRPrimitivesType>(meshOutputType);
+ auto typeName = isPerPrimitive ? "gl_MeshPerPrimitiveEXT" : "gl_MeshPerVertexEXT";
+ auto arrayName = isPerPrimitive ? "gl_MeshPrimitivesEXT" : "gl_MeshVerticesEXT";
+ builder->addTargetIntrinsicDecoration(
+ meshOutputBlockType,
+ CapabilitySet(CapabilityAtom::GLSL),
+ UnownedStringSlice(typeName));
+ builder->addImportDecoration(blockParam, UnownedStringSlice(arrayName));
+ if(isPerPrimitive)
+ {
+ builder->addDecoration(blockParam, kIROp_GLSLPrimitivesRateDecoration);
+ }
+ // // While this is probably a correct thing to do, LRK::VaryingOutput
+ // // isn't really used for redeclaraion of builtin outputs, and assumes
+ // // that it's got a layout location, the correct fix might be to add
+ // // LRK::BuiltinVaryingOutput, but that would be polluting LRK for no
+ // // real gain.
+ // //
+ // IRVarLayout::Builder varLayoutBuilder{builder, IRTypeLayout::Builder{builder}.build()};
+ // varLayoutBuilder.findOrAddResourceInfo(LayoutResourceKind::VaryingOutput);
+ // varLayoutBuilder.setStage(Stage::Mesh);
+ // builder->addLayoutDecoration(blockParam, varLayoutBuilder.build());
+
+ for(auto builtin : builtins)
+ {
+ traverseUses(builtin.param, [&](IRInst* u)
+ {
+ auto p = as<IRGetElementPtr>(u);
+ SLANG_EXPECT(p, "Mesh Output sentinel parameter wasn't used as an array");
+
+ IRBuilderInsertLocScope locScope{builder};
+ builder->setInsertBefore(p);
+ auto e = builder->emitElementAddress(meshOutputBlockType, blockParam, p->getIndex());
+ auto a = builder->emitFieldAddress(builtin.type, e, builtin.key);
+
+ p->replaceUsesWith(a);
+ });
+ }
+}
+
void legalizeEntryPointParameterForGLSL(
GLSLLegalizationContext* context,
+ CodeGenContext* codeGenContext,
IRFunc* func,
IRParam* pp,
IRVarLayout* paramLayout)
@@ -1515,6 +2102,21 @@ void legalizeEntryPointParameterForGLSL(
}
}
+ // Lift Mesh Output decorations to the function
+ // TODO: Ellie, check for duplication and assert consistency
+ if (stage == Stage::Mesh)
+ {
+ if (auto d = pp->findDecoration<IRMeshOutputDecoration>())
+ {
+ // It's illegal to have differently sized indices and primitives
+ // outputs, for consistency, only attach a PrimitivesDecoration to
+ // the function.
+ auto op = as<IRIndicesDecoration>(d) ? kIROp_PrimitivesDecoration : d->getOp();
+ builder->addDecoration(func, op, d->getMaxSize());
+ }
+ }
+
+
// We need to create a global variable that will replace the parameter.
// It seems superficially obvious that the variable should have
// the same type as the parameter.
@@ -1531,8 +2133,8 @@ void legalizeEntryPointParameterForGLSL(
// First we will special-case stage input/outputs that
// don't fit into the standard varying model.
- // For right now we are only doing special-case handling
- // of geometry shader output streams.
+ // - Geometry shader output streams
+ // - Mesh shader outputs
if( auto paramPtrType = as<IROutTypeBase>(paramType) )
{
auto valueType = paramPtrType->getValueType();
@@ -1649,6 +2251,10 @@ void legalizeEntryPointParameterForGLSL(
return;
}
+ if( auto meshOutputType = as<IRMeshOutputType>(valueType) )
+ {
+ return legalizeMeshOutputParam(context, codeGenContext, func, pp, paramLayout, meshOutputType);
+ }
}
// When we have an HLSL ray tracing shader entry point,
@@ -1776,7 +2382,7 @@ void legalizeEntryPointForGLSL(
Session* session,
IRModule* module,
IRFunc* func,
- DiagnosticSink* sink,
+ CodeGenContext* codeGenContext,
GLSLExtensionTracker* glslExtensionTracker)
{
auto entryPointDecor = func->findDecoration<IREntryPointDecoration>();
@@ -1795,7 +2401,7 @@ void legalizeEntryPointForGLSL(
GLSLLegalizationContext context;
context.session = session;
context.stage = stage;
- context.sink = sink;
+ context.sink = codeGenContext->getSink();
context.glslExtensionTracker = glslExtensionTracker;
// We require that the entry-point function has no uses,
@@ -1916,6 +2522,7 @@ void legalizeEntryPointForGLSL(
legalizeEntryPointParameterForGLSL(
&context,
+ codeGenContext,
func,
pp,
paramLayout);
@@ -1957,12 +2564,12 @@ void legalizeEntryPointsForGLSL(
Session* session,
IRModule* module,
const List<IRFunc*>& funcs,
- DiagnosticSink* sink,
+ CodeGenContext* context,
GLSLExtensionTracker* glslExtensionTracker)
{
for (auto func : funcs)
{
- legalizeEntryPointForGLSL(session, module, func, sink, glslExtensionTracker);
+ legalizeEntryPointForGLSL(session, module, func, context, glslExtensionTracker);
}
}
diff --git a/source/slang/slang-ir-glsl-legalize.h b/source/slang/slang-ir-glsl-legalize.h
index 920715be2..8a49fb2ec 100644
--- a/source/slang/slang-ir-glsl-legalize.h
+++ b/source/slang/slang-ir-glsl-legalize.h
@@ -1,6 +1,7 @@
// slang-ir-glsl-legalize.h
#pragma once
#include"../core/slang-list.h"
+#include "slang-compiler.h"
namespace Slang
{
@@ -17,7 +18,7 @@ void legalizeEntryPointsForGLSL(
Session* session,
IRModule* module,
const List<IRFunc*>& func,
- DiagnosticSink* sink,
+ CodeGenContext* context,
GLSLExtensionTracker* glslExtensionTracker);
void legalizeImageSubscriptForGLSL(IRModule* module);
diff --git a/source/slang/slang-ir-inst-defs.h b/source/slang/slang-ir-inst-defs.h
index 9f3111b87..bf73a31d8 100644
--- a/source/slang/slang-ir-inst-defs.h
+++ b/source/slang/slang-ir-inst-defs.h
@@ -164,6 +164,12 @@ INST(Nop, nop, 0, 0)
INST(HLSLTriangleStreamType, TriangleStream, 1, 0)
INST_RANGE(HLSLStreamOutputType, HLSLPointStreamType, HLSLTriangleStreamType)
+ /* MeshOutputType */
+ INST(VerticesType, Vertices, 2, 0)
+ INST(IndicesType, Indices, 2, 0)
+ INST(PrimitivesType, Primitives, 2, 0)
+ INST_RANGE(MeshOutputType, VerticesType, PrimitivesType)
+
/* HLSLStructuredBufferTypeBase */
INST(HLSLStructuredBufferType, StructuredBuffer, 0, 0)
INST(HLSLRWStructuredBufferType, RWStructuredBuffer, 0, 0)
@@ -393,6 +399,8 @@ INST(StructuredBufferLoad, structuredBufferLoad, 2, 0)
//
INST(StructuredBufferStore, structuredBufferStore, 3, 0)
+INST(MeshOutputRef, meshOutputRef, 2, 0)
+
// Construct a vector from a scalar
//
// %dst = constructVectorFromScalar %T %N %val
@@ -693,6 +701,13 @@ INST(HighLevelDeclDecoration, highLevelDecl, 1, 0)
INST(PayloadDecoration, payload, 0, 0)
+ /* Mesh Shader outputs */
+ INST(VerticesDecoration, vertices, 1, 0)
+ INST(IndicesDecoration, indices, 1, 0)
+ INST(PrimitivesDecoration, primitives, 1, 0)
+ INST_RANGE(MeshOutputDecoration, VerticesDecoration, PrimitivesDecoration)
+ INST(GLSLPrimitivesRateDecoration, perprimitive, 0, 0)
+
/* StageAccessDecoration */
INST(StageReadAccessDecoration, stageReadAccess, 0, 0)
INST(StageWriteAccessDecoration, stageWriteAccess, 0, 0)
diff --git a/source/slang/slang-ir-insts.h b/source/slang/slang-ir-insts.h
index f5c3d10ae..d85e56d7e 100644
--- a/source/slang/slang-ir-insts.h
+++ b/source/slang/slang-ir-insts.h
@@ -333,6 +333,7 @@ struct IROutputControlPointsDecoration : IRDecoration
IRIntLit* getControlPointCount() { return cast<IRIntLit>(getOperand(0)); }
};
+// This is used for mesh shaders too
struct IROutputTopologyDecoration : IRDecoration
{
enum { kOp = kIROp_OutputTopologyDecoration };
@@ -755,6 +756,42 @@ struct IRPayloadDecoration : public IRDecoration
IR_LEAF_ISA(PayloadDecoration)
};
+// Mesh shader decorations
+
+struct IRMeshOutputDecoration : public IRDecoration
+{
+ IR_PARENT_ISA(MeshOutputDecoration)
+ IRIntLit* getMaxSize() { return cast<IRIntLit>(getOperand(0)); }
+};
+
+struct IRVerticesDecoration : public IRMeshOutputDecoration
+{
+ IR_LEAF_ISA(VerticesDecoration)
+};
+
+struct IRIndicesDecoration : public IRMeshOutputDecoration
+{
+ IR_LEAF_ISA(IndicesDecoration)
+};
+
+struct IRPrimitivesDecoration : public IRMeshOutputDecoration
+{
+ IR_LEAF_ISA(PrimitivesDecoration)
+};
+
+struct IRGLSLPrimitivesRateDecoration : public IRDecoration
+{
+ IR_LEAF_ISA(GLSLPrimitivesRateDecoration)
+};
+
+struct IRMeshOutputRef : public IRInst
+{
+ enum { kOp = kIROp_MeshOutputRef };
+ IR_LEAF_ISA(MeshOutputRef)
+ IRInst* getIndex() { return getOperand(1); }
+ IRInst* getOutputType() { return cast<IRPtrTypeBase>(getFullType())->getValueType(); }
+};
+
/// An attribute that can be attached to another instruction as an operand.
///
/// Attributes serve a similar role to decorations, in that both are ways
@@ -2252,6 +2289,8 @@ public:
void setInsertInto(IRInst* insertInto) { setInsertLoc(IRInsertLoc::atEnd(insertInto)); }
void setInsertBefore(IRInst* insertBefore) { setInsertLoc(IRInsertLoc::before(insertBefore)); }
+ // TODO: Ellie, contrary to IRInsertLoc::after, this inserts instructions in the order they are emitted, should it have a better name (setInsertBeforeNext)?
+ void setInsertAfter(IRInst* insertAfter);
void setInsertInto(IRModule* module) { setInsertInto(module->getModuleInst()); }
@@ -2440,6 +2479,8 @@ public:
IRConstantBufferType* getConstantBufferType(
IRType* elementType);
+ IRGLSLOutputParameterGroupType* getGLSLOutputParameterGroupType(IRType* valueType);
+
IRConstExprRate* getConstExprRate();
IRGroupSharedRate* getGroupSharedRate();
IRActualGlobalRate* getActualGlobalRate();
@@ -3369,6 +3410,12 @@ public:
addDecoration(inst, kIROp_VulkanHitObjectAttributesDecoration, getIntValue(getIntType(), location));
}
+ void addMeshOutputDecoration(IROp d, IRInst* value, IRInst* maxCount)
+ {
+ SLANG_ASSERT(IRMeshOutputDecoration::isaImpl(d));
+ // TODO: Ellie, correct int type here?
+ addDecoration(value, d, maxCount);
+ }
};
void addHoistableInst(
@@ -3401,6 +3448,20 @@ struct IRBuilderSourceLocRAII
}
};
+// A helper to restore the builder's insert location on destruction
+struct IRBuilderInsertLocScope
+{
+ IRBuilder* builder;
+ IRInsertLoc insertLoc;
+ IRBuilderInsertLocScope(IRBuilder* b)
+ : builder(b), insertLoc(builder->getInsertLoc())
+ {}
+ ~IRBuilderInsertLocScope()
+ {
+ builder->setInsertLoc(insertLoc);
+ }
+};
+
//
void markConstExpr(
diff --git a/source/slang/slang-ir-legalize-mesh-outputs.cpp b/source/slang/slang-ir-legalize-mesh-outputs.cpp
new file mode 100644
index 000000000..7c6d256ab
--- /dev/null
+++ b/source/slang/slang-ir-legalize-mesh-outputs.cpp
@@ -0,0 +1,37 @@
+#include "slang-ir-legalize-mesh-outputs.h"
+#include "slang-ir.h"
+#include "slang-ir-insts.h"
+#include "slang-ir-clone.h"
+
+namespace Slang
+{
+
+void legalizeMeshOutputTypes(IRModule* module)
+{
+ SharedIRBuilder builderStorage;
+ builderStorage.init(module);
+ IRBuilder builder(&builderStorage);
+
+ for (auto inst : module->getGlobalInsts())
+ {
+ if (auto meshOutput = as<IRMeshOutputType>(inst))
+ {
+ auto elemType = meshOutput->getElementType();
+ auto maxCount = meshOutput->getMaxElementCount();
+ auto arrayType = builder.getArrayType(elemType, maxCount);
+ IROp decorationOp
+ = as<IRVerticesType>(meshOutput) ? kIROp_VerticesDecoration
+ : as<IRIndicesType>(meshOutput) ? kIROp_IndicesDecoration
+ : as<IRPrimitivesType>(meshOutput) ? kIROp_PrimitivesDecoration
+ : (SLANG_UNREACHABLE("Missing case for IRMeshOutputType"), IROp(0));
+ // Ensure that all params are marked up as vertices/indices/primitives
+ traverseUses<IRParam>(meshOutput, [&](IRParam* i)
+ {
+ builder.addMeshOutputDecoration(decorationOp, i, maxCount);
+ });
+ meshOutput->replaceUsesWith(arrayType);
+ }
+ }
+}
+
+}
diff --git a/source/slang/slang-ir-legalize-mesh-outputs.h b/source/slang/slang-ir-legalize-mesh-outputs.h
new file mode 100644
index 000000000..98ece8c73
--- /dev/null
+++ b/source/slang/slang-ir-legalize-mesh-outputs.h
@@ -0,0 +1,12 @@
+// slang-ir-legalize-mesh-outputs.h
+#pragma once
+
+#include "slang-ir-insts.h"
+
+namespace Slang
+{
+ struct IRModule;
+
+ // Turn opaque mesh output types into regular arrays
+ void legalizeMeshOutputTypes(IRModule* module);
+}
diff --git a/source/slang/slang-ir-specialize-resources.cpp b/source/slang/slang-ir-specialize-resources.cpp
index 31e214e46..e4ccf40d5 100644
--- a/source/slang/slang-ir-specialize-resources.cpp
+++ b/source/slang/slang-ir-specialize-resources.cpp
@@ -1231,6 +1231,8 @@ bool isIllegalGLSLParameterType(IRType* type)
break;
}
}
+ if (as<IRMeshOutputType>(type))
+ return true;
return false;
}
diff --git a/source/slang/slang-ir-spirv-legalize.cpp b/source/slang/slang-ir-spirv-legalize.cpp
index 9587fd205..32e736381 100644
--- a/source/slang/slang-ir-spirv-legalize.cpp
+++ b/source/slang/slang-ir-spirv-legalize.cpp
@@ -307,11 +307,10 @@ void legalizeIRForSPIRV(
SPIRVEmitSharedContext* context,
IRModule* module,
const List<IRFunc*>& entryPoints,
- DiagnosticSink* sink)
+ CodeGenContext* codeGenContext)
{
- SLANG_UNUSED(sink);
GLSLExtensionTracker extensionTracker;
- legalizeEntryPointsForGLSL(module->getSession(), module, entryPoints, sink, &extensionTracker);
+ legalizeEntryPointsForGLSL(module->getSession(), module, entryPoints, codeGenContext, &extensionTracker);
legalizeSPIRV(context, module);
}
diff --git a/source/slang/slang-ir-spirv-legalize.h b/source/slang/slang-ir-spirv-legalize.h
index 5bf8326fa..499c2bff1 100644
--- a/source/slang/slang-ir-spirv-legalize.h
+++ b/source/slang/slang-ir-spirv-legalize.h
@@ -41,6 +41,6 @@ void legalizeIRForSPIRV(
SPIRVEmitSharedContext* context,
IRModule* module,
const List<IRFunc*>& entryPoints,
- DiagnosticSink* sink);
+ CodeGenContext* codeGenContext);
}
diff --git a/source/slang/slang-ir.cpp b/source/slang/slang-ir.cpp
index 7aa51f31b..1a8f20f1a 100644
--- a/source/slang/slang-ir.cpp
+++ b/source/slang/slang-ir.cpp
@@ -2029,6 +2029,19 @@ namespace Slang
}
}
+ void IRBuilder::setInsertAfter(IRInst* insertAfter)
+ {
+ auto next = insertAfter->getNextInst();
+ if(next)
+ {
+ setInsertBefore(next);
+ }
+ else
+ {
+ setInsertInto(insertAfter->parent);
+ }
+ }
+
IRConstant* IRBuilder::_findOrEmitConstant(
IRConstant& keyInst)
{
@@ -2853,6 +2866,15 @@ namespace Slang
operands);
}
+ IRGLSLOutputParameterGroupType* IRBuilder::getGLSLOutputParameterGroupType(IRType* elementType)
+ {
+ IRInst* operands[] = { elementType };
+ return (IRGLSLOutputParameterGroupType*) getType(
+ kIROp_GLSLOutputParameterGroupType,
+ 1,
+ operands);
+ }
+
IRConstExprRate* IRBuilder::getConstExprRate()
{
return (IRConstExprRate*)getType(kIROp_ConstExprRate);
@@ -5836,7 +5858,29 @@ namespace Slang
// _isTypeOperandEqual handles comparison of types so can defer to it
return _isTypeOperandEqual(a, b);
}
-
+
+ bool isIntegralType(IRType *t)
+ {
+ if(auto basic = as<IRBasicType>(t))
+ {
+ switch(basic->getBaseType())
+ {
+ case BaseType::Int8:
+ case BaseType::Int16:
+ case BaseType::Int:
+ case BaseType::Int64:
+ case BaseType::UInt8:
+ case BaseType::UInt16:
+ case BaseType::UInt:
+ case BaseType::UInt64:
+ return true;
+ default:
+ return false;
+ }
+ }
+ return false;
+ }
+
void findAllInstsBreadthFirst(IRInst* inst, List<IRInst*>& outInsts)
{
Index index = outInsts.getCount();
@@ -6275,6 +6319,7 @@ namespace Slang
case kIROp_FieldAddress:
case kIROp_getElement:
case kIROp_getElementPtr:
+ case kIROp_MeshOutputRef:
case kIROp_constructVectorFromScalar:
case kIROp_swizzle:
case kIROp_swizzleSet: // Doesn't actually "set" anything - just returns the resulting vector
diff --git a/source/slang/slang-ir.h b/source/slang/slang-ir.h
index 59a61958d..ab3f010b0 100644
--- a/source/slang/slang-ir.h
+++ b/source/slang/slang-ir.h
@@ -7,6 +7,8 @@
// similar in spirit to LLVM (but much simpler).
//
+#include <functional>
+
#include "../core/slang-basic.h"
#include "../core/slang-memory-arena.h"
@@ -635,6 +637,7 @@ struct IRInst
IRInst* getOperand(UInt index)
{
+ SLANG_ASSERT(index < getOperandCount());
return getOperands()[index].get();
}
@@ -867,6 +870,9 @@ SIMPLE_IR_TYPE(DifferentialBottomType, Type)
// Note compares nominal types by name alone
bool isTypeEqual(IRType* a, IRType* b);
+// True if this is an integral IRBasicType, not including Char or Ptr types
+bool isIntegralType(IRType* t);
+
void findAllInstsBreadthFirst(IRInst* inst, List<IRInst*>& outInsts);
// Constant Instructions
@@ -1259,6 +1265,21 @@ SIMPLE_IR_TYPE(HLSLPointStreamType, HLSLStreamOutputType)
SIMPLE_IR_TYPE(HLSLLineStreamType, HLSLStreamOutputType)
SIMPLE_IR_TYPE(HLSLTriangleStreamType, HLSLStreamOutputType)
+// Mesh shaders
+// TODO: Ellie, should this parent struct be shared with Patch?
+// IRArrayLikeType? IROpaqueArrayLikeType?
+struct IRMeshOutputType : IRType
+{
+ IRType* getElementType() { return (IRType*)getOperand(0); }
+ IRInst* getMaxElementCount() { return getOperand(1); }
+
+ IR_PARENT_ISA(MeshOutputType)
+};
+
+SIMPLE_IR_TYPE(VerticesType, MeshOutputType)
+SIMPLE_IR_TYPE(IndicesType, MeshOutputType)
+SIMPLE_IR_TYPE(PrimitivesType, MeshOutputType)
+
SIMPLE_IR_TYPE(GLSLInputAttachmentType, Type)
SIMPLE_IR_PARENT_TYPE(ParameterGroupType, PointerLikeType)
SIMPLE_IR_PARENT_TYPE(UniformParameterGroupType, ParameterGroupType)
@@ -1902,6 +1923,126 @@ IRFunc* getParentFunc(IRInst* inst);
uint32_t& _debugGetIRAllocCounter();
#endif
+// TODO: Ellie, comment and move somewhere more appropriate?
+
+template<typename I = IRInst, typename F>
+static void traverseUses(IRInst* inst, F f)
+{
+ auto n = inst->firstUse;
+ IRUse* u;
+ while((u = n) != nullptr)
+ {
+ n = u->nextUse;
+ if(auto s = as<I>(u->getUser()))
+ {
+ f(s);
+ }
+ }
+}
+
+namespace detail
+{
+// A helper to get the singular pointer argument of something callable
+// Use std::function to allow passing in anything from which std::function can
+// be deduced (pointers, lambdas, functors):
+// https://en.cppreference.com/w/cpp/utility/functional/function/deduction_guides
+// argType<T> matches T against R(A*) and returns A
+template<typename R, typename A>
+static A argType(std::function<R(A*)>);
+
+// Get the class type from a pointer to member function
+template<typename R, typename T>
+static T thisArg(R (T::*&&())());
+}
+
+#if __cplusplus >= 201703L
+// A tool to "pattern match" an instruction against multiple cases
+// Use like:
+//
+// ```
+// auto r = instMatch_(myInst,
+// default,
+// [](IRStore* store){ return handleStore... },
+// [](IRType* type){ return handleTypes... },
+// );
+// ```
+//
+// This version returns default if none of the cases match
+template<typename R, typename F, typename... Fs>
+R instMatch(IRInst* i, R def, F f, Fs... fs)
+{
+ static_assert(__cplusplus >= 201703L, "Wait until we're on c++17 to use instMatch");
+ // Recursive case
+ using P = decltype(detail::argType(std::function{std::declval<F>()}));
+ if(auto s = as<P>(i))
+ {
+ return f(s);
+ }
+ return instMatch(i, def, fs...);
}
+// Base case with no eliminators, return the default value
+template<typename R>
+R instMatch(IRInst* i, R def)
+{
+ return def;
+}
+
+// A tool to "pattern match" an instruction against multiple cases
+// Use like:
+//
+// ```
+// instMatch_(myInst,
+// [](IRStore* store){ handleStore... },
+// [](IRType* type){ handleTypes... },
+// [](IRInst* inst){ catch-all case...}
+// );
+// ```
+//
+// This version returns nothing
+template<typename F, typename... Fs>
+void instMatch_(IRInst* i, F f, Fs... fs)
+{
+ static_assert(__cplusplus >= 201703L, "Wait until we're on c++17 to use instMatch_");
+ // Recursive case
+ using P = decltype(detail::argType(std::function{std::declval<F>()}));
+ if(auto s = as<P>(i))
+ {
+ return f(s);
+ }
+ return instMatch_(i, fs...);
+}
+
+template<typename... Fs>
+void instMatch_(IRInst* i)
+{
+ // Base case with no eliminators
+}
+#endif
+
+// A tool to compose a bunch of downcasts and accessors
+// `composeGetters<R>(x, &MyStruct::getFoo, &MyOtherStruct::getBar)` translates to
+// `if(auto y = as<MyStruct>) if(auto z = as<MyOtherStruct>(y->getFoo())) return as<R>(z->getBar())`
+template<typename R, typename T, typename F, typename... Fs>
+R* composeGetters(T* t, F f, Fs... fs)
+{
+ using D = decltype(detail::thisArg(std::declval<F>));
+ if(D* d = as<D>(t))
+ {
+ // TODO: When we're on c++17, use std::invoke
+ // auto* n = std::invoke(f, d);
+ auto* n = (d->*f)();
+ return composeGetters<R>(n, fs...);
+ }
+ return nullptr;
+}
+
+template<typename R, typename T>
+R* composeGetters(T* t)
+{
+ return as<R>(t);
+}
+
+} // namespace Slang
+
#endif
diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp
index cf3781415..dc0af4f96 100644
--- a/source/slang/slang-lower-to-ir.cpp
+++ b/source/slang/slang-lower-to-ir.cpp
@@ -984,6 +984,7 @@ top:
goto top;
}
+ // TODO: Ellie, Is this really unreachable? User code input can get here
SLANG_UNEXPECTED("subscript had no getter");
UNREACHABLE_RETURN(LoweredValInfo());
}
@@ -1925,6 +1926,14 @@ struct ValLoweringVisitor : ValVisitor<ValLoweringVisitor, LoweredValInfo, Lower
return lowerGenericIntrinsicType(type, elementType, count);
}
+ IRType* visitMeshOutputType(MeshOutputType* type)
+ {
+ Type* elementType = type->getElementType();
+ IntVal* count = type->getMaxElementCount();
+
+ return lowerGenericIntrinsicType(type, elementType, count);
+ }
+
IRType* visitExtractExistentialType(ExtractExistentialType* type)
{
auto declRef = type->declRef;
@@ -2138,6 +2147,26 @@ void addVarDecorations(
// TODO: what are other modifiers we need to propagate through?
}
+ if(auto t = composeGetters<IRMeshOutputType>(inst->getFullType(), &IROutTypeBase::getValueType))
+ {
+ IROp op;
+ switch(t->getOp())
+ {
+ case kIROp_VerticesType:
+ op = kIROp_VerticesDecoration;
+ break;
+ case kIROp_IndicesType:
+ op = kIROp_IndicesDecoration;
+ break;
+ case kIROp_PrimitivesType:
+ op = kIROp_PrimitivesDecoration;
+ break;
+ default:
+ SLANG_UNREACHABLE("Missing case for IRMeshOutputType");
+ break;
+ }
+ builder->addMeshOutputDecoration(op, inst, t->getMaxElementCount());
+ }
}
/// If `decl` has a modifier that should turn into a
diff --git a/source/slang/slang-options.cpp b/source/slang/slang-options.cpp
index 58039bb7c..0ad4ccfaf 100644
--- a/source/slang/slang-options.cpp
+++ b/source/slang/slang-options.cpp
@@ -264,6 +264,8 @@ struct OptionsParser
{ ".tesc", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_HULL },
{ ".tese", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_DOMAIN },
{ ".comp", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_COMPUTE },
+ { ".mesh", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_MESH },
+ { ".task", SLANG_SOURCE_LANGUAGE_GLSL, SLANG_STAGE_AMPLIFICATION },
{ ".c", SLANG_SOURCE_LANGUAGE_C, SLANG_STAGE_NONE },
{ ".cpp", SLANG_SOURCE_LANGUAGE_CPP, SLANG_STAGE_NONE },
diff --git a/source/slang/slang-parameter-binding.cpp b/source/slang/slang-parameter-binding.cpp
index 07875c183..db323ff6e 100644
--- a/source/slang/slang-parameter-binding.cpp
+++ b/source/slang/slang-parameter-binding.cpp
@@ -1912,6 +1912,33 @@ static RefPtr<TypeLayout> processEntryPointVaryingParameter(
return arrayTypeLayout;
}
+ else if( auto meshOutputType = as<MeshOutputType>(type) )
+ {
+ // TODO: Ellie, revisit
+ // Note: Bad Things will happen if we have an array input
+ // without a semantic already being enforced.
+
+ // We use the first element to derive the layout for the element type
+ auto elementTypeLayout = processEntryPointVaryingParameter(context, meshOutputType->getElementType(), state, varLayout);
+
+ RefPtr<ArrayTypeLayout> arrayTypeLayout = new ArrayTypeLayout();
+ arrayTypeLayout->elementTypeLayout = elementTypeLayout;
+ arrayTypeLayout->type = arrayType;
+
+ // TODO: Ellie, this is probably not the right place to handle this
+ // On GLSL the indices type is built in and as such doesn't consume
+ // resources.
+ if(!isKhronosTarget(context->getTargetRequest()) || !as<IndicesType>(type))
+ {
+ for (auto rr : elementTypeLayout->resourceInfos)
+ {
+ // TODO: Ellie, explain why only one slot is consumed here
+ arrayTypeLayout->findOrAddResourceInfo(rr.kind)->count = rr.count;
+ }
+ }
+
+ return arrayTypeLayout;
+ }
// Ignore a bunch of types that don't make sense here...
else if (auto textureType = as<TextureType>(type)) { return nullptr; }
else if(auto samplerStateType = as<SamplerStateType>(type)) { return nullptr; }
diff --git a/source/slang/slang-parser.cpp b/source/slang/slang-parser.cpp
index d3dc5964e..7cd0cdce9 100644
--- a/source/slang/slang-parser.cpp
+++ b/source/slang/slang-parser.cpp
@@ -6627,6 +6627,11 @@ namespace Slang
_makeParseModifier("lineadj", HLSLLineAdjModifier::kReflectClassInfo),
_makeParseModifier("triangleadj", HLSLTriangleAdjModifier::kReflectClassInfo),
+ // Modifiers for mesh shader parameters
+ _makeParseModifier("vertices", HLSLVerticesModifier::kReflectClassInfo),
+ _makeParseModifier("indices", HLSLIndicesModifier::kReflectClassInfo),
+ _makeParseModifier("primitives", HLSLPrimitivesModifier::kReflectClassInfo),
+
// Modifiers for unary operator declarations
_makeParseModifier("__prefix", PrefixModifier::kReflectClassInfo),
_makeParseModifier("__postfix", PostfixModifier::kReflectClassInfo),
diff --git a/source/slang/slang-profile-defs.h b/source/slang/slang-profile-defs.h
index acef8cb0a..27027285a 100644
--- a/source/slang/slang-profile-defs.h
+++ b/source/slang/slang-profile-defs.h
@@ -184,6 +184,14 @@ PROFILE(DX_Vertex_6_1, vs_6_1, Vertex, DX_6_1)
PROFILE(DX_Vertex_6_2, vs_6_2, Vertex, DX_6_2)
PROFILE(DX_Vertex_6_3, vs_6_3, Vertex, DX_6_3)
+PROFILE(DX_Mesh_6_5, ms_6_5, Mesh, DX_6_5)
+PROFILE(DX_Mesh_6_6, ms_6_6, Mesh, DX_6_6)
+PROFILE(DX_Mesh_6_7, ms_6_7, Mesh, DX_6_7)
+
+PROFILE(DX_Amplification_6_5, as_6_5, Amplification, DX_6_5)
+PROFILE(DX_Amplification_6_6, as_6_6, Amplification, DX_6_6)
+PROFILE(DX_Amplification_6_7, as_6_7, Amplification, DX_6_7)
+
// TODO: consider making `lib_*_*` alias these...
PROFILE(DX_None_4_0, sm_4_0, Unknown, DX_4_0)
PROFILE(DX_None_4_0_Level_9_0, sm_4_0_level_9_0, Unknown, DX_4_0_Level_9_0)
diff --git a/source/slang/slang-reflection-api.cpp b/source/slang/slang-reflection-api.cpp
index ca64cbed1..2fa17c14b 100644
--- a/source/slang/slang-reflection-api.cpp
+++ b/source/slang/slang-reflection-api.cpp
@@ -335,6 +335,10 @@ SLANG_API SlangTypeKind spReflectionType_GetKind(SlangReflectionType* inType)
{
return SLANG_TYPE_KIND_OUTPUT_STREAM;
}
+ else if( as<MeshOutputType>(type) )
+ {
+ return SLANG_TYPE_KIND_MESH_OUTPUT;
+ }
else if (as<TextureBufferType>(type))
{
return SLANG_TYPE_KIND_TEXTURE_BUFFER;
diff --git a/source/slang/slang-syntax.cpp b/source/slang/slang-syntax.cpp
index f3b590acf..80f57905f 100644
--- a/source/slang/slang-syntax.cpp
+++ b/source/slang/slang-syntax.cpp
@@ -1065,6 +1065,22 @@ Index getFilterCountImpl(const ReflectClassInfo& clsInfo, MemberFilterStyle filt
return as<IntVal>(findInnerMostGenericSubstitution(declRef.substitutions)->getArgs()[1]);
}
+ // MeshOutputType
+ // There's a subtle distinction between this and HLSLPatchType, the size
+ // here is the max possible size of the array, it's free to change at
+ // runtime. There's probably no circumstance where you'd want to be generic
+ // between the two, so we don't deduplicate this code.
+
+ Type* MeshOutputType::getElementType()
+ {
+ return as<Type>(findInnerMostGenericSubstitution(declRef.substitutions)->getArgs()[0]);
+ }
+
+ IntVal* MeshOutputType::getMaxElementCount()
+ {
+ return as<IntVal>(findInnerMostGenericSubstitution(declRef.substitutions)->getArgs()[1]);
+ }
+
// Constructors for types
ArrayExpressionType* getArrayType(
diff --git a/source/slang/slang-type-layout.cpp b/source/slang/slang-type-layout.cpp
index c77aadd68..45bacaa44 100644
--- a/source/slang/slang-type-layout.cpp
+++ b/source/slang/slang-type-layout.cpp
@@ -3240,6 +3240,171 @@ RefPtr<TypeLayout> createTypeLayoutForGlobalGenericTypeParam(
return _createTypeLayoutForGlobalGenericTypeParam(context, type, globalGenericParamDecl).layout;
}
+static TypeLayoutResult createArrayLikeTypeLayout(
+ TypeLayoutContext const& context,
+ Type* type,
+ Type* baseType,
+ IntVal* arrayLength
+ )
+{
+ auto rules = context.rules;
+
+ auto elementResult = _createTypeLayout(
+ context,
+ baseType);
+ auto elementInfo = elementResult.info;
+ auto elementTypeLayout = elementResult.layout;
+
+ // To a first approximation, an array will usually be laid out
+ // by taking the element's type layout and laying out `elementCount`
+ // copies of it. There are of course many details that make
+ // this simplistic version of things not quite work.
+ //
+ // An important complication to deal with is the possibility of
+ // having "unbounded" arrays, which don't specify a size.'
+ // The layout rules for these vary heavily by resource kind and API.
+ //
+
+ auto elementCount = GetElementCount(arrayLength);
+
+ //
+ // We can compute the uniform storage layout of an array using
+ // the rules for the target API.
+ //
+ // TODO: ensure that this does something reasonable with the unbounded
+ // case, or else issue an error message that the target doesn't
+ // support unbounded types.
+ //
+
+ auto arrayUniformInfo = rules->GetArrayLayout(
+ elementInfo,
+ elementCount).getUniformLayout();
+
+ RefPtr<ArrayTypeLayout> typeLayout = new ArrayTypeLayout();
+
+ // Some parts of the array type layout object are easy to fill in:
+ typeLayout->type = type;
+ typeLayout->rules = rules;
+ typeLayout->originalElementTypeLayout = elementTypeLayout;
+ typeLayout->uniformAlignment = arrayUniformInfo.alignment;
+ typeLayout->uniformStride = arrayUniformInfo.elementStride;
+
+ typeLayout->addResourceUsage(LayoutResourceKind::Uniform, arrayUniformInfo.size);
+
+ //
+ // The tricky part in constructing an array type layout comes when
+ // the element type is (or nests) a structure with resource-type
+ // fields, because in that case we need to perform AoS-to-SoA
+ // conversion as part of computing the final type layout, and
+ // we also need to pre-compute an "adjusted" element type
+ // layout that accounts for the striding that happens with
+ // resource-type contents.
+ //
+ // This complication is only made worse when we have to deal with
+ // unbounded-size arrays over such element types, since those
+ // resource-type fields will each end up consuming a full space
+ // in the resulting layout.
+ //
+ // The `maybeAdjustLayoutForArrayElementType` computes an "adjusted"
+ // type layout for the element type which takes the array stride into
+ // account. If it returns the same type layout that was passed in,
+ // then that means no adjustement took place.
+ //
+ // The `additionalSpacesNeededForAdjustedElementType` variable counts
+ // the number of additional register spaces that were consumed,
+ // in the case of an unbounded array.
+ //
+ UInt additionalSpacesNeededForAdjustedElementType = 0;
+ RefPtr<TypeLayout> adjustedElementTypeLayout = maybeAdjustLayoutForArrayElementType(
+ elementTypeLayout,
+ elementCount,
+ additionalSpacesNeededForAdjustedElementType);
+
+ typeLayout->elementTypeLayout = adjustedElementTypeLayout;
+
+ // We will now iterate over the resources consumed by the element
+ // type to compute how they contribute to the resource usage
+ // of the overall array type.
+ //
+ for( auto elementResourceInfo : elementTypeLayout->resourceInfos )
+ {
+ // The uniform case was already handled above
+ if( elementResourceInfo.kind == LayoutResourceKind::Uniform )
+ continue;
+
+ LayoutSize arrayResourceCount = 0;
+
+ // In almost all cases, the resources consumed by an array
+ // will be its element count times the resources consumed
+ // by its element type.
+ //
+ // The first exception to this is arrays of resources when
+ // compiling to GLSL for Vulkan, where an entire array
+ // only consumes a single descriptor-table slot.
+ //
+ if (elementResourceInfo.kind == LayoutResourceKind::DescriptorTableSlot)
+ {
+ arrayResourceCount = elementResourceInfo.count;
+ }
+ // The second exception to this is arrays of an existential type
+ // where the entire array should be specialized to a single concrete type.
+ //
+ else if (elementResourceInfo.kind == LayoutResourceKind::ExistentialTypeParam)
+ {
+ arrayResourceCount = elementResourceInfo.count;
+ }
+ //
+ // The next big exception is when we are forming an unbounded-size
+ // array and the element type got "adjusted," because that means
+ // the array type will need to allocate full spaces for any resource-type
+ // fields in the element type.
+ //
+ // Note: we carefully carve things out so that the case of a simple
+ // array of resources does *not* lead to the element type being adjusted,
+ // so that this logic doesn't trigger and we instead handle it with
+ // the default logic below.
+ //
+ else if(
+ elementCount.isInfinite()
+ && adjustedElementTypeLayout != elementTypeLayout
+ && doesResourceRequireAdjustmentForArrayOfStructs(elementResourceInfo.kind) )
+ {
+ // We want to ignore resource types consumed by the element type
+ // that need adjustement if the array size is infinite, since
+ // we will be allocating whole spaces for that part of the
+ // element's resource usage.
+ }
+ else
+ {
+ arrayResourceCount = elementResourceInfo.count * elementCount;
+ }
+
+ // Now that we've computed how the resource usage of the element type
+ // should contribute to the resource usage of the array, we can
+ // add in that resource usage.
+ //
+ typeLayout->addResourceUsage(
+ elementResourceInfo.kind,
+ arrayResourceCount);
+ }
+
+ // The loop above to compute the resource usage of the array from its
+ // element type ignored any resource-type fields in an unbounded-size
+ // array if they would have been allocated as full register spaces.
+ // Those same fields were counted in `additionalSpacesNeededForAdjustedElementType`,
+ // and need to be added into the total resource usage for the array
+ // if we skipped them as part of the loop (which happens when
+ // we detect that the element type layout had been "adjusted").
+ //
+ if( adjustedElementTypeLayout != elementTypeLayout )
+ {
+ typeLayout->addResourceUsage(LayoutResourceKind::RegisterSpace, additionalSpacesNeededForAdjustedElementType);
+ }
+
+ return TypeLayoutResult(typeLayout, arrayUniformInfo);
+}
+
+
static TypeLayoutResult _createTypeLayout(
TypeLayoutContext const& context,
Type* type)
@@ -3504,159 +3669,7 @@ static TypeLayoutResult _createTypeLayout(
}
else if (auto arrayType = as<ArrayExpressionType>(type))
{
- auto elementResult = _createTypeLayout(
- context,
- arrayType->baseType);
- auto elementInfo = elementResult.info;
- auto elementTypeLayout = elementResult.layout;
-
- // To a first approximation, an array will usually be laid out
- // by taking the element's type layout and laying out `elementCount`
- // copies of it. There are of course many details that make
- // this simplistic version of things not quite work.
- //
- // An important complication to deal with is the possibility of
- // having "unbounded" arrays, which don't specify a size.'
- // The layout rules for these vary heavily by resource kind and API.
- //
-
- auto elementCount = GetElementCount(arrayType->arrayLength);
-
- //
- // We can compute the uniform storage layout of an array using
- // the rules for the target API.
- //
- // TODO: ensure that this does something reasonable with the unbounded
- // case, or else issue an error message that the target doesn't
- // support unbounded types.
- //
-
- auto arrayUniformInfo = rules->GetArrayLayout(
- elementInfo,
- elementCount).getUniformLayout();
-
- RefPtr<ArrayTypeLayout> typeLayout = new ArrayTypeLayout();
-
- // Some parts of the array type layout object are easy to fill in:
- typeLayout->type = type;
- typeLayout->rules = rules;
- typeLayout->originalElementTypeLayout = elementTypeLayout;
- typeLayout->uniformAlignment = arrayUniformInfo.alignment;
- typeLayout->uniformStride = arrayUniformInfo.elementStride;
-
- typeLayout->addResourceUsage(LayoutResourceKind::Uniform, arrayUniformInfo.size);
-
- //
- // The tricky part in constructing an array type layout comes when
- // the element type is (or nests) a structure with resource-type
- // fields, because in that case we need to perform AoS-to-SoA
- // conversion as part of computing the final type layout, and
- // we also need to pre-compute an "adjusted" element type
- // layout that accounts for the striding that happens with
- // resource-type contents.
- //
- // This complication is only made worse when we have to deal with
- // unbounded-size arrays over such element types, since those
- // resource-type fields will each end up consuming a full space
- // in the resulting layout.
- //
- // The `maybeAdjustLayoutForArrayElementType` computes an "adjusted"
- // type layout for the element type which takes the array stride into
- // account. If it returns the same type layout that was passed in,
- // then that means no adjustement took place.
- //
- // The `additionalSpacesNeededForAdjustedElementType` variable counts
- // the number of additional register spaces that were consumed,
- // in the case of an unbounded array.
- //
- UInt additionalSpacesNeededForAdjustedElementType = 0;
- RefPtr<TypeLayout> adjustedElementTypeLayout = maybeAdjustLayoutForArrayElementType(
- elementTypeLayout,
- elementCount,
- additionalSpacesNeededForAdjustedElementType);
-
- typeLayout->elementTypeLayout = adjustedElementTypeLayout;
-
- // We will now iterate over the resources consumed by the element
- // type to compute how they contribute to the resource usage
- // of the overall array type.
- //
- for( auto elementResourceInfo : elementTypeLayout->resourceInfos )
- {
- // The uniform case was already handled above
- if( elementResourceInfo.kind == LayoutResourceKind::Uniform )
- continue;
-
- LayoutSize arrayResourceCount = 0;
-
- // In almost all cases, the resources consumed by an array
- // will be its element count times the resources consumed
- // by its element type.
- //
- // The first exception to this is arrays of resources when
- // compiling to GLSL for Vulkan, where an entire array
- // only consumes a single descriptor-table slot.
- //
- if (elementResourceInfo.kind == LayoutResourceKind::DescriptorTableSlot)
- {
- arrayResourceCount = elementResourceInfo.count;
- }
- // The second exception to this is arrays of an existential type
- // where the entire array should be specialized to a single concrete type.
- //
- else if (elementResourceInfo.kind == LayoutResourceKind::ExistentialTypeParam)
- {
- arrayResourceCount = elementResourceInfo.count;
- }
- //
- // The next big exception is when we are forming an unbounded-size
- // array and the element type got "adjusted," because that means
- // the array type will need to allocate full spaces for any resource-type
- // fields in the element type.
- //
- // Note: we carefully carve things out so that the case of a simple
- // array of resources does *not* lead to the element type being adjusted,
- // so that this logic doesn't trigger and we instead handle it with
- // the default logic below.
- //
- else if(
- elementCount.isInfinite()
- && adjustedElementTypeLayout != elementTypeLayout
- && doesResourceRequireAdjustmentForArrayOfStructs(elementResourceInfo.kind) )
- {
- // We want to ignore resource types consumed by the element type
- // that need adjustement if the array size is infinite, since
- // we will be allocating whole spaces for that part of the
- // element's resource usage.
- }
- else
- {
- arrayResourceCount = elementResourceInfo.count * elementCount;
- }
-
- // Now that we've computed how the resource usage of the element type
- // should contribute to the resource usage of the array, we can
- // add in that resource usage.
- //
- typeLayout->addResourceUsage(
- elementResourceInfo.kind,
- arrayResourceCount);
- }
-
- // The loop above to compute the resource usage of the array from its
- // element type ignored any resource-type fields in an unbounded-size
- // array if they would have been allocated as full register spaces.
- // Those same fields were counted in `additionalSpacesNeededForAdjustedElementType`,
- // and need to be added into the total resource usage for the array
- // if we skipped them as part of the loop (which happens when
- // we detect that the element type layout had been "adjusted").
- //
- if( adjustedElementTypeLayout != elementTypeLayout )
- {
- typeLayout->addResourceUsage(LayoutResourceKind::RegisterSpace, additionalSpacesNeededForAdjustedElementType);
- }
-
- return TypeLayoutResult(typeLayout, arrayUniformInfo);
+ return createArrayLikeTypeLayout(context, arrayType, arrayType->baseType, arrayType->arrayLength);
}
else if (auto declRefType = as<DeclRefType>(type))
{
diff --git a/source/slang/slang.natvis b/source/slang/slang.natvis
index 13334e00c..59f2007ce 100644
--- a/source/slang/slang.natvis
+++ b/source/slang/slang.natvis
@@ -420,6 +420,10 @@
<ExpandedItem Condition="astNodeType == Slang::ASTNodeType::HLSLPointStreamType">(Slang::HLSLPointStreamType*)&amp;astNodeType</ExpandedItem>
<ExpandedItem Condition="astNodeType == Slang::ASTNodeType::HLSLLineStreamType">(Slang::HLSLLineStreamType*)&amp;astNodeType</ExpandedItem>
<ExpandedItem Condition="astNodeType == Slang::ASTNodeType::HLSLTriangleStreamType">(Slang::HLSLTriangleStreamType*)&amp;astNodeType</ExpandedItem>
+ <ExpandedItem Condition="astNodeType == Slang::ASTNodeType::MeshOutputType">(Slang::HLSLMeshOutputType*)&amp;astNodeType</ExpandedItem>
+ <ExpandedItem Condition="astNodeType == Slang::ASTNodeType::VerticesType">(Slang::VerticesType*)&amp;astNodeType</ExpandedItem>
+ <ExpandedItem Condition="astNodeType == Slang::ASTNodeType::IndicesType">(Slang::IndicesType*)&amp;astNodeType</ExpandedItem>
+ <ExpandedItem Condition="astNodeType == Slang::ASTNodeType::PrimitivesType">(Slang::PrimitivesType*)&amp;astNodeType</ExpandedItem>
<ExpandedItem Condition="astNodeType == Slang::ASTNodeType::UntypedBufferResourceType">(Slang::UntypedBufferResourceType*)&amp;astNodeType</ExpandedItem>
<ExpandedItem Condition="astNodeType == Slang::ASTNodeType::HLSLByteAddressBufferType">(Slang::HLSLByteAddressBufferType*)&amp;astNodeType</ExpandedItem>
<ExpandedItem Condition="astNodeType == Slang::ASTNodeType::HLSLRWByteAddressBufferType">(Slang::HLSLRWByteAddressBufferType*)&amp;astNodeType</ExpandedItem>
@@ -565,4 +569,4 @@
<ExpandedItem Condition="astNodeType == Slang::ASTNodeType::ModifiedType">(Slang::ModifiedType*)&amp;astNodeType</ExpandedItem>
</Expand>
</Type>
-</AutoVisualizer> \ No newline at end of file
+</AutoVisualizer>
diff --git a/tests/pipeline/rasterization/mesh/component-write.slang b/tests/pipeline/rasterization/mesh/component-write.slang
new file mode 100644
index 000000000..a34f6ee31
--- /dev/null
+++ b/tests/pipeline/rasterization/mesh/component-write.slang
@@ -0,0 +1,49 @@
+// component-write.slang
+
+// This tests that writing to individual components of the output struct works
+
+//TEST:CROSS_COMPILE:-target spirv -profile glsl_450+spirv_1_4 -entry main -stage mesh
+
+const static float2 positions[3] = {
+ float2(0.0, -0.5),
+ float2(0.5, 0.5),
+ float2(-0.5, 0.5)
+};
+
+const static float3 colors[3] = {
+ float3(1.0, 1.0, 0.0),
+ float3(0.0, 1.0, 1.0),
+ float3(1.0, 0.0, 1.0)
+};
+
+struct Vertex
+{
+ float4 pos : SV_Position;
+ float3 color : Color;
+};
+
+const static uint MAX_VERTS = 3;
+const static uint MAX_PRIMS = 1;
+
+[outputtopology("triangle")]
+[numthreads(3, 1, 1)]
+void main(
+ in uint tig : SV_GroupIndex,
+ out Vertices<Vertex, MAX_VERTS> verts,
+ out Indices<uint3, MAX_PRIMS> triangles
+ )
+{
+ const uint numVertices = 3;
+ const uint numPrimitives = 1;
+ SetMeshOutputCounts(numVertices, numPrimitives);
+
+ if(tig < numVertices) {
+ verts[tig].pos = float4(positions[tig], 0, 1);
+ verts[tig].color = colors[tig];
+ }
+
+ if(tig < numPrimitives) {
+ triangles[tig] = uint3(0,1,2);
+ }
+}
+
diff --git a/tests/pipeline/rasterization/mesh/component-write.slang.glsl b/tests/pipeline/rasterization/mesh/component-write.slang.glsl
new file mode 100644
index 000000000..5234fb62f
--- /dev/null
+++ b/tests/pipeline/rasterization/mesh/component-write.slang.glsl
@@ -0,0 +1,39 @@
+#version 450
+#extension GL_EXT_mesh_shader : require
+layout(row_major) uniform;
+layout(row_major) buffer;
+const vec3 colors_0[3] = { vec3(1.00000000000000000000, 1.00000000000000000000, 0.00000000000000000000), vec3(0.00000000000000000000, 1.00000000000000000000, 1.00000000000000000000), vec3(1.00000000000000000000, 0.00000000000000000000, 1.00000000000000000000) };
+const vec2 positions_0[3] = { vec2(0.00000000000000000000, -0.50000000000000000000), vec2(0.50000000000000000000, 0.50000000000000000000), vec2(-0.50000000000000000000, 0.50000000000000000000) };
+layout(location = 0)
+out vec3 _S1[3];
+
+out gl_MeshPerVertexEXT
+{
+ vec4 gl_Position;
+} gl_MeshVerticesEXT[3];
+
+layout(local_size_x = 3, local_size_y = 1, local_size_z = 1) in;
+layout(max_vertices = 3) out;
+layout(max_primitives = 1) out;
+layout(triangles) out;
+void main()
+{
+ SetMeshOutputsEXT(3U, 1U);
+ if(gl_LocalInvocationIndex < 3U)
+ {
+ gl_MeshVerticesEXT[gl_LocalInvocationIndex].gl_Position = vec4(positions_0[gl_LocalInvocationIndex], 0.00000000000000000000, 1.00000000000000000000);
+ _S1[gl_LocalInvocationIndex] = colors_0[gl_LocalInvocationIndex];
+ }
+ else
+ {
+ }
+ if(gl_LocalInvocationIndex < 1U)
+ {
+ gl_PrimitiveTriangleIndicesEXT[gl_LocalInvocationIndex] = uvec3(0U, 1U, 2U);
+ }
+ else
+ {
+ }
+ return;
+}
+
diff --git a/tests/pipeline/rasterization/mesh/hello.slang b/tests/pipeline/rasterization/mesh/hello.slang
new file mode 100644
index 000000000..5eea900d3
--- /dev/null
+++ b/tests/pipeline/rasterization/mesh/hello.slang
@@ -0,0 +1,49 @@
+// hello.slang
+
+// Test that a simple mesh shader compiles
+
+//TEST:CROSS_COMPILE:-target spirv-assembly -entry main -stage mesh -profile glsl_450+spirv_1_4
+//TEST:CROSS_COMPILE:-target dxil-assembly -entry main -stage mesh -profile sm_6_6
+
+const static float2 positions[3] = {
+ float2(0.0, -0.5),
+ float2(0.5, 0.5),
+ float2(-0.5, 0.5)
+};
+
+const static float3 colors[3] = {
+ float3(1.0, 1.0, 0.0),
+ float3(0.0, 1.0, 1.0),
+ float3(1.0, 0.0, 1.0)
+};
+
+struct Vertex
+{
+ float4 pos : SV_Position;
+ float3 color : Color;
+};
+
+const static uint MAX_VERTS = 3;
+const static uint MAX_PRIMS = 1;
+
+[outputtopology("triangle")]
+[numthreads(3, 1, 1)]
+void main(
+ in uint tig : SV_GroupIndex,
+ out Vertices<Vertex, MAX_VERTS> verts,
+ out Indices<uint3, MAX_PRIMS> triangles
+ )
+{
+ const uint numVertices = 3;
+ const uint numPrimitives = 1;
+ SetMeshOutputCounts(numVertices, numPrimitives);
+
+ if(tig < numVertices) {
+ verts[tig] = {float4(positions[tig], 0, 1), colors[tig]};
+ }
+
+ if(tig < numPrimitives) {
+ triangles[tig] = uint3(0,1,2);
+ }
+}
+
diff --git a/tests/pipeline/rasterization/mesh/hello.slang.glsl b/tests/pipeline/rasterization/mesh/hello.slang.glsl
new file mode 100644
index 000000000..66630012b
--- /dev/null
+++ b/tests/pipeline/rasterization/mesh/hello.slang.glsl
@@ -0,0 +1,46 @@
+#version 450
+#extension GL_EXT_mesh_shader : require
+layout(row_major) uniform;
+layout(row_major) buffer;
+const vec3 colors_0[3] = { vec3(1.00000000000000000000, 1.00000000000000000000, 0.00000000000000000000), vec3(0.00000000000000000000, 1.00000000000000000000, 1.00000000000000000000), vec3(1.00000000000000000000, 0.00000000000000000000, 1.00000000000000000000) };
+const vec2 positions_0[3] = { vec2(0.00000000000000000000, -0.50000000000000000000), vec2(0.50000000000000000000, 0.50000000000000000000), vec2(-0.50000000000000000000, 0.50000000000000000000) };
+layout(location = 0)
+out vec3 _S1[3];
+
+out gl_MeshPerVertexEXT
+{
+ vec4 gl_Position;
+} gl_MeshVerticesEXT[3];
+
+struct Vertex_0
+{
+ vec4 pos_0;
+ vec3 color_0;
+};
+
+layout(local_size_x = 3, local_size_y = 1, local_size_z = 1) in;
+layout(max_vertices = 3) out;
+layout(max_primitives = 1) out;
+layout(triangles) out;
+void main()
+{
+ SetMeshOutputsEXT(3U, 1U);
+ if(gl_LocalInvocationIndex < 3U)
+ {
+ Vertex_0 _S2 = { vec4(positions_0[gl_LocalInvocationIndex], 0.00000000000000000000, 1.00000000000000000000), colors_0[gl_LocalInvocationIndex] };
+ gl_MeshVerticesEXT[gl_LocalInvocationIndex].gl_Position = _S2.pos_0;
+ _S1[gl_LocalInvocationIndex] = _S2.color_0;
+ }
+ else
+ {
+ }
+ if(gl_LocalInvocationIndex < 1U)
+ {
+ gl_PrimitiveTriangleIndicesEXT[gl_LocalInvocationIndex] = uvec3(0U, 1U, 2U);
+ }
+ else
+ {
+ }
+ return;
+}
+
diff --git a/tests/pipeline/rasterization/mesh/hello.slang.hlsl b/tests/pipeline/rasterization/mesh/hello.slang.hlsl
new file mode 100644
index 000000000..ea41895b1
--- /dev/null
+++ b/tests/pipeline/rasterization/mesh/hello.slang.hlsl
@@ -0,0 +1,36 @@
+#pragma pack_matrix(column_major)
+#ifdef SLANG_HLSL_ENABLE_NVAPI
+#include "nvHLSLExtns.h"
+#endif
+
+static const float3 colors_0[int(3)] = { float3(1.00000000000000000000, 1.00000000000000000000, 0.00000000000000000000), float3(0.00000000000000000000, 1.00000000000000000000, 1.00000000000000000000), float3(1.00000000000000000000, 0.00000000000000000000, 1.00000000000000000000) };
+static const float2 positions_0[int(3)] = { float2(0.00000000000000000000, -0.50000000000000000000), float2(0.50000000000000000000, 0.50000000000000000000), float2(-0.50000000000000000000, 0.50000000000000000000) };
+struct Vertex_0
+{
+ float4 pos_0 : SV_Position;
+ float3 color_0 : Color;
+};
+
+[numthreads(3, 1, 1)]
+[outputtopology("triangle")]
+void main(uint tig_0 : SV_GROUPINDEX, vertices out Vertex_0 verts_0[int(3)], indices out uint3 triangles_0[int(1)])
+{
+ SetMeshOutputCounts(3U, 1U);
+ if(tig_0 < 3U)
+ {
+ Vertex_0 _S1 = { float4(positions_0[tig_0], 0.00000000000000000000, 1.00000000000000000000), colors_0[tig_0] };
+ verts_0[tig_0] = _S1;
+ }
+ else
+ {
+ }
+ if(tig_0 < 1U)
+ {
+ triangles_0[tig_0] = uint3(0U, 1U, 2U);
+ }
+ else
+ {
+ }
+ return;
+}
+
diff --git a/tests/pipeline/rasterization/mesh/hlsl-syntax.slang b/tests/pipeline/rasterization/mesh/hlsl-syntax.slang
new file mode 100644
index 000000000..a0b397ca2
--- /dev/null
+++ b/tests/pipeline/rasterization/mesh/hlsl-syntax.slang
@@ -0,0 +1,54 @@
+// hlsl-syntax.slang
+
+// Test that we can ingest hlsl mesh output syntax
+
+//TEST:CROSS_COMPILE:-target spirv -profile glsl_450+spirv_1_4 -entry main -stage mesh
+
+const static float2 positions[3] = {
+ float2(0.0, -0.5),
+ float2(0.5, 0.5),
+ float2(-0.5, 0.5)
+};
+
+const static float3 colors[3] = {
+ float3(1.0, 1.0, 0.0),
+ float3(0.0, 1.0, 1.0),
+ float3(1.0, 0.0, 1.0)
+};
+
+struct Vertex
+{
+ float4 pos : SV_Position;
+ float3 color : Color;
+};
+
+const static uint MAX_VERTS = 3;
+const static uint MAX_PRIMS = 1;
+
+// Test that we can convert the HLSL Syntax to the typed syntax
+void foo(uint tig, out Vertices<Vertex, MAX_VERTS> verts)
+{
+ if(tig < 3) {
+ verts[tig] = {float4(positions[tig], 0, 1), colors[tig]};
+ }
+}
+
+[outputtopology("triangle")]
+[numthreads(3, 1, 1)]
+void main(
+ in uint tig : SV_GroupIndex,
+ out vertices Vertex verts[MAX_VERTS],
+ out indices uint3 triangles[MAX_PRIMS]
+ )
+{
+ const uint numVertices = 3;
+ const uint numPrimitives = 1;
+ SetMeshOutputCounts(numVertices, numPrimitives);
+
+ foo(tig, verts);
+
+ if(tig < numPrimitives) {
+ triangles[tig] = uint3(0,1,2);
+ }
+}
+
diff --git a/tests/pipeline/rasterization/mesh/hlsl-syntax.slang.glsl b/tests/pipeline/rasterization/mesh/hlsl-syntax.slang.glsl
new file mode 100644
index 000000000..4a6b8f86f
--- /dev/null
+++ b/tests/pipeline/rasterization/mesh/hlsl-syntax.slang.glsl
@@ -0,0 +1,51 @@
+#version 450
+#extension GL_EXT_mesh_shader : require
+layout(row_major) uniform;
+layout(row_major) buffer;
+const vec3 colors_0[3] = { vec3(1.00000000000000000000, 1.00000000000000000000, 0.00000000000000000000), vec3(0.00000000000000000000, 1.00000000000000000000, 1.00000000000000000000), vec3(1.00000000000000000000, 0.00000000000000000000, 1.00000000000000000000) };
+const vec2 positions_0[3] = { vec2(0.00000000000000000000, -0.50000000000000000000), vec2(0.50000000000000000000, 0.50000000000000000000), vec2(-0.50000000000000000000, 0.50000000000000000000) };
+layout(location = 0)
+out vec3 _S1[3];
+
+out gl_MeshPerVertexEXT
+{
+ vec4 gl_Position;
+} gl_MeshVerticesEXT[3];
+
+struct Vertex_0
+{
+ vec4 pos_0;
+ vec3 color_0;
+};
+
+void foo_0(uint _S2)
+{
+ if(_S2 < 3U)
+ {
+ Vertex_0 _S3 = { vec4(positions_0[_S2], 0.00000000000000000000, 1.00000000000000000000), colors_0[_S2] };
+ gl_MeshVerticesEXT[_S2].gl_Position = _S3.pos_0;
+ _S1[_S2] = _S3.color_0;
+ }
+ else
+ {
+ }
+ return;
+}
+
+layout(local_size_x = 3, local_size_y = 1, local_size_z = 1) in;
+layout(max_vertices = 3) out;
+layout(max_primitives = 1) out;
+layout(triangles) out;
+void main()
+{
+ SetMeshOutputsEXT(3U, 1U);
+ foo_0(gl_LocalInvocationIndex);
+ if(gl_LocalInvocationIndex < 1U)
+ {
+ gl_PrimitiveTriangleIndicesEXT[gl_LocalInvocationIndex] = uvec3(0U, 1U, 2U);
+ }
+ else
+ {
+ }
+ return;
+}
diff --git a/tests/pipeline/rasterization/mesh/nested-component-write.slang b/tests/pipeline/rasterization/mesh/nested-component-write.slang
new file mode 100644
index 000000000..68bb6dfb9
--- /dev/null
+++ b/tests/pipeline/rasterization/mesh/nested-component-write.slang
@@ -0,0 +1,51 @@
+// nested-component-write.slang
+
+// This tests that writing to individual components nested structs works for
+// mesh shader outputs.
+
+//TEST:CROSS_COMPILE:-target spirv -profile glsl_450+spirv_1_4 -entry main -stage mesh
+
+struct Foo
+{
+ float4 pos : SV_Position;
+ Bar bar;
+};
+
+struct Bar
+{
+ Baz baz : Color;
+};
+
+struct Baz
+{
+ float3 color;
+};
+
+struct Vertex
+{
+ Foo foo;
+};
+
+const static uint MAX_VERTS = 3;
+const static uint MAX_PRIMS = 1;
+
+[outputtopology("triangle")]
+[numthreads(3, 1, 1)]
+void main(
+ in uint tig : SV_GroupIndex,
+ out Vertices<Vertex, MAX_VERTS> verts,
+ out Indices<uint3, MAX_PRIMS> triangles
+ )
+{
+ SetMeshOutputCounts(3, 1);
+
+ if(tig < 3) {
+ verts[tig].foo.pos = float4(0, 0, 0, 1);
+ verts[tig].foo.bar.baz.color = float3(1, 2, 3);
+ }
+
+ if(tig < 1) {
+ triangles[tig] = uint3(0,1,2);
+ }
+}
+
diff --git a/tests/pipeline/rasterization/mesh/nested-component-write.slang.glsl b/tests/pipeline/rasterization/mesh/nested-component-write.slang.glsl
new file mode 100644
index 000000000..4c70955a1
--- /dev/null
+++ b/tests/pipeline/rasterization/mesh/nested-component-write.slang.glsl
@@ -0,0 +1,37 @@
+#version 450
+#extension GL_EXT_mesh_shader : require
+layout(row_major) uniform;
+layout(row_major) buffer;
+layout(location = 0)
+out vec3 _S1[3];
+
+out gl_MeshPerVertexEXT
+{
+ vec4 gl_Position;
+} gl_MeshVerticesEXT[3];
+
+layout(local_size_x = 3, local_size_y = 1, local_size_z = 1) in;
+layout(max_vertices = 3) out;
+layout(max_primitives = 1) out;
+layout(triangles) out;
+void main()
+{
+ SetMeshOutputsEXT(3U, 1U);
+ if(gl_LocalInvocationIndex < 3U)
+ {
+ gl_MeshVerticesEXT[gl_LocalInvocationIndex].gl_Position = vec4(0.00000000000000000000, 0.00000000000000000000, 0.00000000000000000000, 1.00000000000000000000);
+ _S1[gl_LocalInvocationIndex] = vec3(1.00000000000000000000, 2.00000000000000000000, 3.00000000000000000000);
+ }
+ else
+ {
+ }
+ if(gl_LocalInvocationIndex < 1U)
+ {
+ gl_PrimitiveTriangleIndicesEXT[gl_LocalInvocationIndex] = uvec3(0U, 1U, 2U);
+ }
+ else
+ {
+ }
+ return;
+}
+
diff --git a/tests/pipeline/rasterization/mesh/passing-outputs.slang b/tests/pipeline/rasterization/mesh/passing-outputs.slang
new file mode 100644
index 000000000..321c1179e
--- /dev/null
+++ b/tests/pipeline/rasterization/mesh/passing-outputs.slang
@@ -0,0 +1,114 @@
+// component-write.slang
+
+// This tests that writing to individual components of the output struct works
+
+//TEST:CROSS_COMPILE:-target spirv-assembly -entry main -stage mesh -profile glsl_450+spirv_1_4
+
+// DXC is stricter than we are about passing references to individual mesh shader outputs
+// We could get around this by doing what we do for GLSL, i.e. use a temporary
+// variable to pass as the out parameter, and then copy that into the array
+// after the function call.
+//TEST_DISABLED:CROSS_COMPILE:-target dxil-assembly -entry main -stage mesh -profile sm_6_6
+
+const static uint MAX_VERTS = 3;
+const static uint MAX_PRIMS = 1;
+
+struct Texes
+{
+ float2 tex1;
+ float4 tex2;
+}
+
+struct Vertex
+{
+ float4 pos : SV_Position;
+ float3 col : Color;
+ Texes ts : Coord;
+};
+
+void everything<let N : uint>(out Vertices<Vertex, N> vs)
+{
+ vs[0] = {float4(0), float3(1)};
+}
+
+void just_one(out Vertex v)
+{
+ v = {float4(0), float3(1)};
+}
+
+void just_two(out Vertex v, out Vertex w)
+{
+ v = {float4(0), float3(1)};
+ w = v;
+}
+
+void part_of_one(out float4 p)
+{
+ p = float4(1,2,3,4);
+}
+
+void write_struct(out Texes t)
+{
+ t.tex1 = float2(0);
+ t.tex2 = float4(1);
+}
+
+// Split out the things to test to avoid main becoming an unreadable jumble
+void a<let N : uint>(out Vertices<Vertex, N> vs)
+{
+ // Test passing a reference to the entire array
+ everything(vs);
+}
+
+void b<let N : uint>(out Vertices<Vertex, N> vs)
+{
+ // test passing two references to the same element
+ just_two(vs[0], vs[0]);
+}
+
+void c<let N : uint>(out Vertices<Vertex, N> vs, uint tig)
+{
+ // Test passing a reference to an element
+ just_one(vs[tig]);
+}
+
+void d<let N : uint>(out Vertices<Vertex, N> vs, uint tig)
+{
+ // Test passing references to different elements (to check that the operand
+ // rewriting doesn't mess the order)
+ just_two(vs[tig], vs[0]);
+}
+
+void e<let N : uint>(out Vertices<Vertex, N> vs, uint tig)
+{
+ // Test passing a scalar member and a struct member and a struct member's member
+ part_of_one(vs[tig].pos);
+ write_struct(vs[tig].ts);
+ part_of_one(vs[tig].ts.tex2);
+}
+
+[outputtopology("triangle")]
+[numthreads(3, 1, 1)]
+void main(
+ in uint tig : SV_GroupIndex,
+ out Vertices<Vertex, MAX_VERTS> verts,
+ out Indices<uint3, MAX_PRIMS> triangles
+ )
+{
+ const uint numVertices = 3;
+ const uint numPrimitives = 1;
+ SetMeshOutputCounts(numVertices, numPrimitives);
+
+ if(tig < numVertices) {
+ a(verts);
+ b(verts);
+ c(verts, tig);
+ d(verts, tig);
+ e(verts, tig);
+ }
+
+ if(tig < numPrimitives) {
+ triangles[tig] = uint3(0,1,2);
+ }
+}
+
diff --git a/tests/pipeline/rasterization/mesh/passing-outputs.slang.glsl b/tests/pipeline/rasterization/mesh/passing-outputs.slang.glsl
new file mode 100644
index 000000000..cfd7675ab
--- /dev/null
+++ b/tests/pipeline/rasterization/mesh/passing-outputs.slang.glsl
@@ -0,0 +1,172 @@
+#version 450
+#extension GL_EXT_mesh_shader : require
+layout(row_major) uniform;
+layout(row_major) buffer;
+struct Texes_0
+{
+ vec2 tex1_0;
+ vec4 tex2_0;
+};
+
+struct Vertex_0
+{
+ vec4 pos_0;
+ vec3 col_0;
+ Texes_0 ts_0;
+};
+
+void just_two_0(out Vertex_0 v_0, out Vertex_0 w_0)
+{
+ Texes_0 _S1 = { vec2(0.00000000000000000000, 0.00000000000000000000), vec4(0.00000000000000000000, 0.00000000000000000000, 0.00000000000000000000, 0.00000000000000000000) };
+ Vertex_0 _S2 = { vec4(0), vec3(1), _S1 };
+ v_0 = _S2;
+ w_0 = v_0;
+ return;
+}
+
+void just_one_0(out Vertex_0 v_1)
+{
+ Texes_0 _S3 = { vec2(0.00000000000000000000, 0.00000000000000000000), vec4(0.00000000000000000000, 0.00000000000000000000, 0.00000000000000000000, 0.00000000000000000000) };
+ Vertex_0 _S4 = { vec4(0), vec3(1), _S3 };
+ v_1 = _S4;
+ return;
+}
+
+void part_of_one_0(out vec4 p_0)
+{
+ p_0 = vec4(1.00000000000000000000, 2.00000000000000000000, 3.00000000000000000000, 4.00000000000000000000);
+ return;
+}
+
+void write_struct_0(out Texes_0 t_0)
+{
+ t_0.tex1_0 = vec2(0);
+ t_0.tex2_0 = vec4(1);
+ return;
+}
+
+layout(location = 0)
+out vec3 _S5[3];
+
+layout(location = 1)
+out vec2 _S6[3];
+
+layout(location = 2)
+out vec4 _S7[3];
+
+out gl_MeshPerVertexEXT
+{
+ vec4 gl_Position;
+} gl_MeshVerticesEXT[3];
+
+void everything_0()
+{
+ Texes_0 _S8 = { vec2(0.00000000000000000000, 0.00000000000000000000), vec4(0.00000000000000000000, 0.00000000000000000000, 0.00000000000000000000, 0.00000000000000000000) };
+ Vertex_0 _S9 = { vec4(0), vec3(1), _S8 };
+ gl_MeshVerticesEXT[0U].gl_Position = _S9.pos_0;
+ _S5[0U] = _S9.col_0;
+ Texes_0 _S10 = _S9.ts_0;
+ _S6[0U] = _S10.tex1_0;
+ _S7[0U] = _S10.tex2_0;
+ return;
+}
+
+void a_0()
+{
+ everything_0();
+ return;
+}
+
+void b_0()
+{
+ Vertex_0 _S11;
+ Vertex_0 _S12;
+ just_two_0(_S12, _S11);
+ Vertex_0 _S13 = _S12;
+ gl_MeshVerticesEXT[0U].gl_Position = _S13.pos_0;
+ _S5[0U] = _S13.col_0;
+ Texes_0 _S14 = _S13.ts_0;
+ _S6[0U] = _S14.tex1_0;
+ _S7[0U] = _S14.tex2_0;
+ Vertex_0 _S15 = _S11;
+ gl_MeshVerticesEXT[0U].gl_Position = _S15.pos_0;
+ _S5[0U] = _S15.col_0;
+ Texes_0 _S16 = _S15.ts_0;
+ _S6[0U] = _S16.tex1_0;
+ _S7[0U] = _S16.tex2_0;
+ return;
+}
+
+void c_0(uint _S17)
+{
+ Vertex_0 _S18;
+ just_one_0(_S18);
+ Vertex_0 _S19 = _S18;
+ gl_MeshVerticesEXT[_S17].gl_Position = _S19.pos_0;
+ _S5[_S17] = _S19.col_0;
+ Texes_0 _S20 = _S19.ts_0;
+ _S6[_S17] = _S20.tex1_0;
+ _S7[_S17] = _S20.tex2_0;
+ return;
+}
+
+void d_0(uint _S21)
+{
+ Vertex_0 _S22;
+ Vertex_0 _S23;
+ just_two_0(_S23, _S22);
+ Vertex_0 _S24 = _S23;
+ gl_MeshVerticesEXT[_S21].gl_Position = _S24.pos_0;
+ _S5[_S21] = _S24.col_0;
+ Texes_0 _S25 = _S24.ts_0;
+ _S6[_S21] = _S25.tex1_0;
+ _S7[_S21] = _S25.tex2_0;
+ Vertex_0 _S26 = _S22;
+ gl_MeshVerticesEXT[0U].gl_Position = _S26.pos_0;
+ _S5[0U] = _S26.col_0;
+ Texes_0 _S27 = _S26.ts_0;
+ _S6[0U] = _S27.tex1_0;
+ _S7[0U] = _S27.tex2_0;
+ return;
+}
+
+void e_0(uint _S28)
+{
+ part_of_one_0(gl_MeshVerticesEXT[_S28].gl_Position);
+ Texes_0 _S29;
+ write_struct_0(_S29);
+ Texes_0 _S30 = _S29;
+ _S6[_S28] = _S30.tex1_0;
+ _S7[_S28] = _S30.tex2_0;
+ part_of_one_0(_S7[_S28]);
+ return;
+}
+
+layout(local_size_x = 3, local_size_y = 1, local_size_z = 1) in;
+layout(max_vertices = 3) out;
+layout(max_primitives = 1) out;
+layout(triangles) out;
+void main()
+{
+ SetMeshOutputsEXT(3U, 1U);
+ if(gl_LocalInvocationIndex < 3U)
+ {
+ a_0();
+ b_0();
+ c_0(gl_LocalInvocationIndex);
+ d_0(gl_LocalInvocationIndex);
+ e_0(gl_LocalInvocationIndex);
+ }
+ else
+ {
+ }
+ if(gl_LocalInvocationIndex < 1U)
+ {
+ gl_PrimitiveTriangleIndicesEXT[gl_LocalInvocationIndex] = uvec3(0U, 1U, 2U);
+ }
+ else
+ {
+ }
+ return;
+}
+
diff --git a/tests/pipeline/rasterization/mesh/primitive-output.slang b/tests/pipeline/rasterization/mesh/primitive-output.slang
new file mode 100644
index 000000000..fdbcf6912
--- /dev/null
+++ b/tests/pipeline/rasterization/mesh/primitive-output.slang
@@ -0,0 +1,57 @@
+// hello.slang
+
+// Test that a simple mesh shader compiles
+
+//TEST:CROSS_COMPILE:-target spirv -profile glsl_450+spirv_1_4 -entry main -stage mesh
+
+const static float2 positions[3] = {
+ float2(0.0, -0.5),
+ float2(0.5, 0.5),
+ float2(-0.5, 0.5)
+};
+
+const static float3 colors[3] = {
+ float3(1.0, 1.0, 0.0),
+ float3(0.0, 1.0, 1.0),
+ float3(1.0, 0.0, 1.0)
+};
+
+struct Vertex
+{
+ float4 pos : SV_Position;
+ float3 color : Color;
+};
+
+struct Prim
+{
+ float3 triangleNormal : Normal;
+ uint id : SV_PrimitiveID;
+ bool cull : SV_CullPrimitive;
+};
+
+const static uint MAX_VERTS = 3;
+const static uint MAX_PRIMS = 1;
+
+[outputtopology("triangle")]
+[numthreads(3, 1, 1)]
+void main(
+ in uint tig : SV_GroupIndex,
+ out Indices<uint3, MAX_PRIMS> triangles,
+ out Vertices<Vertex, MAX_VERTS> verts,
+ out Primitives<Prim, MAX_PRIMS> primitives
+ )
+{
+ const uint numVertices = 3;
+ const uint numPrimitives = 1;
+ SetMeshOutputCounts(numVertices, numPrimitives);
+
+ if(tig < numVertices) {
+ verts[tig] = {float4(positions[tig], 0, 1), colors[tig]};
+ }
+
+ if(tig < numPrimitives) {
+ triangles[tig] = uint3(0,1,2);
+ primitives[tig] = {float3(0,0,1), tig, false};
+ }
+}
+
diff --git a/tests/pipeline/rasterization/mesh/primitive-output.slang.glsl b/tests/pipeline/rasterization/mesh/primitive-output.slang.glsl
new file mode 100644
index 000000000..b7b4f1d62
--- /dev/null
+++ b/tests/pipeline/rasterization/mesh/primitive-output.slang.glsl
@@ -0,0 +1,66 @@
+#version 450
+#extension GL_EXT_mesh_shader : require
+layout(row_major) uniform;
+layout(row_major) buffer;
+const vec3 colors_0[3] = { vec3(1.00000000000000000000, 1.00000000000000000000, 0.00000000000000000000), vec3(0.00000000000000000000, 1.00000000000000000000, 1.00000000000000000000), vec3(1.00000000000000000000, 0.00000000000000000000, 1.00000000000000000000) };
+const vec2 positions_0[3] = { vec2(0.00000000000000000000, -0.50000000000000000000), vec2(0.50000000000000000000, 0.50000000000000000000), vec2(-0.50000000000000000000, 0.50000000000000000000) };
+layout(location = 0)
+out vec3 _S1[3];
+
+out gl_MeshPerVertexEXT
+{
+ vec4 gl_Position;
+} gl_MeshVerticesEXT[3];
+
+perprimitiveEXT layout(location = 1)
+out vec3 _S2[1];
+
+perprimitiveEXT out gl_MeshPerPrimitiveEXT
+{
+ int gl_PrimitiveID;
+ bool gl_CullPrimitiveEXT;
+} gl_MeshPrimitivesEXT[1];
+
+struct Vertex_0
+{
+ vec4 pos_0;
+ vec3 color_0;
+};
+
+struct Prim_0
+{
+ vec3 triangleNormal_0;
+ uint id_0;
+ bool cull_0;
+};
+
+layout(local_size_x = 3, local_size_y = 1, local_size_z = 1) in;
+layout(max_vertices = 3) out;
+layout(max_primitives = 1) out;
+layout(triangles) out;
+void main()
+{
+ SetMeshOutputsEXT(3U, 1U);
+ if(gl_LocalInvocationIndex < 3U)
+ {
+ Vertex_0 _S3 = { vec4(positions_0[gl_LocalInvocationIndex], 0.00000000000000000000, 1.00000000000000000000), colors_0[gl_LocalInvocationIndex] };
+ gl_MeshVerticesEXT[gl_LocalInvocationIndex].gl_Position = _S3.pos_0;
+ _S1[gl_LocalInvocationIndex] = _S3.color_0;
+ }
+ else
+ {
+ }
+ if(gl_LocalInvocationIndex < 1U)
+ {
+ gl_PrimitiveTriangleIndicesEXT[gl_LocalInvocationIndex] = uvec3(0U, 1U, 2U);
+ Prim_0 _S4 = { vec3(0.00000000000000000000, 0.00000000000000000000, 1.00000000000000000000), gl_LocalInvocationIndex, false };
+ _S2[gl_LocalInvocationIndex] = _S4.triangleNormal_0;
+ gl_MeshPrimitivesEXT[gl_LocalInvocationIndex].gl_PrimitiveID = int(_S4.id_0);
+ gl_MeshPrimitivesEXT[gl_LocalInvocationIndex].gl_CullPrimitiveEXT = _S4.cull_0;
+ }
+ else
+ {
+ }
+ return;
+}
+
diff --git a/tests/pipeline/rasterization/mesh/swizzled-store.slang b/tests/pipeline/rasterization/mesh/swizzled-store.slang
new file mode 100644
index 000000000..f85f9ed62
--- /dev/null
+++ b/tests/pipeline/rasterization/mesh/swizzled-store.slang
@@ -0,0 +1,32 @@
+// component-write.slang
+
+// This tests that writing to individual components of the output struct works
+
+//TEST:CROSS_COMPILE:-target spirv -profile glsl_450+spirv_1_4 -entry main -stage mesh
+
+const static uint MAX_VERTS = 3;
+const static uint MAX_PRIMS = 1;
+
+[outputtopology("triangle")]
+[numthreads(3, 1, 1)]
+void main(
+ in uint tig : SV_GroupIndex,
+ out Vertices<float4, MAX_VERTS> verts : SV_Position,
+ out Indices<uint3, MAX_PRIMS> triangles
+ )
+{
+ const uint numVertices = 3;
+ const uint numPrimitives = 1;
+ SetMeshOutputCounts(numVertices, numPrimitives);
+
+ if(tig < numVertices) {
+ verts[tig].wz = float2(1, 0);
+ verts[tig].x = 0;
+ verts[tig].y = 0;
+ }
+
+ if(tig < numPrimitives) {
+ triangles[tig] = uint3(0,1,2);
+ }
+}
+
diff --git a/tests/pipeline/rasterization/mesh/swizzled-store.slang.glsl b/tests/pipeline/rasterization/mesh/swizzled-store.slang.glsl
new file mode 100644
index 000000000..b2467901e
--- /dev/null
+++ b/tests/pipeline/rasterization/mesh/swizzled-store.slang.glsl
@@ -0,0 +1,35 @@
+#version 450
+#extension GL_EXT_mesh_shader : require
+layout(row_major) uniform;
+layout(row_major) buffer;
+out gl_MeshPerVertexEXT
+{
+ vec4 gl_Position;
+} gl_MeshVerticesEXT[3];
+
+layout(local_size_x = 3, local_size_y = 1, local_size_z = 1) in;
+layout(max_vertices = 3) out;
+layout(max_primitives = 1) out;
+layout(triangles) out;
+void main()
+{
+ SetMeshOutputsEXT(3U, 1U);
+ if(gl_LocalInvocationIndex < 3U)
+ {
+ gl_MeshVerticesEXT[gl_LocalInvocationIndex].gl_Position.wz = vec2(1.00000000000000000000, 0.00000000000000000000);
+ gl_MeshVerticesEXT[gl_LocalInvocationIndex].gl_Position.x = 0.00000000000000000000;
+ gl_MeshVerticesEXT[gl_LocalInvocationIndex].gl_Position.y = 0.00000000000000000000;
+ }
+ else
+ {
+ }
+ if(gl_LocalInvocationIndex < 1U)
+ {
+ gl_PrimitiveTriangleIndicesEXT[gl_LocalInvocationIndex] = uvec3(0U, 1U, 2U);
+ }
+ else
+ {
+ }
+ return;
+}
+