[spirv] Add support for -fvk-bind-register (#1480)

format: -fvk-bind-register <type-number> <space> <binding> <set>

Also created a short alias for it: -vkbr.

This option gives the ultimate manual control of descriptor
assignment. It requires:

* All resources are annotated with :register() in the source code
* -fvk-bind-register is specified for every resource

It overrules all other mechanisms.
It cannot be used together with -fvk-{u|b|s|t}-shift.
This commit is contained in:
Lei Zhang 2018-08-03 07:36:18 -04:00 коммит произвёл GitHub
Родитель d82f291490
Коммит 05cda8da2a
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
18 изменённых файлов: 311 добавлений и 63 удалений

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

@ -144,8 +144,35 @@ constructs when possible. If that is inadequate, we then consider attaching
Descriptors
~~~~~~~~~~~
To specify which Vulkan descriptor a particular resource binds to, use the
``[[vk::binding(X[, Y])]]`` attribute.
The compiler provides multiple mechanisms to specify which Vulkan descriptor
a particular resource binds to.
In the source code, you can use the ``[[vk::binding(X[, Y])]]`` and
``[[vk::counter_binding(X)]]`` attribute. The native ``:register()`` attribute
is also respected.
On the command-line, you can use the ``-fvk-{b|s|t|u}-shift`` or
``-fvk-bind-register`` option.
If you can modify the source code, the ``[[vk::binding(X[, Y])]]`` and
``[[vk::counter_binding(X)]]`` attribute gives you find-grained control over
descriptor assignment.
If you cannot modify the source code, you can use command-line options to change
how ``:register()`` attribute is handled by the compiler. ``-fvk-bind-register``
lets you to specify the descriptor for the source at a certain register.
``-fvk-{b|s|t|u}-shift`` lets you to apply shifts to all register numbers
of a certain register type. They cannot be used together, though.
Without attribute and command-line option, ``:register(xX, spaceY)`` will be
mapped to binding ``X`` in descriptor set ``Y``. Note that register type ``x``
is ignored, so this may cause overlap.
The more specific a mechanism is, the higher precedence it has, and command-line
option has higher precedence over source code attribute.
For more details, see `HLSL register and Vulkan binding`_, `Vulkan specific
attributes`_, and `Vulkan-specific options`_.
Subpass inputs
~~~~~~~~~~~~~~
@ -2829,6 +2856,12 @@ codegen for Vulkan:
- ``-fvk-t-shift N M``, similar to ``-fvk-b-shift``, but for t-type registers.
- ``-fvk-s-shift N M``, similar to ``-fvk-b-shift``, but for s-type registers.
- ``-fvk-u-shift N M``, similar to ``-fvk-b-shift``, but for u-type registers.
- ``-fvk-bind-register xX Y N M`` (short alias: ``-vkbr``): Binds the resouce
at ``register(xX, spaceY)`` to descriptor set ``M`` and binding ``N``. This
option cannot be used together with other binding assignment options.
It requires all source code resources have ``:register()`` attribute and
all registers have corresponding Vulkan descriptors specified using this
option.
- ``-fvk-use-gl-layout``: Uses strict OpenGL ``std140``/``std430``
layout rules for resources.
- ``-fvk-use-dx-layout``: Uses DirectX layout rules for resources.

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

@ -172,6 +172,7 @@ public:
llvm::SmallVector<int32_t, 4> VkTShift; // OPT_fvk_t_shift
llvm::SmallVector<int32_t, 4> VkSShift; // OPT_fvk_s_shift
llvm::SmallVector<int32_t, 4> VkUShift; // OPT_fvk_u_shift
std::vector<std::string> VkBindRegister; // OPT_fvk_bind_register
llvm::SmallVector<llvm::StringRef, 4> SpvExtensions; // OPT_fspv_extension
llvm::StringRef SpvTargetEnv; // OPT_fspv_target_env
llvm::SmallVector<llvm::StringRef, 4> SpvOconfig; // OPT_Oconfig

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

@ -246,6 +246,9 @@ def fvk_s_shift : MultiArg<["-"], "fvk-s-shift", 2>, MetaVarName<"<shift> <space
HelpText<"Specify Vulkan binding number shift for s-type register">;
def fvk_u_shift : MultiArg<["-"], "fvk-u-shift", 2>, MetaVarName<"<shift> <space>">, Group<spirv_Group>, Flags<[CoreOption, DriverOption]>,
HelpText<"Specify Vulkan binding number shift for u-type register">;
def fvk_bind_register : MultiArg<["-"], "fvk-bind-register", 4>, MetaVarName<"<type-number> <space> <binding> <set>">, Group<spirv_Group>, Flags<[CoreOption, DriverOption]>,
HelpText<"Specify Vulkan descriptor set and binding for a specific register">;
def vkbr : MultiArg<["-"], "vkbr", 4>, Flags<[CoreOption, DriverOption]>, Alias<fvk_bind_register>;
def fvk_invert_y: Flag<["-"], "fvk-invert-y">, Group<spirv_Group>, Flags<[CoreOption, DriverOption]>,
HelpText<"Negate SV_Position.y before writing to stage output in VS/DS/GS to accommodate Vulkan's coordinate system">;
def fvk_use_dx_position_w: Flag<["-"], "fvk-use-dx-position-w">, Group<spirv_Group>, Flags<[CoreOption, DriverOption]>,

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

@ -206,6 +206,59 @@ static bool GetTargetVersionFromString(llvm::StringRef ref, unsigned *major, uns
}
}
// SPIRV Change Starts
#ifdef ENABLE_SPIRV_CODEGEN
/// Checks and collects the arguments for -fvk-{b|s|t|u}-shift into *shifts.
static bool handleVkShiftArgs(const InputArgList &args, OptSpecifier id,
const char *name,
llvm::SmallVectorImpl<int32_t> *shifts,
llvm::raw_ostream &errors) {
const auto values = args.getAllArgValues(id);
if (values.empty())
return true;
if (!args.hasArg(OPT_spirv)) {
errors << "-fvk-" << name << "-shift requires -spirv";
return false;
}
if (!args.getLastArgValue(OPT_fvk_bind_register).empty()) {
errors << "-fvk-" << name
<< "-shift cannot be used together with -fvk-bind-register";
return false;
}
shifts->clear();
bool setForAll = false;
for (const auto &val : values) {
int32_t number = 0;
if (val == "all") {
number = -1;
setForAll = true;
} else {
if (llvm::StringRef(val).getAsInteger(10, number)) {
errors << "invalid -fvk-" << name << "-shift argument: " << val;
return false;
}
if (number < 0) {
errors << "negative -fvk-" << name << "-shift argument: " << val;
return false;
}
}
shifts->push_back(number);
}
if (setForAll && shifts->size() > 2) {
errors << "setting all sets via -fvk-" << name
<< "-shift argument should be used alone";
return false;
}
return true;
};
#endif
// SPIRV Change Ends
namespace hlsl {
namespace options {
@ -511,51 +564,14 @@ int ReadDxcOpts(const OptTable *optionTable, unsigned flagsToInclude,
opts.SpvEnableReflect = Args.hasFlag(OPT_fspv_reflect, OPT_INVALID, false);
opts.VkNoWarnIgnoredFeatures = Args.hasFlag(OPT_Wno_vk_ignored_features, OPT_INVALID, false);
// Collects the arguments for -fvk-{b|s|t|u}-shift.
const auto handleVkShiftArgs =
[genSpirv, &Args, &errors](OptSpecifier id, const char *name,
llvm::SmallVectorImpl<int32_t> *shifts) {
const auto values = Args.getAllArgValues(id);
if (!genSpirv && !values.empty()) {
errors << "-fvk-" << name << "-shift requires -spirv";
return false;
}
shifts->clear();
bool setForAll = false;
for (const auto &val : values) {
int32_t number = 0;
if (val == "all") {
number = -1;
setForAll = true;
} else {
if (llvm::StringRef(val).getAsInteger(10, number)) {
errors << "invalid -fvk-" << name << "-shift argument: " << val;
return false;
}
if (number < 0) {
errors << "negative -fvk-" << name << "-shift argument: " << val;
return false;
}
}
shifts->push_back(number);
}
if (setForAll && shifts->size() > 2) {
errors << "setting all sets via -fvk-" << name
<< "-shift argument should be used alone";
return false;
}
return true;
};
if (!handleVkShiftArgs(OPT_fvk_b_shift, "b", &opts.VkBShift) ||
!handleVkShiftArgs(OPT_fvk_t_shift, "t", &opts.VkTShift) ||
!handleVkShiftArgs(OPT_fvk_s_shift, "s", &opts.VkSShift) ||
!handleVkShiftArgs(OPT_fvk_u_shift, "u", &opts.VkUShift))
if (!handleVkShiftArgs(Args, OPT_fvk_b_shift, "b", &opts.VkBShift, errors) ||
!handleVkShiftArgs(Args, OPT_fvk_t_shift, "t", &opts.VkTShift, errors) ||
!handleVkShiftArgs(Args, OPT_fvk_s_shift, "s", &opts.VkSShift, errors) ||
!handleVkShiftArgs(Args, OPT_fvk_u_shift, "u", &opts.VkUShift, errors))
return 1;
opts.VkBindRegister = Args.getAllArgValues(OPT_fvk_bind_register);
opts.VkStageIoOrder = Args.getLastArgValue(OPT_fvk_stage_io_order_EQ, "decl");
if (opts.VkStageIoOrder != "alpha" && opts.VkStageIoOrder != "decl") {
errors << "unknown Vulkan stage I/O location assignment order: "
@ -598,16 +614,16 @@ int ReadDxcOpts(const OptTable *optionTable, unsigned flagsToInclude,
!Args.getLastArgValue(OPT_fspv_extension_EQ).empty() ||
!Args.getLastArgValue(OPT_fspv_target_env_EQ).empty() ||
!Args.getLastArgValue(OPT_Oconfig).empty() ||
!Args.getLastArgValue(OPT_fvk_bind_register).empty() ||
!Args.getLastArgValue(OPT_fvk_b_shift).empty() ||
!Args.getLastArgValue(OPT_fvk_t_shift).empty() ||
!Args.getLastArgValue(OPT_fvk_s_shift).empty() ||
!Args.getLastArgValue(OPT_fvk_u_shift).empty()
) {
!Args.getLastArgValue(OPT_fvk_u_shift).empty()) {
errors << "SPIR-V CodeGen not available. "
"Please recompile with -DENABLE_SPIRV_CODEGEN=ON.";
return 1;
}
#endif
#endif // ENABLE_SPIRV_CODEGEN
// SPIRV Change Ends
opts.Args = std::move(Args);

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

@ -9,6 +9,9 @@
#ifndef LLVM_CLANG_SPIRV_EMITSPIRVOPTIONS_H
#define LLVM_CLANG_SPIRV_EMITSPIRVOPTIONS_H
#include <string>
#include <vector>
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
@ -45,6 +48,7 @@ struct EmitSPIRVOptions {
llvm::SmallVector<int32_t, 4> tShift;
llvm::SmallVector<int32_t, 4> sShift;
llvm::SmallVector<int32_t, 4> uShift;
std::vector<std::string> bindRegister;
llvm::SmallVector<llvm::StringRef, 4> allowedExtensions;
llvm::StringRef targetEnv;
spirv::LayoutRule cBufferLayoutRule;

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

@ -20,6 +20,7 @@
#include "clang/AST/HlslTypes.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "llvm/ADT/SmallBitVector.h"
#include "llvm/ADT/StringMap.h"
#include "llvm/ADT/StringSet.h"
namespace clang {
@ -436,7 +437,8 @@ SpirvEvalInfo DeclResultIdMapper::createExternVar(const VarDecl *var) {
const auto *bindingAttr = var->getAttr<VKBindingAttr>();
const auto *counterBindingAttr = var->getAttr<VKCounterBindingAttr>();
resourceVars.emplace_back(id, regAttr, bindingAttr, counterBindingAttr);
resourceVars.emplace_back(id, var->getLocation(), regAttr, bindingAttr,
counterBindingAttr);
if (const auto *inputAttachment = var->getAttr<VKInputAttachmentIndexAttr>())
theBuilder.decorateInputAttachmentIndex(id, inputAttachment->getIndex());
@ -594,9 +596,9 @@ uint32_t DeclResultIdMapper::createCTBuffer(const HLSLBufferDecl *decl) {
: spirvOptions.tBufferLayoutRule);
astDecls[varDecl].indexInCTBuffer = index++;
}
resourceVars.emplace_back(bufferVar, getResourceBinding(decl),
decl->getAttr<VKBindingAttr>(),
decl->getAttr<VKCounterBindingAttr>());
resourceVars.emplace_back(
bufferVar, decl->getLocation(), getResourceBinding(decl),
decl->getAttr<VKBindingAttr>(), decl->getAttr<VKCounterBindingAttr>());
return bufferVar;
}
@ -642,9 +644,9 @@ uint32_t DeclResultIdMapper::createCTBuffer(const VarDecl *decl) {
.setStorageClass(spv::StorageClass::Uniform)
.setLayoutRule(context->isCBuffer() ? spirvOptions.cBufferLayoutRule
: spirvOptions.tBufferLayoutRule);
resourceVars.emplace_back(bufferVar, getResourceBinding(context),
decl->getAttr<VKBindingAttr>(),
decl->getAttr<VKCounterBindingAttr>());
resourceVars.emplace_back(
bufferVar, decl->getLocation(), getResourceBinding(context),
decl->getAttr<VKBindingAttr>(), decl->getAttr<VKCounterBindingAttr>());
return bufferVar;
}
@ -679,7 +681,8 @@ void DeclResultIdMapper::createGlobalsCBuffer(const VarDecl *var) {
context, /*arraySize*/ 0, ContextUsageKind::Globals, "type.$Globals",
"$Globals");
resourceVars.emplace_back(globals, nullptr, nullptr, nullptr);
resourceVars.emplace_back(globals, SourceLocation(), nullptr, nullptr,
nullptr);
uint32_t index = 0;
for (const auto *decl : typeTranslator.collectDeclsInDeclContext(context))
@ -793,7 +796,8 @@ void DeclResultIdMapper::createCounterVar(
if (!isAlias) {
// Non-alias counter variables should be put in to resourceVars so that
// descriptors can be allocated for them.
resourceVars.emplace_back(counterId, getResourceBinding(decl),
resourceVars.emplace_back(counterId, decl->getLocation(),
getResourceBinding(decl),
decl->getAttr<VKBindingAttr>(),
decl->getAttr<VKCounterBindingAttr>(), true);
assert(declId);
@ -1096,6 +1100,59 @@ private:
uint32_t masterShift; /// Shift amount applies to all sets.
llvm::DenseMap<int32_t, int32_t> perSetShift;
};
/// A class for maintaining the mapping from source code register attributes to
/// descriptor set and number settings.
class RegisterBindingMapper {
public:
/// Takes in the relation between register attributes and descriptor settings.
/// Each relation is represented by four strings:
/// <register-type-number> <space> <descriptor-binding> <set>
bool takeInRelation(const std::vector<std::string> &relation,
std::string *error) {
assert(relation.size() % 4 == 0);
mapping.clear();
for (uint32_t i = 0; i < relation.size(); i += 4) {
int32_t spaceNo = -1, setNo = -1, bindNo = -1;
if (StringRef(relation[i + 1]).getAsInteger(10, spaceNo) || spaceNo < 0) {
*error = "space number: " + relation[i + 1];
return false;
}
if (StringRef(relation[i + 2]).getAsInteger(10, bindNo) || bindNo < 0) {
*error = "binding number: " + relation[i + 2];
return false;
}
if (StringRef(relation[i + 3]).getAsInteger(10, setNo) || setNo < 0) {
*error = "set number: " + relation[i + 3];
return false;
}
mapping[relation[i + 1] + relation[i]] = std::make_pair(setNo, bindNo);
}
return true;
}
/// Returns true and set the correct set and binding number if we can find a
/// descriptor setting for the given register. False otherwise.
bool getSetBinding(const hlsl::RegisterAssignment *regAttr, int *setNo,
int *bindNo) const {
std::ostringstream iss;
iss << regAttr->RegisterSpace << regAttr->RegisterType
<< regAttr->RegisterNumber;
auto found = mapping.find(iss.str());
if (found != mapping.end()) {
*setNo = found->second.first;
*bindNo = found->second.second;
return true;
}
return false;
}
private:
llvm::StringMap<std::pair<int, int>> mapping;
};
} // namespace
bool DeclResultIdMapper::decorateResourceBindings() {
@ -1117,6 +1174,43 @@ bool DeclResultIdMapper::decorateResourceBindings() {
// - m2
// - m3, mX * c2
// Special handling of -fvk-bind-register, which requires
// * All resources are annoated with :register() in the source code
// * -fvk-bind-register is specified for every resource
if (!spirvOptions.bindRegister.empty()) {
RegisterBindingMapper bindingMapper;
std::string error;
if (!bindingMapper.takeInRelation(spirvOptions.bindRegister, &error)) {
emitError("invalid -fvk-bind-register %0", {}) << error;
return false;
}
for (const auto &var : resourceVars)
if (const auto *regAttr = var.getRegister()) {
if (var.isCounter()) {
emitError("-fvk-bind-register for RW/Append/Consume StructuredBuffer "
"umimplemented",
var.getSourceLocation());
} else {
int setNo = 0, bindNo = 0;
if (!bindingMapper.getSetBinding(regAttr, &setNo, &bindNo)) {
emitError("missing -fvk-bind-register for resource",
var.getSourceLocation());
return false;
}
theBuilder.decorateDSetBinding(var.getSpirvId(), setNo, bindNo);
}
} else {
emitError(
"-fvk-bind-register requires register annotations on all resources",
var.getSourceLocation());
return false;
}
return true;
}
BindingSet bindingSet;
// Decorates the given varId of the given category with set number

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

@ -112,13 +112,14 @@ private:
class ResourceVar {
public:
ResourceVar(uint32_t id, const hlsl::RegisterAssignment *r,
const VKBindingAttr *b, const VKCounterBindingAttr *cb,
bool counter = false)
: varId(id), reg(r), binding(b), counterBinding(cb),
ResourceVar(uint32_t id, SourceLocation loc,
const hlsl::RegisterAssignment *r, const VKBindingAttr *b,
const VKCounterBindingAttr *cb, bool counter = false)
: varId(id), srcLoc(loc), reg(r), binding(b), counterBinding(cb),
isCounterVar(counter) {}
uint32_t getSpirvId() const { return varId; }
SourceLocation getSourceLocation() const { return srcLoc; }
const hlsl::RegisterAssignment *getRegister() const { return reg; }
const VKBindingAttr *getBinding() const { return binding; }
bool isCounter() const { return isCounterVar; }
@ -128,6 +129,7 @@ public:
private:
uint32_t varId; ///< <result-id>
SourceLocation srcLoc; ///< Source location
const hlsl::RegisterAssignment *reg; ///< HLSL register assignment
const VKBindingAttr *binding; ///< Vulkan binding assignment
const VKCounterBindingAttr *counterBinding; ///< Vulkan counter binding

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

@ -0,0 +1,10 @@
// Run: %dxc -T ps_6_0 -E main -fvk-bind-register u10 2 10 1
struct S { float4 val; };
RWStructuredBuffer<S> MyBuffer : register(u10, space2);
float4 main() : SV_Target {
return MyBuffer[0].val;
}
// CHECK: :4:24: error: -fvk-bind-register for RW/Append/Consume StructuredBuffer umimplemented

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

@ -0,0 +1,13 @@
// Run: %dxc -T ps_6_0 -E main -fvk-bind-register t5 0 1 2 -vkbr s3 1 3 4
// CHECK: OpDecorate %MyTexture DescriptorSet 2
// CHECK: OpDecorate %MyTexture Binding 1
Texture2D MyTexture : register(t5);
// CHECK: OpDecorate %MySampler DescriptorSet 4
// CHECK: OpDecorate %MySampler Binding 3
SamplerState MySampler : register(s3, space1);
float4 main() : SV_Target {
return MyTexture.Sample(MySampler, float2(0.1, 0.2));
}

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

@ -0,0 +1,10 @@
// Run: %dxc -T ps_6_0 -E main -fvk-bind-register s10 0 -10 0
Texture2D MyTexture;
SamplerState MySampler;
float4 main() : SV_Target {
return MyTexture.Sample(MySampler, float2(0.1, 0.2));
}
// CHECK: error: invalid -fvk-bind-register binding number: -10

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

@ -0,0 +1,10 @@
// Run: %dxc -T ps_6_0 -E main -fvk-bind-register s10 0 10 ff
Texture2D MyTexture;
SamplerState MySampler;
float4 main() : SV_Target {
return MyTexture.Sample(MySampler, float2(0.1, 0.2));
}
// CHECK: error: invalid -fvk-bind-register set number: ff

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

@ -0,0 +1,10 @@
// Run: %dxc -T ps_6_0 -E main -fvk-bind-register s10 5t 10 1
Texture2D MyTexture;
SamplerState MySampler;
float4 main() : SV_Target {
return MyTexture.Sample(MySampler, float2(0.1, 0.2));
}
// CHECK: error: invalid -fvk-bind-register space number: 5t

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

@ -0,0 +1,10 @@
// Run: %dxc -T ps_6_0 -E main -fvk-bind-register s10 0 10 0
Texture2D MyTexture;
SamplerState MySampler;
float4 main() : SV_Target {
return MyTexture.Sample(MySampler, float2(0.1, 0.2));
}
// CHECK: :3:11: error: -fvk-bind-register requires register annotations on all resources

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

@ -0,0 +1,10 @@
// Run: %dxc -T ps_6_0 -E main -fvk-bind-register t5 1 10 1
Texture2D MyTexture : register(t5, space1);
SamplerState MySampler : register(s0);
float4 main() : SV_Target {
return MyTexture.Sample(MySampler, float2(0.1, 0.2));
}
// CHECK: :4:14: error: missing -fvk-bind-register for resource

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

@ -525,6 +525,7 @@ public:
spirvOpts.tShift = opts.VkTShift;
spirvOpts.sShift = opts.VkSShift;
spirvOpts.uShift = opts.VkUShift;
spirvOpts.bindRegister = opts.VkBindRegister;
spirvOpts.allowedExtensions = opts.SpvExtensions;
spirvOpts.targetEnv = opts.SpvTargetEnv;
spirvOpts.enable16BitTypes = opts.Enable16BitTypes;

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

@ -1409,12 +1409,33 @@ TEST_F(FileTest, VulkanRegisterBinding) {
TEST_F(FileTest, VulkanRegisterBindingShift) {
// Resource binding from :register() with shift specified via
// command line option
runFileTest("vk.binding.cl.hlsl");
runFileTest("vk.binding.cl.shift.hlsl");
}
TEST_F(FileTest, VulkanRegisterBindingShiftAllSets) {
// Resource binding from :register() with shift specified for all sets via
// command line option
runFileTest("vk.binding.cl.all-sets.hlsl");
runFileTest("vk.binding.cl.shift.all-sets.hlsl");
}
TEST_F(FileTest, VulkanRegisterBinding1to1Mapping) {
runFileTest("vk.binding.cl.register.hlsl");
}
TEST_F(FileTest, VulkanRegisterBinding1to1MappingInvalidSpaceNo) {
runFileTest("vk.binding.cl.register.invalid-space.hlsl", Expect::Failure);
}
TEST_F(FileTest, VulkanRegisterBinding1to1MappingInvalidSetNo) {
runFileTest("vk.binding.cl.register.invalid-set.hlsl", Expect::Failure);
}
TEST_F(FileTest, VulkanRegisterBinding1to1MappingInvalidBindNo) {
runFileTest("vk.binding.cl.register.invalid-bind.hlsl", Expect::Failure);
}
TEST_F(FileTest, VulkanRegisterBinding1to1MappingMissingAttr) {
runFileTest("vk.binding.cl.register.missing-attr.hlsl", Expect::Failure);
}
TEST_F(FileTest, VulkanRegisterBinding1to1MappingMissingCLOption) {
runFileTest("vk.binding.cl.register.missing-cl.hlsl", Expect::Failure);
}
TEST_F(FileTest, VulkanRegisterBinding1to1MappingAssociatedCounter) {
runFileTest("vk.binding.cl.register.counter.hlsl", Expect::Failure);
}
TEST_F(FileTest, VulkanStructuredBufferCounter) {
// [[vk::counter_binding()]] for RWStructuredBuffer, AppendStructuredBuffer,