summaryrefslogtreecommitdiffstats
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/CMakeLists.txt1
-rw-r--r--examples/reflection-parameter-blocks/README.md4
-rw-r--r--examples/reflection-parameter-blocks/common.slang113
-rw-r--r--examples/reflection-parameter-blocks/main.cpp694
-rw-r--r--examples/reflection-parameter-blocks/shader.slang26
-rw-r--r--examples/reflection-parameter-blocks/vulkan-api.cpp285
-rw-r--r--examples/reflection-parameter-blocks/vulkan-api.h128
7 files changed, 1251 insertions, 0 deletions
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
index 759d99994..9411d10ba 100644
--- a/examples/CMakeLists.txt
+++ b/examples/CMakeLists.txt
@@ -107,6 +107,7 @@ if(SLANG_ENABLE_EXAMPLES)
example(ray-tracing WIN32_EXECUTABLE)
example(ray-tracing-pipeline WIN32_EXECUTABLE)
example(reflection-api)
+ example(reflection-parameter-blocks LINK_WITH_PRIVATE Vulkan-Headers)
example(shader-object)
example(shader-toy WIN32_EXECUTABLE)
example(triangle WIN32_EXECUTABLE)
diff --git a/examples/reflection-parameter-blocks/README.md b/examples/reflection-parameter-blocks/README.md
new file mode 100644
index 000000000..74f08cf0b
--- /dev/null
+++ b/examples/reflection-parameter-blocks/README.md
@@ -0,0 +1,4 @@
+Setting up Parameter Blocks Using Reflection
+============================================
+
+This example shows how to make use of the `ParameterBlock<>` type in Slang shader code, in conjunction with the Slang reflection API, in order to easily set up descriptor sets for Vulkan and descriptor tables for D3D12.
diff --git a/examples/reflection-parameter-blocks/common.slang b/examples/reflection-parameter-blocks/common.slang
new file mode 100644
index 000000000..d8f812246
--- /dev/null
+++ b/examples/reflection-parameter-blocks/common.slang
@@ -0,0 +1,113 @@
+// shader.slang
+
+// This module is part of the `reflection-parameter-blocks`
+// example program.
+//
+// This module is split out from the files that define
+// individual programs, so that we can share some type
+// definitions and utility functions between all of
+// the programs and keep them focused on just defining
+// the shader entry points.
+
+struct Mesh
+{
+ float4x4 modelToWorld;
+ float4x4 modelToWorld_inverseTranspose;
+}
+
+struct Material
+{
+ Texture2D albedoMap;
+ Texture2D glossMap;
+ Texture2D normalMap;
+ SamplerState sampler;
+}
+
+interface ILight
+{
+}
+
+struct DirectionalLight : ILight
+{
+ float3 dir;
+ float3 intensity;
+}
+
+struct ShadowedLight<L : ILight> : ILight
+{
+ L light;
+ Texture2D shadowMap;
+ SamplerComparisonState shadowSampler;
+ float4x4 worldToShadow;
+}
+
+struct EnvironmentMap
+{
+ TextureCube texture;
+ SamplerState sampler;
+}
+
+struct Environment
+{
+ ShadowedLight<DirectionalLight> sunLight;
+ EnvironmentMap envMap;
+ RWStructuredBuffer<float4> output;
+}
+
+struct View
+{
+ float4x4 worldToView;
+ float4x4 viewToProj;
+}
+
+// While the Slang compilation library will *reflect* all of
+// the shader parameters that a program declares,
+// back-ends (such as the SPIR-V code generator) will often
+// strip out parameters that are not used as part of the
+// computation that a shader performs.
+//
+// When shader parameters are stripped from the output
+// binary code, the runtime system for a particular API
+// (e.g., the Vulkan validation layer) cannot check
+// whether a program is correctly handling the binding
+// of those parameters.
+//
+// Our example entry points will thus make use of some
+// utility routines that serve the purpose of allowing
+// us to ensure that specific parameters are seen as
+// "used" during code generation.
+
+void use(inout float4 r, float4 v) { r += v; }
+void use(inout float4 r, float3 v) { r.xyz += v; }
+
+void use(inout float4 r, Texture2D t, SamplerState s)
+{
+ use(r, t.SampleLevel(s, r.xy, 0));
+}
+
+void use(inout float4 r, RWStructuredBuffer<float4> b)
+{
+ use(r, b[int(r.x)]);
+ b[int(r.x)] = r;
+}
+
+void use(inout float4 r, Environment e)
+{
+ use(r, e.sunLight.light.dir);
+ use(r, e.output);
+}
+
+void use(inout float4 r, View v)
+{
+ use(r, v.worldToView[0]);
+}
+
+void use(inout float4 r, Material m)
+{
+ use(r, m.normalMap, m.sampler);
+}
+
+void use(inout float4 r, Mesh m)
+{
+ use(r, m.modelToWorld[0]);
+}
diff --git a/examples/reflection-parameter-blocks/main.cpp b/examples/reflection-parameter-blocks/main.cpp
new file mode 100644
index 000000000..cc20de281
--- /dev/null
+++ b/examples/reflection-parameter-blocks/main.cpp
@@ -0,0 +1,694 @@
+// main.cpp
+
+// Using Parameter Blocks With Reflection
+// ======================================
+//
+// This example program is a companion to the article
+// Using Slang Parameter Blocks, and specifically
+// the section of that article called Using Parameter
+// Blocks With Reflection.
+//
+// Where possible, the code is presented in the
+// same order as the code in the article, so that the
+// two can be read in parallel. When code relates to
+// a sub-section of the article, a comment will be used
+// to reference the relevant section.
+//
+// Boilerplate
+// ===========
+//
+// As is typical for our example programs, this one starts
+// with a certain amount of boilerplate that isn't especially
+// interesting to discuss.
+
+#include "slang-com-ptr.h"
+#include "slang.h"
+typedef SlangResult Result;
+
+#include "core/slang-basic.h"
+#include "examples/example-base/example-base.h"
+using Slang::ComPtr;
+using Slang::String;
+using Slang::List;
+
+// The example code currently only supports Vulkan, but the
+// code is factored with the intention that it could be extended
+// to support D3D12 as well.
+
+#define ENABLE_VULKAN 1
+#define ENABLE_D3D12 0
+
+#if ENABLE_VULKAN
+#include "vulkan-api.h"
+#endif
+
+static const ExampleResources resourceBase("reflection-parameter-blocks");
+static const char* kSourceFileName = "shader.slang";
+
+struct PipelineLayoutReflectionContext
+{
+ gfx::IDevice* _gfxDevice = nullptr;
+ slang::ISession* _slangSession = nullptr;
+ slang::ProgramLayout* _slangProgramLayout = nullptr;
+ slang::IBlob* _slangCompiledProgramBlob = nullptr;
+};
+
+struct PipelineLayoutReflectionContext_Vulkan : PipelineLayoutReflectionContext
+{
+ // What Goes Into a Pipeline Layout?
+ // =================================
+
+ struct PipelineLayoutBuilder
+ {
+ std::vector<VkDescriptorSetLayout> descriptorSetLayouts;
+ std::vector<VkPushConstantRange> pushConstantRanges;
+ };
+
+ // Unlike how things are presented in the document, we do not
+ // nest most of the functions under the `*Builder` types, in
+ // order to allow for more flexibility in the order of
+ // presentation. For example, instead of a
+ // `PipelineLayoutBuilder::finishBuilding()` method, we instead
+ // have a `finishBuildingPipelineLayout` function:
+
+ Result finishBuildingPipelineLayout(
+ PipelineLayoutBuilder& builder,
+ VkPipelineLayout* outPipelineLayout)
+ {
+ filterOutEmptyDescriptorSets(builder);
+
+ VkPipelineLayoutCreateInfo pipelineLayoutInfo = {
+ VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
+
+ pipelineLayoutInfo.setLayoutCount = builder.descriptorSetLayouts.size();
+ pipelineLayoutInfo.pSetLayouts = builder.descriptorSetLayouts.data();
+
+ pipelineLayoutInfo.pushConstantRangeCount = builder.pushConstantRanges.size();
+ pipelineLayoutInfo.pPushConstantRanges = builder.pushConstantRanges.data();
+
+ VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
+ vkAPI.vkCreatePipelineLayout(vkAPI.device, &pipelineLayoutInfo, nullptr, &pipelineLayout);
+
+ *outPipelineLayout = pipelineLayout;
+ return SLANG_OK;
+ }
+
+ // What Goes Into a Descriptor Set Layout?
+ // =======================================
+
+ struct DescriptorSetLayoutBuilder
+ {
+ std::vector<VkDescriptorSetLayoutBinding> descriptorRanges;
+
+ int setIndex = -1;
+ };
+
+
+ // Once we are done traversing the contents of a parameter
+ // block to collect bindings into a `DescriptorSetLayoutBuilder`,
+ // it is a simple matter to create a descriptor set layout using
+ // the Vulkan API, and to install it into the `setLayouts` array
+ // at the index that was reserved.
+ //
+ void finishBuildingDescriptorSetLayout(
+ PipelineLayoutBuilder& pipelineLayoutBuilder,
+ DescriptorSetLayoutBuilder& descriptorSetLayoutBuilder)
+ {
+ if (descriptorSetLayoutBuilder.descriptorRanges.empty())
+ return;
+
+ VkDescriptorSetLayoutCreateInfo descriptorSetLayoutInfo = {
+ VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
+
+ descriptorSetLayoutInfo.bindingCount = descriptorSetLayoutBuilder.descriptorRanges.size();
+ descriptorSetLayoutInfo.pBindings = descriptorSetLayoutBuilder.descriptorRanges.data();
+
+ VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE;
+ vkAPI.vkCreateDescriptorSetLayout(
+ vkAPI.device,
+ &descriptorSetLayoutInfo,
+ nullptr,
+ &descriptorSetLayout);
+
+ pipelineLayoutBuilder.descriptorSetLayouts[descriptorSetLayoutBuilder.setIndex] =
+ descriptorSetLayout;
+ }
+
+ // Parameter Blocks
+ // ================
+
+ void addDescriptorSetForParameterBlock(
+ PipelineLayoutBuilder& pipelineLayoutBuilder,
+ slang::TypeLayoutReflection* parameterBlockTypeLayout)
+ {
+ DescriptorSetLayoutBuilder descriptorSetLayoutBuilder;
+ startBuildingDescriptorSetLayout(pipelineLayoutBuilder, descriptorSetLayoutBuilder);
+
+ addRangesForParameterBlockElement(
+ pipelineLayoutBuilder,
+ descriptorSetLayoutBuilder,
+ parameterBlockTypeLayout->getElementTypeLayout());
+
+ finishBuildingDescriptorSetLayout(pipelineLayoutBuilder, descriptorSetLayoutBuilder);
+ }
+
+ // Automatically-Introduced Uniform Buffer
+ // ---------------------------------------
+
+ void addRangesForParameterBlockElement(
+ PipelineLayoutBuilder& pipelineLayoutBuilder,
+ DescriptorSetLayoutBuilder& descriptorSetLayoutBuilder,
+ slang::TypeLayoutReflection* elementTypeLayout)
+ {
+ if (elementTypeLayout->getSize() > 0)
+ {
+ addAutomaticallyIntroducedUniformBuffer(descriptorSetLayoutBuilder);
+ }
+
+ // Once we have accounted for the possibility of an implicitly-introduced
+ // constant buffer, we can move on and add bindings based on whatever
+ // non-ordinary data (textures, buffers, etc.) is in the element type:
+ //
+ addRanges(pipelineLayoutBuilder, descriptorSetLayoutBuilder, elementTypeLayout);
+ }
+
+ void addAutomaticallyIntroducedUniformBuffer(
+ DescriptorSetLayoutBuilder& descriptorSetLayoutBuilder)
+ {
+ auto vulkanBindingIndex = descriptorSetLayoutBuilder.descriptorRanges.size();
+
+ VkDescriptorSetLayoutBinding binding = {};
+ binding.stageFlags = VK_SHADER_STAGE_ALL;
+ binding.binding = vulkanBindingIndex;
+ binding.descriptorCount = 1;
+ binding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
+
+ descriptorSetLayoutBuilder.descriptorRanges.push_back(binding);
+ }
+
+ // Ordering of Nested Parameter Blocks
+ // -----------------------------------
+
+ void startBuildingDescriptorSetLayout(
+ PipelineLayoutBuilder& pipelineLayoutBuilder,
+ DescriptorSetLayoutBuilder& descriptorSetLayoutBuilder)
+ {
+ descriptorSetLayoutBuilder.setIndex = pipelineLayoutBuilder.descriptorSetLayouts.size();
+ pipelineLayoutBuilder.descriptorSetLayouts.push_back(VK_NULL_HANDLE);
+ }
+
+ // Empty Ranges
+ // ------------
+
+ void filterOutEmptyDescriptorSets(PipelineLayoutBuilder& builder)
+ {
+ std::vector<VkDescriptorSetLayout> filteredDescriptorSetLayouts;
+ for (auto descriptorSetLayout : builder.descriptorSetLayouts)
+ {
+ if (!descriptorSetLayout)
+ continue;
+ filteredDescriptorSetLayouts.push_back(descriptorSetLayout);
+ }
+ std::swap(builder.descriptorSetLayouts, filteredDescriptorSetLayouts);
+ }
+
+ // Descritpor Ranges
+ // =================
+
+ void addDescriptorRanges(
+ DescriptorSetLayoutBuilder& descriptorSetLayoutBuilder,
+ slang::TypeLayoutReflection* typeLayout)
+ {
+ int relativeSetIndex = 0;
+ int rangeCount = typeLayout->getDescriptorSetDescriptorRangeCount(relativeSetIndex);
+
+ for (int rangeIndex = 0; rangeIndex < rangeCount; ++rangeIndex)
+ {
+ addDescriptorRange(
+ descriptorSetLayoutBuilder,
+ typeLayout,
+ relativeSetIndex,
+ rangeIndex);
+ }
+ }
+
+ void addDescriptorRange(
+ DescriptorSetLayoutBuilder& descriptorSetLayoutBuilder,
+ slang::TypeLayoutReflection* typeLayout,
+ int relativeSetIndex,
+ int rangeIndex)
+ {
+ slang::BindingType bindingType =
+ typeLayout->getDescriptorSetDescriptorRangeType(relativeSetIndex, rangeIndex);
+ auto descriptorCount = typeLayout->getDescriptorSetDescriptorRangeDescriptorCount(
+ relativeSetIndex,
+ rangeIndex);
+
+ // Some Ranges Need to Be Skipped
+ // ------------------------------
+ //
+ switch (bindingType)
+ {
+ default:
+ break;
+
+ case slang::BindingType::PushConstant:
+ return;
+ }
+
+ auto bindingIndex = descriptorSetLayoutBuilder.descriptorRanges.size();
+
+ VkDescriptorSetLayoutBinding vulkanBindingRange = {};
+ vulkanBindingRange.binding = bindingIndex;
+ vulkanBindingRange.descriptorCount = descriptorCount;
+ vulkanBindingRange.stageFlags = _currentStageFlags;
+ vulkanBindingRange.descriptorType = mapSlangBindingTypeToVulkanDescriptorType(bindingType);
+
+ descriptorSetLayoutBuilder.descriptorRanges.push_back(vulkanBindingRange);
+ }
+
+ VkDescriptorType mapSlangBindingTypeToVulkanDescriptorType(slang::BindingType bindingType)
+ {
+ switch (bindingType)
+ {
+#define CASE(FROM, TO) \
+ case slang::BindingType::FROM: \
+ return VK_DESCRIPTOR_TYPE_##TO
+
+ CASE(Sampler, SAMPLER);
+ CASE(CombinedTextureSampler, COMBINED_IMAGE_SAMPLER);
+ CASE(Texture, SAMPLED_IMAGE);
+ CASE(MutableTexture, STORAGE_IMAGE);
+ CASE(TypedBuffer, UNIFORM_TEXEL_BUFFER);
+ CASE(MutableTypedBuffer, STORAGE_TEXEL_BUFFER);
+ CASE(ConstantBuffer, UNIFORM_BUFFER);
+ CASE(RawBuffer, STORAGE_BUFFER);
+ CASE(MutableRawBuffer, STORAGE_BUFFER);
+ CASE(InputRenderTarget, INPUT_ATTACHMENT);
+ CASE(InlineUniformData, INLINE_UNIFORM_BLOCK);
+ CASE(RayTracingAccelerationStructure, ACCELERATION_STRUCTURE_KHR);
+
+#undef CASE
+
+ default:
+ return VkDescriptorType(-1);
+ }
+ }
+
+ // Sub-Object Ranges
+ // =================
+
+ void addRanges(
+ PipelineLayoutBuilder& pipelineLayoutBuilder,
+ DescriptorSetLayoutBuilder& descriptorSetLayoutBuilder,
+ slang::TypeLayoutReflection* typeLayout)
+ {
+ addDescriptorRanges(descriptorSetLayoutBuilder, typeLayout);
+ addSubObjectRanges(pipelineLayoutBuilder, typeLayout);
+ }
+
+ void addSubObjectRanges(
+ PipelineLayoutBuilder& pipelineLayoutBuilder,
+ slang::TypeLayoutReflection* typeLayout)
+ {
+ int subObjectRangeCount = typeLayout->getSubObjectRangeCount();
+ for (int subObjectRangeIndex = 0; subObjectRangeIndex < subObjectRangeCount;
+ ++subObjectRangeIndex)
+ {
+ addSubObjectRange(pipelineLayoutBuilder, typeLayout, subObjectRangeIndex);
+ }
+ }
+
+ void addSubObjectRange(
+ PipelineLayoutBuilder& pipelineLayoutBuilder,
+ slang::TypeLayoutReflection* typeLayout,
+ int subObjectRangeIndex)
+ {
+ auto bindingRangeIndex =
+ typeLayout->getSubObjectRangeBindingRangeIndex(subObjectRangeIndex);
+ auto bindingType = typeLayout->getBindingRangeType(bindingRangeIndex);
+ switch (bindingType)
+ {
+ default:
+ return;
+
+ // Nested Parameter Blocks
+ // -----------------------
+
+ case slang::BindingType::ParameterBlock:
+ {
+ auto parameterBlockTypeLayout =
+ typeLayout->getBindingRangeLeafTypeLayout(bindingRangeIndex);
+ addDescriptorSetForParameterBlock(pipelineLayoutBuilder, parameterBlockTypeLayout);
+ }
+ break;
+
+ // Push-Constant Ranges
+ // --------------------
+
+ case slang::BindingType::PushConstant:
+ {
+ auto constantBufferTypeLayout =
+ typeLayout->getBindingRangeLeafTypeLayout(bindingRangeIndex);
+ addPushConstantRangeForConstantBuffer(
+ pipelineLayoutBuilder,
+ constantBufferTypeLayout);
+ }
+ break;
+ }
+ }
+
+ void addPushConstantRangeForConstantBuffer(
+ PipelineLayoutBuilder& pipelineLayoutBuilder,
+ slang::TypeLayoutReflection* pushConstantBufferTypeLayout)
+ {
+ auto elementTypeLayout = pushConstantBufferTypeLayout->getElementTypeLayout();
+ auto elementSize = elementTypeLayout->getSize();
+
+ if (elementSize == 0)
+ return;
+
+ VkPushConstantRange pushConstantRange = {};
+ pushConstantRange.stageFlags = _currentStageFlags;
+ pushConstantRange.offset = 0;
+ pushConstantRange.size = elementSize;
+
+ pipelineLayoutBuilder.pushConstantRanges.push_back(pushConstantRange);
+ }
+
+ // Creating a Pipeline Layout for a Program
+ // ========================================
+
+ Result createPipelineLayout(
+ slang::ProgramLayout* programLayout,
+ VkPipelineLayout* outPipelineLayout)
+ {
+ PipelineLayoutBuilder pipelineLayoutBuilder;
+
+ DescriptorSetLayoutBuilder defaultDescriptorSetLayoutBuilder;
+ startBuildingDescriptorSetLayout(pipelineLayoutBuilder, defaultDescriptorSetLayoutBuilder);
+
+ addGlobalScopeParameters(
+ pipelineLayoutBuilder,
+ defaultDescriptorSetLayoutBuilder,
+ programLayout);
+
+ addEntryPointParameters(
+ pipelineLayoutBuilder,
+ defaultDescriptorSetLayoutBuilder,
+ programLayout);
+
+ finishBuildingDescriptorSetLayout(pipelineLayoutBuilder, defaultDescriptorSetLayoutBuilder);
+ finishBuildingPipelineLayout(pipelineLayoutBuilder, outPipelineLayout);
+
+ return SLANG_OK;
+ }
+
+ // Global Scope
+ // ------------
+
+ void addGlobalScopeParameters(
+ PipelineLayoutBuilder& pipelineLayoutBuilder,
+ DescriptorSetLayoutBuilder& descriptorSetLayoutBuilder,
+ slang::ProgramLayout* programLayout)
+ {
+ _currentStageFlags = VK_SHADER_STAGE_ALL;
+ addRangesForParameterBlockElement(
+ pipelineLayoutBuilder,
+ descriptorSetLayoutBuilder,
+ programLayout->getGlobalParamsTypeLayout());
+ }
+
+ // Entry Points
+ // ------------
+
+ void addEntryPointParameters(
+ PipelineLayoutBuilder& pipelineLayoutBuilder,
+ DescriptorSetLayoutBuilder& descriptorSetLayoutBuilder,
+ slang::ProgramLayout* programLayout)
+ {
+ int entryPointCount = _slangProgramLayout->getEntryPointCount();
+ for (int i = 0; i < entryPointCount; ++i)
+ {
+ auto entryPointLayout = _slangProgramLayout->getEntryPointByIndex(i);
+ addEntryPointParameters(
+ pipelineLayoutBuilder,
+ descriptorSetLayoutBuilder,
+ entryPointLayout);
+ }
+ }
+
+ void addEntryPointParameters(
+ PipelineLayoutBuilder& pipelineLayoutBuilder,
+ DescriptorSetLayoutBuilder& descriptorSetLayoutBuilder,
+ slang::EntryPointLayout* entryPointLayout)
+ {
+ _currentStageFlags = getShaderStageFlags(entryPointLayout->getStage());
+ addRangesForParameterBlockElement(
+ pipelineLayoutBuilder,
+ descriptorSetLayoutBuilder,
+ entryPointLayout->getTypeLayout());
+ }
+
+ VkShaderStageFlags _currentStageFlags = VK_SHADER_STAGE_ALL;
+ VkShaderStageFlags getShaderStageFlags(SlangStage stage)
+ {
+ switch (stage)
+ {
+#define CASE(FROM, TO) \
+ case SLANG_STAGE_##FROM: \
+ return VK_SHADER_STAGE_##TO
+
+ CASE(VERTEX, VERTEX_BIT);
+ CASE(HULL, TESSELLATION_CONTROL_BIT);
+ CASE(DOMAIN, TESSELLATION_EVALUATION_BIT);
+ CASE(GEOMETRY, GEOMETRY_BIT);
+ CASE(FRAGMENT, FRAGMENT_BIT);
+ CASE(COMPUTE, COMPUTE_BIT);
+ CASE(RAY_GENERATION, RAYGEN_BIT_KHR);
+ CASE(ANY_HIT, ANY_HIT_BIT_KHR);
+ CASE(CLOSEST_HIT, CLOSEST_HIT_BIT_KHR);
+ CASE(MISS, MISS_BIT_KHR);
+ CASE(INTERSECTION, INTERSECTION_BIT_KHR);
+ CASE(CALLABLE, CALLABLE_BIT_KHR);
+ CASE(MESH, MESH_BIT_EXT);
+ CASE(AMPLIFICATION, TASK_BIT_EXT);
+
+#undef CASE
+ default:
+ return VK_SHADER_STAGE_ALL;
+ }
+ }
+
+ // Validation
+ // ==========
+ //
+ // The published article covers how to create a pipeline layout
+ // using the reflection API, but for the purposes of an example
+ // program, we should make sure that we validate that the layout
+ // that results from that code is *actually* compatible with the
+ // shader program.
+ //
+ // The remaining operations inside this type provide the support
+ // code to create and validate a pipeline layout based on a
+ // particular compiled compute program. Mismatches between the
+ // pipeline layout and the program should be diagnosed by the
+ // Vulkan validation layer when we attempt to create a pipeline
+ // that uses the two together.
+
+ Result validatePipelineLayout(VkPipelineLayout pipelineLayout)
+ {
+ VkShaderModuleCreateInfo shaderModuleInfo = {VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
+ shaderModuleInfo.pCode = (uint32_t const*)_slangCompiledProgramBlob->getBufferPointer();
+ shaderModuleInfo.codeSize = _slangCompiledProgramBlob->getBufferSize();
+
+ VkShaderModule vkShaderModule;
+ vkAPI.vkCreateShaderModule(vkAPI.device, &shaderModuleInfo, nullptr, &vkShaderModule);
+
+ VkComputePipelineCreateInfo pipelineInfo = {VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO};
+ pipelineInfo.layout = pipelineLayout;
+ pipelineInfo.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
+ pipelineInfo.stage.module = vkShaderModule;
+ pipelineInfo.stage.pName = "main";
+ pipelineInfo.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
+
+ VkPipeline pipeline;
+ vkAPI.vkCreateComputePipelines(
+ vkAPI.device,
+ VK_NULL_HANDLE,
+ 1,
+ &pipelineInfo,
+ nullptr,
+ &pipeline);
+
+ vkAPI.vkDestroyPipeline(vkAPI.device, pipeline, nullptr);
+
+ return SLANG_OK;
+ }
+
+ Result createAndValidatePipelineLayout()
+ {
+ // Here we do a little bit of complicated interaction with
+ // the `gfx` library to allow us to call raw Vulkan API
+ // functions on the same device that `gfx` kindly set up
+ // for us.
+ //
+ gfx::IDevice::InteropHandles handles;
+ SLANG_RETURN_ON_FAIL(_gfxDevice->getNativeDeviceHandles(&handles));
+
+ vkAPI.instance = (VkInstance)handles.handles[0].handleValue;
+ vkAPI.physicalDevice = (VkPhysicalDevice)handles.handles[1].handleValue;
+ vkAPI.device = (VkDevice)handles.handles[2].handleValue;
+
+ vkAPI.initGlobalProcs();
+ vkAPI.initInstanceProcs();
+ vkAPI.initDeviceProcs();
+
+ // Once the setup is dealt with, we can go ahead and
+ // create the pipeline layout, before validating that
+ // it can be used together with the compiled SPIR-V
+ // binary for the program.
+ //
+ VkPipelineLayout pipelineLayout;
+ SLANG_RETURN_ON_FAIL(createPipelineLayout(_slangProgramLayout, &pipelineLayout));
+ SLANG_RETURN_ON_FAIL(validatePipelineLayout(pipelineLayout));
+
+ vkAPI.vkDestroyPipelineLayout(vkAPI.device, pipelineLayout, nullptr);
+
+ return SLANG_OK;
+ }
+
+ VulkanAPI vkAPI;
+};
+
+// More Boilerplate
+// ================
+//
+// The logic below this point is just about setting up the necessary state
+// in the example application for the code above to be run on a simple
+// shader. Nothing here is especially relevant to the task of creating
+// a pipeline layout from Slang reflection information.
+
+struct ReflectionParameterBlocksExampleApp : public TestBase
+{
+ Result execute(int argc, char** argv)
+ {
+ parseOption(argc, argv);
+
+ // We start by initializing the `gfx` system, so that
+ // it can handle most of the details of getting a
+ // Vulkan device up and running.
+
+#ifdef _DEBUG
+ gfx::gfxEnableDebugLayer();
+#endif
+ gfx::IDevice::Desc deviceDesc = {};
+ deviceDesc.deviceType = gfx::DeviceType::Vulkan;
+
+ ComPtr<gfx::IDevice> gfxDevice;
+ SLANG_RETURN_ON_FAIL(gfxCreateDevice(&deviceDesc, gfxDevice.writeRef()));
+
+ // The `gfx` library also creates a Slang session as
+ // part of its startup, so we will use the session
+ // it already created for the compilation in
+ // this example.
+ //
+ auto slangSession = gfxDevice->getSlangSession();
+
+ // Next we go through the fairly routine steps needed to
+ // compile a Slang program from source.
+ //
+ ComPtr<slang::IBlob> diagnostics;
+ Result result = SLANG_OK;
+
+ // We load the source file as a module of Slang code.
+ //
+ String sourceFilePath = resourceBase.resolveResource(kSourceFileName);
+ ComPtr<slang::IModule> module;
+ module = slangSession->loadModule(sourceFilePath.getBuffer(), diagnostics.writeRef());
+ diagnoseIfNeeded(diagnostics);
+ if (!module)
+ return SLANG_FAIL;
+
+ // Next we will collect all of the entry points defined in the module,
+ // to form the list of components we want to link together to form
+ // a program.
+ //
+ List<ComPtr<slang::IComponentType>> componentsToLink;
+ int definedEntryPointCount = module->getDefinedEntryPointCount();
+ for (int i = 0; i < definedEntryPointCount; i++)
+ {
+ ComPtr<slang::IEntryPoint> entryPoint;
+ SLANG_RETURN_ON_FAIL(module->getDefinedEntryPoint(i, entryPoint.writeRef()));
+ componentsToLink.add(ComPtr<slang::IComponentType>(entryPoint.get()));
+ }
+
+ // Once we've collected the list of entry points we want to compose,
+ // we use the Slang compilation API to compose them.
+ //
+ ComPtr<slang::IComponentType> composed;
+ result = slangSession->createCompositeComponentType(
+ (slang::IComponentType**)componentsToLink.getBuffer(),
+ componentsToLink.getCount(),
+ composed.writeRef(),
+ diagnostics.writeRef());
+ diagnoseIfNeeded(diagnostics);
+ SLANG_RETURN_ON_FAIL(result);
+
+ // As the final compilation step, we will use the compilation API
+ // to link the composed code. Think of this as equivalent to
+ // applying the linker to a bunch of `.o` and/or `.a` files to
+ // produce a binary (executable or shared library).
+ //
+ ComPtr<slang::IComponentType> program;
+ result = composed->link(program.writeRef(), diagnostics.writeRef());
+ diagnoseIfNeeded(diagnostics);
+ SLANG_RETURN_ON_FAIL(result);
+
+ // Once the program has been compiled succcessfully, we can
+ // go ahead and grab reflection data from the program.
+ //
+ int targetIndex = 0;
+ slang::ProgramLayout* programLayout =
+ program->getLayout(targetIndex, diagnostics.writeRef());
+ diagnoseIfNeeded(diagnostics);
+ if (!programLayout)
+ {
+ return SLANG_FAIL;
+ }
+
+ // The compiled program can also have binary code (either
+ // for individual entry points, or the entire program)
+ // generated for it.
+ //
+ ComPtr<slang::IBlob> programBinary;
+ result = program->getEntryPointCode(0, 0, programBinary.writeRef(), diagnostics.writeRef());
+ diagnoseIfNeeded(diagnostics);
+ if (SLANG_FAILED(result))
+ return result;
+
+ // Finally, once all of the initialization work is dealt with,
+ // we hand control over to the actual logic of the example.
+ //
+ PipelineLayoutReflectionContext_Vulkan context;
+
+ context._gfxDevice = gfxDevice;
+ context._slangSession = slangSession;
+ context._slangProgramLayout = programLayout;
+ context._slangCompiledProgramBlob = programBinary;
+
+ SLANG_RETURN_ON_FAIL(context.createAndValidatePipelineLayout());
+
+ return SLANG_OK;
+ }
+};
+
+int main(int argc, char* argv[])
+{
+ ReflectionParameterBlocksExampleApp app;
+ if (SLANG_FAILED(app.execute(argc, argv)))
+ {
+ return -1;
+ }
+ return 0;
+}
diff --git a/examples/reflection-parameter-blocks/shader.slang b/examples/reflection-parameter-blocks/shader.slang
new file mode 100644
index 000000000..6419bda3a
--- /dev/null
+++ b/examples/reflection-parameter-blocks/shader.slang
@@ -0,0 +1,26 @@
+// shader.slang
+
+// This shader is part of the `reflection-parameter-blocks`
+// example program.
+//
+// This file is an example program that *only* declares
+// shader parameters in the global scope, and *only* uses
+// explicit parameter blocks.
+
+import common;
+
+ParameterBlock<Environment> environment;
+ParameterBlock<View> view;
+ParameterBlock<Material> material;
+ParameterBlock<Mesh> mesh;
+
+[shader("compute")]
+void main()
+{
+ float4 r = 0;
+
+ use(r, mesh);
+ use(r, material);
+ use(r, view);
+ use(r, environment);
+}
diff --git a/examples/reflection-parameter-blocks/vulkan-api.cpp b/examples/reflection-parameter-blocks/vulkan-api.cpp
new file mode 100644
index 000000000..7fad578b0
--- /dev/null
+++ b/examples/reflection-parameter-blocks/vulkan-api.cpp
@@ -0,0 +1,285 @@
+#include "vulkan-api.h"
+
+#include "slang.h"
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <vector>
+
+#if SLANG_WINDOWS_FAMILY
+#include <windows.h>
+#else
+#include <dlfcn.h>
+#endif
+
+#if _DEBUG
+#define ENABLE_VALIDATION_LAYER 1
+#endif
+
+VKAPI_ATTR VkBool32 VKAPI_CALL debugMessageCallback(
+ VkDebugReportFlagsEXT /*flags*/,
+ VkDebugReportObjectTypeEXT /*objType*/,
+ uint64_t /*srcObject*/,
+ size_t /*location*/,
+ int32_t /*msgCode*/,
+ const char* pLayerPrefix,
+ const char* pMsg,
+ void* /*pUserData*/
+)
+{
+ printf("[%s]: %s\n", pLayerPrefix, pMsg);
+ return 1;
+}
+
+gfx::Result VulkanAPI::initGlobalProcs()
+{
+ // Load vulkan library.
+ const char* dynamicLibraryName = "Unknown";
+
+#if SLANG_WINDOWS_FAMILY
+ dynamicLibraryName = "vulkan-1.dll";
+ HMODULE module = ::LoadLibraryA(dynamicLibraryName);
+#define VK_API_GET_GLOBAL_PROC(x) this->x = (PFN_##x)GetProcAddress(module, #x);
+#elif SLANG_APPLE_FAMILY
+ dynamicLibraryName = "libvulkan.dylib";
+ void* vulkanLibraryHandle = dlopen(dynamicLibraryName, RTLD_NOW);
+#define VK_API_GET_GLOBAL_PROC(x) this->x = (PFN_##x)dlsym(vulkanLibraryHandle, #x);
+#else
+ dynamicLibraryName = "libvulkan.so.1";
+ void* vulkanLibraryHandle = dlopen(dynamicLibraryName, RTLD_NOW);
+#define VK_API_GET_GLOBAL_PROC(x) this->x = (PFN_##x)dlsym(vulkanLibraryHandle, #x);
+#endif
+
+ // Initialize all the global functions.
+ VK_API_ALL_GLOBAL_PROCS(VK_API_GET_GLOBAL_PROC)
+ if (!this->vkCreateInstance)
+ return -1;
+
+ return 0;
+}
+
+gfx::Result initializeVulkanDevice(VulkanAPI& api)
+{
+ if (api.initGlobalProcs() != 0)
+ return -1;
+
+ // Enable validation layer if available.
+ std::vector<const char*> layers;
+#ifdef ENABLE_VALIDATION_LAYER
+ uint32_t propertyCount;
+ if (api.vkEnumerateInstanceLayerProperties(&propertyCount, nullptr) != 0)
+ return -1;
+ std::vector<VkLayerProperties> properties(propertyCount);
+ if (api.vkEnumerateInstanceLayerProperties(&propertyCount, properties.data()) != 0)
+ return -1;
+ for (const auto& p : properties)
+ {
+ if (strcmp(p.layerName, "VK_LAYER_KHRONOS_validation") == 0)
+ {
+ layers.push_back("VK_LAYER_KHRONOS_validation");
+ }
+ }
+#endif
+
+ // Create Vulkan Instance.
+ VkApplicationInfo applicationInfo = {VK_STRUCTURE_TYPE_APPLICATION_INFO};
+ applicationInfo.pApplicationName = "slang-hello-world";
+ applicationInfo.pEngineName = "slang-hello-world";
+ applicationInfo.apiVersion = VK_API_VERSION_1_2;
+ applicationInfo.engineVersion = 1;
+ applicationInfo.applicationVersion = 1;
+ const char* instanceExtensions[] = {
+#if SLANG_APPLE_FAMILY
+ VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME,
+#endif
+ VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME,
+ VK_EXT_DEBUG_REPORT_EXTENSION_NAME,
+ };
+ VkInstanceCreateInfo instanceCreateInfo = {VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
+#if SLANG_APPLE_FAMILY
+ instanceCreateInfo.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
+#endif
+ instanceCreateInfo.pApplicationInfo = &applicationInfo;
+ instanceCreateInfo.enabledExtensionCount = SLANG_COUNT_OF(instanceExtensions);
+ instanceCreateInfo.ppEnabledExtensionNames = &instanceExtensions[0];
+ if (layers.size())
+ {
+ instanceCreateInfo.ppEnabledLayerNames = &layers[0];
+ instanceCreateInfo.enabledLayerCount = (uint32_t)layers.size();
+ }
+ if (api.vkCreateInstance(&instanceCreateInfo, nullptr, &api.instance) != 0)
+ return -1;
+
+ // Load instance functions.
+ api.initInstanceProcs();
+
+#if 0
+ // Create debug report callback.
+ if (api.vkCreateDebugReportCallbackEXT)
+ {
+ VkDebugReportFlagsEXT debugFlags =
+ VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT;
+
+ VkDebugReportCallbackCreateInfoEXT debugCreateInfo = {
+ VK_STRUCTURE_TYPE_DEBUG_REPORT_CREATE_INFO_EXT};
+ debugCreateInfo.pfnCallback = &debugMessageCallback;
+ debugCreateInfo.pUserData = nullptr;
+ debugCreateInfo.flags = debugFlags;
+
+ RETURN_ON_FAIL(api.vkCreateDebugReportCallbackEXT(
+ api.instance,
+ &debugCreateInfo,
+ nullptr,
+ &api.debugReportCallback));
+ }
+#endif
+
+ // Enumerate physical devices.
+ uint32_t numPhysicalDevices = 0;
+ RETURN_ON_FAIL(api.vkEnumeratePhysicalDevices(api.instance, &numPhysicalDevices, nullptr));
+ std::vector<VkPhysicalDevice> physicalDevices;
+ physicalDevices.resize(numPhysicalDevices);
+ RETURN_ON_FAIL(
+ api.vkEnumeratePhysicalDevices(api.instance, &numPhysicalDevices, &physicalDevices[0]));
+
+#if 0
+ // We will use device 0.
+ api.initPhysicalDevice(physicalDevices[0]);
+
+ VkDeviceCreateInfo deviceCreateInfo = {VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO};
+ deviceCreateInfo.queueCreateInfoCount = 1;
+ deviceCreateInfo.pEnabledFeatures = &api.deviceFeatures;
+
+ // Find proper queue family index.
+ uint32_t numQueueFamilies = 0;
+ api.vkGetPhysicalDeviceQueueFamilyProperties(api.physicalDevice, &numQueueFamilies, nullptr);
+
+ std::vector<VkQueueFamilyProperties> queueFamilies;
+ queueFamilies.resize(numQueueFamilies);
+ api.vkGetPhysicalDeviceQueueFamilyProperties(
+ api.physicalDevice,
+ &numQueueFamilies,
+ &queueFamilies[0]);
+
+ // Find a queue that can service our needs.
+ auto requiredQueueFlags = VK_QUEUE_COMPUTE_BIT;
+ for (int i = 0; i < int(numQueueFamilies); ++i)
+ {
+ if ((queueFamilies[i].queueFlags & requiredQueueFlags) == requiredQueueFlags)
+ {
+ api.queueFamilyIndex = i;
+ break;
+ }
+ }
+ if (api.queueFamilyIndex == -1)
+ return -1;
+
+#if SLANG_APPLE_FAMILY
+ const char* deviceExtensions[] = {
+ "VK_KHR_portability_subset",
+ };
+#endif
+
+ VkDeviceQueueCreateInfo queueCreateInfo = {VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO};
+ float queuePriority = 0.0f;
+ queueCreateInfo.queueFamilyIndex = api.queueFamilyIndex;
+ queueCreateInfo.queueCount = 1;
+ queueCreateInfo.pQueuePriorities = &queuePriority;
+ deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo;
+#if SLANG_APPLE_FAMILY
+ deviceCreateInfo.enabledExtensionCount = SLANG_COUNT_OF(deviceExtensions);
+ deviceCreateInfo.ppEnabledExtensionNames = &deviceExtensions[0];
+#endif
+ RETURN_ON_FAIL(api.vkCreateDevice(api.physicalDevice, &deviceCreateInfo, nullptr, &api.device));
+
+#endif
+
+ // Load device functions.
+ api.initDeviceProcs();
+
+ return 0;
+}
+
+gfx::Result VulkanAPI::initInstanceProcs()
+{
+ assert(instance && vkGetInstanceProcAddr != nullptr);
+
+#define VK_API_GET_INSTANCE_PROC(x) x = (PFN_##x)vkGetInstanceProcAddr(instance, #x);
+
+ VK_API_ALL_INSTANCE_PROCS(VK_API_GET_INSTANCE_PROC)
+ // Get optional
+ VK_API_INSTANCE_PROCS_OPT(VK_API_GET_INSTANCE_PROC)
+
+#undef VK_API_GET_INSTANCE_PROC
+
+ return 0;
+}
+
+#if 0
+int VulkanAPI::initPhysicalDevice(VkPhysicalDevice inPhysicalDevice)
+{
+ assert(physicalDevice == VK_NULL_HANDLE);
+ physicalDevice = inPhysicalDevice;
+
+ vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
+ vkGetPhysicalDeviceFeatures(physicalDevice, &deviceFeatures);
+ vkGetPhysicalDeviceMemoryProperties(physicalDevice, &deviceMemoryProperties);
+
+ return 0;
+}
+#endif
+
+gfx::Result VulkanAPI::initDeviceProcs()
+{
+ assert(instance && device && vkGetDeviceProcAddr != nullptr);
+
+#define VK_API_GET_DEVICE_PROC(x) x = (PFN_##x)vkGetDeviceProcAddr(device, #x);
+ VK_API_DEVICE_PROCS(VK_API_GET_DEVICE_PROC)
+#undef VK_API_GET_DEVICE_PROC
+
+ return 0;
+}
+
+#if 0
+int VulkanAPI::findMemoryTypeIndex(uint32_t typeBits, VkMemoryPropertyFlags properties)
+{
+ assert(typeBits);
+
+ const int numMemoryTypes = int(deviceMemoryProperties.memoryTypeCount);
+
+ // bit holds current test bit against typeBits. Ie bit == 1 << typeBits
+
+ uint32_t bit = 1;
+ for (int i = 0; i < numMemoryTypes; ++i, bit += bit)
+ {
+ auto const& memoryType = deviceMemoryProperties.memoryTypes[i];
+ if ((typeBits & bit) && (memoryType.propertyFlags & properties) == properties)
+ {
+ return i;
+ }
+ }
+
+ // assert(!"failed to find a usable memory type");
+ return -1;
+}
+#endif
+
+VulkanAPI::~VulkanAPI()
+{
+#if 0
+ if (vkDestroyDevice)
+ {
+ vkDestroyDevice(device, nullptr);
+ }
+ if (debugReportCallback && vkDestroyDebugReportCallbackEXT)
+ {
+ vkDestroyDebugReportCallbackEXT(instance, debugReportCallback, nullptr);
+ }
+ if (vkDestroyInstance)
+ {
+ vkDestroyInstance(instance, nullptr);
+ }
+#endif
+}
diff --git a/examples/reflection-parameter-blocks/vulkan-api.h b/examples/reflection-parameter-blocks/vulkan-api.h
new file mode 100644
index 000000000..05a84db5d
--- /dev/null
+++ b/examples/reflection-parameter-blocks/vulkan-api.h
@@ -0,0 +1,128 @@
+#pragma once
+
+#include "slang-gfx.h"
+
+#define VK_NO_PROTOTYPES
+#include "vulkan/vulkan.h"
+
+// This file provides basic loading and helper functions for using
+// the Vulkan API.
+
+// The Vulkan function pointers we will use in this example.
+// clang-format off
+#define VK_API_GLOBAL_PROCS(x) \
+ x(vkGetInstanceProcAddr) \
+ x(vkCreateInstance) \
+ x(vkEnumerateInstanceLayerProperties) \
+ x(vkDestroyInstance) \
+ /* */
+
+#define VK_API_INSTANCE_PROCS_OPT(x) \
+ x(vkGetPhysicalDeviceFeatures2) \
+ x(vkGetPhysicalDeviceProperties2) \
+ x(vkCreateDebugReportCallbackEXT) \
+ x(vkDestroyDebugReportCallbackEXT) \
+ x(vkDebugReportMessageEXT) \
+ /* */
+
+#define VK_API_INSTANCE_PROCS(x) \
+ x(vkCreateDevice) \
+ x(vkDestroyDevice) \
+ x(vkEnumeratePhysicalDevices) \
+ x(vkGetPhysicalDeviceProperties) \
+ x(vkGetPhysicalDeviceFeatures) \
+ x(vkGetPhysicalDeviceMemoryProperties) \
+ x(vkGetPhysicalDeviceQueueFamilyProperties) \
+ x(vkGetPhysicalDeviceFormatProperties) \
+ x(vkGetDeviceProcAddr) \
+ /* */
+
+#define VK_API_DEVICE_PROCS(x) \
+ x(vkCreateDescriptorPool) \
+ x(vkDestroyDescriptorPool) \
+ x(vkGetDeviceQueue) \
+ x(vkQueueSubmit) \
+ x(vkQueueWaitIdle) \
+ x(vkCreateBuffer) \
+ x(vkAllocateMemory) \
+ x(vkMapMemory) \
+ x(vkUnmapMemory) \
+ x(vkCmdCopyBuffer) \
+ x(vkDestroyBuffer) \
+ x(vkFreeMemory) \
+ x(vkCreateDescriptorSetLayout) \
+ x(vkDestroyDescriptorSetLayout) \
+ x(vkAllocateDescriptorSets) \
+ x(vkUpdateDescriptorSets) \
+ x(vkCreatePipelineLayout) \
+ x(vkDestroyPipelineLayout) \
+ x(vkCreateComputePipelines) \
+ x(vkDestroyPipeline) \
+ x(vkCreateShaderModule) \
+ x(vkDestroyShaderModule) \
+ x(vkCreateCommandPool) \
+ x(vkDestroyCommandPool) \
+ \
+ x(vkGetBufferMemoryRequirements) \
+ \
+ x(vkCmdBindPipeline) \
+ x(vkCmdBindDescriptorSets) \
+ x(vkCmdDispatch) \
+ \
+ x(vkFreeCommandBuffers) \
+ x(vkAllocateCommandBuffers) \
+ x(vkBeginCommandBuffer) \
+ x(vkEndCommandBuffer) \
+ x(vkBindBufferMemory) \
+ /* */
+
+#define VK_API_ALL_GLOBAL_PROCS(x) \
+ VK_API_GLOBAL_PROCS(x)
+
+#define VK_API_ALL_INSTANCE_PROCS(x) \
+ VK_API_INSTANCE_PROCS(x) \
+
+#define VK_API_ALL_PROCS(x) \
+ VK_API_ALL_GLOBAL_PROCS(x) \
+ VK_API_ALL_INSTANCE_PROCS(x) \
+ VK_API_DEVICE_PROCS(x) \
+ VK_API_INSTANCE_PROCS_OPT(x) \
+ /* */
+
+#define VK_API_DECLARE_PROC(NAME) PFN_##NAME NAME = nullptr;
+// clang-format on
+
+struct VulkanAPI
+{
+ gfx::Result initFromGFX(gfx::IDevice* gfxDevice);
+
+ VkInstance instance = VK_NULL_HANDLE;
+ VkDevice device = VK_NULL_HANDLE;
+ VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;
+
+ VK_API_ALL_PROCS(VK_API_DECLARE_PROC)
+
+ gfx::Result initGlobalProcs();
+
+ /// Initialize the instance functions
+ gfx::Result initInstanceProcs();
+
+ /// Initialize the device functions
+ gfx::Result initDeviceProcs();
+
+ /// Clean up
+ ~VulkanAPI();
+};
+
+#define RETURN_ON_FAIL(x) \
+ { \
+ auto _res = x; \
+ if (_res != 0) \
+ { \
+ return -1; \
+ } \
+ }
+
+// Loads Vulkan library and creates a VkDevice.
+// Returns 0 if successful.
+int initializeVulkanDevice(VulkanAPI& api);