VkBufferView TextureConverter::CreateTexelBufferView(VkFormat format) const { // Create a view of the whole buffer, we'll offset our texel load into it VkBufferViewCreateInfo view_info = { VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO, // VkStructureType sType nullptr, // const void* pNext 0, // VkBufferViewCreateFlags flags m_texel_buffer->GetBuffer(), // VkBuffer buffer format, // VkFormat format 0, // VkDeviceSize offset m_texel_buffer_size // VkDeviceSize range }; VkBufferView view; VkResult res = vkCreateBufferView(g_vulkan_context->GetDevice(), &view_info, nullptr, &view); if (res != VK_SUCCESS) { LOG_VULKAN_ERROR(res, "vkCreateBufferView failed: "); return VK_NULL_HANDLE; } return view; }
tut1_error tut7_create_buffers(struct tut1_physical_device *phy_dev, struct tut2_device *dev, struct tut7_buffer *buffers, uint32_t buffer_count) { /* We have already seen buffer create in Tutorial 4, so we'll go over this quickly. */ uint32_t successful = 0; tut1_error retval = TUT1_ERROR_NONE; VkResult res; for (uint32_t i = 0; i < buffer_count; ++i) { buffers[i].buffer = NULL; buffers[i].buffer_mem = NULL; buffers[i].view = NULL; /* * The buffer CreateInfo is much simpler than the image CreateInfo. The only part of it we didn't see * in Tutorial 4 is sharing the buffer between queue families. The parameters for that are exactly the * same as the image CreateInfo. * * The size of the buffer depends on its format, but let's not worry about translating the size for * each possible format and lazily assume 4 bytes, which covers a lot of formats (even if * overestimating them). Naturally, doing this is not really advised. */ bool shared = buffers[i].sharing_queue_count > 1; VkBufferCreateInfo buffer_info = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .size = buffers[i].size * sizeof(float), .usage = buffers[i].usage, .sharingMode = shared?VK_SHARING_MODE_CONCURRENT:VK_SHARING_MODE_EXCLUSIVE, .queueFamilyIndexCount = shared?buffers[i].sharing_queue_count:0, .pQueueFamilyIndices = shared?buffers[i].sharing_queues:NULL, }; res = vkCreateBuffer(dev->device, &buffer_info, NULL, &buffers[i].buffer); tut1_error_sub_set_vkresult(&retval, res); if (res) continue; VkMemoryRequirements mem_req = {0}; vkGetBufferMemoryRequirements(dev->device, buffers[i].buffer, &mem_req); uint32_t mem_index = tut4_find_suitable_memory(phy_dev, dev, &mem_req, buffers[i].host_visible? VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT: VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); if (mem_index >= phy_dev->memories.memoryTypeCount) continue; VkMemoryAllocateInfo mem_info = { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize = mem_req.size, .memoryTypeIndex = mem_index, }; res = vkAllocateMemory(dev->device, &mem_info, NULL, &buffers[i].buffer_mem); tut1_error_sub_set_vkresult(&retval, res); if (res) continue; res = vkBindBufferMemory(dev->device, buffers[i].buffer, buffers[i].buffer_mem, 0); tut1_error_sub_set_vkresult(&retval, res); if (res) continue; if (buffers[i].make_view) { /* A buffer view can only be created on uniform and storage texel buffers */ if ((buffers[i].usage & VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT) || (buffers[i].usage & VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT)) { VkBufferViewCreateInfo view_info = { .sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO, .buffer = buffers[i].buffer, .format = buffers[i].format, .offset = 0, .range = VK_WHOLE_SIZE, }; res = vkCreateBufferView(dev->device, &view_info, NULL, &buffers[i].view); tut1_error_sub_set_vkresult(&retval, res); if (res) continue; } } ++successful; } tut1_error_set_vkresult(&retval, successful == buffer_count?VK_SUCCESS:VK_INCOMPLETE); return retval; } tut1_error tut7_load_shaders(struct tut2_device *dev, struct tut7_shader *shaders, uint32_t shader_count) { /* * We already saw how to load a shader in Tutorial 3. This function is just an array version of it. Nothing * fancy here. */ uint32_t successful = 0; tut1_error retval = TUT1_ERROR_NONE; tut1_error err; for (uint32_t i = 0; i < shader_count; ++i) { err = tut3_load_shader(dev, shaders[i].spirv_file, &shaders[i].shader); tut1_error_sub_merge(&retval, &err); if (!tut1_error_is_success(&err)) continue; ++successful; } tut1_error_set_vkresult(&retval, successful == shader_count?VK_SUCCESS:VK_INCOMPLETE); return retval; } tut1_error tut7_create_graphics_buffers(struct tut1_physical_device *phy_dev, struct tut2_device *dev, VkSurfaceFormatKHR surface_format, struct tut7_graphics_buffers *graphics_buffers, uint32_t graphics_buffer_count, VkRenderPass *render_pass) { /* * To render on a screen, we need a series of stuff. We need images to render to. We also need to tell our * graphics pipeline that we are going to use those images. In fact, similar to how we make descriptor set and * pipeline layouts to define stuff, then bind the actual sets and pipeline, we only specify what "kind" of * images we will use and then bind the actual images. * * Vulkan uses the concept of render passes to perform the rendering. A render pass could consist of multiple * subpasses, but let's not bother with that for now. Only thing to know is that the images used in rendering, * whether it's color, depth/stencil, input etc, are called "attachments". When creating a render pass, we * define what sort of attachments it would take and what are the dependencies between the subpasses. We are * going with a single subpass, so things would be simpler. The render pass is used to create a graphics * pipeline. * * When we are going to actually render something, we need to bind those attachments using real images. The * construct that holds the attachments together is called a "framebuffer". Commonly, we need color and * depth/stencil attachments for rendering, so we need to provide views on them to a framebuffer. We already * have an image created by the swapchain for our color output. We need to create the depth/stencil buffer * ourselves. * * So I lied when I said in the end of `tut7_create_images` that we won't use that function. For the color * image, we will just create a view (which we already saw how to do in that function), and we will use that * function to create our depth/stencil image and its view. * * If we wanted to render to an image, without presenting on the screen, here is where the difference would be, * that is, we would be creating an image for the color attachment ourselves, instead of using one created by * the swapchain. */ uint32_t successful = 0; tut1_error retval = TUT1_ERROR_NONE; VkResult res; tut1_error err; for (uint32_t i = 0; i < graphics_buffer_count; ++i) { graphics_buffers[i].color_view = NULL; graphics_buffers[i].depth = (struct tut7_image){0}; graphics_buffers[i].framebuffer = NULL; } /* Get a format for the depth/stencil image that supports depth/stencil attachment. */ VkFormat depth_format = tut7_get_supported_depth_stencil_format(phy_dev);; /* * Since the render pass just defines how the attachments look like, we need only one for use with all of our * swapchain images. On the other hand, we need a different framebuffer for each swapchain image (together * with its corresponding depth/stencil image). * * Our render pass has two attachments; the color image and the depth/stencil image. Each of these attachments * needs to be specified separately: * * - In the case of the color image, the format is given by `surface_format`, and in the case of the * depth/stencil image, the format is the one we just decided on above. * - For now, we don't do multisampling, so the number of samples is set to 1 for both attachments. * - At the beginning and end of each subpass, the render pass can either keep or clear/discard the contents of * an attachment. We use one subpass, so this doesn't really matter, but we'll go with clearing the image at * the beginning and keeping the contents at the end. If not specified, the default action (value 0) is to * keep the previous data at the beginning and preserve it at the end as well. * - The render pass also declares a promise that the driver would find the attachment in a certain layout at * the beginning of the render pass, and that the attachment would be at a certain layout at the end. For our * color attachment, it will start and end in the color attachment layout. The case is similar for the * depth/stencil buffer. We don't intend to transition them to another layout. * * Next, we need to declare the subpasses of the render pass. We use only one, so this is more * straightforward. The information required here are: * * - pipeline bind point: whether compute or graphics pipelines are going to use this subpass. We want * graphics now, so we'll go with that, but anyway compute is not even supported (at the time of this * writing). * - Attachments: which attachments to use, and which to simply preserve. If an attachment is neither used nor * preserved, its contents become undefined. If the attachment is decorated with `location=X` in glsl, then * pInputAttachments[X] is used if it's an input, or pColorAttachments[X] is used if it's an output. This is * other than the `binding=Y` decoration that specifies the buffer/image as specified by the descriptor set. * There can be multiple input and color attachments (hence the `location=X` binding required above), but * only one depth/stencil attachment. * * In the end, if we had multiple subpasses, their dependencies would also need to be declared. What's nice * about subpasses is that there is an automatic layout transition between subpasses (as specified by * VkAttachmentReference values), and there is a whole set of rules that allow the driver to safely perform * this transition before or after the subpass has actually started. Again, we have a single subpass, so none * of this matters now. */ VkAttachmentDescription render_pass_attachments[2] = { [0] = { .format = surface_format.format, .samples = VK_SAMPLE_COUNT_1_BIT, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, .initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, .finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, }, [1] = { .format = depth_format, .samples = VK_SAMPLE_COUNT_1_BIT, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, .initialLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, .finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, }, }; VkAttachmentReference render_pass_attachment_references[2] = { [0] = { .attachment = 0, /* corresponds to the index in pAttachments of VkRenderPassCreateInfo */ .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, }, [1] = { .attachment = 1, .layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, }, };
int sample_main(int argc, char *argv[]) { VkResult U_ASSERT_ONLY res; bool U_ASSERT_ONLY pass; struct sample_info info = {}; char sample_title[] = "Texel Buffer Sample"; float texels[] = {1.0, 0.0, 1.0}; const bool depthPresent = false; const bool vertexPresent = false; process_command_line_args(info, argc, argv); init_global_layer_properties(info); init_instance_extension_names(info); init_device_extension_names(info); init_instance(info, sample_title); init_enumerate_device(info); if (info.gpu_props.limits.maxTexelBufferElements < 4) { std::cout << "maxTexelBufferElements too small\n"; exit(-1); } VkFormatProperties props; vkGetPhysicalDeviceFormatProperties(info.gpus[0], VK_FORMAT_R32_SFLOAT, &props); if (!(props.bufferFeatures & VK_FORMAT_FEATURE_UNIFORM_TEXEL_BUFFER_BIT)) { std::cout << "R32_SFLOAT format unsupported for texel buffer\n"; exit(-1); } init_window_size(info, 500, 500); init_connection(info); init_window(info); init_swapchain_extension(info); init_device(info); init_command_pool(info); init_command_buffer(info); execute_begin_command_buffer(info); init_device_queue(info); init_swap_chain(info); VkBufferCreateInfo buf_info = {}; buf_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; buf_info.pNext = NULL; buf_info.usage = VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT; buf_info.size = sizeof(texels); buf_info.queueFamilyIndexCount = 0; buf_info.pQueueFamilyIndices = NULL; buf_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; buf_info.flags = 0; VkBuffer texelBuf; res = vkCreateBuffer(info.device, &buf_info, NULL, &texelBuf); assert(res == VK_SUCCESS); VkMemoryRequirements mem_reqs; vkGetBufferMemoryRequirements(info.device, texelBuf, &mem_reqs); VkMemoryAllocateInfo alloc_info = {}; alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; alloc_info.pNext = NULL; alloc_info.memoryTypeIndex = 0; alloc_info.allocationSize = mem_reqs.size; pass = memory_type_from_properties(info, mem_reqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &alloc_info.memoryTypeIndex); assert(pass && "No mappable, coherent memory"); VkDeviceMemory texelMem; res = vkAllocateMemory(info.device, &alloc_info, NULL, &texelMem); assert(res == VK_SUCCESS); uint8_t *pData; res = vkMapMemory(info.device, texelMem, 0, mem_reqs.size, 0, (void **)&pData); assert(res == VK_SUCCESS); memcpy(pData, &texels, sizeof(texels)); vkUnmapMemory(info.device, texelMem); res = vkBindBufferMemory(info.device, texelBuf, texelMem, 0); assert(res == VK_SUCCESS); VkBufferView texel_view; VkBufferViewCreateInfo view_info = {}; view_info.sType = VK_STRUCTURE_TYPE_BUFFER_VIEW_CREATE_INFO; view_info.pNext = NULL; view_info.buffer = texelBuf; view_info.format = VK_FORMAT_R32_SFLOAT; view_info.offset = 0; view_info.range = sizeof(texels); vkCreateBufferView(info.device, &view_info, NULL, &texel_view); VkDescriptorBufferInfo texel_buffer_info = {}; texel_buffer_info.buffer = texelBuf; texel_buffer_info.offset = 0; texel_buffer_info.range = sizeof(texels); // init_descriptor_and_pipeline_layouts(info, false); VkDescriptorSetLayoutBinding layout_bindings[1]; layout_bindings[0].binding = 0; layout_bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER; layout_bindings[0].descriptorCount = 1; layout_bindings[0].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; layout_bindings[0].pImmutableSamplers = NULL; /* Next take layout bindings and use them to create a descriptor set layout */ VkDescriptorSetLayoutCreateInfo descriptor_layout = {}; descriptor_layout.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; descriptor_layout.pNext = NULL; descriptor_layout.bindingCount = 1; descriptor_layout.pBindings = layout_bindings; info.desc_layout.resize(NUM_DESCRIPTOR_SETS); res = vkCreateDescriptorSetLayout(info.device, &descriptor_layout, NULL, info.desc_layout.data()); assert(res == VK_SUCCESS); /* Now use the descriptor layout to create a pipeline layout */ VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = {}; pPipelineLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pPipelineLayoutCreateInfo.pNext = NULL; pPipelineLayoutCreateInfo.pushConstantRangeCount = 0; pPipelineLayoutCreateInfo.pPushConstantRanges = NULL; pPipelineLayoutCreateInfo.setLayoutCount = NUM_DESCRIPTOR_SETS; pPipelineLayoutCreateInfo.pSetLayouts = info.desc_layout.data(); res = vkCreatePipelineLayout(info.device, &pPipelineLayoutCreateInfo, NULL, &info.pipeline_layout); assert(res == VK_SUCCESS); init_renderpass(info, depthPresent); init_shaders(info, vertShaderText, fragShaderText); init_framebuffers(info, depthPresent); VkDescriptorPoolSize type_count[1]; type_count[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER; type_count[0].descriptorCount = 1; VkDescriptorPoolCreateInfo descriptor_pool = {}; descriptor_pool.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; descriptor_pool.pNext = NULL; descriptor_pool.maxSets = 1; descriptor_pool.poolSizeCount = 1; descriptor_pool.pPoolSizes = type_count; res = vkCreateDescriptorPool(info.device, &descriptor_pool, NULL, &info.desc_pool); assert(res == VK_SUCCESS); VkDescriptorSetAllocateInfo desc_alloc_info[1]; desc_alloc_info[0].sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; desc_alloc_info[0].pNext = NULL; desc_alloc_info[0].descriptorPool = info.desc_pool; desc_alloc_info[0].descriptorSetCount = NUM_DESCRIPTOR_SETS; desc_alloc_info[0].pSetLayouts = info.desc_layout.data(); /* Allocate descriptor set with UNIFORM_BUFFER_DYNAMIC */ info.desc_set.resize(NUM_DESCRIPTOR_SETS); res = vkAllocateDescriptorSets(info.device, desc_alloc_info, info.desc_set.data()); assert(res == VK_SUCCESS); VkWriteDescriptorSet writes[1]; writes[0] = {}; writes[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; writes[0].dstSet = info.desc_set[0]; writes[0].dstBinding = 0; writes[0].descriptorCount = 1; writes[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER; writes[0].pBufferInfo = &texel_buffer_info; writes[0].pTexelBufferView = &texel_view; writes[0].dstArrayElement = 0; vkUpdateDescriptorSets(info.device, 1, writes, 0, NULL); init_pipeline_cache(info); init_pipeline(info, depthPresent, vertexPresent); /* VULKAN_KEY_START */ VkClearValue clear_values[1]; clear_values[0].color.float32[0] = 0.2f; clear_values[0].color.float32[1] = 0.2f; clear_values[0].color.float32[2] = 0.2f; clear_values[0].color.float32[3] = 0.2f; VkSemaphoreCreateInfo imageAcquiredSemaphoreCreateInfo; imageAcquiredSemaphoreCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; imageAcquiredSemaphoreCreateInfo.pNext = NULL; imageAcquiredSemaphoreCreateInfo.flags = 0; res = vkCreateSemaphore(info.device, &imageAcquiredSemaphoreCreateInfo, NULL, &info.imageAcquiredSemaphore); assert(res == VK_SUCCESS); // Get the index of the next available swapchain image: res = vkAcquireNextImageKHR(info.device, info.swap_chain, UINT64_MAX, info.imageAcquiredSemaphore, VK_NULL_HANDLE, &info.current_buffer); // TODO: Deal with the VK_SUBOPTIMAL_KHR and VK_ERROR_OUT_OF_DATE_KHR // return codes assert(res == VK_SUCCESS); VkRenderPassBeginInfo rp_begin; rp_begin.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; rp_begin.pNext = NULL; rp_begin.renderPass = info.render_pass; rp_begin.framebuffer = info.framebuffers[info.current_buffer]; rp_begin.renderArea.offset.x = 0; rp_begin.renderArea.offset.y = 0; rp_begin.renderArea.extent.width = info.width; rp_begin.renderArea.extent.height = info.height; rp_begin.clearValueCount = 1; rp_begin.pClearValues = clear_values; vkCmdBeginRenderPass(info.cmd, &rp_begin, VK_SUBPASS_CONTENTS_INLINE); vkCmdBindPipeline(info.cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, info.pipeline); vkCmdBindDescriptorSets(info.cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, info.pipeline_layout, 0, NUM_DESCRIPTOR_SETS, info.desc_set.data(), 0, NULL); init_viewports(info); init_scissors(info); vkCmdDraw(info.cmd, 3, 1, 0, 0); vkCmdEndRenderPass(info.cmd); res = vkEndCommandBuffer(info.cmd); const VkCommandBuffer cmd_bufs[] = {info.cmd}; VkFenceCreateInfo fenceInfo; VkFence drawFence; fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; fenceInfo.pNext = NULL; fenceInfo.flags = 0; vkCreateFence(info.device, &fenceInfo, NULL, &drawFence); execute_queue_cmdbuf(info, cmd_bufs, drawFence); do { res = vkWaitForFences(info.device, 1, &drawFence, VK_TRUE, FENCE_TIMEOUT); } while (res == VK_TIMEOUT); assert(res == VK_SUCCESS); vkDestroyFence(info.device, drawFence, NULL); execute_present_image(info); wait_seconds(1); /* VULKAN_KEY_END */ if (info.save_images) write_ppm(info, "texel_buffer"); vkDestroySemaphore(info.device, info.imageAcquiredSemaphore, NULL); vkDestroyBufferView(info.device, texel_view, NULL); vkDestroyBuffer(info.device, texelBuf, NULL); vkFreeMemory(info.device, texelMem, NULL); destroy_pipeline(info); destroy_pipeline_cache(info); destroy_descriptor_pool(info); destroy_framebuffers(info); destroy_shaders(info); destroy_renderpass(info); destroy_descriptor_and_pipeline_layouts(info); destroy_swap_chain(info); destroy_command_buffer(info); destroy_command_pool(info); destroy_device(info); destroy_window(info); destroy_instance(info); return 0; }