diff options
Diffstat (limited to 'examples')
| -rw-r--r-- | examples/CMakeLists.txt | 1 | ||||
| -rw-r--r-- | examples/reflection-parameter-blocks/README.md | 4 | ||||
| -rw-r--r-- | examples/reflection-parameter-blocks/common.slang | 113 | ||||
| -rw-r--r-- | examples/reflection-parameter-blocks/main.cpp | 694 | ||||
| -rw-r--r-- | examples/reflection-parameter-blocks/shader.slang | 26 | ||||
| -rw-r--r-- | examples/reflection-parameter-blocks/vulkan-api.cpp | 285 | ||||
| -rw-r--r-- | examples/reflection-parameter-blocks/vulkan-api.h | 128 |
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); |
