diff options
| author | Ellie Hermaszewska <ellieh@nvidia.com> | 2022-11-16 09:49:06 +0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-11-16 09:49:06 +0800 |
| commit | 1643471da0d6239177d11b0301c26d1adf95c0fb (patch) | |
| tree | 9b8fddf92a5f817541e055a2657f9b0a00f90069 | |
| parent | 4917d71100aafcc38a81cd99d2a44a4cb3a89a0c (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
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" } } }, { @@ -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*)&astNodeType</ExpandedItem> <ExpandedItem Condition="astNodeType == Slang::ASTNodeType::HLSLLineStreamType">(Slang::HLSLLineStreamType*)&astNodeType</ExpandedItem> <ExpandedItem Condition="astNodeType == Slang::ASTNodeType::HLSLTriangleStreamType">(Slang::HLSLTriangleStreamType*)&astNodeType</ExpandedItem> + <ExpandedItem Condition="astNodeType == Slang::ASTNodeType::MeshOutputType">(Slang::HLSLMeshOutputType*)&astNodeType</ExpandedItem> + <ExpandedItem Condition="astNodeType == Slang::ASTNodeType::VerticesType">(Slang::VerticesType*)&astNodeType</ExpandedItem> + <ExpandedItem Condition="astNodeType == Slang::ASTNodeType::IndicesType">(Slang::IndicesType*)&astNodeType</ExpandedItem> + <ExpandedItem Condition="astNodeType == Slang::ASTNodeType::PrimitivesType">(Slang::PrimitivesType*)&astNodeType</ExpandedItem> <ExpandedItem Condition="astNodeType == Slang::ASTNodeType::UntypedBufferResourceType">(Slang::UntypedBufferResourceType*)&astNodeType</ExpandedItem> <ExpandedItem Condition="astNodeType == Slang::ASTNodeType::HLSLByteAddressBufferType">(Slang::HLSLByteAddressBufferType*)&astNodeType</ExpandedItem> <ExpandedItem Condition="astNodeType == Slang::ASTNodeType::HLSLRWByteAddressBufferType">(Slang::HLSLRWByteAddressBufferType*)&astNodeType</ExpandedItem> @@ -565,4 +569,4 @@ <ExpandedItem Condition="astNodeType == Slang::ASTNodeType::ModifiedType">(Slang::ModifiedType*)&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; +} + |
