summaryrefslogtreecommitdiff
path: root/source/slang/slang-emit-spirv.cpp
diff options
context:
space:
mode:
authorEllie Hermaszewska <ellieh@nvidia.com>2023-09-05 23:26:59 +0800
committerGitHub <noreply@github.com>2023-09-05 23:26:59 +0800
commit2c2294d3310b24fd73cd41ec51338a736f3a2886 (patch)
tree0e02393fa772e7741eb38079a79f5cacaa1ba7b0 /source/slang/slang-emit-spirv.cpp
parent641f7bdc4ea4f75385c30d833cce4619a411ec67 (diff)
SPIR-V image operations (#3163)
* Add __truncate and __sampledType for spirv_asm Allows some texture tests to start passing * add __isVector Currently unused * Add 1-vector legalization pass (WIP) * Add capabilities for image types * neaten instruction dumping * add 1-vector test * Add a couple of cases to vec1 legalization * Remove texture tests from expected failures * comment * regenerate vs projects * Remove redundant define form synchapi emulation * refactoring image methods * All sample functions refactored * Remove incorrect glsl intrinsics Partially addresses https://github.com/shader-slang/slang/issues/3174 * __subscript image ops via writing funcs * Extract texture struct writing from core.meta.slang * Abstract out cuda intrinsic * Remvoe erroneous call to opDecorateIndex * spirv asm IR utils * Correct position of loads for SPIR-V asm inst operands * Raise constructors to global scope during spir-v legalization * Correct snippet output * Implement most texture sampling ops for SPIR-V * Legalize 1-vectors for glsl too * Make SPIR-V inst operands non-hoistable * Better 1-vector legalization * Put textures in ptrs for spirv * insert missing break * Add vec1 legalization test * Add some missing pieces to slang-ir-insts * Greatly neaten vec1 legalization * a * Neaten vec1 legalization * Add image read and write intrinsics for spir-v * Squash warnings * regenerate vs projects * Drop redundant guards * Drop 5 tests from expected failure list * Inst numbering changes to cross compile tests * vec1 legalization tests only on vk * Correct location of asm op emit * Inline constant in spirv-asm * Correct signedness for lane in wave intrinsics * Extract element from float1 for cuda * squash warnings * Neaten spirv-emit * dedupe more capabilities * warnings * neaten assert * comments * comments
Diffstat (limited to 'source/slang/slang-emit-spirv.cpp')
-rw-r--r--source/slang/slang-emit-spirv.cpp432
1 files changed, 319 insertions, 113 deletions
diff --git a/source/slang/slang-emit-spirv.cpp b/source/slang/slang-emit-spirv.cpp
index 846c4b5b4..07f1b2aee 100644
--- a/source/slang/slang-emit-spirv.cpp
+++ b/source/slang/slang-emit-spirv.cpp
@@ -1294,6 +1294,37 @@ struct SPIRVEmitContext
}
case kIROp_TextureType:
{
+ // Some untyped constants from OpTypeImage
+ // https://registry.khronos.org/SPIR-V/specs/unified1/SPIRV.html#OpTypeImage
+
+ // indicates not a depth image
+ [[maybe_unused]]
+ const SpvWord notDepthImage = 0;
+ // indicates a depth image
+ [[maybe_unused]]
+ const SpvWord isDepthImage = 1;
+ // means no indication as to whether this is a depth or non-depth image
+ const SpvWord unknownDepthImage = 2;
+
+ // indicates non-arrayed content
+ const SpvWord notArrayed = 0;
+ // indicates arrayed content
+ const SpvWord isArrayed = 1;
+
+ // indicates single-sampled content
+ const SpvWord notMultisampled = 0;
+ // indicates multisampled content
+ const SpvWord isMultisampled = 1;
+
+ // indicates this is only known at run time, not at compile time
+ const SpvWord sampledUnknown = 0;
+ // indicates an image compatible with sampling operations
+ const SpvWord sampledImage = 1;
+ // indicates an image compatible with read/write operations (a storage or subpass data image).
+ const SpvWord readWriteImage = 2;
+
+ //
+
const auto texTypeInst = as<IRTextureType>(inst);
const auto sampledType = texTypeInst->getElementType();
SpvDim dim = SpvDim1D; // Silence uninitialized warnings from msvc...
@@ -1318,16 +1349,78 @@ struct SPIRVEmitContext
dim = SpvDimBuffer;
break;
}
- bool arrayed = texTypeInst->isArray();
- SpvWord depth = 2; // No knowledge of if this is a depth image
- bool ms = texTypeInst->isMultisample();
- // TODO: can we do better here?
- SpvWord sampled = 0; // Only known at run time
- // TODO: can we do better?
+ SpvWord arrayed = texTypeInst->isArray() ? isArrayed : notArrayed;
+
+ // Vulkan spec 16.1: "The “Depth” operand of OpTypeImage is ignored."
+ SpvWord depth = unknownDepthImage; // No knowledge of if this is a depth image
+ SpvWord ms = texTypeInst->isMultisample() ? isMultisampled : notMultisampled;
+
+ SpvWord sampled = sampledUnknown;
+ switch(texTypeInst->getAccess())
+ {
+ case SlangResourceAccess::SLANG_RESOURCE_ACCESS_READ_WRITE:
+ case SlangResourceAccess::SLANG_RESOURCE_ACCESS_RASTER_ORDERED:
+ sampled = readWriteImage;
+ break;
+ case SlangResourceAccess::SLANG_RESOURCE_ACCESS_NONE:
+ case SlangResourceAccess::SLANG_RESOURCE_ACCESS_READ:
+ sampled = sampledImage;
+ break;
+ }
+
+ // TODO: we need to do as _emitGLSLImageFormatModifier does,
+ // take a guess at the image format
SpvImageFormat format = SpvImageFormatUnknown;
+
+ //
+ // Capabilities, according to section 3.8
+ //
+ // SPIR-V requires that the sampled/rw info on the image isn't unknown
+ SLANG_ASSERT(sampled == sampledImage || sampled == readWriteImage);
+ switch(dim)
+ {
+ case SpvDim1D:
+ requireSPIRVCapability(sampled == sampledImage ? SpvCapabilitySampled1D : SpvCapabilityImage1D);
+ break;
+ case SpvDim2D:
+ // Also requires Shader or Kernel, but these are a given (?)
+ if(sampled == readWriteImage && ms == isMultisampled && arrayed == isArrayed)
+ requireSPIRVCapability(SpvCapabilityImageMSArray);
+ break;
+ case SpvDim3D:
+ break;
+ case SpvDimCube:
+ // Requires shader also
+ if(sampled == readWriteImage && arrayed == isArrayed)
+ requireSPIRVCapability(SpvCapabilityImageCubeArray);
+ break;
+ case SpvDimRect:
+ requireSPIRVCapability(sampled == sampledImage ? SpvCapabilitySampledRect : SpvCapabilityImageRect);
+ break;
+ case SpvDimBuffer:
+ requireSPIRVCapability(sampled == sampledImage ? SpvCapabilitySampledBuffer : SpvCapabilityImageBuffer);
+ break;
+ case SpvDimSubpassData:
+ requireSPIRVCapability(SpvCapabilityInputAttachment);
+ break;
+ case SpvDimTileImageDataEXT:
+ SLANG_UNIMPLEMENTED_X("OpTypeImage Capabilities for SpvDimTileImageDataEXT");
+ break;
+ }
+ if(format == SpvImageFormatUnknown && sampled == readWriteImage)
+ {
+ // TODO: It may not be necessary to have both of these
+ // depending on if we read or write
+ requireSPIRVCapability(SpvCapabilityStorageImageReadWithoutFormat);
+ requireSPIRVCapability(SpvCapabilityStorageImageWriteWithoutFormat);
+ }
+
+ //
+ // The op itself
+ //
return emitOpTypeImage(
inst,
- sampledType,
+ dropVector(sampledType),
dim,
SpvLiteralInteger::from32(depth),
SpvLiteralInteger::from32(arrayed),
@@ -1503,12 +1596,6 @@ struct SPIRVEmitContext
varInst,
SpvLiteralInteger::from32(int32_t(index))
);
- emitOpDecorateIndex(
- getSection(SpvLogicalSectionID::Annotations),
- nullptr,
- varInst,
- SpvLiteralInteger::from32(int32_t(space))
- );
break;
case LayoutResourceKind::VaryingOutput:
emitOpDecorateLocation(
@@ -3829,7 +3916,6 @@ struct SPIRVEmitContext
for(const auto spvInst : inst->getInsts())
{
const bool isLast = spvInst == inst->getLastChild();
- const SpvOp opcode = SpvOp(spvInst->getOpcodeOperandWord());
const auto parentForOpCode = [this](SpvOp opcode, SpvInstParent* defaultParent) -> SpvInstParent*{
const auto info = m_grammarInfo->opInfos.lookup(opcode);
@@ -3859,122 +3945,242 @@ struct SPIRVEmitContext
}
};
- switch (opcode)
- {
- case SpvOpCapability:
- requireSPIRVCapability((SpvCapability)getIntVal(spvInst->getOperand(1)->getOperand(0)));
- continue;
- case SpvOpExtension:
- ensureExtensionDeclaration(as<IRStringLit>(spvInst->getOperand(1)->getOperand(0))->getStringSlice());
- continue;
- default:
- break;
- }
+ const auto emitSpvAsmOperand = [&](IRSPIRVAsmOperand* operand){
+ switch(operand->getOp())
+ {
+ case kIROp_SPIRVAsmOperandEnum:
+ case kIROp_SPIRVAsmOperandLiteral:
+ {
+ const auto v = as<IRConstant>(operand->getValue());
+ SLANG_ASSERT(v);
+ if(operand->getOperandCount() >= 2)
- last = emitInstCustomOperandFunc(
- parentForOpCode(opcode, parent),
- // We want the "result instruction" to refer to the top level
- // block which assumes its value, the others are free to refer
- // to whatever, so just use the internal spv inst rep
- // TODO: This is not correct, because the instruction which is
- // assigned to result is not necessarily the last instruction
- isLast ? as<IRInst>(inst) : spvInst,
- opcode,
- [&](){
- for(const auto operand : spvInst->getSPIRVOperands())
{
- switch(operand->getOp())
- {
- case kIROp_SPIRVAsmOperandEnum:
- case kIROp_SPIRVAsmOperandLiteral:
+ const auto constantType = cast<IRType>(operand->getOperand(1));
+ SpvInst* constant;
+ switch(v->getOp())
{
- const auto v = as<IRConstant>(operand->getValue());
- SLANG_ASSERT(v);
- if(operand->getOperandCount() >= 2)
- {
- const auto constantType = cast<IRType>(operand->getOperand(1));
- SpvInst* constant;
- switch(v->getOp())
- {
- case kIROp_IntLit:
- {
- // TODO: range checking
- const auto i = cast<IRIntLit>(v)->getValue();
- constant = emitIntConstant(i, constantType);
- break;
- }
- case kIROp_StringLit:
- SLANG_UNIMPLEMENTED_X("String constants in SPIR-V emit");
- default:
- SLANG_UNREACHABLE("Unhandled case in emitSPIRVAsm");
- }
- emitOperand(constant);
- }
- else
- {
- switch(v->getOp())
- {
- case kIROp_StringLit:
- emitOperand(SpvLiteralBits::fromUnownedStringSlice(v->getStringSlice()));
- break;
- case kIROp_IntLit:
- {
- // TODO: range checking
- const auto i = cast<IRIntLit>(v)->getValue();
- emitOperand(SpvLiteralInteger::from32(uint32_t(i)));
- break;
- }
- default:
- SLANG_UNREACHABLE("Unhandled case in emitSPIRVAsm");
- }
- }
- break;
- }
- case kIROp_SPIRVAsmOperandInst:
- {
- const auto i = operand->getValue();
- emitOperand(ensureInst(i));
-
- break;
- }
- case kIROp_SPIRVAsmOperandResult:
+ case kIROp_IntLit:
{
- SLANG_ASSERT(isLast);
- emitOperand(kResultID);
+ // TODO: range checking
+ const auto i = cast<IRIntLit>(v)->getValue();
+ constant = emitIntConstant(i, constantType);
break;
}
- case kIROp_SPIRVAsmOperandId:
- {
- const auto idName = cast<IRStringLit>(operand->getValue())->getStringSlice();
- SpvWord id;
- if(!idMap.tryGetValue(idName, id))
- {
- id = freshID();
- idMap.set(idName, id);
- }
- emitOperand(id);
- break;
+ case kIROp_StringLit:
+ SLANG_UNIMPLEMENTED_X("String constants in SPIR-V emit");
+ default:
+ SLANG_UNREACHABLE("Unhandled case in emitSPIRVAsm");
}
- case kIROp_SPIRVAsmOperandBuiltinVar:
+ emitOperand(constant);
+ }
+ else
+ {
+ switch(v->getOp())
{
- const auto kind = (SpvBuiltIn)(getIntVal(operand->getOperand(0)));
- IRBuilder builder(operand);
- builder.setInsertBefore(operand);
- auto varInst = getBuiltinGlobalVar(builder.getPtrType(kIROp_PtrType, operand->getDataType(), SpvStorageClassInput), kind);
- emitOperand(varInst);
+ case kIROp_StringLit:
+ emitOperand(SpvLiteralBits::fromUnownedStringSlice(v->getStringSlice()));
break;
- }
- case kIROp_SPIRVAsmOperandGLSL450Set:
+ case kIROp_IntLit:
{
- emitOperand(getGLSL450ExtInst());
+ // TODO: range checking
+ const auto i = cast<IRIntLit>(v)->getValue();
+ emitOperand(SpvLiteralInteger::from32(uint32_t(i)));
break;
}
default:
SLANG_UNREACHABLE("Unhandled case in emitSPIRVAsm");
}
}
+ break;
}
- );
+ case kIROp_SPIRVAsmOperandInst:
+ {
+ const auto i = operand->getValue();
+ emitOperand(ensureInst(i));
+
+ break;
+ }
+ case kIROp_SPIRVAsmOperandResult:
+ {
+ SLANG_ASSERT(isLast);
+ emitOperand(kResultID);
+ break;
+ }
+ case kIROp_SPIRVAsmOperandId:
+ {
+ const auto idName = cast<IRStringLit>(operand->getValue())->getStringSlice();
+ SpvWord id;
+ if(!idMap.tryGetValue(idName, id))
+ {
+ id = freshID();
+ idMap.set(idName, id);
+ }
+ emitOperand(id);
+ break;
+ }
+ case kIROp_SPIRVAsmOperandSampledType:
+ {
+ // Make a 4 vector of the component type
+ IRBuilder builder(m_irModule);
+ const auto elementType = cast<IRType>(operand->getValue());
+ const auto sampledType = builder.getVectorType(dropVector(elementType), 4);
+ emitOperand(ensureInst(sampledType));
+ break;
+ }
+ case kIROp_SPIRVAsmOperandBuiltinVar:
+ {
+ const auto kind = (SpvBuiltIn)(getIntVal(operand->getOperand(0)));
+ IRBuilder builder(operand);
+ builder.setInsertBefore(operand);
+ auto varInst = getBuiltinGlobalVar(builder.getPtrType(kIROp_PtrType, operand->getDataType(), SpvStorageClassInput), kind);
+ emitOperand(varInst);
+ break;
+ }
+ case kIROp_SPIRVAsmOperandGLSL450Set:
+ {
+ emitOperand(getGLSL450ExtInst());
+ break;
+ }
+ default:
+ SLANG_UNREACHABLE("Unhandled case in emitSPIRVAsm");
+ }
+ };
+
+ if(spvInst->getOpcodeOperand()->getOp() == kIROp_SPIRVAsmOperandTruncate)
+ {
+ const auto getSlangType = [&](IRSPIRVAsmOperand* operand) -> IRType*{
+ switch(operand->getOp())
+ {
+ case kIROp_SPIRVAsmOperandInst:
+ return cast<IRType>(operand->getValue());
+ case kIROp_SPIRVAsmOperandSampledType:
+ {
+ // Make a 4 vector of the component type
+ IRBuilder builder(m_irModule);
+ const auto elementType = cast<IRType>(operand->getValue());
+ return builder.getVectorType(dropVector(elementType), 4);
+ }
+ case kIROp_SPIRVAsmOperandEnum:
+ case kIROp_SPIRVAsmOperandLiteral:
+ case kIROp_SPIRVAsmOperandResult:
+ case kIROp_SPIRVAsmOperandId:
+ SLANG_UNEXPECTED("truncate should have been given slang types");
+ default:
+ SLANG_UNREACHABLE("Unhandled case in emitSPIRVAsm");
+ }
+ };
+
+ SLANG_ASSERT(spvInst->getSPIRVOperands().getCount() == 4);
+ const auto toType = getSlangType(spvInst->getSPIRVOperands()[0]);
+ const auto toIdOperand = spvInst->getSPIRVOperands()[1];
+ const auto fromType = getSlangType(spvInst->getSPIRVOperands()[2]);
+ const auto fromIdOperand = spvInst->getSPIRVOperands()[3];
+
+ // The component types must be the same
+ SLANG_ASSERT(isTypeEqual(dropVector(toType), dropVector(fromType)));
+
+ // If we don't need truncation, but a different result ID is
+ // expected, then just unify them in the idMap
+ if(isTypeEqual(toType, fromType))
+ {
+ // TODO: if this is the last inst, we should just remove it
+ // and rewrite the penultimate one
+ last = emitInstCustomOperandFunc(
+ parent,
+ isLast ? as<IRInst>(inst) : spvInst,
+ SpvOpCopyObject,
+ [&](){
+ emitOperand(toType);
+ emitSpvAsmOperand(toIdOperand);
+ emitSpvAsmOperand(fromIdOperand);
+ }
+ );
+ }
+ // Otherwise, if we are truncating to a scalar, extract the first element
+ else if(!as<IRVectorType>(toType))
+ {
+ last = emitInstCustomOperandFunc(
+ parent,
+ isLast ? as<IRInst>(inst) : spvInst,
+ SpvOpCompositeExtract,
+ [&](){
+ emitOperand(toType);
+ emitSpvAsmOperand(toIdOperand);
+ emitSpvAsmOperand(fromIdOperand);
+ emitOperand(SpvLiteralInteger::from32(0));
+ }
+ );
+ }
+ // Otherwise, if we are truncating to a 1-vector from a scalar
+ else if(as<IRVectorType>(toType) && !as<IRVectorType>(fromType))
+ {
+ last = emitInstCustomOperandFunc(
+ parent,
+ isLast ? as<IRInst>(inst) : spvInst,
+ SpvOpCompositeConstruct,
+ [&](){
+ emitOperand(toType);
+ emitSpvAsmOperand(toIdOperand);
+ emitSpvAsmOperand(fromIdOperand);
+ }
+ );
+ }
+ // Otherwise, we are truncating a vector to a smaller vector
+ else
+ {
+ const auto toVector = cast<IRVectorType>(toType);
+ const auto toVectorSize = getIntVal(toVector->getElementCount());
+ const auto fromVector = cast<IRVectorType>(fromType);
+ const auto fromVectorSize = getIntVal(fromVector->getElementCount());
+ if(toVectorSize > fromVectorSize)
+ m_sink->diagnose(inst, Diagnostics::spirvInvalidTruncate);
+ last = emitInstCustomOperandFunc(
+ parent,
+ isLast ? as<IRInst>(inst) : spvInst,
+ SpvOpVectorShuffle,
+ [&](){
+ emitOperand(toType);
+ emitSpvAsmOperand(toIdOperand);
+ emitSpvAsmOperand(fromIdOperand);
+ emitOperand(emitOpUndef(parent, nullptr, fromVector));
+ for(Int32 i = 0; i < toVectorSize; ++i)
+ emitOperand(SpvLiteralInteger::from32(i));
+ }
+ );
+ }
+ }
+ else
+ {
+ const SpvOp opcode = SpvOp(spvInst->getOpcodeOperandWord());
+
+ switch (opcode)
+ {
+ case SpvOpCapability:
+ requireSPIRVCapability((SpvCapability)getIntVal(spvInst->getOperand(1)->getOperand(0)));
+ continue;
+ case SpvOpExtension:
+ ensureExtensionDeclaration(as<IRStringLit>(spvInst->getOperand(1)->getOperand(0))->getStringSlice());
+ continue;
+ default:
+ break;
+ }
+
+ last = emitInstCustomOperandFunc(
+ parentForOpCode(opcode, parent),
+ // We want the "result instruction" to refer to the top level
+ // block which assumes its value, the others are free to refer
+ // to whatever, so just use the internal spv inst rep
+ // TODO: This is not correct, because the instruction which is
+ // assigned to result is not necessarily the last instruction
+ isLast ? as<IRInst>(inst) : spvInst,
+ opcode,
+ [&](){
+ for(const auto operand : spvInst->getSPIRVOperands())
+ emitSpvAsmOperand(operand);
+ }
+ );
+ }
}
for(const auto& [name, id] : idMap)