Started working on sample for VK_EXT_host_image_copy

First working version
Work-in-progress
This commit is contained in:
Sascha Willems 2024-06-20 16:50:43 +02:00
parent 196e6a4c6b
commit 122793da95
2 changed files with 524 additions and 0 deletions

View file

@ -114,6 +114,7 @@ set(EXAMPLES
gltfskinning
graphicspipelinelibrary
hdr
hostimagecopy
imgui
indirectdraw
inlineuniformblocks

View file

@ -0,0 +1,523 @@
/*
* Vulkan Example - Host image copy using VK_EXT_host_image_copy
*
* This sample shows how to use host image copies to directly upload an image to the devic without having to use staging
*
* Work-in-progress
*
* Copyright (C) 2024 by Sascha Willems - www.saschawillems.de
*
* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
*/
#include "vulkanexamplebase.h"
#include <ktx.h>
#include <ktxvulkan.h>
class VulkanExample : public VulkanExampleBase
{
public:
// Pointers for functions added by the host image copy extension;
PFN_vkCopyMemoryToImageEXT vkCopyMemoryToImageEXT{ nullptr };
PFN_vkTransitionImageLayoutEXT vkTransitionImageLayoutEXT{ nullptr };
VkPhysicalDeviceHostImageCopyFeaturesEXT enabledPhysicalDeviceHostImageCopyFeaturesEXT{};
// Vertex layout for this example
struct Vertex {
float pos[3];
float uv[2];
float normal[3];
};
// Contains all Vulkan objects that are required to store and use a texture
// Note that this repository contains a texture class (VulkanTexture.hpp) that encapsulates texture loading functionality in a class that is used in subsequent demos
struct Texture {
VkSampler sampler{ VK_NULL_HANDLE };
VkImage image{ VK_NULL_HANDLE };
VkDeviceMemory deviceMemory{ VK_NULL_HANDLE };
VkImageView view{ VK_NULL_HANDLE };
uint32_t width{ 0 };
uint32_t height{ 0 };
uint32_t mipLevels{ 0 };
} texture;
vks::Buffer vertexBuffer;
vks::Buffer indexBuffer;
uint32_t indexCount{ 0 };
struct UniformData {
glm::mat4 projection;
glm::mat4 modelView;
glm::vec4 viewPos;
// This is used to change the bias for the level-of-detail (mips) in the fragment shader
float lodBias = 0.0f;
} uniformData;
vks::Buffer uniformBuffer;
VkPipeline pipeline{ VK_NULL_HANDLE };
VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE };
VkDescriptorSet descriptorSet{ VK_NULL_HANDLE };
VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE };
VulkanExample() : VulkanExampleBase()
{
title = "Host image copy";
camera.type = Camera::CameraType::lookat;
camera.setPosition(glm::vec3(0.0f, 0.0f, -2.5f));
camera.setRotation(glm::vec3(0.0f, 15.0f, 0.0f));
camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 256.0f);
// Enable required extensions
enabledInstanceExtensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
enabledDeviceExtensions.push_back(VK_KHR_FORMAT_FEATURE_FLAGS_2_EXTENSION_NAME);
enabledDeviceExtensions.push_back(VK_KHR_COPY_COMMANDS_2_EXTENSION_NAME);
enabledDeviceExtensions.push_back(VK_EXT_HOST_IMAGE_COPY_EXTENSION_NAME);
// Enable host image copy feature
enabledPhysicalDeviceHostImageCopyFeaturesEXT.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_HOST_IMAGE_COPY_FEATURES_EXT;
enabledPhysicalDeviceHostImageCopyFeaturesEXT.hostImageCopy = VK_TRUE;
deviceCreatepNextChain = &enabledPhysicalDeviceHostImageCopyFeaturesEXT;
}
~VulkanExample()
{
if (device) {
destroyTextureImage(texture);
vkDestroyPipeline(device, pipeline, nullptr);
vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
vertexBuffer.destroy();
indexBuffer.destroy();
uniformBuffer.destroy();
}
}
// Enable physical device features required for this example
virtual void getEnabledFeatures()
{
// Enable anisotropic filtering if supported
if (deviceFeatures.samplerAnisotropy) {
enabledFeatures.samplerAnisotropy = VK_TRUE;
};
}
/*
Upload texture image data to the GPU
Vulkan offers two types of image tiling (memory layout):
Linear tiled images:
These are stored as is and can be copied directly to. But due to the linear nature they're not a good match for GPUs and format and feature support is very limited.
It's not advised to use linear tiled images for anything else than copying from host to GPU if buffer copies are not an option.
Linear tiling is thus only implemented for learning purposes, one should always prefer optimal tiled image.
Optimal tiled images:
These are stored in an implementation specific layout matching the capability of the hardware. They usually support more formats and features and are much faster.
Optimal tiled images are stored on the device and not accessible by the host. So they can't be written directly to (like liner tiled images) and always require
some sort of data copy, either from a buffer or a linear tiled image.
In Short: Always use optimal tiled images for rendering.
*/
void loadTexture()
{
// We use the Khronos texture format (https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/)
std::string filename = getAssetPath() + "textures/metalplate01_rgba.ktx";
// Texture data contains 4 channels (RGBA) with unnormalized 8-bit values, this is the most commonly supported format
VkFormat format = VK_FORMAT_R8G8B8A8_UNORM;
ktxResult result;
ktxTexture* ktxTexture;
#if defined(__ANDROID__)
// Textures are stored inside the apk on Android (compressed)
// So they need to be loaded via the asset manager
AAsset* asset = AAssetManager_open(androidApp->activity->assetManager, filename.c_str(), AASSET_MODE_STREAMING);
if (!asset) {
vks::tools::exitFatal("Could not load texture from " + filename + "\n\nMake sure the assets submodule has been checked out and is up-to-date.", -1);
}
size_t size = AAsset_getLength(asset);
assert(size > 0);
ktx_uint8_t *textureData = new ktx_uint8_t[size];
AAsset_read(asset, textureData, size);
AAsset_close(asset);
result = ktxTexture_CreateFromMemory(textureData, size, KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture);
delete[] textureData;
#else
if (!vks::tools::fileExists(filename)) {
vks::tools::exitFatal("Could not load texture from " + filename + "\n\nMake sure the assets submodule has been checked out and is up-to-date.", -1);
}
result = ktxTexture_CreateFromNamedFile(filename.c_str(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture);
#endif
assert(result == KTX_SUCCESS);
// Get properties required for using and upload texture data from the ktx texture object
texture.width = ktxTexture->baseWidth;
texture.height = ktxTexture->baseHeight;
texture.mipLevels = ktxTexture->numLevels;
ktx_uint8_t *ktxTextureData = ktxTexture_GetData(ktxTexture);
ktx_size_t ktxTextureSize = ktxTexture_GetSize(ktxTexture);
// Copy data to an optimal tiled image using a direct
// Create optimal tiled target image on the device
VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo();
imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
imageCreateInfo.format = format;
imageCreateInfo.mipLevels = texture.mipLevels;
imageCreateInfo.arrayLayers = 1;
imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
// Set initial layout of the image to undefined
imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
imageCreateInfo.extent = { texture.width, texture.height, 1 };
// @todo: commtn
imageCreateInfo.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_HOST_TRANSFER_BIT_EXT;
VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &texture.image));
VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo();
VkMemoryRequirements memReqs = {};
vkGetImageMemoryRequirements(device, texture.image, &memReqs);
memAllocInfo.allocationSize = memReqs.size;
memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &texture.deviceMemory));
VK_CHECK_RESULT(vkBindImageMemory(device, texture.image, texture.deviceMemory, 0));
// @todo: comment
std::vector<VkMemoryToImageCopyEXT> memoryToImageCopies{};
for (uint32_t i = 0; i < texture.mipLevels; i++) {
ktx_size_t offset;
KTX_error_code ret = ktxTexture_GetImageOffset(ktxTexture, i, 0, 0, &offset);
assert(ret == KTX_SUCCESS);
// Setup a buffer image copy structure for the current mip level
VkMemoryToImageCopyEXT memoryToImageCopy = {};
memoryToImageCopy.sType = VK_STRUCTURE_TYPE_MEMORY_TO_IMAGE_COPY_EXT;
memoryToImageCopy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
memoryToImageCopy.imageSubresource.mipLevel = i;
memoryToImageCopy.imageSubresource.baseArrayLayer = 0;
memoryToImageCopy.imageSubresource.layerCount = 1;
memoryToImageCopy.imageExtent.width = ktxTexture->baseWidth >> i;
memoryToImageCopy.imageExtent.height = ktxTexture->baseHeight >> i;
memoryToImageCopy.imageExtent.depth = 1;
memoryToImageCopy.pHostPointer = ktxTextureData + offset;
memoryToImageCopies.push_back(memoryToImageCopy);
}
VkImageSubresourceRange subresourceRange{};
subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
subresourceRange.baseMipLevel = 0;
subresourceRange.levelCount = texture.mipLevels;
subresourceRange.layerCount = 1;
VkHostImageLayoutTransitionInfoEXT hostImageLayoutTransitionInfo{};
hostImageLayoutTransitionInfo.sType = VK_STRUCTURE_TYPE_HOST_IMAGE_LAYOUT_TRANSITION_INFO_EXT;
hostImageLayoutTransitionInfo.image = texture.image;
hostImageLayoutTransitionInfo.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
hostImageLayoutTransitionInfo.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
hostImageLayoutTransitionInfo.subresourceRange = subresourceRange;
vkTransitionImageLayoutEXT(device, 1, &hostImageLayoutTransitionInfo);
VkCopyMemoryToImageInfoEXT copyMemoryInfo{};
copyMemoryInfo.sType = VK_STRUCTURE_TYPE_COPY_MEMORY_TO_IMAGE_INFO_EXT;
copyMemoryInfo.dstImage = texture.image;
copyMemoryInfo.dstImageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
copyMemoryInfo.regionCount = static_cast<uint32_t>(memoryToImageCopies.size());
copyMemoryInfo.pRegions = memoryToImageCopies.data();
vkCopyMemoryToImageEXT(device, &copyMemoryInfo);
ktxTexture_Destroy(ktxTexture);
// Create a texture sampler
VkSamplerCreateInfo sampler = vks::initializers::samplerCreateInfo();
sampler.magFilter = VK_FILTER_LINEAR;
sampler.minFilter = VK_FILTER_LINEAR;
sampler.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
sampler.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
sampler.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
sampler.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
sampler.mipLodBias = 0.0f;
sampler.compareOp = VK_COMPARE_OP_NEVER;
sampler.minLod = 0.0f;
sampler.maxLod = (float)texture.mipLevels;
if (vulkanDevice->features.samplerAnisotropy) {
sampler.maxAnisotropy = vulkanDevice->properties.limits.maxSamplerAnisotropy;
sampler.anisotropyEnable = VK_TRUE;
} else {
sampler.maxAnisotropy = 1.0;
sampler.anisotropyEnable = VK_FALSE;
}
sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE;
VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &texture.sampler));
// Create image view
VkImageViewCreateInfo view = vks::initializers::imageViewCreateInfo();
view.viewType = VK_IMAGE_VIEW_TYPE_2D;
view.format = format;
view.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
view.subresourceRange.baseMipLevel = 0;
view.subresourceRange.baseArrayLayer = 0;
view.subresourceRange.layerCount = 1;
view.subresourceRange.levelCount = texture.mipLevels;
view.image = texture.image;
VK_CHECK_RESULT(vkCreateImageView(device, &view, nullptr, &texture.view));
}
// Free all Vulkan resources used by a texture object
void destroyTextureImage(Texture texture)
{
vkDestroyImageView(device, texture.view, nullptr);
vkDestroyImage(device, texture.image, nullptr);
vkDestroySampler(device, texture.sampler, nullptr);
vkFreeMemory(device, texture.deviceMemory, nullptr);
}
void buildCommandBuffers()
{
VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
VkClearValue clearValues[2];
clearValues[0].color = defaultClearColor;
clearValues[1].depthStencil = { 1.0f, 0 };
VkRenderPassBeginInfo renderPassBeginInfo = vks::initializers::renderPassBeginInfo();
renderPassBeginInfo.renderPass = renderPass;
renderPassBeginInfo.renderArea.offset.x = 0;
renderPassBeginInfo.renderArea.offset.y = 0;
renderPassBeginInfo.renderArea.extent.width = width;
renderPassBeginInfo.renderArea.extent.height = height;
renderPassBeginInfo.clearValueCount = 2;
renderPassBeginInfo.pClearValues = clearValues;
for (int32_t i = 0; i < drawCmdBuffers.size(); ++i)
{
// Set target frame buffer
renderPassBeginInfo.framebuffer = frameBuffers[i];
VK_CHECK_RESULT(vkBeginCommandBuffer(drawCmdBuffers[i], &cmdBufInfo));
vkCmdBeginRenderPass(drawCmdBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
VkViewport viewport = vks::initializers::viewport((float)width, (float)height, 0.0f, 1.0f);
vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport);
VkRect2D scissor = vks::initializers::rect2D(width, height, 0, 0);
vkCmdSetScissor(drawCmdBuffers[i], 0, 1, &scissor);
vkCmdBindDescriptorSets(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr);
vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
VkDeviceSize offsets[1] = { 0 };
vkCmdBindVertexBuffers(drawCmdBuffers[i], 0, 1, &vertexBuffer.buffer, offsets);
vkCmdBindIndexBuffer(drawCmdBuffers[i], indexBuffer.buffer, 0, VK_INDEX_TYPE_UINT32);
vkCmdDrawIndexed(drawCmdBuffers[i], indexCount, 1, 0, 0, 0);
drawUI(drawCmdBuffers[i]);
vkCmdEndRenderPass(drawCmdBuffers[i]);
VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i]));
}
}
// Creates a vertex and index buffer for a quad made of two triangles
// This is used to display the texture on
void generateQuad()
{
// Setup vertices for a single uv-mapped quad made from two triangles
std::vector<Vertex> vertices =
{
{ { 1.0f, 1.0f, 0.0f }, { 1.0f, 1.0f },{ 0.0f, 0.0f, 1.0f } },
{ { -1.0f, 1.0f, 0.0f }, { 0.0f, 1.0f },{ 0.0f, 0.0f, 1.0f } },
{ { -1.0f, -1.0f, 0.0f }, { 0.0f, 0.0f },{ 0.0f, 0.0f, 1.0f } },
{ { 1.0f, -1.0f, 0.0f }, { 1.0f, 0.0f },{ 0.0f, 0.0f, 1.0f } }
};
// Setup indices
std::vector<uint32_t> indices = { 0,1,2, 2,3,0 };
indexCount = static_cast<uint32_t>(indices.size());
// Create buffers and upload data to the GPU
struct StagingBuffers {
vks::Buffer vertices;
vks::Buffer indices;
} stagingBuffers;
// Host visible source buffers (staging)
VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &stagingBuffers.vertices, vertices.size() * sizeof(Vertex), vertices.data()));
VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &stagingBuffers.indices, indices.size() * sizeof(uint32_t), indices.data()));
// Device local destination buffers
VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &vertexBuffer, vertices.size() * sizeof(Vertex)));
VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &indexBuffer, indices.size() * sizeof(uint32_t)));
// Copy from host do device
vulkanDevice->copyBuffer(&stagingBuffers.vertices, &vertexBuffer, queue);
vulkanDevice->copyBuffer(&stagingBuffers.indices, &indexBuffer, queue);
// Clean up
stagingBuffers.vertices.destroy();
stagingBuffers.indices.destroy();
}
void setupDescriptors()
{
// Pool
std::vector<VkDescriptorPoolSize> poolSizes = {
vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1),
// The sample uses a combined image + sampler descriptor to sample the texture in the fragment shader
vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1)
};
VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2);
VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool));
// Layout
std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {
// Binding 0 : Vertex shader uniform buffer
vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0),
// Binding 1 : Fragment shader image sampler
vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1)
};
VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
// Set
VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1);
VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet));
// Setup a descriptor image info for the current texture to be used as a combined image sampler
VkDescriptorImageInfo textureDescriptor;
textureDescriptor.imageView = texture.view;
textureDescriptor.sampler = texture.sampler;
textureDescriptor.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
std::vector<VkWriteDescriptorSet> writeDescriptorSets = {
// Binding 0 : Vertex shader uniform buffer
vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor),
// Binding 1 : Fragment shader texture sampler
// Fragment shader: layout (binding = 1) uniform sampler2D samplerColor;
vks::initializers::writeDescriptorSet(descriptorSet,
VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, // The descriptor set will use a combined image sampler (as opposed to splitting image and sampler)
1, // Shader binding point 1
&textureDescriptor) // Pointer to the descriptor image for our texture
};
vkUpdateDescriptorSets(device, static_cast<uint32_t>(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr);
}
void preparePipelines()
{
// Layout
VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayout, 1);
VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout));
// Pipeline
VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_NONE, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0);
VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
VkPipelineDepthStencilStateCreateInfo depthStencilState = vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
VkPipelineViewportStateCreateInfo viewportState = vks::initializers::pipelineViewportStateCreateInfo(1, 1, 0);
VkPipelineMultisampleStateCreateInfo multisampleState = vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT, 0);
std::vector<VkDynamicState> dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
VkPipelineDynamicStateCreateInfo dynamicState = vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
std::array<VkPipelineShaderStageCreateInfo,2> shaderStages;
// Shaders
shaderStages[0] = loadShader(getShadersPath() + "texture/texture.vert.spv", VK_SHADER_STAGE_VERTEX_BIT);
shaderStages[1] = loadShader(getShadersPath() + "texture/texture.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT);
// Vertex input state
std::vector<VkVertexInputBindingDescription> vertexInputBindings = {
vks::initializers::vertexInputBindingDescription(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX)
};
std::vector<VkVertexInputAttributeDescription> vertexInputAttributes = {
vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, pos)),
vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32_SFLOAT, offsetof(Vertex, uv)),
vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, normal)),
};
VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
vertexInputState.vertexBindingDescriptionCount = static_cast<uint32_t>(vertexInputBindings.size());
vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data();
vertexInputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexInputAttributes.size());
vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data();
VkGraphicsPipelineCreateInfo pipelineCreateInfo = vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass, 0);
pipelineCreateInfo.pVertexInputState = &vertexInputState;
pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
pipelineCreateInfo.pRasterizationState = &rasterizationState;
pipelineCreateInfo.pColorBlendState = &colorBlendState;
pipelineCreateInfo.pMultisampleState = &multisampleState;
pipelineCreateInfo.pViewportState = &viewportState;
pipelineCreateInfo.pDepthStencilState = &depthStencilState;
pipelineCreateInfo.pDynamicState = &dynamicState;
pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
pipelineCreateInfo.pStages = shaderStages.data();
VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline));
}
// Prepare and initialize uniform buffer containing shader uniforms
void prepareUniformBuffers()
{
// Vertex shader uniform buffer block
VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &uniformBuffer, sizeof(uniformData), &uniformData));
VK_CHECK_RESULT(uniformBuffer.map());
}
void updateUniformBuffers()
{
uniformData.projection = camera.matrices.perspective;
uniformData.modelView = camera.matrices.view;
uniformData.viewPos = camera.viewPos;
memcpy(uniformBuffer.mapped, &uniformData, sizeof(uniformData));
}
void prepare()
{
VulkanExampleBase::prepare();
// Get the function pointers required host image copies
vkCopyMemoryToImageEXT = reinterpret_cast<PFN_vkCopyMemoryToImageEXT>(vkGetDeviceProcAddr(device, "vkCopyMemoryToImageEXT"));
vkTransitionImageLayoutEXT = reinterpret_cast<PFN_vkTransitionImageLayoutEXT>(vkGetDeviceProcAddr(device, "vkTransitionImageLayoutEXT"));
loadTexture();
generateQuad();
prepareUniformBuffers();
setupDescriptors();
preparePipelines();
buildCommandBuffers();
prepared = true;
}
void draw()
{
VulkanExampleBase::prepareFrame();
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer];
VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE));
VulkanExampleBase::submitFrame();
}
virtual void render()
{
if (!prepared)
return;
updateUniformBuffers();
draw();
}
virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay)
{
if (overlay->header("Settings")) {
if (overlay->sliderFloat("LOD bias", &uniformData.lodBias, 0.0f, (float)texture.mipLevels)) {
updateUniformBuffers();
}
}
}
};
VULKAN_EXAMPLE_MAIN()