yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

Gangzheng TongConvert gfx unit tests and examples to use slang-rhi (#7577)43d0c2100

master
21.5 KiB526 linesraw
1// main.cpp
2
3// This file provides the application code for the `hello-world` example.
4//
5
6// This example uses Vulkan to run a simple compute shader written in Slang.
7// The goal is to demonstrate how to use the Slang API to cross compile
8// shader code.
9//
10#include "core/slang-string-util.h"
11#include "examples/example-base/example-base.h"
12#include "examples/example-base/test-base.h"
13#include "slang-com-ptr.h"
14#include "slang.h"
15#include "vulkan-api.h"
16
17using Slang::ComPtr;
18
19static const ExampleResources resourceBase("hello-world");
20
21struct HelloWorldExample : public TestBase
22{
23    // The Vulkan functions pointers result from loading the vulkan library.
24    VulkanAPI vkAPI;
25
26    // Vulkan objects used in this example.
27    VkQueue queue;
28    VkCommandPool commandPool = VK_NULL_HANDLE;
29
30    // Input and output buffers.
31    VkBuffer inOutBuffers[3] = {};
32    VkDeviceMemory bufferMemories[3] = {};
33
34    const size_t inputElementCount = 16;
35    const size_t bufferSize = sizeof(float) * inputElementCount;
36
37    // We use a staging buffer allocated on host-visible memory to
38    // upload/download data from GPU.
39    VkBuffer stagingBuffer = VK_NULL_HANDLE;
40    VkDeviceMemory stagingMemory = VK_NULL_HANDLE;
41
42    VkDescriptorSetLayout descriptorSetLayout = VK_NULL_HANDLE;
43    VkPipelineLayout pipelineLayout = VK_NULL_HANDLE;
44    VkPipeline pipeline = VK_NULL_HANDLE;
45
46    // Initializes the Vulkan instance and device.
47    int initVulkanInstanceAndDevice();
48
49    // This function contains the most interesting part of this example.
50    // It loads the `hello-world.slang` shader and compile it using the Slang API
51    // into a SPIRV module, then create a Vulkan pipeline from the compiled shader.
52    int createComputePipelineFromShader();
53
54    // Creates the input and output buffers.
55    int createInOutBuffers();
56
57    // Sets up descriptor set bindings and dispatches the compute task.
58    int dispatchCompute();
59
60    // Reads back and prints the result of the compute task.
61    int printComputeResults();
62
63    // Main logic of this example.
64    int run();
65
66    ~HelloWorldExample();
67};
68
69
70int exampleMain(int argc, char** argv)
71{
72    HelloWorldExample example;
73    example.parseOption(argc, argv);
74    return example.run();
75}
76
77/************************************************************/
78/* HelloWorldExample Implementation */
79/************************************************************/
80
81int HelloWorldExample::run()
82{
83    // If VK failed to initialize, skip running but return success anyway.
84    // This allows our automated testing to distinguish between essential failures and the
85    // case where the application is just not supported.
86    if (int result = initVulkanInstanceAndDevice())
87        return (vkAPI.device == VK_NULL_HANDLE) ? 0 : result;
88    RETURN_ON_FAIL(createComputePipelineFromShader());
89    RETURN_ON_FAIL(createInOutBuffers());
90    RETURN_ON_FAIL(dispatchCompute());
91    RETURN_ON_FAIL(printComputeResults());
92    return 0;
93}
94
95int HelloWorldExample::initVulkanInstanceAndDevice()
96{
97    if (initializeVulkanDevice(vkAPI) != 0)
98    {
99        printf("Failed to load Vulkan.\n");
100        return -1;
101    }
102
103    VkCommandPoolCreateInfo poolCreateInfo = {};
104    poolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
105    poolCreateInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
106    poolCreateInfo.queueFamilyIndex = vkAPI.queueFamilyIndex;
107    RETURN_ON_FAIL(vkAPI.vkCreateCommandPool(vkAPI.device, &poolCreateInfo, nullptr, &commandPool));
108
109    vkAPI.vkGetDeviceQueue(vkAPI.device, vkAPI.queueFamilyIndex, 0, &queue);
110    return 0;
111}
112
113int HelloWorldExample::createComputePipelineFromShader()
114{
115    // First we need to create slang global session with work with the Slang API.
116    ComPtr<slang::IGlobalSession> slangGlobalSession;
117    RETURN_ON_FAIL(slang::createGlobalSession(slangGlobalSession.writeRef()));
118
119    // Next we create a compilation session to generate SPIRV code from Slang source.
120    slang::SessionDesc sessionDesc = {};
121    slang::TargetDesc targetDesc = {};
122    targetDesc.format = SLANG_SPIRV;
123    targetDesc.profile = slangGlobalSession->findProfile("spirv_1_5");
124    targetDesc.flags = 0;
125
126
127    sessionDesc.targets = &targetDesc;
128    sessionDesc.targetCount = 1;
129    sessionDesc.compilerOptionEntryCount = 0;
130
131    ComPtr<slang::ISession> session;
132    RETURN_ON_FAIL(slangGlobalSession->createSession(sessionDesc, session.writeRef()));
133
134    // Once the session has been obtained, we can start loading code into it.
135    //
136    // The simplest way to load code is by calling `loadModule` with the name of a Slang
137    // module. A call to `loadModule("hello-world")` will behave more or less as if you
138    // wrote:
139    //
140    //      import hello_world;
141    //
142    // In a Slang shader file. The compiler will use its search paths to try to locate
143    // `hello-world.slang`, then compile and load that file. If a matching module had
144    // already been loaded previously, that would be used directly.
145    slang::IModule* slangModule = nullptr;
146    {
147        ComPtr<slang::IBlob> diagnosticBlob;
148        Slang::String path = resourceBase.resolveResource("hello-world.slang");
149        slangModule = session->loadModule(path.getBuffer(), diagnosticBlob.writeRef());
150        diagnoseIfNeeded(diagnosticBlob);
151        if (!slangModule)
152            return -1;
153    }
154
155    // Loading the `hello-world` module will compile and check all the shader code in it,
156    // including the shader entry points we want to use. Now that the module is loaded
157    // we can look up those entry points by name.
158    //
159    // Note: If you are using this `loadModule` approach to load your shader code it is
160    // important to tag your entry point functions with the `[shader("...")]` attribute
161    // (e.g., `[shader("compute")] void computeMain(...)`). Without that information there
162    // is no umambiguous way for the compiler to know which functions represent entry
163    // points when it parses your code via `loadModule()`.
164    //
165    ComPtr<slang::IEntryPoint> entryPoint;
166    slangModule->findEntryPointByName("computeMain", entryPoint.writeRef());
167
168    // At this point we have a few different Slang API objects that represent
169    // pieces of our code: `module`, `vertexEntryPoint`, and `fragmentEntryPoint`.
170    //
171    // A single Slang module could contain many different entry points (e.g.,
172    // four vertex entry points, three fragment entry points, and two compute
173    // shaders), and before we try to generate output code for our target API
174    // we need to identify which entry points we plan to use together.
175    //
176    // Modules and entry points are both examples of *component types* in the
177    // Slang API. The API also provides a way to build a *composite* out of
178    // other pieces, and that is what we are going to do with our module
179    // and entry points.
180    //
181    Slang::List<slang::IComponentType*> componentTypes;
182    componentTypes.add(slangModule);
183    componentTypes.add(entryPoint);
184
185    // Actually creating the composite component type is a single operation
186    // on the Slang session, but the operation could potentially fail if
187    // something about the composite was invalid (e.g., you are trying to
188    // combine multiple copies of the same module), so we need to deal
189    // with the possibility of diagnostic output.
190    //
191    ComPtr<slang::IComponentType> composedProgram;
192    {
193        ComPtr<slang::IBlob> diagnosticsBlob;
194        SlangResult result = session->createCompositeComponentType(
195            componentTypes.getBuffer(),
196            componentTypes.getCount(),
197            composedProgram.writeRef(),
198            diagnosticsBlob.writeRef());
199        diagnoseIfNeeded(diagnosticsBlob);
200        RETURN_ON_FAIL(result);
201    }
202
203    // Now we can call `composedProgram->getEntryPointCode()` to retrieve the
204    // compiled SPIRV code that we will use to create a vulkan compute pipeline.
205    // This will trigger the final Slang compilation and spirv code generation.
206    ComPtr<slang::IBlob> spirvCode;
207    {
208        ComPtr<slang::IBlob> diagnosticsBlob;
209        SlangResult result = composedProgram->getEntryPointCode(
210            0,
211            0,
212            spirvCode.writeRef(),
213            diagnosticsBlob.writeRef());
214        diagnoseIfNeeded(diagnosticsBlob);
215        RETURN_ON_FAIL(result);
216
217        if (isTestMode())
218        {
219            printEntrypointHashes(1, 1, composedProgram);
220        }
221    }
222
223    // The following steps are all Vulkan API calls to create a pipeline.
224
225    // First we need to create a descriptor set layout and a pipeline layout.
226    // In this example, the pipeline layout is simple: we have a single descriptor
227    // set with three buffer descriptors for our input/output storage buffers.
228    // General applications typically has much more complicated pipeline layouts,
229    // and should consider using Slang's reflection API to learn about the shader
230    // parameter layout of a shader program. However, Slang's reflection API is
231    // out of scope of this example.
232    VkDescriptorSetLayoutCreateInfo descSetLayoutCreateInfo = {
233        VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO};
234    descSetLayoutCreateInfo.bindingCount = 3;
235    VkDescriptorSetLayoutBinding bindings[3];
236    for (int i = 0; i < 3; i++)
237    {
238        auto& binding = bindings[i];
239        binding.binding = i;
240        binding.descriptorCount = 1;
241        binding.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
242        binding.stageFlags = VK_SHADER_STAGE_ALL;
243        binding.pImmutableSamplers = nullptr;
244    }
245    descSetLayoutCreateInfo.pBindings = bindings;
246    RETURN_ON_FAIL(vkAPI.vkCreateDescriptorSetLayout(
247        vkAPI.device,
248        &descSetLayoutCreateInfo,
249        nullptr,
250        &descriptorSetLayout));
251    VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = {
252        VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO};
253    pipelineLayoutCreateInfo.setLayoutCount = 1;
254    pipelineLayoutCreateInfo.pSetLayouts = &descriptorSetLayout;
255    RETURN_ON_FAIL(vkAPI.vkCreatePipelineLayout(
256        vkAPI.device,
257        &pipelineLayoutCreateInfo,
258        nullptr,
259        &pipelineLayout));
260
261    // Next we create a shader module from the compiled SPIRV code.
262    VkShaderModuleCreateInfo shaderCreateInfo = {VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO};
263    shaderCreateInfo.codeSize = spirvCode->getBufferSize();
264    shaderCreateInfo.pCode = static_cast<const uint32_t*>(spirvCode->getBufferPointer());
265    VkShaderModule vkShaderModule;
266    RETURN_ON_FAIL(
267        vkAPI.vkCreateShaderModule(vkAPI.device, &shaderCreateInfo, nullptr, &vkShaderModule));
268
269    // Now we have all we need to create a compute pipeline.
270    VkComputePipelineCreateInfo pipelineCreateInfo = {
271        VK_STRUCTURE_TYPE_COMPUTE_PIPELINE_CREATE_INFO};
272    pipelineCreateInfo.stage.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
273    pipelineCreateInfo.stage.module = vkShaderModule;
274    pipelineCreateInfo.stage.stage = VK_SHADER_STAGE_COMPUTE_BIT;
275    pipelineCreateInfo.stage.pName = "main";
276    pipelineCreateInfo.layout = pipelineLayout;
277    RETURN_ON_FAIL(vkAPI.vkCreateComputePipelines(
278        vkAPI.device,
279        VK_NULL_HANDLE,
280        1,
281        &pipelineCreateInfo,
282        nullptr,
283        &pipeline));
284
285    // We can destroy shader module now since it will no longer be used.
286    vkAPI.vkDestroyShaderModule(vkAPI.device, vkShaderModule, nullptr);
287
288    return 0;
289}
290
291int HelloWorldExample::createInOutBuffers()
292{
293    // Create input and output buffers that resides in device-local memory.
294    for (int i = 0; i < 3; i++)
295    {
296        VkBufferCreateInfo bufferCreateInfo = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
297        bufferCreateInfo.size = bufferSize;
298        bufferCreateInfo.usage = VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
299                                 VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
300                                 VK_BUFFER_USAGE_TRANSFER_DST_BIT;
301        RETURN_ON_FAIL(
302            vkAPI.vkCreateBuffer(vkAPI.device, &bufferCreateInfo, nullptr, &inOutBuffers[i]));
303        VkMemoryRequirements memoryReqs = {};
304        vkAPI.vkGetBufferMemoryRequirements(vkAPI.device, inOutBuffers[i], &memoryReqs);
305
306        int memoryTypeIndex = vkAPI.findMemoryTypeIndex(
307            memoryReqs.memoryTypeBits,
308            VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
309        assert(memoryTypeIndex >= 0);
310
311        VkMemoryPropertyFlags actualMemoryProperites =
312            vkAPI.deviceMemoryProperties.memoryTypes[memoryTypeIndex].propertyFlags;
313
314        VkMemoryAllocateInfo allocateInfo = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
315        allocateInfo.allocationSize = memoryReqs.size;
316        allocateInfo.memoryTypeIndex = memoryTypeIndex;
317        RETURN_ON_FAIL(
318            vkAPI.vkAllocateMemory(vkAPI.device, &allocateInfo, nullptr, &bufferMemories[i]));
319        RETURN_ON_FAIL(
320            vkAPI.vkBindBufferMemory(vkAPI.device, inOutBuffers[i], bufferMemories[i], 0));
321    }
322
323    // Create the device memory and buffer object used for reading/writing
324    // data to/from the device local buffers.
325    {
326        VkBufferCreateInfo bufferCreateInfo = {VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
327        bufferCreateInfo.size = bufferSize;
328        bufferCreateInfo.usage =
329            VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
330        RETURN_ON_FAIL(
331            vkAPI.vkCreateBuffer(vkAPI.device, &bufferCreateInfo, nullptr, &stagingBuffer));
332        VkMemoryRequirements memoryReqs = {};
333        vkAPI.vkGetBufferMemoryRequirements(vkAPI.device, stagingBuffer, &memoryReqs);
334
335        int memoryTypeIndex = vkAPI.findMemoryTypeIndex(
336            memoryReqs.memoryTypeBits,
337            VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
338        assert(memoryTypeIndex >= 0);
339
340        VkMemoryPropertyFlags actualMemoryProperites =
341            vkAPI.deviceMemoryProperties.memoryTypes[memoryTypeIndex].propertyFlags;
342
343        VkMemoryAllocateInfo allocateInfo = {VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO};
344        allocateInfo.allocationSize = memoryReqs.size;
345        allocateInfo.memoryTypeIndex = memoryTypeIndex;
346        RETURN_ON_FAIL(
347            vkAPI.vkAllocateMemory(vkAPI.device, &allocateInfo, nullptr, &stagingMemory));
348        RETURN_ON_FAIL(vkAPI.vkBindBufferMemory(vkAPI.device, stagingBuffer, stagingMemory, 0));
349    }
350
351    // Map staging buffer and writes in the initial input content.
352    float* stagingBufferData = nullptr;
353    vkAPI.vkMapMemory(vkAPI.device, stagingMemory, 0, bufferSize, 0, (void**)&stagingBufferData);
354    if (!stagingBufferData)
355        return -1;
356    for (size_t i = 0; i < inputElementCount; i++)
357        stagingBufferData[i] = static_cast<float>(i);
358    vkAPI.vkUnmapMemory(vkAPI.device, stagingMemory);
359
360    // Create a temporary command buffer for recording commands that writes initial
361    // data into the input buffers.
362    VkCommandBuffer uploadCommandBuffer;
363    VkCommandBufferAllocateInfo commandBufferAllocInfo = {
364        VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
365    commandBufferAllocInfo.commandBufferCount = 1;
366    commandBufferAllocInfo.commandPool = commandPool;
367    commandBufferAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
368    RETURN_ON_FAIL(vkAPI.vkAllocateCommandBuffers(
369        vkAPI.device,
370        &commandBufferAllocInfo,
371        &uploadCommandBuffer));
372
373    VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
374    vkAPI.vkBeginCommandBuffer(uploadCommandBuffer, &beginInfo);
375    VkBufferCopy bufferCopy = {};
376    bufferCopy.size = bufferSize;
377    vkAPI.vkCmdCopyBuffer(uploadCommandBuffer, stagingBuffer, inOutBuffers[0], 1, &bufferCopy);
378    vkAPI.vkCmdCopyBuffer(uploadCommandBuffer, stagingBuffer, inOutBuffers[1], 1, &bufferCopy);
379    vkAPI.vkEndCommandBuffer(uploadCommandBuffer);
380    VkSubmitInfo submitInfo = {VK_STRUCTURE_TYPE_SUBMIT_INFO};
381    submitInfo.commandBufferCount = 1;
382    submitInfo.pCommandBuffers = &uploadCommandBuffer;
383    vkAPI.vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE);
384    vkAPI.vkQueueWaitIdle(queue);
385    vkAPI.vkFreeCommandBuffers(vkAPI.device, commandPool, 1, &uploadCommandBuffer);
386    return 0;
387}
388
389int HelloWorldExample::dispatchCompute()
390{
391    // Create a descriptor pool.
392    VkDescriptorPoolCreateInfo descriptorPoolCreateInfo = {
393        VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO};
394    VkDescriptorPoolSize poolSizes[] = {
395        VkDescriptorPoolSize{VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 16}};
396    descriptorPoolCreateInfo.maxSets = 4;
397    descriptorPoolCreateInfo.poolSizeCount = sizeof(poolSizes) / sizeof(VkDescriptorPoolSize);
398    descriptorPoolCreateInfo.pPoolSizes = poolSizes;
399    descriptorPoolCreateInfo.flags = 0;
400    VkDescriptorPool descriptorPool = VK_NULL_HANDLE;
401    RETURN_ON_FAIL(vkAPI.vkCreateDescriptorPool(
402        vkAPI.device,
403        &descriptorPoolCreateInfo,
404        nullptr,
405        &descriptorPool));
406
407    // Allocate descriptor set.
408    VkDescriptorSetAllocateInfo descSetAllocInfo = {VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO};
409    descSetAllocInfo.descriptorPool = descriptorPool;
410    descSetAllocInfo.descriptorSetCount = 1;
411    descSetAllocInfo.pSetLayouts = &descriptorSetLayout;
412    VkDescriptorSet descriptorSet = VK_NULL_HANDLE;
413    RETURN_ON_FAIL(vkAPI.vkAllocateDescriptorSets(vkAPI.device, &descSetAllocInfo, &descriptorSet));
414
415    // Write descriptor set.
416    VkWriteDescriptorSet descriptorSetWrites[3] = {};
417    VkDescriptorBufferInfo bufferInfo[3];
418    for (int i = 0; i < 3; i++)
419    {
420        bufferInfo[i].buffer = inOutBuffers[i];
421        bufferInfo[i].offset = 0;
422        bufferInfo[i].range = bufferSize;
423
424        descriptorSetWrites[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
425        descriptorSetWrites[i].descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER;
426        descriptorSetWrites[i].descriptorCount = 1;
427        descriptorSetWrites[i].dstBinding = i;
428        descriptorSetWrites[i].dstSet = descriptorSet;
429        descriptorSetWrites[i].pBufferInfo = &bufferInfo[i];
430    }
431    vkAPI.vkUpdateDescriptorSets(vkAPI.device, 3, descriptorSetWrites, 0, nullptr);
432
433    // Allocate command buffer and record dispatch commands.
434    VkCommandBuffer commandBuffer;
435    VkCommandBufferAllocateInfo commandBufferAllocInfo = {
436        VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
437    commandBufferAllocInfo.commandBufferCount = 1;
438    commandBufferAllocInfo.commandPool = commandPool;
439    commandBufferAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
440    RETURN_ON_FAIL(
441        vkAPI.vkAllocateCommandBuffers(vkAPI.device, &commandBufferAllocInfo, &commandBuffer));
442    VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
443    vkAPI.vkBeginCommandBuffer(commandBuffer, &beginInfo);
444    vkAPI.vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline);
445    vkAPI.vkCmdBindDescriptorSets(
446        commandBuffer,
447        VK_PIPELINE_BIND_POINT_COMPUTE,
448        pipelineLayout,
449        0,
450        1,
451        &descriptorSet,
452        0,
453        nullptr);
454    vkAPI.vkCmdDispatch(commandBuffer, (uint32_t)inputElementCount, 1, 1);
455    vkAPI.vkEndCommandBuffer(commandBuffer);
456
457    // Submit command buffer and wait.
458    VkSubmitInfo submitInfo = {VK_STRUCTURE_TYPE_SUBMIT_INFO};
459    submitInfo.commandBufferCount = 1;
460    submitInfo.pCommandBuffers = &commandBuffer;
461    vkAPI.vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE);
462    vkAPI.vkQueueWaitIdle(queue);
463    vkAPI.vkFreeCommandBuffers(vkAPI.device, commandPool, 1, &commandBuffer);
464
465    // Clean up.
466    vkAPI.vkDestroyDescriptorPool(vkAPI.device, descriptorPool, nullptr);
467    return 0;
468}
469
470int HelloWorldExample::printComputeResults()
471{
472    // Allocate command buffer to read back data.
473    VkCommandBuffer commandBuffer;
474    VkCommandBufferAllocateInfo commandBufferAllocInfo = {
475        VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO};
476    commandBufferAllocInfo.commandBufferCount = 1;
477    commandBufferAllocInfo.commandPool = commandPool;
478    commandBufferAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
479    RETURN_ON_FAIL(
480        vkAPI.vkAllocateCommandBuffers(vkAPI.device, &commandBufferAllocInfo, &commandBuffer));
481
482    // Record commands to copy output buffer into staging buffer.
483    VkCommandBufferBeginInfo beginInfo = {VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO};
484    vkAPI.vkBeginCommandBuffer(commandBuffer, &beginInfo);
485    VkBufferCopy bufferCopy = {};
486    bufferCopy.size = bufferSize;
487    vkAPI.vkCmdCopyBuffer(commandBuffer, inOutBuffers[2], stagingBuffer, 1, &bufferCopy);
488    vkAPI.vkEndCommandBuffer(commandBuffer);
489
490    // Execute command buffer and wait.
491    VkSubmitInfo submitInfo = {VK_STRUCTURE_TYPE_SUBMIT_INFO};
492    submitInfo.commandBufferCount = 1;
493    submitInfo.pCommandBuffers = &commandBuffer;
494    vkAPI.vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE);
495    vkAPI.vkQueueWaitIdle(queue);
496    vkAPI.vkFreeCommandBuffers(vkAPI.device, commandPool, 1, &commandBuffer);
497
498    // Map and read back staging buffer.
499    float* stagingBufferData = nullptr;
500    vkAPI.vkMapMemory(vkAPI.device, stagingMemory, 0, bufferSize, 0, (void**)&stagingBufferData);
501    if (!stagingBufferData)
502        return -1;
503    for (size_t i = 0; i < inputElementCount; i++)
504    {
505        printf("%f\n", stagingBufferData[i]);
506    }
507    return 0;
508}
509
510HelloWorldExample::~HelloWorldExample()
511{
512    if (vkAPI.device == VK_NULL_HANDLE)
513        return;
514
515    vkAPI.vkDestroyPipeline(vkAPI.device, pipeline, nullptr);
516    for (int i = 0; i < 3; i++)
517    {
518        vkAPI.vkDestroyBuffer(vkAPI.device, inOutBuffers[i], nullptr);
519        vkAPI.vkFreeMemory(vkAPI.device, bufferMemories[i], nullptr);
520    }
521    vkAPI.vkDestroyBuffer(vkAPI.device, stagingBuffer, nullptr);
522    vkAPI.vkFreeMemory(vkAPI.device, stagingMemory, nullptr);
523    vkAPI.vkDestroyPipelineLayout(vkAPI.device, pipelineLayout, nullptr);
524    vkAPI.vkDestroyDescriptorSetLayout(vkAPI.device, descriptorSetLayout, nullptr);
525    vkAPI.vkDestroyCommandPool(vkAPI.device, commandPool, nullptr);
526}