diff options
Diffstat (limited to 'examples/reflection-parameter-blocks/main.cpp')
| -rw-r--r-- | examples/reflection-parameter-blocks/main.cpp | 694 |
1 files changed, 694 insertions, 0 deletions
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; +} |
