diff options
| author | Yong He <yonghe@outlook.com> | 2021-04-16 10:35:42 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2021-04-16 10:35:42 -0700 |
| commit | 2886bc35e7b023370a8b8d56d78e5335eee2eb98 (patch) | |
| tree | ef40d6029f2f024a95602efb921c27383cf887f5 /examples/hello-world/main.cpp | |
| parent | 79e92395f8ce3d92c446e3bb3250d19ce33decd5 (diff) | |
Add Hello world example. (#1797)
Diffstat (limited to 'examples/hello-world/main.cpp')
| -rw-r--r-- | examples/hello-world/main.cpp | 739 |
1 files changed, 413 insertions, 326 deletions
diff --git a/examples/hello-world/main.cpp b/examples/hello-world/main.cpp index 6b9104072..da149a8e0 100644 --- a/examples/hello-world/main.cpp +++ b/examples/hello-world/main.cpp @@ -1,137 +1,157 @@ // main.cpp -// This file implements an extremely simple example of loading and -// executing a Slang shader program. This is primarily an example -// of how to use Slang as a "drop-in" replacement for an existing -// HLSL compiler like the `D3DCompile` API. More advanced usage -// of advanced Slang language and API features is left to the -// next example. +// This file provides the application code for the `hello-world` example. // -// The comments in the file will attempt to explain concepts as -// they are introduced. -// -// Of course, in order to use the Slang API, we need to include -// its header. We have set up the build options for this project -// so that it is as simple as: + +// This example uses Vulkan to run a simple compute shader written in Slang. +// The goal is to demonstrate how to use the Slang API to cross compile +// shader code. // #include <slang.h> -// -// Other build setups are possible, and Slang doesn't assume that -// its include directory must be added to your global include -// path. - -// For the purposes of keeping the demo code as simple as possible, -// while still retaining some level of portability, our examples -// make use of a small platform and graphics API abstraction layer, -// which is included in the Slang source distribution under the -// `tools/` directory. -// -// Applications can of course use Slang without ever touching this -// abstraction layer, so we will not focus on it when explaining -// examples, except in places where best practices for interacting -// with Slang may depend on an application/engine making certain -// design choices in their abstraction layer. -// -#include "slang-gfx.h" -#include "gfx-util/shader-cursor.h" -#include "tools/platform/window.h" -#include "slang-com-ptr.h" -#include "source/core/slang-basic.h" +#include <slang-com-ptr.h> + +#include "vulkan-api.h" #include "examples/example-base/example-base.h" -using namespace gfx; -using namespace Slang; +using Slang::ComPtr; -// For the purposes of a small example, we will define the vertex data for a -// single triangle directly in the source file. It should be easy to extend -// this example to load data from an external source, if desired. -// -struct Vertex +struct HelloWorldExample { - float position[3]; - float color[3]; + // The Vulkan functions pointers result from loading the vulkan library. + VulkanAPI vkAPI; + + // Vulkan objects used in this example. + VkQueue queue; + VkCommandPool commandPool = VK_NULL_HANDLE; + + // Input and output buffers. + VkBuffer inOutBuffers[3] = {}; + VkDeviceMemory bufferMemories[3] = {}; + + const size_t inputElementCount = 16; + const size_t bufferSize = sizeof(float) * inputElementCount; + + // We use a staging buffer allocated on host-visible memory to + // upload/download data from GPU. + VkBuffer stagingBuffer = VK_NULL_HANDLE; + VkDeviceMemory stagingMemory = VK_NULL_HANDLE; + + VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE; + VkPipelineLayout pipelineLayout = VK_NULL_HANDLE; + VkPipeline pipeline = VK_NULL_HANDLE; + + // Initializes the Vulkan instance and device. + int initVulkanInstanceAndDevice(); + + // This function contains the most interesting part of this example. + // It loads the `hello-world.slang` shader and compile it using the Slang API + // into a SPIRV module, then create a Vulkan pipeline from the compiled shader. + int createComputePipelineFromShader(); + + // Creates the input and output buffers. + int createInOutBuffers(); + + // Sets up descriptor set bindings and dispatches the compute task. + int dispatchCompute(); + + // Reads back and prints the result of the compute task. + int printComputeResults(); + + // Main logic of this example. + int run(); + + ~HelloWorldExample(); + }; -static const int kVertexCount = 3; -static const Vertex kVertexData[kVertexCount] = +int main() { - { { 0, 0, 0.5 }, { 1, 0, 0 } }, - { { 0, 1, 0.5 }, { 0, 0, 1 } }, - { { 1, 0, 0.5 }, { 0, 1, 0 } }, -}; + HelloWorldExample example; + return example.run(); +} -// The example application will be implemented as a `struct`, so that -// we can scope the resources it allocates without using global variables. -// -struct HelloWorld : public WindowedAppBase +/************************************************************/ +/* HelloWorldExample Implementation */ +/************************************************************/ + +int HelloWorldExample::run() { + RETURN_ON_FAIL(initVulkanInstanceAndDevice()); + RETURN_ON_FAIL(createComputePipelineFromShader()); + RETURN_ON_FAIL(createInOutBuffers()); + RETURN_ON_FAIL(dispatchCompute()); + RETURN_ON_FAIL(printComputeResults()); + return 0; +} -// Many Slang API functions return detailed diagnostic information -// (error messages, warnings, etc.) as a "blob" of data, or return -// a null blob pointer instead if there were no issues. -// -// For convenience, we define a subroutine that will dump the information -// in a diagnostic blob if one is produced, and skip it otherwise. -// -void diagnoseIfNeeded(slang::IBlob* diagnosticsBlob) +int HelloWorldExample::initVulkanInstanceAndDevice() { - if( diagnosticsBlob != nullptr ) + if (initializeVulkanDevice(vkAPI) != 0) { - printf("%s", (const char*) diagnosticsBlob->getBufferPointer()); + printf("Failed to load Vulkan.\n"); + return -1; } + + VkCommandPoolCreateInfo poolCreateInfo = {}; + poolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; + poolCreateInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; + poolCreateInfo.queueFamilyIndex = vkAPI.queueFamilyIndex; + RETURN_ON_FAIL(vkAPI.vkCreateCommandPool( + vkAPI.device, &poolCreateInfo, nullptr, &commandPool)); + + vkAPI.vkGetDeviceQueue(vkAPI.device, vkAPI.queueFamilyIndex, 0, &queue); + return 0; } -// The main task an application cares about is compiling shader code -// from souce (if needed) and loading it through the chosen graphics API. -// -// In addition, an application may want to receive reflection information -// about the program, which is what a `slang::ProgramLayout` provides. -// -gfx::Result loadShaderProgram( - gfx::IDevice* device, - gfx::IShaderProgram** outProgram) +int HelloWorldExample::createComputePipelineFromShader() { - // We need to obatin a compilation session (`slang::ISession`) that will provide - // a scope to all the compilation and loading of code we do. - // - // Our example application uses the `gfx` graphics API abstraction layer, which already - // creates a Slang compilation session for us, so we just grab and use it here. - ComPtr<slang::ISession> slangSession; - slangSession = device->getSlangSession(); + // First we need to create slang global session with work with the Slang API. + ComPtr<slang::IGlobalSession> slangGlobalSession; + RETURN_ON_FAIL(slang::createGlobalSession(slangGlobalSession.writeRef())); + + // Next we create a compilation session to generate SPIRV code from Slang source. + slang::SessionDesc sessionDesc = {}; + slang::TargetDesc targetDesc = {}; + targetDesc.format = SLANG_SPIRV; + targetDesc.profile = slangGlobalSession->findProfile("glsl440"); + sessionDesc.targetCount = 1; + sessionDesc.targets = &targetDesc; + + ComPtr<slang::ISession> session; + RETURN_ON_FAIL(slangGlobalSession->createSession(sessionDesc, session.writeRef())); - // We can now start loading code into the slang session. + // Once the session has been obtained, we can start loading code into it. // // The simplest way to load code is by calling `loadModule` with the name of a Slang - // module. A call to `loadModule("MyStuff")` will behave more or less as if you + // module. A call to `loadModule("hello-world")` will behave more or less as if you // wrote: // - // import MyStuff; + // import hello_world; // // In a Slang shader file. The compiler will use its search paths to try to locate - // `MyModule.slang`, then compile and load that file. If a matching module had + // `hello-world.slang`, then compile and load that file. If a matching module had // already been loaded previously, that would be used directly. - // - ComPtr<slang::IBlob> diagnosticsBlob; - slang::IModule* module = slangSession->loadModule("shaders", diagnosticsBlob.writeRef()); - diagnoseIfNeeded(diagnosticsBlob); - if(!module) - return SLANG_FAIL; + slang::IModule* slangModule = nullptr; + { + ComPtr<slang::IBlob> diagnosticBlob; + slangModule = session->loadModule("hello-world", diagnosticBlob.writeRef()); + diagnoseIfNeeded(diagnosticBlob); + if (!slangModule) + return -1; + } - // Loading the `shaders` module will compile and check all the shader code in it, + // Loading the `hello-world` module will compile and check all the shader code in it, // including the shader entry points we want to use. Now that the module is loaded // we can look up those entry points by name. // // Note: If you are using this `loadModule` approach to load your shader code it is // important to tag your entry point functions with the `[shader("...")]` attribute - // (e.g., `[shader("vertex")] void vertexMain(...)`). Without that information there + // (e.g., `[shader("compute")] void computeMain(...)`). Without that information there // is no umambiguous way for the compiler to know which functions represent entry // points when it parses your code via `loadModule()`. // - ComPtr<slang::IEntryPoint> vertexEntryPoint; - SLANG_RETURN_ON_FAIL(module->findEntryPointByName("vertexMain", vertexEntryPoint.writeRef())); - // - ComPtr<slang::IEntryPoint> fragmentEntryPoint; - SLANG_RETURN_ON_FAIL(module->findEntryPointByName("fragmentMain", fragmentEntryPoint.writeRef())); + ComPtr<slang::IEntryPoint> entryPoint; + slangModule->findEntryPointByName("computeMain", entryPoint.writeRef()); // At this point we have a few different Slang API objects that represent // pieces of our code: `module`, `vertexEntryPoint`, and `fragmentEntryPoint`. @@ -147,18 +167,8 @@ gfx::Result loadShaderProgram( // and entry points. // Slang::List<slang::IComponentType*> componentTypes; - componentTypes.add(module); - - // Later on when we go to extract compiled kernel code for our vertex - // and fragment shaders, we will need to make use of their order within - // the composition, so we will record the relative ordering of the entry - // points here as we add them. - int entryPointCount = 0; - int vertexEntryPointIndex = entryPointCount++; - componentTypes.add(vertexEntryPoint); - - int fragmentEntryPointIndex = entryPointCount++; - componentTypes.add(fragmentEntryPoint); + componentTypes.add(slangModule); + componentTypes.add(entryPoint); // Actually creating the composite component type is a single operation // on the Slang session, but the operation could potentially fail if @@ -166,233 +176,310 @@ gfx::Result loadShaderProgram( // combine multiple copies of the same module), so we need to deal // with the possibility of diagnostic output. // - ComPtr<slang::IComponentType> linkedProgram; - SlangResult result = slangSession->createCompositeComponentType( - componentTypes.getBuffer(), - componentTypes.getCount(), - linkedProgram.writeRef(), - diagnosticsBlob.writeRef()); - diagnoseIfNeeded(diagnosticsBlob); - SLANG_RETURN_ON_FAIL(result); - - // Once we've described the particular composition of entry points - // that we want to compile, we defer to the graphics API layer - // to extract compiled kernel code and load it into the API-specific - // program representation. - // - gfx::IShaderProgram::Desc programDesc = {}; - programDesc.pipelineType = gfx::PipelineType::Graphics; - programDesc.slangProgram = linkedProgram; - SLANG_RETURN_ON_FAIL(device->createProgram(programDesc, outProgram)); - - return SLANG_OK; -} + ComPtr<slang::IComponentType> composedProgram; + { + ComPtr<slang::IBlob> diagnosticsBlob; + SlangResult result = session->createCompositeComponentType( + componentTypes.getBuffer(), + componentTypes.getCount(), + composedProgram.writeRef(), + diagnosticsBlob.writeRef()); + diagnoseIfNeeded(diagnosticsBlob); + RETURN_ON_FAIL(result); + } -// -// The above function shows the core of what is required to use the -// Slang API as a simple compiler (e.g., a drop-in replacement for -// fxc or dxc). -// -// The rest of this file implements an extremely simple rendering application -// that will execute the vertex/fragment shaders loaded with the function -// we have just defined. -// + // Now we can call `composedProgram->getEntryPointCode()` to retrieve the + // compiled SPIRV code that we will use to create a vulkan compute pipeline. + // This will trigger the final Slang compilation and spirv code generation. + ComPtr<slang::IBlob> spirvCode; + { + ComPtr<slang::IBlob> diagnosticsBlob; + SlangResult result = composedProgram->getEntryPointCode( + 0, 0, spirvCode.writeRef(), diagnosticsBlob.writeRef()); + diagnoseIfNeeded(diagnosticsBlob); + RETURN_ON_FAIL(result); + } -// We will define global variables for the various platform and -// graphics API objects that our application needs: -// -// As a reminder, *none* of these are Slang API objects. All -// of them come from the utility library we are using to simplify -// building an example program. -// -ComPtr<gfx::IPipelineState> gPipelineState; -ComPtr<gfx::IBufferResource> gVertexBuffer; + // The following steps are all Vulkan API calls to create a pipeline. + + // First we need to create a descriptor set layout and a pipeline layout. + // In this example, the pipeline layout is simple: we have a single descriptor + // set with three buffer descriptors for our input/output storage buffers. + // General applications typically has much more complicated pipeline layouts, + // and should consider using Slang's reflection API to learn about the shader + // parameter layout of a shader program. However, Slang's reflection API is + // out of scope of this example. + VkDescriptorSetLayoutCreateInfo descSetLayoutCreateInfo = { + VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO}; + descSetLayoutCreateInfo.bindingCount = 3; + VkDescriptorSetLayoutBinding bindings[3]; + for (int i = 0; i < 3; i++) + { + auto& binding = bindings[i]; + binding.binding = i; + binding.descriptorCount = 1; + binding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + binding.stageFlags = VK_SHADER_STAGE_ALL; + binding.pImmutableSamplers = nullptr; + } + descSetLayoutCreateInfo.pBindings = bindings; + RETURN_ON_FAIL(vkAPI.vkCreateDescriptorSetLayout( + vkAPI.device, &descSetLayoutCreateInfo, nullptr, &descriptorSetLayout)); + VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = { + VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO}; + pipelineLayoutCreateInfo.setLayoutCount = 1; + pipelineLayoutCreateInfo.pSetLayouts = &descriptorSetLayout; + RETURN_ON_FAIL(vkAPI.vkCreatePipelineLayout( + vkAPI.device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); + + // Next we create a shader module from the compiled SPIRV code. + VkShaderModuleCreateInfo shaderCreateInfo = {VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO}; + shaderCreateInfo.codeSize = spirvCode->getBufferSize(); + shaderCreateInfo.pCode = static_cast<const uint32_t*>(spirvCode->getBufferPointer()); + VkShaderModule vkShaderModule; + RETURN_ON_FAIL( + vkAPI.vkCreateShaderModule(vkAPI.device, &shaderCreateInfo, nullptr, &vkShaderModule)); + + // Now we have all we need to create a compute pipeline. + VkComputePipelineCreateInfo pipelineCreateInfo = { + VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO}; + pipelineCreateInfo.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; + pipelineCreateInfo.stage.module = vkShaderModule; + pipelineCreateInfo.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT; + pipelineCreateInfo.stage.pName = "main"; + pipelineCreateInfo.layout = pipelineLayout; + RETURN_ON_FAIL(vkAPI.vkCreateComputePipelines( + vkAPI.device, VK_NULL_HANDLE, 1, &pipelineCreateInfo, nullptr, &pipeline)); + + // We can destroy shader module now since it will no longer be used. + vkAPI.vkDestroyShaderModule(vkAPI.device, vkShaderModule, nullptr); + + return 0; +} -// Now that we've covered the function that actually loads and -// compiles our Slang shade code, we can go through the rest -// of the application code without as much commentary. -// -Slang::Result initialize() +int HelloWorldExample::createInOutBuffers() { - // Create a window for our application to render into. - // - initializeBase("hello-world", 1024, 768); - - // We will create objects needed to configur the "input assembler" - // (IA) stage of the D3D pipeline. - // - // First, we create an input layout: - // - InputElementDesc inputElements[] = { - { "POSITION", 0, Format::RGB_Float32, offsetof(Vertex, position) }, - { "COLOR", 0, Format::RGB_Float32, offsetof(Vertex, color) }, - }; - auto inputLayout = gDevice->createInputLayout( - &inputElements[0], - 2); - if(!inputLayout) return SLANG_FAIL; - - // Next we allocate a vertex buffer for our pre-initialized - // vertex data. - // - IBufferResource::Desc vertexBufferDesc; - vertexBufferDesc.init(kVertexCount * sizeof(Vertex)); - vertexBufferDesc.setDefaults(IResource::Usage::VertexBuffer); - gVertexBuffer = gDevice->createBufferResource( - IResource::Usage::VertexBuffer, - vertexBufferDesc, - &kVertexData[0]); - if(!gVertexBuffer) return SLANG_FAIL; - - // Now we will use our `loadShaderProgram` function to load - // the code from `shaders.slang` into the graphics API. - // - ComPtr<IShaderProgram> shaderProgram; - SLANG_RETURN_ON_FAIL(loadShaderProgram(gDevice, shaderProgram.writeRef())); - - // Following the D3D12/Vulkan style of API, we need a pipeline state object - // (PSO) to encapsulate the configuration of the overall graphics pipeline. - // - GraphicsPipelineStateDesc desc; - desc.inputLayout = inputLayout; - desc.program = shaderProgram; - desc.framebufferLayout = gFramebufferLayout; - auto pipelineState = gDevice->createGraphicsPipelineState(desc); - if (!pipelineState) - return SLANG_FAIL; + // Create input and output buffers that resides in device-local memory. + for (int i = 0; i < 3; i++) + { + VkBufferCreateInfo bufferCreateInfo = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; + bufferCreateInfo.size = bufferSize; + bufferCreateInfo.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | + VK_BUFFER_USAGE_TRANSFER_SRC_BIT | + VK_BUFFER_USAGE_TRANSFER_DST_BIT; + RETURN_ON_FAIL( + vkAPI.vkCreateBuffer(vkAPI.device, &bufferCreateInfo, nullptr, &inOutBuffers[i])); + VkMemoryRequirements memoryReqs = {}; + vkAPI.vkGetBufferMemoryRequirements(vkAPI.device, inOutBuffers[i], &memoryReqs); + + int memoryTypeIndex = vkAPI.findMemoryTypeIndex( + memoryReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); + assert(memoryTypeIndex >= 0); + + VkMemoryPropertyFlags actualMemoryProperites = + vkAPI.deviceMemoryProperties.memoryTypes[memoryTypeIndex].propertyFlags; + + VkMemoryAllocateInfo allocateInfo = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; + allocateInfo.allocationSize = memoryReqs.size; + allocateInfo.memoryTypeIndex = memoryTypeIndex; + RETURN_ON_FAIL( + vkAPI.vkAllocateMemory(vkAPI.device, &allocateInfo, nullptr, &bufferMemories[i])); + RETURN_ON_FAIL( + vkAPI.vkBindBufferMemory(vkAPI.device, inOutBuffers[i], bufferMemories[i], 0)); + } - gPipelineState = pipelineState; + // Create the device memory and buffer object used for reading/writing + // data to/from the device local buffers. + { + VkBufferCreateInfo bufferCreateInfo = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO}; + bufferCreateInfo.size = bufferSize; + bufferCreateInfo.usage = + VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; + RETURN_ON_FAIL( + vkAPI.vkCreateBuffer(vkAPI.device, &bufferCreateInfo, nullptr, &stagingBuffer)); + VkMemoryRequirements memoryReqs = {}; + vkAPI.vkGetBufferMemoryRequirements(vkAPI.device, stagingBuffer, &memoryReqs); + + int memoryTypeIndex = vkAPI.findMemoryTypeIndex( + memoryReqs.memoryTypeBits, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + assert(memoryTypeIndex >= 0); + + VkMemoryPropertyFlags actualMemoryProperites = + vkAPI.deviceMemoryProperties.memoryTypes[memoryTypeIndex].propertyFlags; + + VkMemoryAllocateInfo allocateInfo = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO}; + allocateInfo.allocationSize = memoryReqs.size; + allocateInfo.memoryTypeIndex = memoryTypeIndex; + RETURN_ON_FAIL( + vkAPI.vkAllocateMemory(vkAPI.device, &allocateInfo, nullptr, &stagingMemory)); + RETURN_ON_FAIL(vkAPI.vkBindBufferMemory(vkAPI.device, stagingBuffer, stagingMemory, 0)); + } - return SLANG_OK; + // Map staging buffer and writes in the initial input content. + float* stagingBufferData = nullptr; + vkAPI.vkMapMemory(vkAPI.device, stagingMemory, 0, bufferSize, 0, (void**)&stagingBufferData); + if (!stagingBufferData) + return -1; + for (size_t i = 0; i < inputElementCount; i++) + stagingBufferData[i] = static_cast<float>(i); + vkAPI.vkUnmapMemory(vkAPI.device, stagingMemory); + + // Create a temporary command buffer for recording commands that writes initial + // data into the input buffers. + VkCommandBuffer uploadCommandBuffer; + VkCommandBufferAllocateInfo commandBufferAllocInfo = { + VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO}; + commandBufferAllocInfo.commandBufferCount = 1; + commandBufferAllocInfo.commandPool = commandPool; + commandBufferAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + RETURN_ON_FAIL(vkAPI.vkAllocateCommandBuffers(vkAPI.device, &commandBufferAllocInfo, &uploadCommandBuffer)); + + VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; + vkAPI.vkBeginCommandBuffer(uploadCommandBuffer, &beginInfo); + VkBufferCopy bufferCopy = {}; + bufferCopy.size = bufferSize; + vkAPI.vkCmdCopyBuffer(uploadCommandBuffer, stagingBuffer, inOutBuffers[0], 1, &bufferCopy); + vkAPI.vkCmdCopyBuffer(uploadCommandBuffer, stagingBuffer, inOutBuffers[1], 1, &bufferCopy); + vkAPI.vkEndCommandBuffer(uploadCommandBuffer); + VkSubmitInfo submitInfo = {VK_STRUCTURE_TYPE_SUBMIT_INFO}; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &uploadCommandBuffer; + vkAPI.vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); + vkAPI.vkQueueWaitIdle(queue); + vkAPI.vkFreeCommandBuffers(vkAPI.device, commandPool, 1, &uploadCommandBuffer); + return 0; } -// With the initialization out of the way, we can now turn our attention -// to the per-frame rendering logic. As with the initialization, there is -// nothing really Slang-specific here, so the commentary doesn't need -// to be very detailed. -// -virtual void renderFrame(int frameBufferIndex) override +int HelloWorldExample::dispatchCompute() { - ComPtr<ICommandBuffer> commandBuffer = gTransientHeaps[frameBufferIndex]->createCommandBuffer(); - auto renderEncoder = commandBuffer->encodeRenderCommands(gRenderPass, gFramebuffers[frameBufferIndex]); - - gfx::Viewport viewport = {}; - viewport.maxZ = 1.0f; - viewport.extentX = (float)windowWidth; - viewport.extentY = (float)windowHeight; - renderEncoder->setViewportAndScissor(viewport); - - // In order to bind shader parameters to the pipeline, we need - // to know how those parameters were assigned to locations/bindings/registers - // for the target graphics API. - // - // The Slang compiler assigns locations to parameters in a deterministic - // fashion, so it is possible for a programmer to hard-code locations - // into their application code that will match up with their shaders. - // - // Hard-coding of locations can become intractable as an application needs - // to support more different target platforms and graphics APIs, as well - // as more shaders with different specialized variants. - // - // Rather than rely on hard-coded locations, our examples will make use of - // reflection information provided by the Slang compiler (see `programLayout` - // above), and our example graphics API layer will translate that reflection - // information into a layout for a "root shader object." - // - // The root object will store values/bindings for all of the parameters in - // the `IShaderProgram` used to create the pipeline state. At a conceptual - // level we can think of `rootObject` as representing the "global scope" of - // the shader program that was loaded; it has entries for each global shader - // parameter that was declared. - // - // Readers who are familiar with D3D12 or Vulkan might think of this root - // layout as being similar in spirit to a "root signature" or "pipeline layout." - // - // We start parameter binding by binding the pipeline state in command encoder. - // This method will return a transient root shader object for us to write our - // shader parameters into. - // - auto rootObject = renderEncoder->bindPipeline(gPipelineState); - - // We will update the model-view-projection matrix that is passed - // into the shader code via the `Uniforms` buffer on a per-frame - // basis, even though the data that is loaded does not change - // per-frame (we always use an identity matrix). - // - auto deviceInfo = gDevice->getDeviceInfo(); - - // We know that `rootObject` is a root shader object created - // from our program, and that it is set up to hold values for - // all the parameter of that program. In order to actually - // set values, we need to be able to look up the location - // of speciic parameter that we want to set. - // - // Our example graphics API layer supports this operation - // with the idea of a *shader cursor* which can be thought - // of as pointing "into" a particular shader object at - // some location/offset. This design choice abstracts over - // the many ways that different platforms and APIs represent - // the necessary offset information. - // - // We construct an initial shader cursor that points at the - // entire shader program. You can think of this as akin to - // a diretory path of `/` for the root directory in a file - // system. - // - ShaderCursor rootCursor(rootObject); - // - // Next, we use a convenience overload of `operator[]` to - // navigate from the root cursor down to the parameter we - // want to set. - // - // The operation `rootCursor["Uniforms"]` looks up the - // offset/location of the global shader parameter `Uniforms` - // (which is a uniform/constant buffer), and the subsequent - // `["modelViewProjection"]` step navigates from there down - // to the member named `modelViewProjection` in that buffer. - // - // Once we have formed a cursor that "points" at the - // model-view projection matrix, we can set its data directly. - // - rootCursor["Uniforms"]["modelViewProjection"].setData( - deviceInfo.identityProjectionMatrix, sizeof(float) * 16); - // - // Some readers might be concerned about the performance o - // the above operations because of the use of strings. For - // those readers, here are two things to note: - // - // * While these `operator[]` steps do need to perform string - // comparisons, they do *not* make copies of the strings or - // perform any heap allocation. - // - // * There are other overloads of `operator[]` that use the - // *index* of a parameter/field instead of its name, and those - // operations have fixed/constant overhead and perform no - // string comparisons. The indices used are independent of - // the target platform and graphics API, and can thus be - // hard-coded even in cross-platform code. - // - - // We also need to set up a few pieces of fixed-function pipeline - // state that are not bound by the pipeline state above. - // - renderEncoder->setVertexBuffer(0, gVertexBuffer, sizeof(Vertex)); - renderEncoder->setPrimitiveTopology(PrimitiveTopology::TriangleList); - - // Finally, we are ready to issue a draw call for a single triangle. - // - renderEncoder->draw(3); - renderEncoder->endEncoding(); - commandBuffer->close(); - gQueue->executeCommandBuffer(commandBuffer); - - // With that, we are done drawing for one frame, and ready for the next. - // - gSwapchain->present(); + // Create a descriptor pool. + VkDescriptorPoolCreateInfo descriptorPoolCreateInfo = { + VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO}; + VkDescriptorPoolSize poolSizes[] = { + VkDescriptorPoolSize{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 16}}; + descriptorPoolCreateInfo.maxSets = 4; + descriptorPoolCreateInfo.poolSizeCount = sizeof(poolSizes) / sizeof(VkDescriptorPoolSize); + descriptorPoolCreateInfo.pPoolSizes = poolSizes; + descriptorPoolCreateInfo.flags = 0; + VkDescriptorPool descriptorPool = VK_NULL_HANDLE; + RETURN_ON_FAIL(vkAPI.vkCreateDescriptorPool( + vkAPI.device, &descriptorPoolCreateInfo, nullptr, &descriptorPool)); + + // Allocate descriptor set. + VkDescriptorSetAllocateInfo descSetAllocInfo = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO}; + descSetAllocInfo.descriptorPool = descriptorPool; + descSetAllocInfo.descriptorSetCount = 1; + descSetAllocInfo.pSetLayouts = &descriptorSetLayout; + VkDescriptorSet descriptorSet = VK_NULL_HANDLE; + RETURN_ON_FAIL(vkAPI.vkAllocateDescriptorSets(vkAPI.device, &descSetAllocInfo, &descriptorSet)); + + // Write descriptor set. + VkWriteDescriptorSet descriptorSetWrites[3] = {}; + VkDescriptorBufferInfo bufferInfo[3]; + for (int i = 0; i < 3; i++) + { + bufferInfo[i].buffer = inOutBuffers[i]; + bufferInfo[i].offset = 0; + bufferInfo[i].range = bufferSize; + + descriptorSetWrites[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + descriptorSetWrites[i].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER; + descriptorSetWrites[i].descriptorCount = 1; + descriptorSetWrites[i].dstBinding = i; + descriptorSetWrites[i].dstSet = descriptorSet; + descriptorSetWrites[i].pBufferInfo = &bufferInfo[i]; + } + vkAPI.vkUpdateDescriptorSets(vkAPI.device, 3, descriptorSetWrites, 0, nullptr); + + // Allocate command buffer and record dispatch commands. + VkCommandBuffer commandBuffer; + VkCommandBufferAllocateInfo commandBufferAllocInfo = { + VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO}; + commandBufferAllocInfo.commandBufferCount = 1; + commandBufferAllocInfo.commandPool = commandPool; + commandBufferAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + RETURN_ON_FAIL( + vkAPI.vkAllocateCommandBuffers(vkAPI.device, &commandBufferAllocInfo, &commandBuffer)); + VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; + vkAPI.vkBeginCommandBuffer(commandBuffer, &beginInfo); + vkAPI.vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); + vkAPI.vkCmdBindDescriptorSets( + commandBuffer, + VK_PIPELINE_BIND_POINT_COMPUTE, + pipelineLayout, + 0, + 1, + &descriptorSet, + 0, + nullptr); + vkAPI.vkCmdDispatch(commandBuffer, (uint32_t)inputElementCount, 1, 1); + vkAPI.vkEndCommandBuffer(commandBuffer); + + // Submit command buffer and wait. + VkSubmitInfo submitInfo = {VK_STRUCTURE_TYPE_SUBMIT_INFO}; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + vkAPI.vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); + vkAPI.vkQueueWaitIdle(queue); + vkAPI.vkFreeCommandBuffers(vkAPI.device, commandPool, 1, &commandBuffer); + + // Clean up. + vkAPI.vkDestroyDescriptorPool(vkAPI.device, descriptorPool, nullptr); + return 0; } -}; +int HelloWorldExample::printComputeResults() +{ + // Allocate command buffer to read back data. + VkCommandBuffer commandBuffer; + VkCommandBufferAllocateInfo commandBufferAllocInfo = { + VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO}; + commandBufferAllocInfo.commandBufferCount = 1; + commandBufferAllocInfo.commandPool = commandPool; + commandBufferAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + RETURN_ON_FAIL( + vkAPI.vkAllocateCommandBuffers(vkAPI.device, &commandBufferAllocInfo, &commandBuffer)); + + // Record commands to copy output buffer into staging buffer. + VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; + vkAPI.vkBeginCommandBuffer(commandBuffer, &beginInfo); + VkBufferCopy bufferCopy = {}; + bufferCopy.size = bufferSize; + vkAPI.vkCmdCopyBuffer(commandBuffer, inOutBuffers[2], stagingBuffer, 1, &bufferCopy); + vkAPI.vkEndCommandBuffer(commandBuffer); + + // Execute command buffer and wait. + VkSubmitInfo submitInfo = {VK_STRUCTURE_TYPE_SUBMIT_INFO}; + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &commandBuffer; + vkAPI.vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE); + vkAPI.vkQueueWaitIdle(queue); + vkAPI.vkFreeCommandBuffers(vkAPI.device, commandPool, 1, &commandBuffer); + + // Map and read back staging buffer. + float* stagingBufferData = nullptr; + vkAPI.vkMapMemory(vkAPI.device, stagingMemory, 0, bufferSize, 0, (void**)&stagingBufferData); + if (!stagingBufferData) + return -1; + for (size_t i = 0; i < inputElementCount; i++) + { + printf("%f\n", stagingBufferData[i]); + } + return 0; +} -// This macro instantiates an appropriate main function to -// run the application defined above. -PLATFORM_UI_MAIN(innerMain<HelloWorld>) +HelloWorldExample::~HelloWorldExample() +{ + vkAPI.vkDestroyPipeline(vkAPI.device, pipeline, nullptr); + for (int i = 0; i < 3; i++) + { + vkAPI.vkDestroyBuffer(vkAPI.device, inOutBuffers[i], nullptr); + vkAPI.vkFreeMemory(vkAPI.device, bufferMemories[i], nullptr); + } + vkAPI.vkDestroyBuffer(vkAPI.device, stagingBuffer, nullptr); + vkAPI.vkFreeMemory(vkAPI.device, stagingMemory, nullptr); + vkAPI.vkDestroyPipelineLayout(vkAPI.device, pipelineLayout, nullptr); + vkAPI.vkDestroyDescriptorSetLayout(vkAPI.device, descriptorSetLayout, nullptr); + vkAPI.vkDestroyCommandPool(vkAPI.device, commandPool, nullptr); +} |
