Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/ReleaseNotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ line upon naming the release. Refer to previous for appropriate section names.
- Fix a crash generating DXIL from sources containing a dynamic resource heap
access that was discarded. Identified during development of SPIR-V support for
[descriptor heaps](https://github.com/microsoft/DirectXShaderCompiler/pull/8517#discussion_r3752113078).
- SPIR-V: Fixed matrix ordering for vertex input attributes of square matrices.

#### HLSL Language

Expand Down
128 changes: 117 additions & 11 deletions tools/clang/lib/SPIRV/SpirvEmitter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,82 @@ const StructType *lowerStructType(const SpirvCodeGenOptions &spirvOptions,
return output;
}

bool hasAnySemantic(const DeclaratorDecl *decl) {
if (!decl)
return false;

for (auto *annotation : decl->getUnusualAnnotations())
if (isa<hlsl::SemanticDecl>(annotation))
return true;

return false;
}

// Walks expression base chain and returns true if it is rooted in an input
// parameter or stage variable.
bool isVertexInputExpr(const Expr *expr) {
if (!expr)
return false;

expr = expr->IgnoreParenCasts();
bool hasSemanticField = false;

while (expr) {
if (const auto *member = dyn_cast<MemberExpr>(expr)) {
if (const auto *decl = dyn_cast<DeclaratorDecl>(member->getMemberDecl()))
hasSemanticField |= hasAnySemantic(decl);
expr = member->getBase()->IgnoreParenCasts();
continue;
}

if (const auto *subscript = dyn_cast<ArraySubscriptExpr>(expr)) {
expr = subscript->getBase()->IgnoreParenCasts();
continue;
}

if (const auto *vecElem = dyn_cast<HLSLVectorElementExpr>(expr)) {
expr = vecElem->getBase()->IgnoreParenCasts();
continue;
}

if (const auto *declRef = dyn_cast<DeclRefExpr>(expr)) {
if (const auto *parm = dyn_cast<ParmVarDecl>(declRef->getDecl()))
return canActAsInParmVar(parm) &&
(hasSemanticField || hasAnySemantic(parm));
Comment on lines +620 to +623
return false;
}

break;
}

return false;
}

// Walks instruction provenance to detect stage input origin.
bool originatesFromInputStorage(SpirvInstruction *inst) {
if (!inst)
return false;

if (inst->getStorageClass() == spv::StorageClass::Input)
return true;

switch (inst->getKind()) {
case SpirvInstruction::IK_Load:
return originatesFromInputStorage(cast<SpirvLoad>(inst)->getPointer());
case SpirvInstruction::IK_AccessChain:
return originatesFromInputStorage(cast<SpirvAccessChain>(inst)->getBase());
case SpirvInstruction::IK_CopyObject:
return originatesFromInputStorage(cast<SpirvCopyObject>(inst)->getPointer());
case SpirvInstruction::IK_CompositeExtract:
return originatesFromInputStorage(
cast<SpirvCompositeExtract>(inst)->getComposite());
case SpirvInstruction::IK_UnaryOp:
return originatesFromInputStorage(cast<SpirvUnaryOp>(inst)->getOperand());
default:
return false;
Comment thread
LukasBanana marked this conversation as resolved.
}
}

} // namespace

SpirvEmitter::SpirvEmitter(CompilerInstance &ci)
Expand Down Expand Up @@ -12347,36 +12423,66 @@ SpirvInstruction *SpirvEmitter::processIntrinsicMul(const CallExpr *callExpr) {
// mul(vector, matrix)
{
QualType vecElemType = {}, matElemType = {};
uint32_t elemCount = 0, numRows = 0;
uint32_t elemCount = 0, numRows = 0, numCols = 0;
if (isVectorType(arg0Type, &vecElemType, &elemCount) &&
isMxNMatrix(arg1Type, &matElemType, &numRows)) {
isMxNMatrix(arg1Type, &matElemType, &numRows, &numCols)) {
assert(elemCount == numRows);

if (vecElemType->isFloatingType() && matElemType->isFloatingType())
if (vecElemType->isFloatingType() && matElemType->isFloatingType()) {
const bool isSquare = (numRows == numCols);
const bool fromVertexInput =
isVertexInputExpr(arg1) || originatesFromInputStorage(arg1Id);
Comment on lines +12433 to +12434

// Workaround: if matrix originates from vertex input and is square,
// emit OpVectorTimesMatrix without operand swapping.
// Otherwise, row_major cannot be emulated here because
// SPIR-V vertex attributes cannot be decorated with row_major layout.
if (isSquare && fromVertexInput)
return spvBuilder.createBinaryOp(spv::Op::OpVectorTimesMatrix,
returnType, arg0Id, arg1Id, loc,
range);
Comment thread
LukasBanana marked this conversation as resolved.

// Default path (existing behavior): swap operands and emit MatrixTimesVector.
return spvBuilder.createBinaryOp(spv::Op::OpMatrixTimesVector,
returnType, arg1Id, arg0Id, loc,
range);
else
} else {
return processNonFpVectorTimesMatrix(arg0Type, arg0Id, arg1Type, arg1Id,
callExpr->getExprLoc(), nullptr,
range);
}
}
}

// mul(matrix, vector)
{
QualType vecElemType = {}, matElemType = {};
uint32_t elemCount = 0, numCols = 0;
if (isMxNMatrix(arg0Type, &matElemType, nullptr, &numCols) &&
uint32_t elemCount = 0, numRows = 0, numCols = 0;
if (isMxNMatrix(arg0Type, &matElemType, &numRows, &numCols) &&
isVectorType(arg1Type, &vecElemType, &elemCount)) {
assert(elemCount == numCols);
if (vecElemType->isFloatingType() && matElemType->isFloatingType())
return spvBuilder.createBinaryOp(spv::Op::OpVectorTimesMatrix,
returnType, arg1Id, arg0Id, loc,
range);
else

if (vecElemType->isFloatingType() && matElemType->isFloatingType()) {
const bool isSquare = (numRows == numCols);
const bool fromVertexInput =
isVertexInputExpr(arg0) || originatesFromInputStorage(arg0Id);
Comment thread
LukasBanana marked this conversation as resolved.

// Workaround: if matrix originates from vertex input and is square,
// emit OpMatrixTimesVector without operand swapping.
Comment thread
LukasBanana marked this conversation as resolved.
// Otherwise, row_major cannot be emulated here because
// SPIR-V vertex attributes cannot be decorated with row_major layout.
if (isSquare && fromVertexInput)
return spvBuilder.createBinaryOp(spv::Op::OpMatrixTimesVector,
returnType, arg0Id, arg1Id, loc,
range);

// Default path (existing behavior): swap operands and emit VectorTimesMatrix.
return spvBuilder.createBinaryOp(spv::Op::OpVectorTimesMatrix, returnType,
arg1Id, arg0Id, loc, range);
} else {
return processNonFpMatrixTimesVector(arg0Type, arg0Id, arg1Type, arg1Id,
callExpr->getExprLoc(), range);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// RUN: %dxc -T vs_6_0 -E main -fspv-target-env=vulkan1.3 %s -spirv | FileCheck %s

struct VSIn {
float2x2 rotationA : ROTATIONA;
float2 positionA : POSITIONA;
float2x2 rotationB : ROTATIONB;
float2 positionB : POSITIONB;
};

cbuffer SceneInput : register(b0) {
float4x4 projection;
};

// Matrix types from vertex input attributes must not be emitted as implicitly transposed in SPIR-V,
// because they cannot be decorated with the row_major/column_major type qualifiers.
// The mul() intrinsic's operands must therefore be emitted as-is,
// which results in a double flip of matrix ordering: (1) in the vertex input attribute and (2) in the mul() intrinsic.
// These cancel each other out and result in the same matrix transformation between DXIL and SPIR-V.
// The second mul() intrinsic in this test must be emitted as before,
// with flipped operands and OpVectorTimesMatrix instruction.
float4 main(VSIn input) : SV_Position {
// CHECK: [[rotationA:%[0-9]+]] = OpLoad %mat2v2float {{%[a-zA-Z0-9_]+}}
// CHECK-NEXT: [[positionA:%[0-9]+]] = OpLoad %v2float {{%[a-zA-Z0-9_]+}}
// CHECK: [[rotationB:%[0-9]+]] = OpLoad %mat2v2float {{%[a-zA-Z0-9_]+}}
// CHECK-NEXT: [[positionB:%[0-9]+]] = OpLoad %v2float {{%[a-zA-Z0-9_]+}}
// CHECK: [[mulA:%[0-9]+]] = OpMatrixTimesVector %v2float [[rotationA]] [[positionA]]
float4 worldSpacePositionA = float4(mul(input.rotationA, input.positionA) + input.positionA, 0.0, 1.0);

// CHECK: [[worldSpacePositionA:%[0-9]+]] = OpCompositeConstruct %v4float
// CHECK: [[mulB:%[0-9]+]] = OpVectorTimesMatrix %v2float [[positionB]] [[rotationB]]
float4 worldSpacePositionB = float4(mul(input.positionB, input.rotationB) + input.positionB, 0.0, 1.0);

// CHECK: [[worldSpacePositionB:%[0-9]+]] = OpCompositeConstruct %v4float
// CHECK: [[projection:%[0-9]+]] = OpLoad %mat4v4float {{%[a-zA-Z0-9_]+}}
// CHECK: [[projectionMulA:%[0-9]+]] = OpVectorTimesMatrix %v4float [[worldSpacePositionA]] [[projection]]
// CHECK: [[projectionMulB:%[0-9]+]] = OpVectorTimesMatrix %v4float [[worldSpacePositionB]] [[projection]]
return mul(projection, worldSpacePositionA) + mul(projection, worldSpacePositionB);
}
2 changes: 1 addition & 1 deletion utils/lit/lit/TestingConfig.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def _find_git_windows_unix_tools(tools_needed):
return lit.util.to_string(candidate_path)
except:
continue
raise(f"fail to find {tools_needed} which are required for DXC tests")
raise RuntimeError(f"failed to find {tools_needed} which are required for DXC tests")

class TestingConfig:
""""
Expand Down