Merge pull request #1256 from KhronosGroup/fix-1252

HLSL: Add a resource remapping API similar to MSL.
This commit is contained in:
Hans-Kristian Arntzen 2020-01-09 15:35:26 +01:00 committed by GitHub
commit 74107a04d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 375 additions and 70 deletions

View File

@ -312,7 +312,7 @@ if (SPIRV_CROSS_STATIC)
endif()
set(spirv-cross-abi-major 0)
set(spirv-cross-abi-minor 21)
set(spirv-cross-abi-minor 22)
set(spirv-cross-abi-patch 0)
if (SPIRV_CROSS_SHARED)
@ -491,6 +491,10 @@ if (SPIRV_CROSS_CLI)
target_link_libraries(spirv-cross-msl-resource-binding-test spirv-cross-c)
set_target_properties(spirv-cross-msl-resource-binding-test PROPERTIES LINK_FLAGS "${spirv-cross-link-flags}")
add_executable(spirv-cross-hlsl-resource-binding-test tests-other/hlsl_resource_bindings.cpp)
target_link_libraries(spirv-cross-hlsl-resource-binding-test spirv-cross-c)
set_target_properties(spirv-cross-hlsl-resource-binding-test PROPERTIES LINK_FLAGS "${spirv-cross-link-flags}")
add_executable(spirv-cross-msl-ycbcr-conversion-test tests-other/msl_ycbcr_conversion_test.cpp)
target_link_libraries(spirv-cross-msl-ycbcr-conversion-test spirv-cross-c)
set_target_properties(spirv-cross-msl-ycbcr-conversion-test PROPERTIES LINK_FLAGS "${spirv-cross-link-flags}")
@ -513,6 +517,8 @@ if (SPIRV_CROSS_CLI)
COMMAND $<TARGET_FILE:spirv-cross-msl-constexpr-test> ${CMAKE_CURRENT_SOURCE_DIR}/tests-other/msl_constexpr_test.spv)
add_test(NAME spirv-cross-msl-resource-binding-test
COMMAND $<TARGET_FILE:spirv-cross-msl-resource-binding-test> ${CMAKE_CURRENT_SOURCE_DIR}/tests-other/msl_resource_binding.spv)
add_test(NAME spirv-cross-hlsl-resource-binding-test
COMMAND $<TARGET_FILE:spirv-cross-hlsl-resource-binding-test> ${CMAKE_CURRENT_SOURCE_DIR}/tests-other/hlsl_resource_binding.spv)
add_test(NAME spirv-cross-msl-ycbcr-conversion-test
COMMAND $<TARGET_FILE:spirv-cross-msl-ycbcr-conversion-test> ${CMAKE_CURRENT_SOURCE_DIR}/tests-other/msl_ycbcr_conversion_test.spv)
add_test(NAME spirv-cross-msl-ycbcr-conversion-test-2

View File

@ -1694,6 +1694,62 @@ static inline bool opcode_is_sign_invariant(spv::Op opcode)
return false;
}
}
struct SetBindingPair
{
uint32_t desc_set;
uint32_t binding;
inline bool operator==(const SetBindingPair &other) const
{
return desc_set == other.desc_set && binding == other.binding;
}
inline bool operator<(const SetBindingPair &other) const
{
return desc_set < other.desc_set || (desc_set == other.desc_set && binding < other.binding);
}
};
struct StageSetBinding
{
spv::ExecutionModel model;
uint32_t desc_set;
uint32_t binding;
inline bool operator==(const StageSetBinding &other) const
{
return model == other.model && desc_set == other.desc_set && binding == other.binding;
}
};
struct InternalHasher
{
inline size_t operator()(const SetBindingPair &value) const
{
// Quality of hash doesn't really matter here.
auto hash_set = std::hash<uint32_t>()(value.desc_set);
auto hash_binding = std::hash<uint32_t>()(value.binding);
return (hash_set * 0x10001b31) ^ hash_binding;
}
inline size_t operator()(const StageSetBinding &value) const
{
// Quality of hash doesn't really matter here.
auto hash_model = std::hash<uint32_t>()(value.model);
auto hash_set = std::hash<uint32_t>()(value.desc_set);
auto tmp_hash = (hash_model * 0x10001b31) ^ hash_set;
return (tmp_hash * 0x10001b31) ^ value.binding;
}
};
// Special constant used in a {MSL,HLSL}ResourceBinding desc_set
// element to indicate the bindings for the push constants.
static const uint32_t ResourceBindingPushConstantDescriptorSet = ~(0u);
// Special constant used in a {MSL,HLSL}ResourceBinding binding
// element to indicate the bindings for the push constants.
static const uint32_t ResourceBindingPushConstantBinding = 0;
} // namespace SPIRV_CROSS_NAMESPACE
namespace std

View File

@ -789,6 +789,60 @@ spvc_result spvc_compiler_hlsl_set_resource_binding_flags(spvc_compiler compiler
#endif
}
spvc_result spvc_compiler_hlsl_add_resource_binding(spvc_compiler compiler,
const spvc_hlsl_resource_binding *binding)
{
#if SPIRV_CROSS_C_API_HLSL
if (compiler->backend != SPVC_BACKEND_HLSL)
{
compiler->context->report_error("HLSL function used on a non-HLSL backend.");
return SPVC_ERROR_INVALID_ARGUMENT;
}
auto &hlsl = *static_cast<CompilerHLSL *>(compiler->compiler.get());
HLSLResourceBinding bind;
bind.binding = binding->binding;
bind.desc_set = binding->desc_set;
bind.stage = static_cast<spv::ExecutionModel>(binding->stage);
bind.cbv.register_binding = binding->cbv.register_binding;
bind.cbv.register_space = binding->cbv.register_space;
bind.uav.register_binding = binding->uav.register_binding;
bind.uav.register_space = binding->uav.register_space;
bind.srv.register_binding = binding->srv.register_binding;
bind.srv.register_space = binding->srv.register_space;
bind.sampler.register_binding = binding->sampler.register_binding;
bind.sampler.register_space = binding->sampler.register_space;
hlsl.add_hlsl_resource_binding(bind);
return SPVC_SUCCESS;
#else
(void)binding;
compiler->context->report_error("HLSL function used on a non-HLSL backend.");
return SPVC_ERROR_INVALID_ARGUMENT;
#endif
}
spvc_bool spvc_compiler_hlsl_is_resource_used(spvc_compiler compiler, SpvExecutionModel model, unsigned set,
unsigned binding)
{
#if SPIRV_CROSS_C_API_HLSL
if (compiler->backend != SPVC_BACKEND_HLSL)
{
compiler->context->report_error("HLSL function used on a non-HLSL backend.");
return SPVC_FALSE;
}
auto &hlsl = *static_cast<CompilerHLSL *>(compiler->compiler.get());
return hlsl.is_hlsl_resource_binding_used(static_cast<spv::ExecutionModel>(model), set, binding) ? SPVC_TRUE :
SPVC_FALSE;
#else
(void)model;
(void)set;
(void)binding;
compiler->context->report_error("HLSL function used on a non-HLSL backend.");
return SPVC_FALSE;
#endif
}
spvc_bool spvc_compiler_msl_is_rasterization_disabled(spvc_compiler compiler)
{
#if SPIRV_CROSS_C_API_MSL
@ -2157,6 +2211,26 @@ void spvc_msl_resource_binding_init(spvc_msl_resource_binding *binding)
#endif
}
void spvc_hlsl_resource_binding_init(spvc_hlsl_resource_binding *binding)
{
#if SPIRV_CROSS_C_API_HLSL
HLSLResourceBinding binding_default;
binding->desc_set = binding_default.desc_set;
binding->binding = binding_default.binding;
binding->cbv.register_binding = binding_default.cbv.register_binding;
binding->cbv.register_space = binding_default.cbv.register_space;
binding->srv.register_binding = binding_default.srv.register_binding;
binding->srv.register_space = binding_default.srv.register_space;
binding->uav.register_binding = binding_default.uav.register_binding;
binding->uav.register_space = binding_default.uav.register_space;
binding->sampler.register_binding = binding_default.sampler.register_binding;
binding->sampler.register_space = binding_default.sampler.register_space;
binding->stage = static_cast<SpvExecutionModel>(binding_default.stage);
#else
memset(binding, 0, sizeof(*binding));
#endif
}
void spvc_msl_constexpr_sampler_init(spvc_msl_constexpr_sampler *sampler)
{
#if SPIRV_CROSS_C_API_MSL

View File

@ -33,7 +33,7 @@ extern "C" {
/* Bumped if ABI or API breaks backwards compatibility. */
#define SPVC_C_API_VERSION_MAJOR 0
/* Bumped if APIs or enumerations are added in a backwards compatible way. */
#define SPVC_C_API_VERSION_MINOR 21
#define SPVC_C_API_VERSION_MINOR 22
/* Bumped if internal implementation details change. */
#define SPVC_C_API_VERSION_PATCH 0
@ -469,6 +469,7 @@ SPVC_PUBLIC_API void spvc_msl_sampler_ycbcr_conversion_init(spvc_msl_sampler_ycb
/* Maps to C++ API. */
typedef enum spvc_hlsl_binding_flag_bits
{
SPVC_HLSL_BINDING_AUTO_NONE_BIT = 0,
SPVC_HLSL_BINDING_AUTO_PUSH_CONSTANT_BIT = 1 << 0,
SPVC_HLSL_BINDING_AUTO_CBV_BIT = 1 << 1,
SPVC_HLSL_BINDING_AUTO_SRV_BIT = 1 << 2,
@ -478,6 +479,31 @@ typedef enum spvc_hlsl_binding_flag_bits
} spvc_hlsl_binding_flag_bits;
typedef unsigned spvc_hlsl_binding_flags;
#define SPVC_HLSL_PUSH_CONSTANT_DESC_SET (~(0u))
#define SPVC_HLSL_PUSH_CONSTANT_BINDING (0)
/* Maps to C++ API. */
typedef struct spvc_hlsl_resource_binding_mapping
{
unsigned register_space;
unsigned register_binding;
} spvc_hlsl_resource_binding_mapping;
typedef struct spvc_hlsl_resource_binding
{
SpvExecutionModel stage;
unsigned desc_set;
unsigned binding;
spvc_hlsl_resource_binding_mapping cbv, uav, srv, sampler;
} spvc_hlsl_resource_binding;
/*
* Initializes the resource binding struct.
* The defaults are non-zero.
*/
SPVC_PUBLIC_API void spvc_hlsl_resource_binding_init(spvc_hlsl_resource_binding *binding);
/* Maps to the various spirv_cross::Compiler*::Option structures. See C++ API for defaults and details. */
typedef enum spvc_compiler_option
{
@ -621,6 +647,13 @@ SPVC_PUBLIC_API spvc_variable_id spvc_compiler_hlsl_remap_num_workgroups_builtin
SPVC_PUBLIC_API spvc_result spvc_compiler_hlsl_set_resource_binding_flags(spvc_compiler compiler,
spvc_hlsl_binding_flags flags);
SPVC_PUBLIC_API spvc_result spvc_compiler_hlsl_add_resource_binding(spvc_compiler compiler,
const spvc_hlsl_resource_binding *binding);
SPVC_PUBLIC_API spvc_bool spvc_compiler_hlsl_is_resource_used(spvc_compiler compiler,
SpvExecutionModel model,
unsigned set,
unsigned binding);
/*
* MSL specifics.
* Maps to C++ API.

View File

@ -2934,16 +2934,15 @@ void CompilerHLSL::emit_texture_op(const Instruction &i)
string CompilerHLSL::to_resource_binding(const SPIRVariable &var)
{
// TODO: Basic implementation, might need special consideration for RW/RO structured buffers,
// RW/RO images, and so on.
const auto &type = get<SPIRType>(var.basetype);
if (!has_decoration(var.self, DecorationBinding))
// We can remap push constant blocks, even if they don't have any binding decoration.
if (type.storage != StorageClassPushConstant && !has_decoration(var.self, DecorationBinding))
return "";
const auto &type = get<SPIRType>(var.basetype);
char space = '\0';
HLSLBindingFlags resource_flags = 0;
HLSLBindingFlagBits resource_flags = HLSL_BINDING_AUTO_NONE_BIT;
switch (type.basetype)
{
@ -3011,8 +3010,16 @@ string CompilerHLSL::to_resource_binding(const SPIRVariable &var)
if (!space)
return "";
return to_resource_register(resource_flags, space, get_decoration(var.self, DecorationBinding),
get_decoration(var.self, DecorationDescriptorSet));
uint32_t desc_set =
resource_flags == HLSL_BINDING_AUTO_PUSH_CONSTANT_BIT ? ResourceBindingPushConstantDescriptorSet : 0u;
uint32_t binding = resource_flags == HLSL_BINDING_AUTO_PUSH_CONSTANT_BIT ? ResourceBindingPushConstantBinding : 0u;
if (has_decoration(var.self, DecorationBinding))
binding = get_decoration(var.self, DecorationBinding);
if (has_decoration(var.self, DecorationDescriptorSet))
desc_set = get_decoration(var.self, DecorationDescriptorSet);
return to_resource_register(resource_flags, space, binding, desc_set);
}
string CompilerHLSL::to_resource_binding_sampler(const SPIRVariable &var)
@ -3025,10 +3032,54 @@ string CompilerHLSL::to_resource_binding_sampler(const SPIRVariable &var)
get_decoration(var.self, DecorationDescriptorSet));
}
string CompilerHLSL::to_resource_register(uint32_t flags, char space, uint32_t binding, uint32_t space_set)
void CompilerHLSL::remap_hlsl_resource_binding(HLSLBindingFlagBits type, uint32_t &desc_set, uint32_t &binding)
{
if ((flags & resource_binding_flags) == 0)
auto itr = resource_bindings.find({ get_execution_model(), desc_set, binding });
if (itr != end(resource_bindings))
{
auto &remap = itr->second;
remap.second = true;
switch (type)
{
case HLSL_BINDING_AUTO_PUSH_CONSTANT_BIT:
case HLSL_BINDING_AUTO_CBV_BIT:
desc_set = remap.first.cbv.register_space;
binding = remap.first.cbv.register_binding;
break;
case HLSL_BINDING_AUTO_SRV_BIT:
desc_set = remap.first.srv.register_space;
binding = remap.first.srv.register_binding;
break;
case HLSL_BINDING_AUTO_SAMPLER_BIT:
desc_set = remap.first.sampler.register_space;
binding = remap.first.sampler.register_binding;
break;
case HLSL_BINDING_AUTO_UAV_BIT:
desc_set = remap.first.uav.register_space;
binding = remap.first.uav.register_binding;
break;
default:
break;
}
}
}
string CompilerHLSL::to_resource_register(HLSLBindingFlagBits flag, char space, uint32_t binding, uint32_t space_set)
{
if ((flag & resource_binding_flags) == 0)
{
remap_hlsl_resource_binding(flag, space_set, binding);
// The push constant block did not have a binding, and there were no remap for it,
// so, declare without register binding.
if (flag == HLSL_BINDING_AUTO_PUSH_CONSTANT_BIT && space_set == ResourceBindingPushConstantDescriptorSet)
return "";
if (hlsl_options.shader_model >= 51)
return join(" : register(", space, binding, ", space", space_set, ")");
else
@ -5215,3 +5266,16 @@ string CompilerHLSL::get_unique_identifier()
{
return join("_", unique_identifier_count++, "ident");
}
void CompilerHLSL::add_hlsl_resource_binding(const HLSLResourceBinding &binding)
{
StageSetBinding tuple = { binding.stage, binding.desc_set, binding.binding };
resource_bindings[tuple] = { binding, false };
}
bool CompilerHLSL::is_hlsl_resource_binding_used(ExecutionModel model, uint32_t desc_set, uint32_t binding) const
{
StageSetBinding tuple = { model, desc_set, binding };
auto itr = resource_bindings.find(tuple);
return itr != end(resource_bindings) && itr->second.second;
}

View File

@ -44,6 +44,8 @@ struct RootConstants
// For finer control, decorations may be removed from specific resources instead with unset_decoration().
enum HLSLBindingFlagBits
{
HLSL_BINDING_AUTO_NONE_BIT = 0,
// Push constant (root constant) resources will be declared as CBVs (b-space) without a register() declaration.
// A register will be automatically assigned by the D3D compiler, but must therefore be reflected in D3D-land.
// Push constants do not normally have a DecorationBinding set, but if they do, this can be used to ignore it.
@ -67,6 +69,28 @@ enum HLSLBindingFlagBits
};
using HLSLBindingFlags = uint32_t;
// By matching stage, desc_set and binding for a SPIR-V resource,
// register bindings are set based on whether the HLSL resource is a
// CBV, UAV, SRV or Sampler. A single binding in SPIR-V might contain multiple
// resource types, e.g. COMBINED_IMAGE_SAMPLER, and SRV/Sampler bindings will be used respectively.
// On SM 5.0 and lower, register_space is ignored.
//
// To remap a push constant block which does not have any desc_set/binding associated with it,
// use ResourceBindingPushConstant{DescriptorSet,Binding} as values for desc_set/binding.
// For deeper control of push constants, set_root_constant_layouts() can be used instead.
struct HLSLResourceBinding
{
spv::ExecutionModel stage = spv::ExecutionModelMax;
uint32_t desc_set = 0;
uint32_t binding = 0;
struct Binding
{
uint32_t register_space = 0;
uint32_t register_binding = 0;
} cbv, uav, srv, sampler;
};
class CompilerHLSL : public CompilerGLSL
{
public:
@ -145,6 +169,14 @@ public:
// Controls how resource bindings are declared in the output HLSL.
void set_resource_binding_flags(HLSLBindingFlags flags);
// resource is a resource binding to indicate the HLSL CBV, SRV, UAV or sampler binding
// to use for a particular SPIR-V description set
// and binding. If resource bindings are provided,
// is_hlsl_resource_binding_used() will return true after calling ::compile() if
// the set/binding combination was used by the HLSL code.
void add_hlsl_resource_binding(const HLSLResourceBinding &resource);
bool is_hlsl_resource_binding_used(spv::ExecutionModel model, uint32_t set, uint32_t binding) const;
private:
std::string type_to_glsl(const SPIRType &type, uint32_t id = 0) override;
std::string image_type_hlsl(const SPIRType &type, uint32_t id);
@ -178,7 +210,7 @@ private:
std::string to_sampler_expression(uint32_t id);
std::string to_resource_binding(const SPIRVariable &var);
std::string to_resource_binding_sampler(const SPIRVariable &var);
std::string to_resource_register(HLSLBindingFlags flags, char space, uint32_t binding, uint32_t set);
std::string to_resource_register(HLSLBindingFlagBits flag, char space, uint32_t binding, uint32_t set);
void emit_sampled_image_op(uint32_t result_type, uint32_t result_id, uint32_t image_id, uint32_t samp_id) override;
void emit_access_chain(const Instruction &instruction);
void emit_load(const Instruction &instruction);
@ -267,6 +299,9 @@ private:
std::string get_unique_identifier();
uint32_t unique_identifier_count = 0;
std::unordered_map<StageSetBinding, std::pair<HLSLResourceBinding, bool>, InternalHasher> resource_bindings;
void remap_hlsl_resource_binding(HLSLBindingFlagBits type, uint32_t &desc_set, uint32_t &binding);
};
} // namespace SPIRV_CROSS_NAMESPACE

View File

@ -90,7 +90,7 @@ bool CompilerMSL::is_msl_vertex_attribute_used(uint32_t location)
return vtx_attrs_in_use.count(location) != 0;
}
bool CompilerMSL::is_msl_resource_binding_used(ExecutionModel model, uint32_t desc_set, uint32_t binding)
bool CompilerMSL::is_msl_resource_binding_used(ExecutionModel model, uint32_t desc_set, uint32_t binding) const
{
StageSetBinding tuple = { model, desc_set, binding };
auto itr = resource_bindings.find(tuple);
@ -12641,35 +12641,3 @@ void CompilerMSL::analyze_argument_buffers()
}
}
}
bool CompilerMSL::SetBindingPair::operator==(const SetBindingPair &other) const
{
return desc_set == other.desc_set && binding == other.binding;
}
bool CompilerMSL::SetBindingPair::operator<(const SetBindingPair &other) const
{
return desc_set < other.desc_set || (desc_set == other.desc_set && binding < other.binding);
}
bool CompilerMSL::StageSetBinding::operator==(const StageSetBinding &other) const
{
return model == other.model && desc_set == other.desc_set && binding == other.binding;
}
size_t CompilerMSL::InternalHasher::operator()(const SetBindingPair &value) const
{
// Quality of hash doesn't really matter here.
auto hash_set = std::hash<uint32_t>()(value.desc_set);
auto hash_binding = std::hash<uint32_t>()(value.binding);
return (hash_set * 0x10001b31) ^ hash_binding;
}
size_t CompilerMSL::InternalHasher::operator()(const StageSetBinding &value) const
{
// Quality of hash doesn't really matter here.
auto hash_model = std::hash<uint32_t>()(value.model);
auto hash_set = std::hash<uint32_t>()(value.desc_set);
auto tmp_hash = (hash_model * 0x10001b31) ^ hash_set;
return (tmp_hash * 0x10001b31) ^ value.binding;
}

View File

@ -218,11 +218,13 @@ struct MSLConstexprSampler
// Special constant used in a MSLResourceBinding desc_set
// element to indicate the bindings for the push constants.
static const uint32_t kPushConstDescSet = ~(0u);
// Kinda deprecated. Just use ResourceBindingPushConstant{DescriptorSet,Binding} directly.
static const uint32_t kPushConstDescSet = ResourceBindingPushConstantDescriptorSet;
// Special constant used in a MSLResourceBinding binding
// element to indicate the bindings for the push constants.
static const uint32_t kPushConstBinding = 0;
// Kinda deprecated. Just use ResourceBindingPushConstant{DescriptorSet,Binding} directly.
static const uint32_t kPushConstBinding = ResourceBindingPushConstantBinding;
// Special constant used in a MSLResourceBinding binding
// element to indicate the buffer binding for swizzle buffers.
@ -440,7 +442,7 @@ public:
// Constexpr samplers are always assumed to be emitted.
// No specific MSLResourceBinding remapping is required for constexpr samplers as long as they are remapped
// by remap_constexpr_sampler(_by_binding).
bool is_msl_resource_binding_used(spv::ExecutionModel model, uint32_t set, uint32_t binding);
bool is_msl_resource_binding_used(spv::ExecutionModel model, uint32_t set, uint32_t binding) const;
// This must only be called after a successful call to CompilerMSL::compile().
// For a variable resource ID obtained through reflection API, report the automatically assigned resource index.
@ -786,28 +788,6 @@ protected:
std::set<std::string> typedef_lines;
SmallVector<uint32_t> vars_needing_early_declaration;
struct SetBindingPair
{
uint32_t desc_set;
uint32_t binding;
bool operator==(const SetBindingPair &other) const;
bool operator<(const SetBindingPair &other) const;
};
struct StageSetBinding
{
spv::ExecutionModel model;
uint32_t desc_set;
uint32_t binding;
bool operator==(const StageSetBinding &other) const;
};
struct InternalHasher
{
size_t operator()(const SetBindingPair &value) const;
size_t operator()(const StageSetBinding &value) const;
};
std::unordered_map<StageSetBinding, std::pair<MSLResourceBinding, bool>, InternalHasher> resource_bindings;
uint32_t next_metal_resource_index_buffer = 0;

Binary file not shown.

View File

@ -0,0 +1,89 @@
// Testbench for HLSL resource binding APIs.
// It does not validate output at the moment, but it's useful for ad-hoc testing.
#include <spirv_cross_c.h>
#include <vector>
#include <stdio.h>
#include <stdlib.h>
#define SPVC_CHECKED_CALL(x) do { \
if ((x) != SPVC_SUCCESS) { \
fprintf(stderr, "Failed at line %d.\n", __LINE__); \
exit(1); \
} \
} while(0)
static std::vector<SpvId> read_file(const char *path)
{
long len;
FILE *file = fopen(path, "rb");
if (!file)
return {};
fseek(file, 0, SEEK_END);
len = ftell(file);
rewind(file);
std::vector<SpvId> buffer(len / sizeof(SpvId));
if (fread(buffer.data(), 1, len, file) != (size_t)len)
{
fclose(file);
return {};
}
fclose(file);
return buffer;
}
int main(int argc, char **argv)
{
if (argc != 2)
return EXIT_FAILURE;
auto buffer = read_file(argv[1]);
if (buffer.empty())
return EXIT_FAILURE;
spvc_context ctx;
spvc_parsed_ir parsed_ir;
spvc_compiler compiler;
SPVC_CHECKED_CALL(spvc_context_create(&ctx));
SPVC_CHECKED_CALL(spvc_context_parse_spirv(ctx, buffer.data(), buffer.size(), &parsed_ir));
SPVC_CHECKED_CALL(spvc_context_create_compiler(ctx, SPVC_BACKEND_HLSL, parsed_ir, SPVC_CAPTURE_MODE_TAKE_OWNERSHIP, &compiler));
spvc_compiler_options opts;
SPVC_CHECKED_CALL(spvc_compiler_create_compiler_options(compiler, &opts));
SPVC_CHECKED_CALL(spvc_compiler_options_set_uint(opts, SPVC_COMPILER_OPTION_HLSL_SHADER_MODEL, 51));
SPVC_CHECKED_CALL(spvc_compiler_install_compiler_options(compiler, opts));
spvc_hlsl_resource_binding binding;
spvc_hlsl_resource_binding_init(&binding);
binding.stage = SpvExecutionModelFragment;
binding.desc_set = 1;
binding.binding = 4;
binding.srv.register_space = 2;
binding.srv.register_binding = 3;
binding.sampler.register_space = 4;
binding.sampler.register_binding = 5;
SPVC_CHECKED_CALL(spvc_compiler_hlsl_add_resource_binding(compiler, &binding));
binding.desc_set = SPVC_HLSL_PUSH_CONSTANT_DESC_SET;
binding.binding = SPVC_HLSL_PUSH_CONSTANT_BINDING;
binding.cbv.register_space = 0;
binding.cbv.register_binding = 4;
SPVC_CHECKED_CALL(spvc_compiler_hlsl_add_resource_binding(compiler, &binding));
const char *str;
SPVC_CHECKED_CALL(spvc_compiler_compile(compiler, &str));
fprintf(stderr, "Output:\n%s\n", str);
if (!spvc_compiler_hlsl_is_resource_used(compiler, SpvExecutionModelFragment, 1, 4))
return EXIT_FAILURE;
if (!spvc_compiler_hlsl_is_resource_used(compiler, SpvExecutionModelFragment, SPVC_HLSL_PUSH_CONSTANT_DESC_SET, SPVC_HLSL_PUSH_CONSTANT_BINDING))
return EXIT_FAILURE;
}