[spirv] Add support for dual-source blending (#1251)

In HLSL, dual-source color blending is enabled only via API;
the shader needs no special marks: it only writes SV_Target0
& SV_Target1 like normal.

But in Vulkan, to enable dual-source blending, the shader need
to mark the two participating output variables with Index = 0
and Index = 1, respectively.

So we introduce a new attribute, vk::index(), to let developers
to specify the index of an output variable so dual-source
blending can be enabled.

See Vulkan spec "26.1.2. Dual-Source Blending".
This commit is contained in:
Lei Zhang 2018-04-26 16:02:12 -04:00 коммит произвёл GitHub
Родитель c82edd6c5f
Коммит 9b856626d6
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
11 изменённых файлов: 221 добавлений и 92 удалений

Просмотреть файл

@ -275,6 +275,8 @@ The namespace ``vk`` will be used for all Vulkan attributes:
- ``builtin("X")``: For specifying an entity should be translated into a certain
Vulkan builtin variable. Allowed on function parameters, function returns,
and struct fields.
- ``index(X)``: For specifying the index at a specific pixel shader output
location. Used for dual-source blending.
Only ``vk::`` attributes in the above list are supported. Other attributes will
result in warnings and be ignored by the compiler. All C++11 attributes will

Просмотреть файл

@ -879,6 +879,14 @@ def VKLocation : InheritableAttr {
let Documentation = [Undocumented];
}
def VKIndex : InheritableAttr {
let Spellings = [CXX11<"vk", "index">];
let Subjects = SubjectList<[Function, ParmVar, Field], ErrorDiag>;
let Args = [IntArgument<"Number">];
let LangOpts = [SPIRV];
let Documentation = [Undocumented];
}
def VKBinding : InheritableAttr {
let Spellings = [CXX11<"vk", "binding">];
let Subjects = SubjectList<[GlobalVar, HLSLBuffer], ErrorDiag, "ExpectedGlobalVarOrCTBuffer">;

Просмотреть файл

@ -375,6 +375,9 @@ public:
/// \brief Decorates the given target <result-id> with the given location.
void decorateLocation(uint32_t targetId, uint32_t location);
/// \brief Decorates the given target <result-id> with the given index.
void decorateIndex(uint32_t targetId, uint32_t index);
/// \brief Decorates the given target <result-id> with the given descriptor
/// set and binding number.
void decorateDSetBinding(uint32_t targetId, uint32_t setNumber,

Просмотреть файл

@ -99,11 +99,11 @@ std::string StageVar::getSemanticStr() const {
// Use what is in the source code.
// TODO: this looks like a hack to make the current tests happy.
// Should consider remove it and fix all tests.
if (semanticIndex == 0)
return semanticStr;
if (semanticInfo.index == 0)
return semanticInfo.str;
std::ostringstream ss;
ss << semanticName.str() << semanticIndex;
ss << semanticInfo.name.str() << semanticInfo.index;
return ss.str();
}
@ -181,8 +181,7 @@ bool CounterVarFields::assign(const CounterVarFields &srcFields,
return true;
}
DeclResultIdMapper::SemanticInfo
DeclResultIdMapper::getStageVarSemantic(const NamedDecl *decl) {
SemanticInfo DeclResultIdMapper::getStageVarSemantic(const NamedDecl *decl) {
for (auto *annotation : decl->getUnusualAnnotations()) {
if (auto *sema = dyn_cast<hlsl::SemanticDecl>(annotation)) {
llvm::StringRef semanticStr = sema->SemanticName;
@ -305,12 +304,12 @@ SpirvEvalInfo DeclResultIdMapper::getDeclEvalInfo(const ValueDecl *decl) {
return *info;
}
emitFatalError("found unregistered decl", decl->getLocation())
<< decl->getName();
emitNote("please file a bug report on "
"https://github.com/Microsoft/DirectXShaderCompiler/issues with "
"source code if possible",
{});
emitFatalError("found unregistered decl", decl->getLocation())
<< decl->getName();
emitNote("please file a bug report on "
"https://github.com/Microsoft/DirectXShaderCompiler/issues with "
"source code if possible",
{});
return 0;
}
@ -813,37 +812,53 @@ namespace {
/// the same location.
class LocationSet {
public:
/// Maximum number of indices supported
const static uint32_t kMaxIndex = 2;
/// Maximum number of locations supported
// Typically we won't have that many stage input or output variables.
// Using 64 should be fine here.
const static uint32_t kMaxLoc = 64;
LocationSet() : usedLocs(kMaxLoc, false), nextLoc(0) {}
LocationSet() {
for (uint32_t i = 0; i < kMaxIndex; ++i) {
usedLocs[i].resize(kMaxLoc);
nextLoc[i] = 0;
}
}
/// Uses the given location.
void useLoc(uint32_t loc) { usedLocs.set(loc); }
void useLoc(uint32_t loc, uint32_t index = 0) {
assert(index < kMaxIndex);
usedLocs[index].set(loc);
}
/// Uses the next |count| available location.
int useNextLocs(uint32_t count) {
while (usedLocs[nextLoc])
nextLoc++;
int useNextLocs(uint32_t count, uint32_t index = 0) {
assert(index < kMaxIndex);
auto &locs = usedLocs[index];
auto &next = nextLoc[index];
while (locs[next])
next++;
int toUse = nextLoc;
int toUse = next;
for (uint32_t i = 0; i < count; ++i) {
assert(!usedLocs[nextLoc]);
usedLocs.set(nextLoc++);
assert(!locs[next]);
locs.set(next++);
}
return toUse;
}
/// Returns true if the given location number is already used.
bool isLocUsed(uint32_t loc) { return usedLocs[loc]; }
bool isLocUsed(uint32_t loc, uint32_t index = 0) {
assert(index < kMaxIndex);
return usedLocs[index][loc];
}
private:
llvm::SmallBitVector usedLocs; ///< All previously used locations
uint32_t nextLoc; ///< Next available location
llvm::SmallBitVector usedLocs[kMaxIndex]; ///< All previously used locations
uint32_t nextLoc[kMaxIndex]; ///< Next available location
};
/// A class for managing resource bindings to avoid duplicate uses of the same
@ -926,17 +941,14 @@ bool DeclResultIdMapper::finalizeStageIOLocations(bool forInput) {
bool noError = true;
for (const auto &var : stageVars) {
// Skip those stage variables we are not handling for this call
if (forInput != isInputStorageClass(var))
continue;
// Skip builtins
if (var.isSpirvBuitin())
// Skip builtins & those stage variables we are not handling for this call
if (var.isSpirvBuitin() || forInput != isInputStorageClass(var))
continue;
const auto *attr = var.getLocationAttr();
const auto loc = attr->getNumber();
const auto attrLoc = attr->getLocation(); // Attr source code location
const auto idx = var.getIndexAttr() ? var.getIndexAttr()->getNumber() : 0;
if (loc >= LocationSet::kMaxLoc) {
emitError("stage %select{output|input}0 location #%1 too large",
@ -946,15 +958,17 @@ bool DeclResultIdMapper::finalizeStageIOLocations(bool forInput) {
}
// Make sure the same location is not assigned more than once
if (locSet.isLocUsed(loc)) {
if (locSet.isLocUsed(loc, idx)) {
emitError("stage %select{output|input}0 location #%1 already assigned",
attrLoc)
<< forInput << loc;
noError = false;
}
locSet.useLoc(loc);
locSet.useLoc(loc, idx);
theBuilder.decorateLocation(var.getSpirvId(), loc);
if (var.getIndexAttr())
theBuilder.decorateIndex(var.getSpirvId(), idx);
}
return noError;
@ -964,30 +978,28 @@ bool DeclResultIdMapper::finalizeStageIOLocations(bool forInput) {
LocationSet locSet;
for (const auto &var : stageVars) {
if (forInput != isInputStorageClass(var))
if (var.isSpirvBuitin() || forInput != isInputStorageClass(var))
continue;
if (!var.isSpirvBuitin()) {
if (var.getLocationAttr() != nullptr) {
// We have checked that not all of the stage variables have explicit
// location assignment.
emitError("partial explicit stage %select{output|input}0 location "
"assignment via vk::location(X) unsupported",
{})
<< forInput;
return false;
}
if (var.getLocationAttr()) {
// We have checked that not all of the stage variables have explicit
// location assignment.
emitError("partial explicit stage %select{output|input}0 location "
"assignment via vk::location(X) unsupported",
{})
<< forInput;
return false;
}
// Only SV_Target, SV_Depth, SV_DepthLessEqual, SV_DepthGreaterEqual,
// SV_StencilRef, SV_Coverage are allowed in the pixel shader.
// Arbitrary semantics are disallowed in pixel shader.
if (var.getSemantic() &&
var.getSemantic()->GetKind() == hlsl::Semantic::Kind::Target) {
theBuilder.decorateLocation(var.getSpirvId(), var.getSemanticIndex());
locSet.useLoc(var.getSemanticIndex());
} else {
vars.push_back(&var);
}
const auto &semaInfo = var.getSemanticInfo();
// We should special rules for SV_Target: the location number comes from the
// semantic string index.
if (semaInfo.isTarget()) {
theBuilder.decorateLocation(var.getSpirvId(), semaInfo.index);
locSet.useLoc(semaInfo.index);
} else {
vars.push_back(&var);
}
}
@ -1205,7 +1217,10 @@ bool DeclResultIdMapper::createStageVars(const hlsl::SigPoint *sigPoint,
// Found semantic attached directly to this Decl. This means we need to
// map this decl to a single stage variable.
const auto semanticKind = semanticToUse->semantic->GetKind();
if (!validateVKAttributes(decl))
return false;
const auto semanticKind = semanticToUse->getKind();
// Error out when the given semantic is invalid in this shader model
if (hlsl::SigPoint::GetInterpretation(semanticKind, sigPoint->GetKind(),
@ -1298,8 +1313,7 @@ bool DeclResultIdMapper::createStageVars(const hlsl::SigPoint *sigPoint,
theBuilder.getConstantUint32(arraySize));
StageVar stageVar(
sigPoint, semanticToUse->str, semanticToUse->semantic,
semanticToUse->name, semanticToUse->index, builtinAttr, typeId,
sigPoint, *semanticToUse, builtinAttr, typeId,
// For HS/DS/GS, we have already stripped the outmost arrayness on type.
typeTranslator.getLocationCount(type));
const auto name = namePrefix.str() + "." + stageVar.getSemanticStr();
@ -1311,11 +1325,12 @@ bool DeclResultIdMapper::createStageVars(const hlsl::SigPoint *sigPoint,
stageVar.setSpirvId(varId);
stageVar.setLocationAttr(decl->getAttr<VKLocationAttr>());
stageVar.setIndexAttr(decl->getAttr<VKIndexAttr>());
stageVars.push_back(stageVar);
// Emit OpDecorate* instructions to link this stage variable with the HLSL
// semantic it is created for
theBuilder.decorateHlslSemantic(varId, stageVar.getSemanticStr());
theBuilder.decorateHlslSemantic(varId, stageVar.getSemanticInfo().str);
// We have semantics attached to this decl, which means it must be a
// function/parameter/variable. All are DeclaratorDecls.
@ -1792,9 +1807,8 @@ uint32_t DeclResultIdMapper::getBuiltinVar(spv::BuiltIn builtIn) {
hlsl::DxilParamInputQual::In, shaderModel.GetKind(),
/*isPatchConstant=*/false));
StageVar stageVar(sigPoint, /*semaStr=*/"", hlsl::Semantic::GetInvalid(),
/*semaName=*/"", /*semaIndex=*/0, /*builtinAttr=*/nullptr,
type, /*locCount=*/0);
StageVar stageVar(sigPoint, /*semaInfo=*/{}, /*builtinAttr=*/nullptr, type,
/*locCount=*/0);
stageVar.setIsSpirvBuiltin();
stageVar.setSpirvId(varId);
@ -1819,7 +1833,7 @@ uint32_t DeclResultIdMapper::createSpirvStageVar(StageVar *stageVar,
using spv::BuiltIn;
const auto sigPoint = stageVar->getSigPoint();
const auto semanticKind = stageVar->getSemantic()->GetKind();
const auto semanticKind = stageVar->getSemanticInfo().getKind();
const auto sigPointKind = sigPoint->GetKind();
const uint32_t type = stageVar->getSpirvTypeId();
@ -2194,13 +2208,49 @@ uint32_t DeclResultIdMapper::createSpirvStageVar(StageVar *stageVar,
}
default:
emitError("semantic %0 unimplemented", srcLoc)
<< stageVar->getSemantic()->GetName();
<< stageVar->getSemanticStr();
break;
}
return 0;
}
bool DeclResultIdMapper::validateVKAttributes(const NamedDecl *decl) {
bool success = true;
if (const auto *idxAttr = decl->getAttr<VKIndexAttr>()) {
if (!shaderModel.IsPS()) {
emitError("vk::index only allowed in pixel shader",
idxAttr->getLocation());
success = false;
}
const auto *locAttr = decl->getAttr<VKLocationAttr>();
if (!locAttr) {
emitError("vk::index should be used together with vk::location for "
"dual-source blending",
idxAttr->getLocation());
success = false;
} else {
const auto locNumber = locAttr->getNumber();
if (locNumber != 0) {
emitError("dual-source blending should use vk::location 0",
locAttr->getLocation());
success = false;
}
}
const auto idxNumber = idxAttr->getNumber();
if (idxNumber != 0 && idxNumber != 1) {
emitError("dual-source blending only accepts 0 or 1 as vk::index",
idxAttr->getLocation());
success = false;
}
}
return success;
}
bool DeclResultIdMapper::validateVKBuiltins(const NamedDecl *decl,
const hlsl::SigPoint *sigPoint) {
bool success = true;

Просмотреть файл

@ -32,16 +32,29 @@
namespace clang {
namespace spirv {
/// A struct containing information about a particular HLSL semantic.
struct SemanticInfo {
llvm::StringRef str; ///< The original semantic string
const hlsl::Semantic *semantic; ///< The unique semantic object
llvm::StringRef name; ///< The semantic string without index
uint32_t index; ///< The semantic index
SourceLocation loc; ///< Source code location
bool isValid() const { return semantic != nullptr; }
inline hlsl::Semantic::Kind getKind() const;
/// \brief Returns true if this semantic is a SV_Target.
inline bool isTarget() const;
};
/// \brief The class containing HLSL and SPIR-V information about a Vulkan stage
/// (builtin/input/output) variable.
class StageVar {
public:
inline StageVar(const hlsl::SigPoint *sig, llvm::StringRef semaStr,
const hlsl::Semantic *sema, llvm::StringRef semaName,
uint32_t semaIndex, const VKBuiltInAttr *builtin,
uint32_t type, uint32_t locCount)
: sigPoint(sig), semanticStr(semaStr), semantic(sema),
semanticName(semaName), semanticIndex(semaIndex), builtinAttr(builtin),
inline StageVar(const hlsl::SigPoint *sig, SemanticInfo semaInfo,
const VKBuiltInAttr *builtin, uint32_t type,
uint32_t locCount)
: sigPoint(sig), semanticInfo(std::move(semaInfo)), builtinAttr(builtin),
typeId(type), valueId(0), isBuiltin(false),
storageClass(spv::StorageClass::Max), location(nullptr),
locationCount(locCount) {
@ -49,7 +62,8 @@ public:
}
const hlsl::SigPoint *getSigPoint() const { return sigPoint; }
const hlsl::Semantic *getSemantic() const { return semantic; }
const SemanticInfo &getSemanticInfo() const { return semanticInfo; }
std::string getSemanticStr() const;
uint32_t getSpirvTypeId() const { return typeId; }
@ -58,9 +72,6 @@ public:
const VKBuiltInAttr *getBuiltInAttr() const { return builtinAttr; }
std::string getSemanticStr() const;
uint32_t getSemanticIndex() const { return semanticIndex; }
bool isSpirvBuitin() const { return isBuiltin; }
void setIsSpirvBuiltin() { isBuiltin = true; }
@ -70,20 +81,17 @@ public:
const VKLocationAttr *getLocationAttr() const { return location; }
void setLocationAttr(const VKLocationAttr *loc) { location = loc; }
const VKIndexAttr *getIndexAttr() const { return indexAttr; }
void setIndexAttr(const VKIndexAttr *idx) { indexAttr = idx; }
uint32_t getLocationCount() const { return locationCount; }
private:
/// HLSL SigPoint. It uniquely identifies each set of parameters that may be
/// input or output for each entry point.
const hlsl::SigPoint *sigPoint;
/// Original HLSL semantic string in the source code.
llvm::StringRef semanticStr;
/// HLSL semantic.
const hlsl::Semantic *semantic;
/// Original HLSL semantic string (without index) in the source code.
llvm::StringRef semanticName;
/// HLSL semantic index.
uint32_t semanticIndex;
/// Information about HLSL semantic string.
SemanticInfo semanticInfo;
/// SPIR-V BuiltIn attribute.
const VKBuiltInAttr *builtinAttr;
/// SPIR-V <type-id>.
@ -96,6 +104,8 @@ private:
spv::StorageClass storageClass;
/// Location assignment if input/output variable.
const VKLocationAttr *location;
/// Index assignment if PS output variable
const VKIndexAttr *indexAttr;
/// How many locations this stage variable takes.
uint32_t locationCount;
};
@ -514,17 +524,6 @@ private:
const DeclContext *decl, uint32_t arraySize, ContextUsageKind usageKind,
llvm::StringRef typeName, llvm::StringRef varName);
/// A struct containing information about a particular HLSL semantic.
struct SemanticInfo {
llvm::StringRef str; ///< The original semantic string
const hlsl::Semantic *semantic; ///< The unique semantic object
llvm::StringRef name; ///< The semantic string without index
uint32_t index; ///< The semantic index
SourceLocation loc; ///< Source code location
bool isValid() const { return semantic != nullptr; }
};
/// Returns the given decl's HLSL semantic information.
static SemanticInfo getStageVarSemantic(const NamedDecl *decl);
@ -565,6 +564,9 @@ private:
uint32_t createSpirvStageVar(StageVar *, const NamedDecl *decl,
const llvm::StringRef name, SourceLocation);
/// Returns true if all vk:: attributes usages are valid.
bool validateVKAttributes(const NamedDecl *decl);
/// Returns true if all vk::builtin usages are valid.
bool validateVKBuiltins(const NamedDecl *decl,
const hlsl::SigPoint *sigPoint);
@ -711,6 +713,14 @@ public:
GlPerVertex glPerVertex;
};
hlsl::Semantic::Kind SemanticInfo::getKind() const {
assert(semantic);
return semantic->GetKind();
}
bool SemanticInfo::isTarget() const {
return semantic && semantic->GetKind() == hlsl::Semantic::Kind::Target;
}
void CounterIdAliasPair::assign(const CounterIdAliasPair &srcPair,
ModuleBuilder &builder,
TypeTranslator &translator) const {

Просмотреть файл

@ -859,6 +859,11 @@ void ModuleBuilder::decorateLocation(uint32_t targetId, uint32_t location) {
theModule.addDecoration(d, targetId);
}
void ModuleBuilder::decorateIndex(uint32_t targetId, uint32_t index) {
const Decoration *d = Decoration::getIndex(theContext, index);
theModule.addDecoration(d, targetId);
}
void ModuleBuilder::decorateSpecId(uint32_t targetId, uint32_t specId) {
const Decoration *d = Decoration::getSpecId(theContext, specId);
theModule.addDecoration(d, targetId);

Просмотреть файл

@ -10483,6 +10483,10 @@ void hlsl::HandleDeclAttributeForHLSL(Sema &S, Decl *D, const AttributeList &A,
declAttr = ::new (S.Context) VKLocationAttr(A.getRange(), S.Context,
ValidateAttributeIntArg(S, A), A.getAttributeSpellingListIndex());
break;
case AttributeList::AT_VKIndex:
declAttr = ::new (S.Context) VKIndexAttr(A.getRange(), S.Context,
ValidateAttributeIntArg(S, A), A.getAttributeSpellingListIndex());
break;
case AttributeList::AT_VKBinding:
declAttr = ::new (S.Context) VKBindingAttr(A.getRange(), S.Context,
ValidateAttributeIntArg(S, A), ValidateAttributeIntArg(S, A, 1),

Просмотреть файл

@ -0,0 +1,14 @@
// Run: %dxc -T ps_6_0 -E main
struct PSOut {
[[vk::location(1), vk::index(5)]] float4 a: SV_Target0;
[[vk::location(0), vk::index(1)]] float4 b: SV_Target1;
};
PSOut main() {
PSOut o = (PSOut)0;
return o;
}
// CHECK: :4:7: error: dual-source blending should use vk::location 0
// CHECK: :4:24: error: dual-source blending only accepts 0 or 1 as vk::index

Просмотреть файл

@ -0,0 +1,10 @@
// Run: %dxc -T vs_6_0 -E main
int main(
[[vk::index(1)]] int a : A
) : B {
return a;
}
// CHECK: :4:7: error: vk::index only allowed in pixel shader
// CHECK: :4:7: error: vk::index should be used together with vk::location for dual-source blending

Просмотреть файл

@ -0,0 +1,16 @@
// Run: %dxc -T ps_6_0 -E main
// CHECK: OpDecorate %out_var_SV_Target0 Location 0
// CHECK: OpDecorate %out_var_SV_Target0 Index 0
// CHECK: OpDecorate %out_var_SV_Target1 Location 0
// CHECK: OpDecorate %out_var_SV_Target1 Index 1
struct PSOut {
[[vk::location(0), vk::index(0)]] float4 a: SV_Target0;
[[vk::location(0), vk::index(1)]] float4 b: SV_Target1;
};
PSOut main() {
PSOut o = (PSOut)0;
return o;
}

Просмотреть файл

@ -482,6 +482,15 @@ TEST_F(FileTest, SemanticInstanceIDPS) {
runFileTest("semantic.instance-id.ps.hlsl");
}
TEST_F(FileTest, SemanticTargetPS) { runFileTest("semantic.target.ps.hlsl"); }
TEST_F(FileTest, SemanticTargetDualBlend) {
runFileTest("semantic.target.dual-blend.hlsl");
}
TEST_F(FileTest, SemanticTargetDualBlendError1) {
runFileTest("semantic.target.dual-blend.error1.hlsl", Expect::Failure);
}
TEST_F(FileTest, SemanticTargetDualBlendError2) {
runFileTest("semantic.target.dual-blend.error2.hlsl", Expect::Failure);
}
TEST_F(FileTest, SemanticDepthPS) { runFileTest("semantic.depth.ps.hlsl"); }
TEST_F(FileTest, SemanticDepthGreaterEqualPS) {
runFileTest("semantic.depth-greater-equal.ps.hlsl");
@ -1443,9 +1452,7 @@ TEST_F(FileTest, NonFpColMajorError) {
TEST_F(FileTest, NamespaceFunctions) {
runFileTest("namespace.functions.hlsl");
}
TEST_F(FileTest, NamespaceGlobals) {
runFileTest("namespace.globals.hlsl");
}
TEST_F(FileTest, NamespaceGlobals) { runFileTest("namespace.globals.hlsl"); }
TEST_F(FileTest, NamespaceResources) {
runFileTest("namespace.resources.hlsl");
}