diff options
| author | Yong He <yonghe@outlook.com> | 2023-09-26 23:56:06 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-09-27 14:56:06 +0800 |
| commit | ebe8ddefc48478307d5f206cd3e40c41d28a36e3 (patch) | |
| tree | 8e13977979909a26394eea532d8b95cd5ad0f6d1 /source | |
| parent | c5c8cfbb360d9a763f549df48636effde839eacd (diff) | |
Various SPIRV fixes. (#3231)
* Various SPIRV fixes.
- Geometry shader support (WIP).
- Fix texture get dimension and load.
- Fold global GetElement(MakeArray/MakeVector) insts.
- Call spvopt to inline all functions.
- Translate OpImageSubscript.
- Emit struct member names and global variable names.
- Fix lowering of OpBitNot -> OpNot, instead of OpBitReverse.
* Fix test.
* Fix geometry shader.
* Fix geometry shader emit.
* Add atomic Image access test.
* Fix tests.
* don't fail if spirv-opt fails.
* Update comments.
* Fix test.
* Cleanups.
* indentation
---------
Co-authored-by: Yong He <yhe@nvidia.com>
Co-authored-by: Ellie Hermaszewska <ellieh@nvidia.com>
Diffstat (limited to 'source')
| -rw-r--r-- | source/slang/hlsl.meta.slang | 74 | ||||
| -rw-r--r-- | source/slang/slang-diagnostic-defs.h | 17 | ||||
| -rw-r--r-- | source/slang/slang-emit-spirv-ops.h | 61 | ||||
| -rw-r--r-- | source/slang/slang-emit-spirv.cpp | 296 | ||||
| -rw-r--r-- | source/slang/slang-emit.cpp | 22 | ||||
| -rw-r--r-- | source/slang/slang-ir-array-reg-to-mem.cpp | 87 | ||||
| -rw-r--r-- | source/slang/slang-ir-array-reg-to-mem.h | 16 | ||||
| -rw-r--r-- | source/slang/slang-ir-glsl-legalize.cpp | 204 | ||||
| -rw-r--r-- | source/slang/slang-ir-insts.h | 1 | ||||
| -rw-r--r-- | source/slang/slang-ir-lower-buffer-element-type.cpp | 19 | ||||
| -rw-r--r-- | source/slang/slang-ir-peephole.cpp | 41 | ||||
| -rw-r--r-- | source/slang/slang-ir-spirv-legalize.cpp | 255 | ||||
| -rw-r--r-- | source/slang/slang-ir-util.cpp | 8 | ||||
| -rw-r--r-- | source/slang/slang-ir-util.h | 2 | ||||
| -rw-r--r-- | source/slang/slang-ir.cpp | 16 | ||||
| -rw-r--r-- | source/slang/slang-ir.h | 2 | ||||
| -rw-r--r-- | source/slang/slang-lower-to-ir.cpp | 4 | ||||
| -rw-r--r-- | source/slang/slang-spirv-opt.cpp | 68 | ||||
| -rw-r--r-- | source/slang/slang-spirv-opt.h | 10 | ||||
| -rw-r--r-- | source/slang/slang-spirv-val.cpp | 1 | ||||
| -rw-r--r-- | source/slang/slang-stdlib-textures.cpp | 321 |
21 files changed, 1254 insertions, 271 deletions
diff --git a/source/slang/hlsl.meta.slang b/source/slang/hlsl.meta.slang index d95c1e4d6..da729f67a 100644 --- a/source/slang/hlsl.meta.slang +++ b/source/slang/hlsl.meta.slang @@ -1167,11 +1167,27 @@ __magic_type(HLSLPointStreamType) __intrinsic_type($(kIROp_HLSLPointStreamType)) struct PointStream { - __target_intrinsic(glsl, "EmitVertex()") - void Append(T value); + [KnownBuiltin("GeometryStreamAppend")] + void Append(T value) + { + __target_switch + { + case glsl: __intrinsic_asm "EmitVertex()"; + case hlsl: __intrinsic_asm ".Append"; + case spirv: spirv_asm { OpEmitVertex; }; + } + } - __target_intrinsic(glsl, "EndPrimitive()") - void RestartStrip(); + [KnownBuiltin("GeometryStreamRestart")] + void RestartStrip() + { + __target_switch + { + case glsl: __intrinsic_asm "EndPrimitive()"; + case hlsl: __intrinsic_asm ".RestartStrip"; + case spirv: spirv_asm { OpEndPrimitive; }; + } + } }; __generic<T> @@ -1179,11 +1195,27 @@ __magic_type(HLSLLineStreamType) __intrinsic_type($(kIROp_HLSLLineStreamType)) struct LineStream { - __target_intrinsic(glsl, "EmitVertex()") - void Append(T value); + [KnownBuiltin("GeometryStreamAppend")] + void Append(T value) + { + __target_switch + { + case glsl: __intrinsic_asm "EmitVertex()"; + case hlsl: __intrinsic_asm ".Append"; + case spirv: spirv_asm { OpEmitVertex; }; + } + } - __target_intrinsic(glsl, "EndPrimitive()") - void RestartStrip(); + [KnownBuiltin("GeometryStreamRestart")] + void RestartStrip() + { + __target_switch + { + case glsl: __intrinsic_asm "EndPrimitive()"; + case hlsl: __intrinsic_asm ".RestartStrip"; + case spirv: spirv_asm { OpEndPrimitive; }; + } + } }; __generic<T> @@ -1191,11 +1223,27 @@ __magic_type(HLSLTriangleStreamType) __intrinsic_type($(kIROp_HLSLTriangleStreamType)) struct TriangleStream { - __target_intrinsic(glsl, "EmitVertex()") - void Append(T value); + [KnownBuiltin("GeometryStreamAppend")] + void Append(T value) + { + __target_switch + { + case glsl: __intrinsic_asm "EmitVertex()"; + case hlsl: __intrinsic_asm ".Append"; + case spirv: spirv_asm { OpEmitVertex; }; + } + } - __target_intrinsic(glsl, "EndPrimitive()") - void RestartStrip(); + [KnownBuiltin("GeometryStreamRestart")] + void RestartStrip() + { + __target_switch + { + case glsl: __intrinsic_asm "EndPrimitive()"; + case hlsl: __intrinsic_asm ".RestartStrip"; + case spirv: spirv_asm { OpEndPrimitive; }; + } + } }; #define VECTOR_MAP_UNARY(TYPE, COUNT, FUNC, VALUE) \ @@ -7994,7 +8042,7 @@ struct RayQuery <let rayFlagsGeneric : RAY_FLAG = RAY_FLAG_NONE> uint iCandidateOrCommitted = 0; return spirv_asm { - %rr:$$bool = OpRayQueryGetIntersectionCandidateAABBOpaqueKHR &this $iCandidateOrCommitted; + %rr:$$bool = OpRayQueryGetIntersectionCandidateAABBOpaqueKHR &this; result:$$bool = OpLogicalNot %rr; }; } diff --git a/source/slang/slang-diagnostic-defs.h b/source/slang/slang-diagnostic-defs.h index 14f45e802..183a9bc28 100644 --- a/source/slang/slang-diagnostic-defs.h +++ b/source/slang/slang-diagnostic-defs.h @@ -667,12 +667,12 @@ DIAGNOSTIC(42001, Error, invalidUseOfTorchTensorTypeInDeviceFunc, "invalid use o DIAGNOSTIC(50010, Internal, missingExistentialBindingsForParameter, "missing argument for existential parameter slot") -DIAGNOSTIC(50020, Error, invalidTessCoordType, "TessCoord must have vec2 or vec3 type.") -DIAGNOSTIC(50020, Error, invalidFragCoordType, "FragCoord must be a vec4.") -DIAGNOSTIC(50020, Error, invalidInvocationIdType, "InvocationId must have int type.") -DIAGNOSTIC(50020, Error, invalidThreadIdType, "ThreadId must have int type.") -DIAGNOSTIC(50020, Error, invalidPrimitiveIdType, "PrimitiveId must have int type.") -DIAGNOSTIC(50020, Error, invalidPatchVertexCountType, "PatchVertexCount must have int type.") +DIAGNOSTIC(50020, Error, invalidTessCoordType, "TessCoord must have vec2 or vec3 type.") +DIAGNOSTIC(50020, Error, invalidFragCoordType, "FragCoord must be a vec4.") +DIAGNOSTIC(50020, Error, invalidInvocationIdType, "InvocationId must have int type.") +DIAGNOSTIC(50020, Error, invalidThreadIdType, "ThreadId must have int type.") +DIAGNOSTIC(50020, Error, invalidPrimitiveIdType, "PrimitiveId must have int type.") +DIAGNOSTIC(50020, Error, invalidPatchVertexCountType, "PatchVertexCount must have int type.") DIAGNOSTIC(50022, Error, worldIsNotDefined, "world '$0' is not defined.") DIAGNOSTIC(50023, Error, stageShouldProvideWorldAttribute, "'$0' should provide 'World' attribute.") DIAGNOSTIC(50040, Error, componentHasInvalidTypeForPositionOutput, "'$0': component used as 'loc' output must be of vec4 type.") @@ -693,7 +693,7 @@ DIAGNOSTIC(50052, Error, hullShaderRequiresTessLevelOuter, "'HullShader' require DIAGNOSTIC(50053, Error, invalidTessellationDomian, "'Domain' should be either 'triangles' or 'quads'.") DIAGNOSTIC(50053, Error, invalidTessellationOutputTopology, "'OutputTopology' must be one of: 'point', 'line', 'triangle_cw', or 'triangle_ccw'.") DIAGNOSTIC(50053, Error, invalidTessellationPartitioning, "'Partitioning' must be one of: 'integer', 'pow2', 'fractional_even', or 'fractional_odd'.") -DIAGNOSTIC(50053, Error, invalidTessellationDomain, "'Domain' should be either 'triangles' or 'quads'.") +DIAGNOSTIC(50053, Error, invalidTessellationDomain, "'Domain' should be either 'triangles' or 'quads'.") DIAGNOSTIC(50082, Error, importingFromPackedBufferUnsupported, "importing type '$0' from PackedBuffer is not supported by the GLSL backend.") DIAGNOSTIC(51090, Error, cannotGenerateCodeForExternComponentType, "cannot generate code for extern component type '$0'.") @@ -716,7 +716,7 @@ DIAGNOSTIC(52006, Error, compilerNotDefinedForTransition, "compiler not defined DIAGNOSTIC(52007, Error, typeCannotBeUsedInDynamicDispatch, "failed to generate dynamic dispatch code for type '$0'.") 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(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") @@ -728,6 +728,7 @@ DIAGNOSTIC(55102, Error, invalidTorchKernelParamType, "'$0' is not a valid param DIAGNOSTIC(56001, Error, unableToAutoMapCUDATypeToHostType, "Could not automatically map '$0' to a host type. Automatic binding generation failed for '$1'") +DIAGNOSTIC(57001, Warning, spirvOptFailed, "spirv-opt failed. $0") // // 8xxxx - Issues specific to a particular library/technology/platform/etc. // diff --git a/source/slang/slang-emit-spirv-ops.h b/source/slang/slang-emit-spirv-ops.h index 9c4ec290d..bc53b70ae 100644 --- a/source/slang/slang-emit-spirv-ops.h +++ b/source/slang/slang-emit-spirv-ops.h @@ -20,6 +20,20 @@ SpvInst* emitOpName( return emitInst(parent, inst, SpvOpName, target, name); } +// https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#OpMemberName +template<typename T> +SpvInst* emitOpMemberName( + SpvInstParent* parent, + IRInst* inst, + const T& target, + int index, + const UnownedStringSlice& name +) +{ + static_assert(isSingular<T>); + return emitInst(parent, inst, SpvOpMemberName, target, SpvLiteralInteger::from32(index), name); +} + // https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#OpExtension SpvInst* emitOpExtension(SpvInstParent* parent, IRInst* inst, const UnownedStringSlice& name) { @@ -109,6 +123,36 @@ SpvInst* emitOpExecutionModeLocalSizeId( ); } +// https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#OpExecutionMode +template<typename T> +SpvInst* emitOpExecutionModeOutputVertices( + SpvInstParent* parent, + IRInst* inst, + const T& entryPoint, + const SpvLiteralInteger& vertexCount +) +{ + static_assert(isSingular<T>); + return emitInst( + parent, inst, SpvOpExecutionMode, entryPoint, SpvExecutionModeOutputVertices, vertexCount + ); +} + +// https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#OpExecutionMode +template<typename T> +SpvInst* emitOpExecutionModeInvocations( + SpvInstParent* parent, + IRInst* inst, + const T& entryPoint, + const SpvLiteralInteger& invocations +) +{ + static_assert(isSingular<T>); + return emitInst( + parent, inst, SpvOpExecutionMode, entryPoint, SpvExecutionModeInvocations, invocations + ); +} + // https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#OpCapability SpvInst* emitOpCapability(SpvInstParent* parent, IRInst* inst, SpvCapability capability) { @@ -1111,6 +1155,23 @@ SpvInst* emitOpCompositeExtract( ); } +// https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#OpVectorExtractDynamic +template<typename T1, typename T2, typename T3> +SpvInst* emitOpVectorExtractDynamic( + SpvInstParent* parent, + IRInst* inst, + const T1& idResultType, + const T2& composite, + const T3& index) +{ + static_assert(isSingular<T1>); + static_assert(isSingular<T2>); + static_assert(isSingular<T3>); + return emitInst( + parent, inst, SpvOpVectorExtractDynamic, idResultType, kResultID, composite, index + ); +} + // https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#OpCopyObject template<typename T1, typename T2> SpvInst* emitOpCopyObject( diff --git a/source/slang/slang-emit-spirv.cpp b/source/slang/slang-emit-spirv.cpp index 0aa4d4c60..14fe2e17b 100644 --- a/source/slang/slang-emit-spirv.cpp +++ b/source/slang/slang-emit-spirv.cpp @@ -872,7 +872,10 @@ struct SPIRVEmitContext key.type = type; SpvInst* result = nullptr; if (m_spvIntConstants.tryGetValue(key, result)) + { + m_mapIRInstToSpvInst[inst] = result; return result; + } switch (type->getOp()) { case kIROp_Int64Type: @@ -900,6 +903,7 @@ struct SPIRVEmitContext } } m_spvIntConstants[key] = result; + m_mapIRInstToSpvInst[inst] = result; return result; } SpvInst* emitFloatConstant(IRFloatingPointValue val, IRType* type, IRInst* inst = nullptr) @@ -909,7 +913,10 @@ struct SPIRVEmitContext key.type = type; SpvInst* result = nullptr; if (m_spvFloatConstants.tryGetValue(key, result)) + { + m_mapIRInstToSpvInst[inst] = result; return result; + } if (type->getOp() == kIROp_DoubleType) { result = emitOpConstant( @@ -935,6 +942,7 @@ struct SPIRVEmitContext { SLANG_UNEXPECTED("missing case in SPIR-V emitFloatConstant"); } + m_mapIRInstToSpvInst[inst] = result; m_spvFloatConstants[key] = result; return result; } @@ -1404,7 +1412,9 @@ struct SPIRVEmitContext case kIROp_IntLit: case kIROp_FloatLit: case kIROp_StringLit: + { return emitLit(inst); + } case kIROp_MakeVectorFromScalar: { const auto scalar = inst->getOperand(0); @@ -1479,6 +1489,10 @@ struct SPIRVEmitContext return emitGetStringHash(inst); case kIROp_AllocateOpaqueHandle: return nullptr; + case kIROp_HLSLTriangleStreamType: + case kIROp_HLSLLineStreamType: + case kIROp_HLSLPointStreamType: + return nullptr; default: { if (as<IRSPIRVAsmOperand>(inst)) @@ -1490,6 +1504,84 @@ struct SPIRVEmitContext } } + static SpvImageFormat getSpvImageFormat(IRTextureTypeBase* type) + { + ImageFormat imageFormat = type->hasFormat() ? (ImageFormat)type->getFormat() : ImageFormat::unknown; + switch (imageFormat) + { + case ImageFormat::unknown: return SpvImageFormatUnknown; + case ImageFormat::rgba32f: return SpvImageFormatRgba32f; + case ImageFormat::rgba16f: return SpvImageFormatRgba16f; + case ImageFormat::rg32f: return SpvImageFormatRg32f; + case ImageFormat::rg16f: return SpvImageFormatRg16f; + case ImageFormat::r11f_g11f_b10f: return SpvImageFormatR11fG11fB10f; + case ImageFormat::r32f: return SpvImageFormatR32f; + case ImageFormat::r16f: return SpvImageFormatR16f; + case ImageFormat::rgba16: return SpvImageFormatRgba16; + case ImageFormat::rgb10_a2: return SpvImageFormatRgb10A2; + case ImageFormat::rgba8: return SpvImageFormatRgba8; + case ImageFormat::rg16: return SpvImageFormatRg16; + case ImageFormat::rg8: return SpvImageFormatRg8; + case ImageFormat::r16: return SpvImageFormatR16; + case ImageFormat::r8: return SpvImageFormatR8; + case ImageFormat::rgba16_snorm: return SpvImageFormatRgba16Snorm; + case ImageFormat::rgba8_snorm: return SpvImageFormatRgba8Snorm; + case ImageFormat::rg16_snorm: return SpvImageFormatRg16Snorm; + case ImageFormat::rg8_snorm: return SpvImageFormatRg8Snorm; + case ImageFormat::r16_snorm: return SpvImageFormatR16Snorm; + case ImageFormat::r8_snorm: return SpvImageFormatR8Snorm; + case ImageFormat::rgba32i: return SpvImageFormatRgba32i; + case ImageFormat::rgba16i: return SpvImageFormatRgba16i; + case ImageFormat::rgba8i: return SpvImageFormatRgba8i; + case ImageFormat::rg32i: return SpvImageFormatRg32i; + case ImageFormat::rg16i: return SpvImageFormatRg16i; + case ImageFormat::rg8i: return SpvImageFormatRg8i; + case ImageFormat::r32i: return SpvImageFormatR32i; + case ImageFormat::r16i: return SpvImageFormatR16i; + case ImageFormat::r8i: return SpvImageFormatR8i; + case ImageFormat::rgba32ui: return SpvImageFormatRgba32ui; + case ImageFormat::rgba16ui: return SpvImageFormatRgba16ui; + case ImageFormat::rgb10_a2ui: return SpvImageFormatRgb10a2ui; + case ImageFormat::rgba8ui: return SpvImageFormatRgba8ui; + case ImageFormat::rg32ui: return SpvImageFormatRg32ui; + case ImageFormat::rg16ui: return SpvImageFormatRg16ui; + case ImageFormat::rg8ui: return SpvImageFormatRg8ui; + case ImageFormat::r32ui: return SpvImageFormatR32ui; + case ImageFormat::r16ui: return SpvImageFormatR16ui; + case ImageFormat::r8ui: return SpvImageFormatR8ui; + case ImageFormat::r64ui: return SpvImageFormatR64ui; + case ImageFormat::r64i: return SpvImageFormatR64i; + default: SLANG_UNIMPLEMENTED_X("unknown image format for spirv emit"); + } + } + + SpvCapability getImageFormatCapability(SpvImageFormat format) + { + switch (format) + { + case SpvImageFormatUnknown: + case SpvImageFormatRgba32f: + case SpvImageFormatRgba16f: + case SpvImageFormatR32f: + case SpvImageFormatRgba8: + case SpvImageFormatRgba8Snorm: + case SpvImageFormatRgba32i: + case SpvImageFormatRgba16i: + case SpvImageFormatRgba8i: + case SpvImageFormatR32i: + case SpvImageFormatRgba32ui: + case SpvImageFormatRgba16ui: + case SpvImageFormatRgba8ui: + case SpvImageFormatR32ui: + return SpvCapabilityShader; + case SpvImageFormatR64ui: + case SpvImageFormatR64i: + return SpvCapabilityInt64ImageEXT; + default: + return SpvCapabilityStorageImageExtendedFormats; + } + } + SpvInst* ensureTextureType(IRInst* assignee, IRTextureTypeBase* inst) { // Some untyped constants from OpTypeImage @@ -1565,9 +1657,7 @@ struct SPIRVEmitContext break; } - // TODO: we need to do as _emitGLSLImageFormatModifier does, - // take a guess at the image format - SpvImageFormat format = SpvImageFormatUnknown; + SpvImageFormat format = getSpvImageFormat(inst); // // Capabilities, according to section 3.8 @@ -1611,6 +1701,10 @@ struct SPIRVEmitContext requireSPIRVCapability(SpvCapabilityStorageImageReadWithoutFormat); requireSPIRVCapability(SpvCapabilityStorageImageWriteWithoutFormat); } + + auto formatCapability = getImageFormatCapability(format); + if (formatCapability != SpvCapabilityShader) + requireSPIRVCapability(formatCapability); // // The op itself @@ -1648,10 +1742,34 @@ struct SPIRVEmitContext return result; } - void emitVarLayout(SpvInst* varInst, IRVarLayout* layout) + bool _maybeEmitInterpolationModifierDecoration(IRInterpolationMode mode, SpvInst* varInst) + { + switch (mode) + { + case IRInterpolationMode::NoInterpolation: + emitOpDecorate(getSection(SpvLogicalSectionID::Annotations), nullptr, varInst, SpvDecorationFlat); + return true; + case IRInterpolationMode::NoPerspective: + emitOpDecorate(getSection(SpvLogicalSectionID::Annotations), nullptr, varInst, SpvDecorationNoPerspective); + return true; + case IRInterpolationMode::Linear: + return true; + case IRInterpolationMode::Sample: + emitOpDecorate(getSection(SpvLogicalSectionID::Annotations), nullptr, varInst, SpvDecorationSample); + return true; + case IRInterpolationMode::Centroid: + emitOpDecorate(getSection(SpvLogicalSectionID::Annotations), nullptr, varInst, SpvDecorationCentroid); + return true; + default: + return false; + } + } + + void emitVarLayout(IRInst* var, SpvInst* varInst, IRVarLayout* layout) { bool needDefaultSetBindingDecoration = false; bool hasExplicitSetBinding = false; + bool isInput = false; for (auto rr : layout->getOffsetAttrs()) { UInt index = rr->getOffset(); @@ -1668,6 +1786,7 @@ struct SPIRVEmitContext varInst, SpvLiteralInteger::from32(int32_t(index)) ); + isInput = true; break; case LayoutResourceKind::VaryingOutput: emitOpDecorateLocation( @@ -1730,7 +1849,43 @@ struct SPIRVEmitContext varInst, SpvLiteralInteger::from32(int32_t(0))); } + + bool anyModifiers = false; + for (auto dd : var->getDecorations()) + { + if (dd->getOp() != kIROp_InterpolationModeDecoration) + continue; + + auto decoration = (IRInterpolationModeDecoration*)dd; + + anyModifiers |= _maybeEmitInterpolationModifierDecoration(decoration->getMode(), varInst); + } + + // If the user didn't explicitly qualify a varying + // with integer type, then we need to explicitly + // add the `flat` modifier for GLSL. + if (!anyModifiers) + { + // Only emit a default `flat` for fragment + // stage varying inputs. + if (layout + && layout->getStage() == Stage::Fragment + && layout->usesResourceKind(LayoutResourceKind::VaryingInput)) + { + if (isIntegralScalarOrCompositeType(var->getDataType())) + emitOpDecorate(getSection(SpvLogicalSectionID::Annotations), nullptr, varInst, SpvDecorationFlat); + } + } } + + void maybeEmitName(SpvInst* spvInst, IRInst* irInst) + { + if (auto nameDecor = irInst->findDecoration<IRNameHintDecoration>()) + { + emitOpName(getSection(SpvLogicalSectionID::DebugNames), nullptr, spvInst, nameDecor->getName()); + } + } + /// Emit a global parameter definition. SpvInst* emitGlobalParam(IRGlobalParam* param) { @@ -1752,8 +1907,9 @@ struct SPIRVEmitContext storageClass ); if (auto layout = getVarLayout(param)) - emitVarLayout(varInst, layout); - return varInst; + emitVarLayout(param, varInst, layout); + maybeEmitName(varInst, param); + return varInst; } /// Emit a global variable definition. @@ -1773,7 +1929,8 @@ struct SPIRVEmitContext storageClass ); if(layout) - emitVarLayout(varInst, layout); + emitVarLayout(globalVar, varInst, layout); + maybeEmitName(varInst, globalVar); return varInst; } @@ -2195,6 +2352,8 @@ struct SPIRVEmitContext return emitImageLoad(parent, as<IRImageLoad>(inst)); case kIROp_ImageStore: return emitImageStore(parent, as<IRImageStore>(inst)); + case kIROp_ImageSubscript: + return emitImageSubscript(parent, as<IRImageSubscript>(inst)); } } @@ -2208,6 +2367,13 @@ struct SPIRVEmitContext return emitInst(parent, store, SpvOpImageWrite, store->getImage(), store->getCoord(), store->getValue()); } + SpvInst* emitImageSubscript(SpvInstParent* parent, IRImageSubscript* subscript) + { + IRBuilder builder(subscript); + builder.setInsertBefore(subscript); + return emitInst(parent, subscript, SpvOpImageTexelPointer, subscript->getDataType(), kResultID, subscript->getImage(), subscript->getCoord(), builder.getIntValue(builder.getIntType(), 0)); + } + SpvInst* emitGetStringHash(IRInst* inst) { auto getStringHashInst = as<IRGetStringHash>(inst); @@ -2244,20 +2410,23 @@ struct SPIRVEmitContext } case kIROp_BoolLit: { + SpvInst* spvInst = nullptr; if (cast<IRBoolLit>(inst)->getValue()) { - return emitOpConstantTrue( + spvInst = emitOpConstantTrue( inst, inst->getDataType() ); } else { - return emitOpConstantFalse( + spvInst = emitOpConstantFalse( inst, inst->getDataType() ); } + m_mapIRInstToSpvInst[inst] = spvInst; + return spvInst; } case kIROp_StringLit: { @@ -2396,13 +2565,16 @@ struct SPIRVEmitContext params ); - // Stage specific execution mode declarations. + // Stage specific execution mode and capability declarations. switch (entryPointDecor->getProfile().getStage()) { case Stage::Fragment: //OpExecutionMode %main OriginUpperLeft emitInst(getSection(SpvLogicalSectionID::ExecutionModes), nullptr, SpvOpExecutionMode, dstID, SpvExecutionModeOriginUpperLeft); break; + case Stage::Geometry: + requireSPIRVCapability(SpvCapabilityGeometry); + break; default: break; } @@ -2436,7 +2608,61 @@ struct SPIRVEmitContext ); } break; + case kIROp_MaxVertexCountDecoration: + { + auto section = getSection(SpvLogicalSectionID::ExecutionModes); + auto maxVertexCount = cast<IRMaxVertexCountDecoration>(decoration); + emitOpExecutionModeOutputVertices( + section, + decoration, + dstID, + SpvLiteralInteger::from32(int32_t(getIntVal(maxVertexCount->getCount()))) + ); + } + break; + case kIROp_InstanceDecoration: + { + auto decor = as<IRInstanceDecoration>(decoration); + auto count = int32_t(getIntVal(decor->getCount())); + auto section = getSection(SpvLogicalSectionID::ExecutionModes); + emitOpExecutionModeInvocations(section, decoration, dstID, SpvLiteralInteger::from32(count)); + } + break; + case kIROp_TriangleInputPrimitiveTypeDecoration: + emitOpExecutionMode(getSection(SpvLogicalSectionID::ExecutionModes), decoration, dstID, SpvExecutionModeTriangles); + break; + case kIROp_LineInputPrimitiveTypeDecoration: + emitOpExecutionMode(getSection(SpvLogicalSectionID::ExecutionModes), decoration, dstID, SpvExecutionModeInputLines); + break; + case kIROp_LineAdjInputPrimitiveTypeDecoration: + emitOpExecutionMode(getSection(SpvLogicalSectionID::ExecutionModes), decoration, dstID, SpvExecutionModeInputLinesAdjacency); + break; + case kIROp_PointInputPrimitiveTypeDecoration: + emitOpExecutionMode(getSection(SpvLogicalSectionID::ExecutionModes), decoration, dstID, SpvExecutionModeInputPoints); + break; + case kIROp_TriangleAdjInputPrimitiveTypeDecoration: + emitOpExecutionMode(getSection(SpvLogicalSectionID::ExecutionModes), decoration, dstID, SpvExecutionModeInputTrianglesAdjacency); + break; + case kIROp_StreamOutputTypeDecoration: + { + auto decor = as<IRStreamOutputTypeDecoration>(decoration); + IRType* type = decor->getStreamType(); + switch (type->getOp()) + { + case kIROp_HLSLPointStreamType: + emitOpExecutionMode(getSection(SpvLogicalSectionID::ExecutionModes), decoration, dstID, SpvExecutionModeOutputPoints); + break; + case kIROp_HLSLLineStreamType: + emitOpExecutionMode(getSection(SpvLogicalSectionID::ExecutionModes), decoration, dstID, SpvExecutionModeOutputLineStrip); + break; + case kIROp_HLSLTriangleStreamType: + emitOpExecutionMode(getSection(SpvLogicalSectionID::ExecutionModes), decoration, dstID, SpvExecutionModeOutputTriangleStrip); + break; + default: SLANG_ASSERT(!"Unknown stream out type"); + } + } + break; case kIROp_SPIRVBufferBlockDecoration: { emitOpDecorate( @@ -2491,6 +2717,16 @@ struct SPIRVEmitContext int32_t id = 0; for (auto field : structType->getFields()) { + if (auto fieldNameDecor = field->getKey()->findDecoration<IRNameHintDecoration>()) + { + emitOpMemberName( + getSection(SpvLogicalSectionID::DebugNames), + nullptr, + spvStructID, + id, + fieldNameDecor->getName()); + } + IRIntegerValue offset = 0; if (auto offsetDecor = field->getKey()->findDecoration<IRPackOffsetDecoration>()) { @@ -2807,7 +3043,9 @@ struct SPIRVEmitContext SpvInst* emitParam(SpvInstParent* parent, IRInst* inst) { - return emitOpFunctionParameter(parent, inst, inst->getFullType()); + auto paramSpvInst = emitOpFunctionParameter(parent, inst, inst->getFullType()); + maybeEmitName(paramSpvInst, inst); + return paramSpvInst; } SpvInst* emitVar(SpvInstParent* parent, IRInst* inst) @@ -2819,7 +3057,9 @@ struct SPIRVEmitContext { storageClass = (SpvStorageClass)ptrType->getAddressSpace(); } - return emitOpVariable(parent, inst, inst->getFullType(), storageClass); + auto varSpvInst = emitOpVariable(parent, inst, inst->getFullType(), storageClass); + maybeEmitName(varSpvInst, inst); + return varSpvInst; } /// Cached `IRParam` indices in an `IRBlock`. For use in `getParamIndexInBlock`. @@ -2948,7 +3188,7 @@ struct SPIRVEmitContext int paramIndex = getParamIndexInBlock(block, inst); // Emit a Phi instruction. - return emitInstCustomOperandFunc(parent, inst, SpvOpPhi, [&]() { + auto phiSpvInst = emitInstCustomOperandFunc(parent, inst, SpvOpPhi, [&]() { emitOperand(inst->getFullType()); emitOperand(kResultID); // Find phi arguments from incoming branch instructions that target `block`. @@ -2979,6 +3219,9 @@ struct SPIRVEmitContext emitOperand(getIRInstSpvID(sourceBlock)); } }); + + maybeEmitName(phiSpvInst, inst); + return phiSpvInst; } SpvInst* emitCall(SpvInstParent* parent, IRCall* inst) @@ -3389,15 +3632,22 @@ struct SPIRVEmitContext IRBuilder builder(m_irModule); builder.setInsertBefore(inst); - auto index = getIntVal(inst->getIndex()); - - return emitOpCompositeExtract( - parent, - inst, - inst->getFullType(), - inst->getBase(), - makeArray(SpvLiteralInteger::from32((int32_t)index)) - ); + if (auto index = as<IRIntLit>(inst->getIndex())) + { + return emitOpCompositeExtract( + parent, + inst, + inst->getFullType(), + inst->getBase(), + makeArray(SpvLiteralInteger::from32((int32_t)index->getValue())) + ); + } + else + { + SLANG_ASSERT(as<IRVectorType>(baseTy)); + // SPIRV Only allows dynamic element extract on vector types. + return emitOpVectorExtractDynamic(parent, inst, inst->getFullType(), inst->getBase(), inst->getIndex()); + } } SpvInst* emitLoad(SpvInstParent* parent, IRLoad* inst) @@ -3910,7 +4160,7 @@ struct SPIRVEmitContext if (isBool) opCode = SpvOpLogicalNot; else - opCode = SpvOpBitReverse; + opCode = SpvOpNot; break; case kIROp_Rsh: opCode = isSigned ? SpvOpShiftRightArithmetic : SpvOpShiftRightLogical; diff --git a/source/slang/slang-emit.cpp b/source/slang/slang-emit.cpp index f2906dfc3..15ce25c3c 100644 --- a/source/slang/slang-emit.cpp +++ b/source/slang/slang-emit.cpp @@ -72,7 +72,7 @@ #include "slang-legalize-types.h" #include "slang-lower-to-ir.h" #include "slang-mangle.h" - +#include "slang-spirv-opt.h" #include "slang-syntax.h" #include "slang-type-layout.h" #include "slang-visitor.h" @@ -773,15 +773,20 @@ Result linkAndOptimizeIR( switch (target) { case CodeGenTarget::GLSL: + case CodeGenTarget::SPIRV: + case CodeGenTarget::SPIRVAssembly: { - auto glslExtensionTracker = as<GLSLExtensionTracker>(options.sourceEmitter->getExtensionTracker()); + GLSLExtensionTracker glslExtensionTracker; + GLSLExtensionTracker* glslExtensionTrackerPtr = options.sourceEmitter + ? as<GLSLExtensionTracker>(options.sourceEmitter->getExtensionTracker()) + : &glslExtensionTracker; legalizeEntryPointsForGLSL( session, irModule, irEntryPoints, codeGenContext, - glslExtensionTracker); + glslExtensionTrackerPtr); #if 0 dumpIRIfEnabled(codeGenContext, irModule, "GLSL LEGALIZED"); @@ -1259,11 +1264,18 @@ SlangResult emitSPIRVForEntryPointsDirectly( auto irModule = linkedIR.module; auto irEntryPoints = linkedIR.entryPoints; - List<uint8_t> spirv; + List<uint8_t> spirv, outSpirv; emitSPIRVFromIR(codeGenContext, irModule, irEntryPoints, spirv); + String optErr; + if (SLANG_FAILED(optimizeSPIRV(spirv, optErr, outSpirv))) + { + codeGenContext->getSink()->diagnose(SourceLoc(), Diagnostics::spirvOptFailed, optErr); + outSpirv = _Move(spirv); + } + auto artifact = ArtifactUtil::createArtifactForCompileTarget(asExternal(codeGenContext->getTargetFormat())); - artifact->addRepresentationUnknown(ListBlob::moveCreate(spirv)); + artifact->addRepresentationUnknown(ListBlob::moveCreate(outSpirv)); ArtifactUtil::addAssociated(artifact, linkedIR.metadata); diff --git a/source/slang/slang-ir-array-reg-to-mem.cpp b/source/slang/slang-ir-array-reg-to-mem.cpp new file mode 100644 index 000000000..6f749f242 --- /dev/null +++ b/source/slang/slang-ir-array-reg-to-mem.cpp @@ -0,0 +1,87 @@ +#include "slang-ir-array-reg-to-mem.h" + +#include "slang-ir.h" +#include "slang-ir-insts.h" +#include "slang-ir-util.h" + +namespace Slang +{ + bool eliminateArrayTypeParameters(IRFunc* func) + { + IRBuilder builder(func); + bool changed = false; + List<UInt> arrayParamIds; + UInt idx = 0; + List<IRParam*> paramWorkList; + for (auto param : func->getParams()) + { + if (auto arrayType = as<IRArrayTypeBase>(param->getFullType())) + { + paramWorkList.add(param); + arrayParamIds.add(idx); + } + idx++; + } + for (auto param : paramWorkList) + { + // We have an array type parameter, so we need to replace it with a pointer to the array + // type. + // + // We will also need to insert a `load` instruction at the start of the function body + // to load the actual pointer value from the parameter. + // + if (auto arrayType = as<IRArrayTypeBase>(param->getFullType())) + { + changed = true; + builder.setInsertBefore(param); + auto ptrArrayType = builder.getPtrType(arrayType); + auto newParam = builder.emitParam(ptrArrayType); + setInsertAfterOrdinaryInst(&builder, param); + auto regVal = builder.emitLoad(newParam); + param->replaceUsesWith(regVal); + param->removeAndDeallocate(); + } + } + if (changed) + { + // The function is modified, we need to also update its type. + List<IRType*> paramTypes; + for (auto param : func->getParams()) + { + paramTypes.add(param->getFullType()); + } + auto newFuncType = builder.getFuncType((UInt)paramTypes.getCount(), paramTypes.getBuffer(), func->getResultType()); + func->setFullType(newFuncType); + + // Update all the call sites to pass the arrays by pointer. + traverseUses(func, [&](IRUse* use) + { + if (const auto call = as<IRCall>(use->getUser())) + { + builder.setInsertBefore(call); + for (auto paramId : arrayParamIds) + { + auto arg = call->getArg(paramId); + auto var = builder.emitVar(as<IRPtrTypeBase>(paramTypes[paramId])->getValueType()); + builder.emitStore(var, arg); + call->setArg(paramId, var); + } + } + }); + } + return changed; + } + + bool eliminateArrayTypeSSARegisters(IRModule* module) + { + bool changed = false; + for (auto inst : module->getGlobalInsts()) + { + if (auto func = as<IRFunc>(inst)) + { + changed |= eliminateArrayTypeParameters(func); + } + } + return changed; + } +} diff --git a/source/slang/slang-ir-array-reg-to-mem.h b/source/slang/slang-ir-array-reg-to-mem.h new file mode 100644 index 000000000..941125e1c --- /dev/null +++ b/source/slang/slang-ir-array-reg-to-mem.h @@ -0,0 +1,16 @@ +// slang-ir-array-reg-to-mem.h +#pragma once + +namespace Slang +{ + struct IRModule; + struct IRCall; + struct IRInst; + struct IRFunc; + + /// Eliminate SSA registers and IRParams of array type and turn them into pointers to memory objects. + bool eliminateArrayTypeSSARegisters(IRModule* module); + + bool eliminateArrayTypeParameters(IRFunc* func); + +} diff --git a/source/slang/slang-ir-glsl-legalize.cpp b/source/slang/slang-ir-glsl-legalize.cpp index 590404bf3..eb808bc97 100644 --- a/source/slang/slang-ir-glsl-legalize.cpp +++ b/source/slang/slang-ir-glsl-legalize.cpp @@ -7,6 +7,7 @@ #include "slang-ir-insts.h" #include "slang-ir-inst-pass-base.h" #include "slang-ir-specialize-function-call.h" +#include "slang-ir-util.h" #include "slang-glsl-extension-tracker.h" #include "../../external/spirv-headers/include/spirv/unified1/spirv.h" @@ -2377,135 +2378,128 @@ void legalizeEntryPointParameterForGLSL( // will differ a bit between the pointer and non-pointer // cases. auto paramType = pp->getDataType(); - + auto valueType = paramType; // First we will special-case stage input/outputs that // don't fit into the standard varying model. // - Geometry shader output streams // - Mesh shader outputs // - Mesh shader payload input - if( auto paramPtrType = as<IROutTypeBase>(paramType) ) + if (auto paramPtrType = as<IROutTypeBase>(paramType)) { - auto valueType = paramPtrType->getValueType(); - if( const auto gsStreamType = as<IRHLSLStreamOutputType>(valueType) ) - { - // An output stream type like `TriangleStream<Foo>` should - // more or less translate into `out Foo` (plus scalarization). + valueType = paramPtrType->getValueType(); + } + if( const auto gsStreamType = as<IRHLSLStreamOutputType>(valueType) ) + { + // An output stream type like `TriangleStream<Foo>` should + // more or less translate into `out Foo` (plus scalarization). - auto globalOutputVal = createGLSLGlobalVaryings( - context, - codeGenContext, - builder, - valueType, - paramLayout, - LayoutResourceKind::VaryingOutput, - stage, - pp); + auto globalOutputVal = createGLSLGlobalVaryings( + context, + codeGenContext, + builder, + valueType, + paramLayout, + LayoutResourceKind::VaryingOutput, + stage, + pp); - // TODO: a GS output stream might be passed into other - // functions, so that we should really be modifying - // any function that has one of these in its parameter - // list (and in the limit we should be leagalizing any - // type that nests these...). - // - // For now we will just try to deal with `Append` calls - // directly in this function. + // TODO: a GS output stream might be passed into other + // functions, so that we should really be modifying + // any function that has one of these in its parameter + // list (and in the limit we should be leagalizing any + // type that nests these...). + // + // For now we will just try to deal with `Append` calls + // directly in this function. - for( auto bb = func->getFirstBlock(); bb; bb = bb->getNextBlock() ) + for( auto bb = func->getFirstBlock(); bb; bb = bb->getNextBlock() ) + { + for( auto ii = bb->getFirstInst(); ii; ii = ii->getNextInst() ) { - for( auto ii = bb->getFirstInst(); ii; ii = ii->getNextInst() ) - { - // Is it a call? - if(ii->getOp() != kIROp_Call) - continue; + // Is it a call? + if(ii->getOp() != kIROp_Call) + continue; - // Is it calling the append operation? - auto callee = ii->getOperand(0); - for(;;) + // Is it calling the append operation? + auto callee = ii->getOperand(0); + for(;;) + { + // If the instruction is `specialize(X,...)` then + // we want to look at `X`, and if it is `generic { ... return R; }` + // then we want to look at `R`. We handle this + // iteratively here. + // + // TODO: This idiom seems to come up enough that we + // should probably have a dedicated convenience routine + // for this. + // + // Alternatively, we could switch the IR encoding so + // that decorations are added to the generic instead of the + // value it returns. + // + switch(callee->getOp()) { - // If the instruction is `specialize(X,...)` then - // we want to look at `X`, and if it is `generic { ... return R; }` - // then we want to look at `R`. We handle this - // iteratively here. - // - // TODO: This idiom seems to come up enough that we - // should probably have a dedicated convenience routine - // for this. - // - // Alternatively, we could switch the IR encoding so - // that decorations are added to the generic instead of the - // value it returns. - // - switch(callee->getOp()) + case kIROp_Specialize: { - case kIROp_Specialize: - { - callee = cast<IRSpecialize>(callee)->getOperand(0); - continue; - } + callee = cast<IRSpecialize>(callee)->getOperand(0); + continue; + } - case kIROp_Generic: + case kIROp_Generic: + { + auto genericResult = findGenericReturnVal(cast<IRGeneric>(callee)); + if(genericResult) { - auto genericResult = findGenericReturnVal(cast<IRGeneric>(callee)); - if(genericResult) - { - callee = genericResult; - continue; - } + callee = genericResult; + continue; } - - default: - break; } + + default: break; } - if(callee->getOp() != kIROp_Func) - continue; - - // HACK: we will identify the operation based - // on the target-intrinsic definition that was - // given to it. - auto decoration = as<IRTargetIntrinsicDecoration>(findBestTargetDecoration(callee, CapabilityAtom::GLSL)); - if(!decoration) - continue; + break; + } + if(callee->getOp() != kIROp_Func) + continue; - if(decoration->getDefinition() != UnownedStringSlice::fromLiteral("EmitVertex()")) - { - continue; - } + if (getBuiltinFuncName(callee) != UnownedStringSlice::fromLiteral("GeometryStreamAppend")) + { + continue; + } - // Okay, we have a declaration, and we want to modify it! + // Okay, we have a declaration, and we want to modify it! - builder->setInsertBefore(ii); + builder->setInsertBefore(ii); - assign(builder, globalOutputVal, ScalarizedVal::value(ii->getOperand(2))); - } + assign(builder, globalOutputVal, ScalarizedVal::value(ii->getOperand(2))); } + } - // We will still have references to the parameter coming - // from the `EmitVertex` calls, so we need to replace it - // with something. There isn't anything reasonable to - // replace it with that would have the right type, so - // we will replace it with an undefined value, knowing - // that the emitted code will not actually reference it. - // - // TODO: This approach to generating geometry shader code - // is not ideal, and we should strive to find a better - // approach that involes coding the `EmitVertex` operation - // directly in the stdlib, similar to how ray-tracing - // operations like `TraceRay` are handled. - // - builder->setInsertBefore(func->getFirstBlock()->getFirstOrdinaryInst()); - auto undefinedVal = builder->emitUndefined(pp->getFullType()); - pp->replaceUsesWith(undefinedVal); + // We will still have references to the parameter coming + // from the `EmitVertex` calls, so we need to replace it + // with something. There isn't anything reasonable to + // replace it with that would have the right type, so + // we will replace it with an undefined value, knowing + // that the emitted code will not actually reference it. + // + // TODO: This approach to generating geometry shader code + // is not ideal, and we should strive to find a better + // approach that involes coding the `EmitVertex` operation + // directly in the stdlib, similar to how ray-tracing + // operations like `TraceRay` are handled. + // + builder->setInsertBefore(func->getFirstBlock()->getFirstOrdinaryInst()); + auto undefinedVal = builder->emitUndefined(pp->getFullType()); + pp->replaceUsesWith(undefinedVal); - return; - } - if( auto meshOutputType = as<IRMeshOutputType>(valueType) ) - { - return legalizeMeshOutputParam(context, codeGenContext, func, pp, paramLayout, meshOutputType); - } + return; } - else if(pp->findDecoration<IRHLSLMeshPayloadDecoration>()) + if( auto meshOutputType = as<IRMeshOutputType>(valueType) ) + { + return legalizeMeshOutputParam(context, codeGenContext, func, pp, paramLayout, meshOutputType); + } + if(pp->findDecoration<IRHLSLMeshPayloadDecoration>()) { return legalizeMeshPayloadInputParam(context, codeGenContext, pp); } @@ -2543,7 +2537,7 @@ void legalizeEntryPointParameterForGLSL( // Is the parameter type a special pointer type // that indicates the parameter is used for `out` // or `inout` access? - if(auto paramPtrType = as<IROutTypeBase>(paramType) ) + if( as<IROutTypeBase>(paramType) ) { // Okay, we have the more interesting case here, // where the parameter was being passed by reference. @@ -2551,12 +2545,10 @@ void legalizeEntryPointParameterForGLSL( // type, which will replace the parameter, along with // one or more global variables for the actual input/output. - auto valueType = paramPtrType->getValueType(); - auto localVariable = builder->emitVar(valueType); auto localVal = ScalarizedVal::address(localVariable); - if( const auto inOutType = as<IRInOutType>(paramPtrType) ) + if( const auto inOutType = as<IRInOutType>(paramType) ) { // In the `in out` case we need to declare two // sets of global variables: one for the `in` diff --git a/source/slang/slang-ir-insts.h b/source/slang/slang-ir-insts.h index 04f993838..37d4e7e18 100644 --- a/source/slang/slang-ir-insts.h +++ b/source/slang/slang-ir-insts.h @@ -2129,6 +2129,7 @@ struct IRCall : IRInst return IROperandList<IRInst>(getOperands() + 1, getOperands() + getOperandCount()); } IRInst* getArg(UInt index) { return getOperand(index + 1); } + void setArg(UInt index, IRInst* arg) { setOperand(index + 1, arg); } }; struct IRLoad : IRInst diff --git a/source/slang/slang-ir-lower-buffer-element-type.cpp b/source/slang/slang-ir-lower-buffer-element-type.cpp index 1c02af82a..a5227ed68 100644 --- a/source/slang/slang-ir-lower-buffer-element-type.cpp +++ b/source/slang/slang-ir-lower-buffer-element-type.cpp @@ -184,7 +184,7 @@ namespace Slang IRBuilder builder(structType); builder.setInsertAfter(structType); auto func = builder.createFunc(); - auto funcType = builder.getFuncType(1, (IRType**)&structType, arrayType); + auto funcType = builder.getFuncType(1, (IRType**)&arrayType, structType); func->setFullType(funcType); builder.addNameHintDecoration(func, UnownedStringSlice("packStorage")); builder.setInsertInto(func); @@ -638,6 +638,23 @@ namespace Slang break; case kIROp_StructuredBufferGetDimensions: break; + case kIROp_Call: + { + // If we are calling a function with an l-value pointer from buffer access, + // we need to materialize the object as a local variable, and pass the address + // of the local variable to the function. + builder.setInsertBefore(user); + auto newLoad = builder.emitLoad(loweredElementTypeInfo.loweredType, ptrVal); + auto unpackedVal = builder.emitCallInst((IRType*)originalElementType, loweredElementTypeInfo.convertLoweredToOriginal, 1, &newLoad); + auto var = builder.emitVar((IRType*)originalElementType); + builder.emitStore(var, unpackedVal); + use->set(var); + builder.setInsertAfter(user); + auto newVal = builder.emitLoad(var); + auto packedVal = builder.emitCallInst((IRType*)loweredElementTypeInfo.loweredType, loweredElementTypeInfo.convertOriginalToLowered, 1, &newVal); + builder.emitStore(ptrVal, packedVal); + } + break; default: SLANG_UNREACHABLE("unhandled inst of a buffer/pointer value that needs storage lowering."); break; diff --git a/source/slang/slang-ir-peephole.cpp b/source/slang/slang-ir-peephole.cpp index d344590b1..a7e89eb4f 100644 --- a/source/slang/slang-ir-peephole.cpp +++ b/source/slang/slang-ir-peephole.cpp @@ -334,7 +334,46 @@ struct PeepholeContext : InstPassBase changed = true; } } - else if (inst->getOperand(0)->getOp() == kIROp_MakeArrayFromElement) + else if (inst->getOperand(0)->getOp() == kIROp_MakeVector) + { + auto index = as<IRIntLit>(as<IRGetElement>(inst)->getIndex()); + if (!index) + break; + auto opCount = inst->getOperand(0)->getOperandCount(); + IRIntegerValue startIndex = 0; + for (UInt i = 0; i < opCount; i++) + { + auto element = inst->getOperand(0)->getOperand(i); + if (auto elementVecType = as<IRVectorType>(element->getDataType())) + { + auto vecSize = as<IRIntLit>(elementVecType->getElementCount()); + if (!vecSize) + break; + if (index->getValue() >= startIndex && index->getValue() < startIndex + vecSize->getValue()) + { + IRBuilder builder(module); + builder.setInsertBefore(inst); + auto newElement = builder.emitElementExtract(element, builder.getIntValue(builder.getIntType(), index->getValue() - startIndex)); + inst->replaceUsesWith(newElement); + maybeRemoveOldInst(inst); + changed = true; + break; + } + } + else + { + if (startIndex == index->getValue()) + { + inst->replaceUsesWith(element); + maybeRemoveOldInst(inst); + changed = true; + break; + } + startIndex++; + } + } + } + else if (inst->getOperand(0)->getOp() == kIROp_MakeArrayFromElement || inst->getOperand(0)->getOp() == kIROp_MakeVectorFromScalar) { inst->replaceUsesWith(inst->getOperand(0)->getOperand(0)); maybeRemoveOldInst(inst); diff --git a/source/slang/slang-ir-spirv-legalize.cpp b/source/slang/slang-ir-spirv-legalize.cpp index 309fc5e38..c45378fa3 100644 --- a/source/slang/slang-ir-spirv-legalize.cpp +++ b/source/slang/slang-ir-spirv-legalize.cpp @@ -12,6 +12,12 @@ #include "slang-ir-layout.h" #include "slang-ir-util.h" #include "slang-ir-dominators.h" +#include "slang-ir-array-reg-to-mem.h" +#include "slang-ir-sccp.h" +#include "slang-ir-dce.h" +#include "slang-ir-simplify-cfg.h" +#include "slang-ir-peephole.h" +#include "slang-ir-redundancy-removal.h" namespace Slang { @@ -255,8 +261,121 @@ struct SPIRVLegalizationContext : public SourceEmitterBase return false; } + void inferTextureFormat(IRInst* textureInst, IRTextureTypeBase* textureType) + { + ImageFormat format = ImageFormat::unknown; + if (auto decor = textureInst->findDecoration<IRFormatDecoration>()) + { + format = decor->getFormat(); + } + if (format == ImageFormat::unknown) + { + // If the texture has no format decoration, try to infer it from the type. + auto elementType = textureType->getElementType(); + Int vectorWidth = 1; + if (auto elementVecType = as<IRVectorType>(elementType)) + { + if (auto intLitVal = as<IRIntLit>(elementVecType->getElementCount())) + { + vectorWidth = (Int)intLitVal->getValue(); + } + else + { + vectorWidth = 0; + } + elementType = elementVecType->getElementType(); + } + switch (elementType->getOp()) + { + case kIROp_UIntType: + switch (vectorWidth) + { + case 1: format = ImageFormat::r32ui; break; + case 2: format = ImageFormat::rg32ui; break; + case 4: format = ImageFormat::rgba32ui; break; + } + break; + case kIROp_IntType: + switch (vectorWidth) + { + case 1: format = ImageFormat::r32i; break; + case 2: format = ImageFormat::rg32i; break; + case 4: format = ImageFormat::rgba32i; break; + } + break; + case kIROp_UInt16Type: + switch (vectorWidth) + { + case 1: format = ImageFormat::r16ui; break; + case 2: format = ImageFormat::rg16ui; break; + case 4: format = ImageFormat::rgba16ui; break; + } + break; + case kIROp_Int16Type: + switch (vectorWidth) + { + case 1: format = ImageFormat::r16i; break; + case 2: format = ImageFormat::rg16i; break; + case 4: format = ImageFormat::rgba16i; break; + } + break; + case kIROp_UInt8Type: + switch (vectorWidth) + { + case 1: format = ImageFormat::r8ui; break; + case 2: format = ImageFormat::rg8ui; break; + case 4: format = ImageFormat::rgba8ui; break; + } + break; + case kIROp_Int8Type: + switch (vectorWidth) + { + case 1: format = ImageFormat::r8i; break; + case 2: format = ImageFormat::rg8i; break; + case 4: format = ImageFormat::rgba8i; break; + } + break; + case kIROp_Int64Type: + switch (vectorWidth) + { + case 1: format = ImageFormat::r64i; break; + default: break; + } + break; + case kIROp_UInt64Type: + switch (vectorWidth) + { + case 1: format = ImageFormat::r64ui; break; + default: break; + } + break; + } + } + if (format != ImageFormat::unknown) + { + IRBuilder builder(textureInst->getModule()); + builder.setInsertBefore(textureInst); + List<IRInst*> args; + args.add(textureType->getOperand(0)); + if (textureType->getOperandCount() >= 2) + args.add(textureType->getOperand(1)); + else + args.add(builder.getIntValue(builder.getUIntType(), 0)); + args.add(builder.getIntValue(builder.getUIntType(), IRIntegerValue(format))); + + auto newType = (IRType*)builder.emitIntrinsicInst(builder.getTypeKind(), textureType->getOp(), 3, args.getBuffer()); + textureInst->setFullType(newType); + } + } + void processGlobalParam(IRGlobalParam* inst) { + // If the param is a texture, infer its format. + if (auto textureType = as<IRTextureTypeBase>(inst->getDataType())) + { + inferTextureFormat(inst, textureType); + } + // If the global param is not a pointer type, make it so and insert explicit load insts. auto ptrType = as<IRPtrTypeBase>(inst->getDataType()); if (!ptrType) @@ -724,6 +843,33 @@ struct SPIRVLegalizationContext : public SourceEmitterBase addUsersToWorkList(newStore); } + void processImageSubscript(IRImageSubscript* subscript) + { + if (auto ptrType = as<IRPtrTypeBase>(subscript->getDataType())) + { + if (ptrType->hasAddressSpace()) + return; + IRBuilder builder(m_sharedContext->m_irModule); + builder.setInsertBefore(subscript); + auto newPtrType = builder.getPtrType( + ptrType->getOp(), + ptrType->getValueType(), + SpvStorageClassImage); + subscript->setFullType(newPtrType); + + // HACK: assumes the image operand is a load and replace it with + // the pointer to satisfy SPIRV requirements. + // We should consider changing the front-end to pass `this` by ref + // for the __ref accessor so we will be guaranteed to have a pointer + // image operand here. + auto image = subscript->getImage(); + if (auto load = as<IRLoad>(image)) + subscript->setOperand(0, load->getPtr()); + + addUsersToWorkList(subscript); + } + } + void processFieldAddress(IRFieldAddress* inst) { if (auto ptrType = as<IRPtrTypeBase>(inst->getBase()->getDataType())) @@ -942,7 +1088,7 @@ struct SPIRVLegalizationContext : public SourceEmitterBase } } - void processConstructor(IRInst* inst) + void maybeHoistConstructInstToGlobalScope(IRInst* inst) { // If all of the operands to this instruction are global, we can hoist // this constructor to be a global too. This is important to make sure @@ -950,11 +1096,47 @@ struct SPIRVLegalizationContext : public SourceEmitterBase // constant vectors (using OpConstantComposite). UIndex opIndex = 0; for (auto operand = inst->getOperands(); opIndex < inst->getOperandCount(); operand++, opIndex++) - if(operand->get()->getParent() != m_module->getModuleInst()) + if (operand->get()->getParent() != m_module->getModuleInst()) return; inst->insertAtEnd(m_module->getModuleInst()); } + void processConstructor(IRInst* inst) + { + maybeHoistConstructInstToGlobalScope(inst); + + if (inst->getOp() == kIROp_MakeVector + && inst->getParent()->getOp() == kIROp_Module + && inst->getOperandCount() != (UInt)getIntVal(as<IRVectorType>(inst->getDataType())->getElementCount())) + { + // SPIRV's OpConstantComposite inst requires the number of operands to + // exactly match the number of elements of the composite, so the general + // form of vector construction will not work, and we need to convert it. + // + List<IRInst*> args; + IRBuilder builder(inst); + builder.setInsertBefore(inst); + for (UInt i = 0; i < inst->getOperandCount(); i++) + { + auto operand = inst->getOperand(i); + if (auto operandVecType = as<IRVectorType>(operand->getDataType())) + { + auto operandVecSize = getIntVal(operandVecType->getElementCount()); + for (IRIntegerValue j = 0; j < operandVecSize; j++) + { + args.add(builder.emitElementExtract(operand, j)); + } + } + else + { + args.add(operand); + } + } + auto newMakeVector = builder.emitMakeVector(inst->getDataType(), args); + inst->replaceUsesWith(newMakeVector); + } + } + static bool isAsmInst(IRInst* inst) { return (as<IRSPIRVAsmInst>(inst) || as<IRSPIRVAsmOperand>(inst)); @@ -970,6 +1152,18 @@ struct SPIRVLegalizationContext : public SourceEmitterBase } } + void legalizeSPIRVEntryPoint(IRFunc* func, IREntryPointDecoration* entryPointDecor) + { + if (entryPointDecor->getProfile().getStage() == Stage::Geometry) + { + if (!func->findDecoration<IRInstanceDecoration>()) + { + IRBuilder builder(func); + builder.addDecoration(func, kIROp_InstanceDecoration, builder.getIntValue(builder.getUIntType(), 1)); + } + } + } + void processModule() { // Process global params before anything else, so we don't generate inefficient @@ -1010,6 +1204,9 @@ struct SPIRVLegalizationContext : public SourceEmitterBase case kIROp_FieldAddress: processFieldAddress(as<IRFieldAddress>(inst)); break; + case kIROp_ImageSubscript: + processImageSubscript(as<IRImageSubscript>(inst)); + break; case kIROp_RWStructuredBufferGetElementPtr: processRWStructuredBufferGetElementPtr(as<IRRWStructuredBufferGetElementPtr>(inst)); break; @@ -1078,13 +1275,20 @@ struct SPIRVLegalizationContext : public SourceEmitterBase t->replaceUsesWith(builder.getPtrType(kIROp_PtrType, lowered.structType, SpvStorageClassStorageBuffer)); } - // SPIRV requires a dominator block to appear before dominated blocks. - // After legalizing the control flow, we need to sort our blocks to ensure this is true. for (auto globalInst : m_module->getGlobalInsts()) { - if (auto func = as<IRGlobalValueWithCode>(globalInst)) + if (auto func = as<IRFunc>(globalInst)) + { + if (auto entryPointDecor = func->findDecoration<IREntryPointDecoration>()) + { + legalizeSPIRVEntryPoint(func, entryPointDecor); + } + // SPIRV requires a dominator block to appear before dominated blocks. + // After legalizing the control flow, we need to sort our blocks to ensure this is true. sortBlocksInFunc(func); + } } + } }; @@ -1205,15 +1409,52 @@ void buildEntryPointReferenceGraph(SPIRVEmitSharedContext* context, IRModule* mo visit(workList[i].entryPoint, workList[i].inst); } +void simplifyIRForSpirvLegalization(DiagnosticSink* sink, IRModule* module) +{ + bool changed = true; + const int kMaxIterations = 8; + const int kMaxFuncIterations = 16; + int iterationCounter = 0; + + while (changed && iterationCounter < kMaxIterations) + { + if (sink && sink->getErrorCount()) + break; + + changed = false; + + changed |= applySparseConditionalConstantPropagationForGlobalScope(module, sink); + changed |= peepholeOptimizeGlobalScope(module); + + for (auto inst : module->getGlobalInsts()) + { + auto func = as<IRGlobalValueWithCode>(inst); + if (!func) + continue; + bool funcChanged = true; + int funcIterationCount = 0; + while (funcChanged && funcIterationCount < kMaxFuncIterations) + { + funcChanged = false; + funcChanged |= applySparseConditionalConstantPropagation(func, sink); + funcChanged |= peepholeOptimize(func); + funcChanged |= removeRedundancyInFunc(func); + funcChanged |= simplifyCFG(func); + eliminateDeadCode(func); + } + } + } +} + void legalizeIRForSPIRV( SPIRVEmitSharedContext* context, IRModule* module, const List<IRFunc*>& entryPoints, CodeGenContext* codeGenContext) { - GLSLExtensionTracker extensionTracker; - legalizeEntryPointsForGLSL(module->getSession(), module, entryPoints, codeGenContext, &extensionTracker); + SLANG_UNUSED(entryPoints); legalizeSPIRV(context, module); + simplifyIRForSpirvLegalization(codeGenContext->getSink(), module); buildEntryPointReferenceGraph(context, module); } diff --git a/source/slang/slang-ir-util.cpp b/source/slang/slang-ir-util.cpp index 4a92d263e..dd6cd8892 100644 --- a/source/slang/slang-ir-util.cpp +++ b/source/slang/slang-ir-util.cpp @@ -1128,6 +1128,14 @@ IRVarLayout* findVarLayout(IRInst* value) } +UnownedStringSlice getBuiltinFuncName(IRInst* callee) +{ + auto decor = getResolvedInstForDecorations(callee)->findDecoration<IRKnownBuiltinDecoration>(); + if (!decor) + return UnownedStringSlice(); + return decor->getName(); +} + UnownedStringSlice getBasicTypeNameHint(IRType* basicType) { switch (basicType->getOp()) diff --git a/source/slang/slang-ir-util.h b/source/slang/slang-ir-util.h index 20bac0cbf..8af4f7536 100644 --- a/source/slang/slang-ir-util.h +++ b/source/slang/slang-ir-util.h @@ -257,6 +257,8 @@ HashSet<IRBlock*> getParentBreakBlockSet(IRDominatorTree* dom, IRBlock* block); IRVarLayout* findVarLayout(IRInst* value); +UnownedStringSlice getBuiltinFuncName(IRInst* callee); + // Run an operation over every block in a module template<typename F> static void overAllBlocks(IRModule* module, F f) diff --git a/source/slang/slang-ir.cpp b/source/slang/slang-ir.cpp index ff6c8c39e..5fa558e15 100644 --- a/source/slang/slang-ir.cpp +++ b/source/slang/slang-ir.cpp @@ -4708,7 +4708,12 @@ namespace Slang IRInst* base, IRInst* index) { - auto inst = createInst<IRFieldAddress>( + if (auto vectorFromScalar = as<IRMakeVectorFromScalar>(base)) + return vectorFromScalar->getOperand(0); + if (base->getOp() == kIROp_MakeArrayFromElement) + return base->getOperand(0); + + auto inst = createInst<IRGetElement>( this, kIROp_GetElement, type, @@ -4737,15 +4742,8 @@ namespace Slang type = getVectorType(matrixType->getElementType(), matrixType->getColumnCount()); } SLANG_RELEASE_ASSERT(type); - auto inst = createInst<IRGetElement>( - this, - kIROp_GetElement, - type, - base, - index); - addInst(inst); - return inst; + return emitElementExtract(type, base, index); } IRInst* IRBuilder::emitElementExtract( diff --git a/source/slang/slang-ir.h b/source/slang/slang-ir.h index ef803b642..a24e2e5c7 100644 --- a/source/slang/slang-ir.h +++ b/source/slang/slang-ir.h @@ -1388,6 +1388,8 @@ struct IRResourceType : IRResourceTypeBase { IRType* getElementType() { return (IRType*)getOperand(0); } IRType* getSampleCount() { return (IRType*)getOperand(1); } + bool hasFormat() { return getOperandCount() >= 2; } + IRIntegerValue getFormat() { return getIntVal(getOperand(2)); } IR_PARENT_ISA(ResourceType) }; diff --git a/source/slang/slang-lower-to-ir.cpp b/source/slang/slang-lower-to-ir.cpp index b3a309d5a..452b24c30 100644 --- a/source/slang/slang-lower-to-ir.cpp +++ b/source/slang/slang-lower-to-ir.cpp @@ -1364,12 +1364,12 @@ static void addLinkageDecoration( ? pyExportModifier->name.getUnownedSlice() : decl->getName()->text.getUnownedSlice()); } - else if (as<KnownBuiltinAttribute>(modifier)) + else if (auto knownBuiltinModifier = as<KnownBuiltinAttribute>(modifier)) { // We add this to the internal instruction, like other name-like // decorations, for instance "nameHint". This prevents it becoming // lost during specialization. - builder->addKnownBuiltinDecoration(inInst, decl->getName()->text.getUnownedSlice()); + builder->addKnownBuiltinDecoration(inInst, knownBuiltinModifier->name.getUnownedSlice()); } } if (as<InterfaceDecl>(decl->parentDecl) && diff --git a/source/slang/slang-spirv-opt.cpp b/source/slang/slang-spirv-opt.cpp new file mode 100644 index 000000000..34c87db9f --- /dev/null +++ b/source/slang/slang-spirv-opt.cpp @@ -0,0 +1,68 @@ +#include "slang-spirv-opt.h" +#include "slang-spirv-val.h" + +namespace Slang +{ + +struct RemoveFileRAII +{ + String fileName; + + RemoveFileRAII(String inFileName) + :fileName(inFileName) + {} + + ~RemoveFileRAII() + { + File::remove(fileName); + } +}; + +SlangResult optimizeSPIRV(const List<uint8_t>& spirv, String& outErr, List<uint8_t>& outSpv) +{ + // Set up our process + CommandLine commandLine; + commandLine.m_executableLocation.setName("spirv-opt"); + commandLine.addArg("--merge-return"); + commandLine.addArg("--inline-entry-points-exhaustive"); + commandLine.addArg("--eliminate-dead-functions"); + commandLine.addArg("--eliminate-local-single-block"); + commandLine.addArg("--eliminate-local-single-store"); + commandLine.addArg("--eliminate-dead-code-aggressive"); + + commandLine.addArg("-o"); + String outFileName; + File::generateTemporary(UnownedStringSlice("out_spv"), outFileName); + RemoveFileRAII removeFile(outFileName); + + commandLine.addArg(outFileName); + + RefPtr<Process> p; + + // If we failed to even start the process, then spirv-opt isn't available + SLANG_RETURN_ON_FAIL(Process::create(commandLine, 0, p)); + const auto in = p->getStream(StdStreamType::In); + const auto out = p->getStream(StdStreamType::Out); + const auto err = p->getStream(StdStreamType::ErrorOut); + + List<Byte> outErrData; + SLANG_RETURN_ON_FAIL(StreamUtil::readAndWrite(in, spirv.getArrayView(), out, outSpv, err, outErrData)); + + outSpv.clear(); + File::readAllBytes(outFileName, outSpv); + + SLANG_RETURN_ON_FAIL(p->waitForTermination(3600000)); + + outErr = String( + reinterpret_cast<const char*>(outErrData.begin()), + reinterpret_cast<const char*>(outErrData.end()) + ); + + const auto ret = p->getReturnValue(); + if (ret != 0) + return SLANG_FAIL; + + return debugValidateSPIRV(outSpv); +} + +} diff --git a/source/slang/slang-spirv-opt.h b/source/slang/slang-spirv-opt.h new file mode 100644 index 000000000..c7dfde157 --- /dev/null +++ b/source/slang/slang-spirv-opt.h @@ -0,0 +1,10 @@ +#pragma once + +#include <cstdint> +#include "slang-compiler.h" + +namespace Slang +{ +SlangResult optimizeSPIRV(const List<uint8_t>& spirv, String& outErr, List<uint8_t>& outSpv); +} + diff --git a/source/slang/slang-spirv-val.cpp b/source/slang/slang-spirv-val.cpp index a6bc29306..b5a02f3fe 100644 --- a/source/slang/slang-spirv-val.cpp +++ b/source/slang/slang-spirv-val.cpp @@ -55,6 +55,7 @@ SlangResult debugValidateSPIRV(const List<uint8_t>& spirv) commandLine.m_executableLocation.setName("spirv-val"); commandLine.addArg("--target-env"); commandLine.addArg("vulkan1.2"); + commandLine.addArg("--scalar-block-layout"); RefPtr<Process> p; const auto createResult = Process::create(commandLine, 0, p); // If we failed to even start the process, then validation isn't available diff --git a/source/slang/slang-stdlib-textures.cpp b/source/slang/slang-stdlib-textures.cpp index 76ca51380..9254bf966 100644 --- a/source/slang/slang-stdlib-textures.cpp +++ b/source/slang/slang-stdlib-textures.cpp @@ -1,4 +1,5 @@ #include "slang-stdlib-textures.h" +#include <spirv/unified1/spirv.h> #define EMIT_LINE_DIRECTIVE() sb << "#line " << (__LINE__+1) << " \"slang-stdlib-textures.cpp\"\n" @@ -256,138 +257,231 @@ void TextureTypeInfo::writeQueryFunctions() // `GetDimensions` const char* dimParamTypes[] = {"out float ", "out int ", "out uint "}; - for(auto t : dimParamTypes) - for(int includeMipInfo = 0; includeMipInfo < 2; ++includeMipInfo) + const char* dimParamTypesInner[] = { "float", "int", "uint" }; + for (int tid = 0; tid < 3; tid++) { - StringBuilder glsl; - { - - glsl << "("; + auto t = dimParamTypes[tid]; + auto rawT = dimParamTypesInner[tid]; - int aa = 1; - String lodStr = ", 0"; + for (int includeMipInfo = 0; includeMipInfo < 2; ++includeMipInfo) + { + int sizeDimCount = 0; + StringBuilder params; if (includeMipInfo) - { - int mipLevelArg = aa++; - lodStr = ", int($"; - lodStr.append(mipLevelArg); - lodStr.append(")"); - } + params << "uint mipLevel, "; - String opStr = " = textureSize($0" + lodStr; - switch( access ) - { - case SLANG_RESOURCE_ACCESS_READ_WRITE: - case SLANG_RESOURCE_ACCESS_RASTER_ORDERED: - opStr = " = imageSize($0"; - break; - - default: - break; - } - - - int cc = 0; - switch(baseShape) + switch (baseShape) { case TextureFlavor::Shape::Shape1D: - glsl << "($" << aa++ << opStr << ")"; - if (isArray) - { - glsl << ".x"; - } - glsl << ")"; - cc = 1; + params << t << "width"; + sizeDimCount = 1; break; case TextureFlavor::Shape::Shape2D: case TextureFlavor::Shape::ShapeCube: - glsl << "($" << aa++ << opStr << ").x)"; - glsl << ", ($" << aa++ << opStr << ").y)"; - cc = 2; + params << t << "width,"; + params << t << "height"; + sizeDimCount = 2; break; case TextureFlavor::Shape::Shape3D: - glsl << "($" << aa++ << opStr << ").x)"; - glsl << ", ($" << aa++ << opStr << ").y)"; - glsl << ", ($" << aa++ << opStr << ").z)"; - cc = 3; + params << t << "width,"; + params << t << "height,"; + params << t << "depth"; + sizeDimCount = 3; break; default: - SLANG_UNEXPECTED("unhandled resource shape"); + assert(!"unexpected"); break; } - if(isArray) + if (isArray) { - glsl << ", ($" << aa++ << opStr << ")." << kComponentNames[cc] << ")"; + params << ", " << t << "elements"; + sizeDimCount++; } - if(isMultisample) + if (isMultisample) { - glsl << ", ($" << aa++ << " = textureSamples($0))"; + params << ", " << t << "sampleCount"; } if (includeMipInfo) + params << ", " << t << "numberOfLevels"; + + + StringBuilder glsl; { - glsl << ", ($" << aa++ << " = textureQueryLevels($0))"; - } + glsl << "("; + int aa = 1; + String lodStr = ", 0"; + if (includeMipInfo) + { + int mipLevelArg = aa++; + lodStr = ", int($"; + lodStr.append(mipLevelArg); + lodStr.append(")"); + } - glsl << ")"; - } + String opStr = " = textureSize($0" + lodStr; + switch (access) + { + case SLANG_RESOURCE_ACCESS_READ_WRITE: + case SLANG_RESOURCE_ACCESS_RASTER_ORDERED: + opStr = " = imageSize($0"; + break; - StringBuilder params; - if(includeMipInfo) - params << "uint mipLevel, "; + default: + break; + } - switch(baseShape) - { - case TextureFlavor::Shape::Shape1D: - params << t << "width"; - break; + int cc = 0; + switch (baseShape) + { + case TextureFlavor::Shape::Shape1D: + glsl << "($" << aa++ << opStr << ")"; + if (isArray) + { + glsl << ".x"; + } + glsl << ")"; + cc = 1; + break; + + case TextureFlavor::Shape::Shape2D: + case TextureFlavor::Shape::ShapeCube: + glsl << "($" << aa++ << opStr << ").x)"; + glsl << ", ($" << aa++ << opStr << ").y)"; + cc = 2; + break; + + case TextureFlavor::Shape::Shape3D: + glsl << "($" << aa++ << opStr << ").x)"; + glsl << ", ($" << aa++ << opStr << ").y)"; + glsl << ", ($" << aa++ << opStr << ").z)"; + cc = 3; + break; + + default: + SLANG_UNEXPECTED("unhandled resource shape"); + break; + } - case TextureFlavor::Shape::Shape2D: - case TextureFlavor::Shape::ShapeCube: - params << t << "width,"; - params << t << "height"; - break; + if (isArray) + { + glsl << ", ($" << aa++ << opStr << ")." << kComponentNames[cc] << ")"; + } - case TextureFlavor::Shape::Shape3D: - params << t << "width,"; - params << t << "height,"; - params << t << "depth"; - break; + if (isMultisample) + { + glsl << ", ($" << aa++ << " = textureSamples($0))"; + } - default: - assert(!"unexpected"); - break; - } + if (includeMipInfo) + { + glsl << ", ($" << aa++ << " = textureQueryLevels($0))"; + } - if(isArray) - { - params << ", " << t << "elements"; - } - if(isMultisample) - { - params << ", " << t << "sampleCount"; - } + glsl << ")"; + } - if(includeMipInfo) - params << ", " << t << "numberOfLevels"; + StringBuilder spirv; + { + spirv << "OpCapability ImageQuery; "; + spirv << "%vecSize:$$uint"; + if (sizeDimCount > 1) spirv << sizeDimCount; + spirv << " = "; + if (isMultisample) + spirv << "OpImageQuerySize $this;"; + else + spirv << "OpImageQuerySizeLod $this $0;"; + auto convertAndStore = [&](UnownedStringSlice uintSourceVal, const char* destParam) + { + if (UnownedStringSlice(rawT) == "uint") + { + spirv << "OpStore &" << destParam << " %" << uintSourceVal << ";"; + } + else + { + if (UnownedStringSlice(rawT) == "int") + { + spirv << "%c_" << uintSourceVal << " : $$" << rawT << " = OpBitcast %" << uintSourceVal << "; "; + } + else + { + spirv << "%c_" << uintSourceVal << " : $$" << rawT << " = OpConvertUToF %" << uintSourceVal << "; "; + } + spirv << "OpStore &" << destParam << "%c_" << uintSourceVal << ";"; + } + }; + auto extractSizeComponent = [&](int componentId, const char* destParam) + { + String elementVal = String("_") + destParam; + if (sizeDimCount == 1) + { + spirv << "%" << elementVal << " : $$uint = OpCopyObject %vecSize; "; + } + else + { + spirv << "%" << elementVal << " : $$uint = OpCompositeExtract %vecSize " << componentId << "; "; + } + convertAndStore(elementVal.getUnownedSlice(), destParam); + }; + switch (baseShape) + { + case TextureFlavor::Shape::Shape1D: + extractSizeComponent(0, "width"); + break; + + case TextureFlavor::Shape::Shape2D: + case TextureFlavor::Shape::ShapeCube: + extractSizeComponent(0, "width"); + extractSizeComponent(1, "height"); + break; + + case TextureFlavor::Shape::Shape3D: + extractSizeComponent(0, "width"); + extractSizeComponent(1, "height"); + extractSizeComponent(2, "depth"); + break; + + default: + assert(!"unexpected"); + break; + } - sb << " __glsl_version(450)\n"; - sb << " __glsl_extension(GL_EXT_samplerless_texture_functions)\n"; - writeFunc( - "void", - "GetDimensions", - params, - glsl, - "", - "", - ReadNoneMode::Always); + if (isArray) + { + extractSizeComponent(sizeDimCount - 1, "elements"); + } + + if (isMultisample) + { + spirv << "%_sampleCount : $$uint = OpImageQuerySamples $this;"; + convertAndStore(UnownedStringSlice("_sampleCount"), "sampleCount"); + } + + if (includeMipInfo) + { + spirv << "%_levelCount : $$uint = OpImageQueryLevels $this;"; + convertAndStore(UnownedStringSlice("_levelCount"), "numberOfLevels"); + } + } + + sb << " __glsl_version(450)\n"; + sb << " __glsl_extension(GL_EXT_samplerless_texture_functions)\n"; + writeFunc( + "void", + "GetDimensions", + params, + glsl, + spirv, + "", + ReadNoneMode::Always); + } } // `GetSamplePosition()` @@ -493,6 +587,40 @@ void TextureTypeInfo::writeQueryFunctions() } } + // SPIRV + auto getSpirvIntrinsic = [&](bool hasSampleIndex, bool hasOffset) + { + StringBuilder spirv; + spirv << "%lod:$$int = OpCompositeExtract $location " << base.coordCount + isArray << "; "; + spirv << "%coord:$$int" << base.coordCount + isArray << " = OpVectorShuffle $location $location "; + for (int i = 0; i < base.coordCount + isArray; i++) + spirv << " " << i; + spirv << "; "; + spirv << "%sampled:__sampledType(T) = "; + if (access == SLANG_RESOURCE_ACCESS_READ_WRITE) + spirv << "OpImageRead"; + else + spirv << "OpImageFetch"; + spirv << " $this %coord "; + uint32_t operandMask = 0; + if (!hasSampleIndex) + operandMask |= SpvImageOperandsLodMask; + if (hasOffset) + operandMask |= SpvImageOperandsConstOffsetMask; + if (hasSampleIndex) + operandMask |= SpvImageOperandsSampleMask; + spirv << operandMask << " "; + if (operandMask & SpvImageOperandsLodMask) + spirv << " %lod"; + if (operandMask & SpvImageOperandsConstOffsetMask) + spirv << " $offset"; + if (operandMask & SpvImageOperandsSampleMask) + spirv << " $sampleIndex"; + spirv << ";"; + spirv << i << "__truncate $$T result __sampledType(T) %sampled;"; + return spirv.produceString(); + }; + sb << i << "__glsl_extension(GL_EXT_samplerless_texture_functions)"; writeFunc( "T", @@ -508,7 +636,7 @@ void TextureTypeInfo::writeQueryFunctions() kGLSLLoadLODSwizzle[loadCoordCount], ")$z") : cat("$c", glslFuncName, "($0, $1)$z"), - "", + getSpirvIntrinsic(isMultisample, false), cudaBuilder ); @@ -527,7 +655,8 @@ void TextureTypeInfo::writeQueryFunctions() "$c", glslFuncName, "($0, ($1).", kGLSLLoadCoordsSwizzle[loadCoordCount], ", ($1).", kGLSLLoadLODSwizzle[loadCoordCount], ", $2)$z") - : cat("$c", glslFuncName, "($0, $1, 0, $2)$z") + : cat("$c", glslFuncName, "($0, $1, 0, $2)$z"), + getSpirvIntrinsic(isMultisample, true) ); writeFunc( |
