diff --git a/attachments/26_shader_textures.slang b/attachments/26_shader_textures.slang index 18f7064e..348b3a16 100644 --- a/attachments/26_shader_textures.slang +++ b/attachments/26_shader_textures.slang @@ -14,6 +14,7 @@ ConstantBuffer ubo; struct VSOutput { float4 pos : SV_Position; + float3 fragColor; float2 fragTexCoord; }; @@ -21,6 +22,7 @@ struct VSOutput VSOutput vertMain(VSInput input) { VSOutput output; output.pos = mul(ubo.proj, mul(ubo.view, mul(ubo.model, float4(input.inPosition, 0.0, 1.0)))); + output.fragColor = input.inColor; output.fragTexCoord = input.inTexCoord; return output; } diff --git a/attachments/26_texture_mapping.cpp b/attachments/26_texture_mapping.cpp index 7531ef8b..26811191 100644 --- a/attachments/26_texture_mapping.cpp +++ b/attachments/26_texture_mapping.cpp @@ -48,15 +48,14 @@ struct Vertex static vk::VertexInputBindingDescription getBindingDescription() { - return {0, sizeof(Vertex), vk::VertexInputRate::eVertex}; + return {.binding = 0, .stride = sizeof(Vertex), .inputRate = vk::VertexInputRate::eVertex}; } static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos)), - vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color)), - vk::VertexInputAttributeDescription(2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord))}; + return {{{.location = 0, .binding = 0, .format = vk::Format::eR32G32Sfloat, .offset = offsetof(Vertex, pos)}, + {.location = 1, .binding = 0, .format = vk::Format::eR32G32B32Sfloat, .offset = offsetof(Vertex, color)}, + {.location = 2, .binding = 0, .format = vk::Format::eR32G32Sfloat, .offset = offsetof(Vertex, texCoord)}}}; } }; @@ -73,8 +72,7 @@ const std::vector vertices = { {{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}, {0.0f, 1.0f}}, {{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}, {1.0f, 1.0f}}}; -const std::vector indices = { - 0, 1, 2, 2, 3, 0}; +const std::vector indices = {0, 1, 2, 2, 3, 0}; class HelloTriangleApplication { @@ -151,7 +149,7 @@ class HelloTriangleApplication static void framebufferResizeCallback(GLFWwindow *window, int width, int height) { - auto app = static_cast(glfwGetWindowUserPointer(window)); + auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); app->framebufferResized = true; } @@ -279,7 +277,7 @@ class HelloTriangleApplication vk::DebugUtilsMessageSeverityFlagsEXT severityFlags(vk::DebugUtilsMessageSeverityFlagBitsEXT::eWarning | vk::DebugUtilsMessageSeverityFlagBitsEXT::eError); vk::DebugUtilsMessageTypeFlagsEXT messageTypeFlags( - vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); + vk::DebugUtilsMessageTypeFlagBitsEXT::eGeneral | vk::DebugUtilsMessageTypeFlagBitsEXT::ePerformance | vk::DebugUtilsMessageTypeFlagBitsEXT::eValidation); vk::DebugUtilsMessengerCreateInfoEXT debugUtilsMessengerCreateInfoEXT{.messageSeverity = severityFlags, .messageType = messageTypeFlags, .pfnUserCallback = &debugCallback}; @@ -316,7 +314,6 @@ class HelloTriangleApplication // Check if the physicalDevice supports the required features auto features = physicalDevice.template getFeatures2(); bool supportsRequiredFeatures = features.template get().features.samplerAnisotropy && @@ -411,21 +408,18 @@ class HelloTriangleApplication { assert(swapChainImageViews.empty()); - vk::ImageViewCreateInfo imageViewCreateInfo{.viewType = vk::ImageViewType::e2D, - .format = swapChainSurfaceFormat.format, - .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + swapChainImageViews.reserve(swapChainImages.size()); for (auto &image : swapChainImages) { - imageViewCreateInfo.image = image; - swapChainImageViews.emplace_back(device, imageViewCreateInfo); + swapChainImageViews.emplace_back(createImageView(image, swapChainSurfaceFormat.format)); } } void createDescriptorSetLayout() { - std::array bindings = { - vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), - vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr)}; + std::array bindings{ + {{.binding = 0, .descriptorType = vk::DescriptorType::eUniformBuffer, .descriptorCount = 1, .stageFlags = vk::ShaderStageFlagBits::eVertex}, + {.binding = 1, .descriptorType = vk::DescriptorType::eCombinedImageSampler, .descriptorCount = 1, .stageFlags = vk::ShaderStageFlagBits::eFragment}}}; vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = static_cast(bindings.size()), .pBindings = bindings.data()}; descriptorSetLayout = vk::raii::DescriptorSetLayout(device, layoutInfo); @@ -441,7 +435,10 @@ class HelloTriangleApplication auto bindingDescription = Vertex::getBindingDescription(); auto attributeDescriptions = Vertex::getAttributeDescriptions(); - vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &bindingDescription, .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), .pVertexAttributeDescriptions = attributeDescriptions.data()}; + vk::PipelineVertexInputStateCreateInfo vertexInputInfo{.vertexBindingDescriptionCount = 1, + .pVertexBindingDescriptions = &bindingDescription, + .vertexAttributeDescriptionCount = static_cast(attributeDescriptions.size()), + .pVertexAttributeDescriptions = attributeDescriptions.data()}; vk::PipelineInputAssemblyStateCreateInfo inputAssembly{.topology = vk::PrimitiveTopology::eTriangleList}; vk::PipelineViewportStateCreateInfo viewportState{.viewportCount = 1, .scissorCount = 1}; @@ -449,7 +446,7 @@ class HelloTriangleApplication .rasterizerDiscardEnable = vk::False, .polygonMode = vk::PolygonMode::eFill, .cullMode = vk::CullModeFlagBits::eBack, - .frontFace = vk::FrontFace::eClockwise, + .frontFace = vk::FrontFace::eCounterClockwise, .depthBiasEnable = vk::False, .lineWidth = 1.0f}; @@ -465,7 +462,7 @@ class HelloTriangleApplication std::vector dynamicStates = {vk::DynamicState::eViewport, vk::DynamicState::eScissor}; vk::PipelineDynamicStateCreateInfo dynamicState{.dynamicStateCount = static_cast(dynamicStates.size()), .pDynamicStates = dynamicStates.data()}; - vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 0, .pushConstantRangeCount = 0}; + vk::PipelineLayoutCreateInfo pipelineLayoutInfo{.setLayoutCount = 1, .pSetLayouts = &*descriptorSetLayout, .pushConstantRangeCount = 0}; pipelineLayout = vk::raii::PipelineLayout(device, pipelineLayoutInfo); vk::StructureChain pipelineCreateInfoChain = { @@ -503,9 +500,8 @@ class HelloTriangleApplication throw std::runtime_error("failed to load texture image!"); } - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + auto [stagingBuffer, stagingBufferMemory] = + createBuffer(imageSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent); void *data = stagingBufferMemory.mapMemory(0, imageSize); memcpy(data, pixels, imageSize); @@ -513,78 +509,83 @@ class HelloTriangleApplication stbi_image_free(pixels); - createImage(texWidth, texHeight, vk::Format::eR8G8B8A8Srgb, vk::ImageTiling::eOptimal, vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, vk::MemoryPropertyFlagBits::eDeviceLocal, textureImage, textureImageMemory); - - transitionImageLayout(textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); - copyBufferToImage(stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); - transitionImageLayout(textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); + std::tie(textureImage, textureImageMemory) = createImage(texWidth, + texHeight, + vk::Format::eR8G8B8A8Srgb, + vk::ImageTiling::eOptimal, + vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled, + vk::MemoryPropertyFlagBits::eDeviceLocal); + + vk::raii::CommandBuffer commandBuffer = beginSingleTimeCommands(); + transitionImageLayout(commandBuffer, textureImage, vk::ImageLayout::eUndefined, vk::ImageLayout::eTransferDstOptimal); + copyBufferToImage(commandBuffer, stagingBuffer, textureImage, static_cast(texWidth), static_cast(texHeight)); + transitionImageLayout(commandBuffer, textureImage, vk::ImageLayout::eTransferDstOptimal, vk::ImageLayout::eShaderReadOnlyOptimal); + endSingleTimeCommands(std::move(commandBuffer)); } void createTextureImageView() { - textureImageView = createImageView(textureImage, vk::Format::eR8G8B8A8Srgb, vk::ImageAspectFlagBits::eColor); + textureImageView = createImageView(*textureImage, vk::Format::eR8G8B8A8Srgb); } void createTextureSampler() { vk::PhysicalDeviceProperties properties = physicalDevice.getProperties(); - vk::SamplerCreateInfo samplerInfo{ - .magFilter = vk::Filter::eLinear, - .minFilter = vk::Filter::eLinear, - .mipmapMode = vk::SamplerMipmapMode::eLinear, - .addressModeU = vk::SamplerAddressMode::eRepeat, - .addressModeV = vk::SamplerAddressMode::eRepeat, - .addressModeW = vk::SamplerAddressMode::eRepeat, - .mipLodBias = 0.0f, - .anisotropyEnable = vk::True, - .maxAnisotropy = properties.limits.maxSamplerAnisotropy, - .compareEnable = vk::False, - .compareOp = vk::CompareOp::eAlways}; + vk::SamplerCreateInfo samplerInfo{.magFilter = vk::Filter::eLinear, + .minFilter = vk::Filter::eLinear, + .mipmapMode = vk::SamplerMipmapMode::eLinear, + .addressModeU = vk::SamplerAddressMode::eRepeat, + .addressModeV = vk::SamplerAddressMode::eRepeat, + .addressModeW = vk::SamplerAddressMode::eRepeat, + .mipLodBias = 0.0f, + .anisotropyEnable = vk::True, + .maxAnisotropy = properties.limits.maxSamplerAnisotropy, + .compareEnable = vk::False, + .compareOp = vk::CompareOp::eAlways}; textureSampler = vk::raii::Sampler(device, samplerInfo); } - vk::raii::ImageView createImageView(vk::raii::Image &image, vk::Format format, vk::ImageAspectFlags aspectFlags) + vk::raii::ImageView createImageView(vk::Image const &image, vk::Format format) { vk::ImageViewCreateInfo viewInfo{ .image = image, .viewType = vk::ImageViewType::e2D, .format = format, - .subresourceRange = {aspectFlags, 0, 1, 0, 1}}; + .subresourceRange = {.aspectMask = vk::ImageAspectFlagBits::eColor, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1}}; return vk::raii::ImageView(device, viewInfo); } - void createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Image &image, vk::raii::DeviceMemory &imageMemory) + std::pair createImage(uint32_t width, uint32_t height, vk::Format format, vk::ImageTiling tiling, vk::ImageUsageFlags usage, vk::MemoryPropertyFlags properties) { - vk::ImageCreateInfo imageInfo{ - .imageType = vk::ImageType::e2D, - .format = format, - .extent = {width, height, 1}, - .mipLevels = 1, - .arrayLayers = 1, - .samples = vk::SampleCountFlagBits::e1, - .tiling = tiling, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive}; - - image = vk::raii::Image(device, imageInfo); + vk::ImageCreateInfo imageInfo{.imageType = vk::ImageType::e2D, + .format = format, + .extent = {width, height, 1}, + .mipLevels = 1, + .arrayLayers = 1, + .samples = vk::SampleCountFlagBits::e1, + .tiling = tiling, + .usage = usage, + .sharingMode = vk::SharingMode::eExclusive}; + + vk::raii::Image image = vk::raii::Image(device, imageInfo); vk::MemoryRequirements memRequirements = image.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; - imageMemory = vk::raii::DeviceMemory(device, allocInfo); + vk::MemoryAllocateInfo allocInfo{.allocationSize = memRequirements.size, + .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + vk::raii::DeviceMemory imageMemory = vk::raii::DeviceMemory(device, allocInfo); image.bindMemory(imageMemory, 0); + + return {std::move(image), std::move(imageMemory)}; } - void transitionImageLayout(const vk::raii::Image &image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) + void transitionImageLayout(vk::raii::CommandBuffer &commandBuffer, const vk::raii::Image &image, vk::ImageLayout oldLayout, vk::ImageLayout newLayout) { - auto commandBuffer = beginSingleTimeCommands(); - - vk::ImageMemoryBarrier barrier{ - .oldLayout = oldLayout, - .newLayout = newLayout, - .image = image, - .subresourceRange = {vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1}}; + vk::ImageMemoryBarrier barrier{.oldLayout = oldLayout, + .newLayout = newLayout, + .srcQueueFamilyIndex = vk::QueueFamilyIgnored, + .dstQueueFamilyIndex = vk::QueueFamilyIgnored, + .image = image, + .subresourceRange = {.aspectMask = vk::ImageAspectFlagBits::eColor, .levelCount = 1, .layerCount = 1}}; vk::PipelineStageFlags sourceStage; vk::PipelineStageFlags destinationStage; @@ -609,85 +610,86 @@ class HelloTriangleApplication { throw std::invalid_argument("unsupported layout transition!"); } - commandBuffer->pipelineBarrier(sourceStage, destinationStage, {}, {}, nullptr, barrier); - endSingleTimeCommands(*commandBuffer); + commandBuffer.pipelineBarrier(sourceStage, destinationStage, {}, {}, {}, barrier); } - void copyBufferToImage(const vk::raii::Buffer &buffer, vk::raii::Image &image, uint32_t width, uint32_t height) + void copyBufferToImage(vk::raii::CommandBuffer &commandBuffer, const vk::raii::Buffer &buffer, vk::raii::Image &image, uint32_t width, uint32_t height) { - std::unique_ptr commandBuffer = beginSingleTimeCommands(); - vk::BufferImageCopy region{ - .bufferOffset = 0, - .bufferRowLength = 0, - .bufferImageHeight = 0, - .imageSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1}, - .imageOffset = {0, 0, 0}, - .imageExtent = {width, height, 1}}; - commandBuffer->copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, {region}); - endSingleTimeCommands(*commandBuffer); + vk::BufferImageCopy region{.bufferOffset = 0, + .bufferRowLength = 0, + .bufferImageHeight = 0, + .imageSubresource = {.aspectMask = vk::ImageAspectFlagBits::eColor, .mipLevel = 0, .baseArrayLayer = 0, .layerCount = 1}, + .imageOffset = {0, 0, 0}, + .imageExtent = {width, height, 1}}; + commandBuffer.copyBufferToImage(buffer, image, vk::ImageLayout::eTransferDstOptimal, region); } void createVertexBuffer() { - vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + vk::DeviceSize bufferSize = sizeof(vertices[0]) * vertices.size(); + + auto [stagingBuffer, stagingBufferMemory] = + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent); void *dataStaging = stagingBufferMemory.mapMemory(0, bufferSize); memcpy(dataStaging, vertices.data(), bufferSize); stagingBufferMemory.unmapMemory(); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eVertexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, vertexBuffer, vertexBufferMemory); + std::tie(vertexBuffer, vertexBufferMemory) = + createBuffer(bufferSize, vk::BufferUsageFlagBits::eVertexBuffer | vk::BufferUsageFlagBits::eTransferDst, vk::MemoryPropertyFlagBits::eDeviceLocal); copyBuffer(stagingBuffer, vertexBuffer, bufferSize); } + std::pair createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties) + { + vk::BufferCreateInfo bufferInfo{.size = size, .usage = usage, .sharingMode = vk::SharingMode::eExclusive}; + vk::raii::Buffer buffer = vk::raii::Buffer(device, bufferInfo); + vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); + vk::MemoryAllocateInfo allocInfo{.allocationSize = memRequirements.size, .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; + vk::raii::DeviceMemory bufferMemory = vk::raii::DeviceMemory(device, allocInfo); + buffer.bindMemory(*bufferMemory, 0); + return {std::move(buffer), std::move(bufferMemory)}; + } + void createIndexBuffer() { vk::DeviceSize bufferSize = sizeof(indices[0]) * indices.size(); - vk::raii::Buffer stagingBuffer({}); - vk::raii::DeviceMemory stagingBufferMemory({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, stagingBuffer, stagingBufferMemory); + auto [stagingBuffer, stagingBufferMemory] = + createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferSrc, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent); void *data = stagingBufferMemory.mapMemory(0, bufferSize); - memcpy(data, indices.data(), bufferSize); + memcpy(data, indices.data(), (size_t) bufferSize); stagingBufferMemory.unmapMemory(); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eTransferDst | vk::BufferUsageFlagBits::eIndexBuffer, vk::MemoryPropertyFlagBits::eDeviceLocal, indexBuffer, indexBufferMemory); + std::tie(indexBuffer, indexBufferMemory) = + createBuffer(bufferSize, vk::BufferUsageFlagBits::eIndexBuffer | vk::BufferUsageFlagBits::eTransferDst, vk::MemoryPropertyFlagBits::eDeviceLocal); copyBuffer(stagingBuffer, indexBuffer, bufferSize); } void createUniformBuffers() { - uniformBuffers.clear(); - uniformBuffersMemory.clear(); - uniformBuffersMapped.clear(); - for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DeviceSize bufferSize = sizeof(UniformBufferObject); - vk::raii::Buffer buffer({}); - vk::raii::DeviceMemory bufferMem({}); - createBuffer(bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent, buffer, bufferMem); + vk::DeviceSize bufferSize = sizeof(UniformBufferObject); + auto [buffer, bufferMem] = createBuffer( + bufferSize, vk::BufferUsageFlagBits::eUniformBuffer, vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent); uniformBuffers.emplace_back(std::move(buffer)); uniformBuffersMemory.emplace_back(std::move(bufferMem)); - uniformBuffersMapped.emplace_back(uniformBuffersMemory[i].mapMemory(0, bufferSize)); + uniformBuffersMapped.emplace_back(uniformBuffersMemory.back().mapMemory(0, bufferSize)); } } void createDescriptorPool() { - std::array poolSize{ - vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), - vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT)}; - vk::DescriptorPoolCreateInfo poolInfo{ - .flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, - .maxSets = MAX_FRAMES_IN_FLIGHT, - .poolSizeCount = static_cast(poolSize.size()), - .pPoolSizes = poolSize.data()}; + std::array poolSize{{{.type = vk::DescriptorType::eUniformBuffer, .descriptorCount = MAX_FRAMES_IN_FLIGHT}, + {.type = vk::DescriptorType::eCombinedImageSampler, .descriptorCount = MAX_FRAMES_IN_FLIGHT}}}; + vk::DescriptorPoolCreateInfo poolInfo{.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = MAX_FRAMES_IN_FLIGHT, + .poolSizeCount = static_cast(poolSize.size()), + .pPoolSizes = poolSize.data()}; descriptorPool = vk::raii::DescriptorPool(device, poolInfo); } @@ -695,73 +697,46 @@ class HelloTriangleApplication { std::vector layouts(MAX_FRAMES_IN_FLIGHT, descriptorSetLayout); vk::DescriptorSetAllocateInfo allocInfo{ - .descriptorPool = descriptorPool, - .descriptorSetCount = static_cast(layouts.size()), - .pSetLayouts = layouts.data()}; + .descriptorPool = descriptorPool, + .descriptorSetCount = static_cast(layouts.size()), + .pSetLayouts = layouts.data()}; descriptorSets.clear(); descriptorSets = device.allocateDescriptorSets(allocInfo); for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ - .buffer = uniformBuffers[i], - .offset = 0, - .range = sizeof(UniformBufferObject)}; - vk::DescriptorImageInfo imageInfo{ - .sampler = textureSampler, - .imageView = textureImageView, - .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}; - std::array descriptorWrites{ - vk::WriteDescriptorSet{ - .dstSet = descriptorSets[i], - .dstBinding = 0, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eUniformBuffer, - .pBufferInfo = &bufferInfo}, - vk::WriteDescriptorSet{ - .dstSet = descriptorSets[i], - .dstBinding = 1, - .dstArrayElement = 0, - .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, - .pImageInfo = &imageInfo}}; + vk::DescriptorBufferInfo bufferInfo{.buffer = uniformBuffers[i], .offset = 0, .range = sizeof(UniformBufferObject)}; + vk::DescriptorImageInfo imageInfo{.sampler = textureSampler, .imageView = textureImageView, .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}; + + std::array descriptorWrites{{{.dstSet = descriptorSets[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &bufferInfo}, + {.dstSet = descriptorSets[i], + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &imageInfo}}}; device.updateDescriptorSets(descriptorWrites, {}); } } - void createBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags properties, vk::raii::Buffer &buffer, vk::raii::DeviceMemory &bufferMemory) - { - vk::BufferCreateInfo bufferInfo{ - .size = size, - .usage = usage, - .sharingMode = vk::SharingMode::eExclusive}; - buffer = vk::raii::Buffer(device, bufferInfo); - vk::MemoryRequirements memRequirements = buffer.getMemoryRequirements(); - vk::MemoryAllocateInfo allocInfo{ - .allocationSize = memRequirements.size, - .memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties)}; - bufferMemory = vk::raii::DeviceMemory(device, allocInfo); - buffer.bindMemory(bufferMemory, 0); - } - - std::unique_ptr beginSingleTimeCommands() + vk::raii::CommandBuffer beginSingleTimeCommands() { - vk::CommandBufferAllocateInfo allocInfo{ - .commandPool = commandPool, - .level = vk::CommandBufferLevel::ePrimary, - .commandBufferCount = 1}; - std::unique_ptr commandBuffer = std::make_unique(std::move(vk::raii::CommandBuffers(device, allocInfo).front())); + vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; + vk::raii::CommandBuffer commandBuffer = std::move(vk::raii::CommandBuffers(device, allocInfo).front()); - vk::CommandBufferBeginInfo beginInfo{ - .flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; - commandBuffer->begin(beginInfo); + vk::CommandBufferBeginInfo beginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}; + commandBuffer.begin(beginInfo); - return commandBuffer; + return std::move(commandBuffer); } - void endSingleTimeCommands(vk::raii::CommandBuffer &commandBuffer) + void endSingleTimeCommands(vk::raii::CommandBuffer &&commandBuffer) { commandBuffer.end(); @@ -772,13 +747,9 @@ class HelloTriangleApplication void copyBuffer(vk::raii::Buffer &srcBuffer, vk::raii::Buffer &dstBuffer, vk::DeviceSize size) { - vk::CommandBufferAllocateInfo allocInfo{.commandPool = commandPool, .level = vk::CommandBufferLevel::ePrimary, .commandBufferCount = 1}; - vk::raii::CommandBuffer commandCopyBuffer = std::move(device.allocateCommandBuffers(allocInfo).front()); - commandCopyBuffer.begin(vk::CommandBufferBeginInfo{.flags = vk::CommandBufferUsageFlagBits::eOneTimeSubmit}); + vk::raii::CommandBuffer commandCopyBuffer = beginSingleTimeCommands(); commandCopyBuffer.copyBuffer(*srcBuffer, *dstBuffer, vk::BufferCopy{.size = size}); - commandCopyBuffer.end(); - queue.submit(vk::SubmitInfo{.commandBufferCount = 1, .pCommandBuffers = &*commandCopyBuffer}, nullptr); - queue.waitIdle(); + endSingleTimeCommands(std::move(commandCopyBuffer)); } uint32_t findMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags properties) @@ -807,7 +778,8 @@ class HelloTriangleApplication { auto &commandBuffer = commandBuffers[frameIndex]; commandBuffer.begin({}); - // Before starting rendering, transition the swapchain image to COLOR_ATTACHMENT_OPTIMAL + + // Before starting rendering, transition the swapchain image to vk::ImageLayout::eColorAttachmentOptimal transition_image_layout( imageIndex, vk::ImageLayout::eUndefined, @@ -834,11 +806,12 @@ class HelloTriangleApplication commandBuffer.setViewport(0, vk::Viewport(0.0f, 0.0f, static_cast(swapChainExtent.width), static_cast(swapChainExtent.height), 0.0f, 1.0f)); commandBuffer.setScissor(0, vk::Rect2D(vk::Offset2D(0, 0), swapChainExtent)); commandBuffer.bindVertexBuffers(0, *vertexBuffer, {0}); - commandBuffer.bindIndexBuffer(*indexBuffer, 0, vk::IndexType::eUint16); + commandBuffer.bindIndexBuffer(*indexBuffer, 0, vk::IndexTypeValue::value); commandBuffer.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, pipelineLayout, 0, *descriptorSets[frameIndex], nullptr); - commandBuffer.drawIndexed(indices.size(), 1, 0, 0, 0); + commandBuffer.drawIndexed(static_cast(indices.size()), 1, 0, 0, 0); commandBuffer.endRendering(); - // After rendering, transition the swapchain image to PRESENT_SRC + + // After rendering, transition the swapchain image to vk::ImageLayout::ePresentSrcKHR transition_image_layout( imageIndex, vk::ImageLayout::eColorAttachmentOptimal, @@ -871,11 +844,11 @@ class HelloTriangleApplication .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = swapChainImages[imageIndex], .subresourceRange = { - .aspectMask = vk::ImageAspectFlagBits::eColor, - .baseMipLevel = 0, - .levelCount = 1, - .baseArrayLayer = 0, - .layerCount = 1}}; + .aspectMask = vk::ImageAspectFlagBits::eColor, + .baseMipLevel = 0, + .levelCount = 1, + .baseArrayLayer = 0, + .layerCount = 1}}; vk::DependencyInfo dependency_info = { .dependencyFlags = {}, .imageMemoryBarrierCount = 1, @@ -909,7 +882,8 @@ class HelloTriangleApplication UniformBufferObject ubo{}; ubo.model = rotate(glm::mat4(1.0f), time * glm::radians(90.0f), glm::vec3(0.0f, 0.0f, 1.0f)); ubo.view = lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); - ubo.proj = glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); + ubo.proj = + glm::perspective(glm::radians(45.0f), static_cast(swapChainExtent.width) / static_cast(swapChainExtent.height), 0.1f, 10.0f); ubo.proj[1][1] *= -1; memcpy(uniformBuffersMapped[currentImage], &ubo, sizeof(ubo)); diff --git a/en/06_Texture_mapping/02_Combined_image_sampler.adoc b/en/06_Texture_mapping/02_Combined_image_sampler.adoc index d3dfc1b7..508d31f3 100644 --- a/en/06_Texture_mapping/02_Combined_image_sampler.adoc +++ b/en/06_Texture_mapping/02_Combined_image_sampler.adoc @@ -8,10 +8,10 @@ We looked at descriptors for the first time in the uniform buffers part of the t In this chapter, we will look at a new type of descriptor: _combined image sampler_. This descriptor makes it possible for shaders to access an image resource through a sampler object like the one we created in the previous chapter. -It's worth noting that Vulkan provides flexibility in how textures are accessed in shaders through different descriptor types. While we'll be using a _combined image sampler_ in this tutorial, Vulkan also supports separate descriptors for samplers (`VK_DESCRIPTOR_TYPE_SAMPLER`) and sampled images (`VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE`). Using separate descriptors allows you to reuse the same sampler with multiple images or access the same image with different sampling parameters. This can be more efficient in scenarios where you have many textures that use identical sampling configurations. However, the combined image sampler is often more convenient and can offer better performance on some hardware due to optimized cache usage. +It's worth noting that Vulkan provides flexibility in how textures are accessed in shaders through different descriptor types. While we'll be using a _combined image sampler_ in this tutorial, Vulkan also supports separate descriptors for samplers (`vk::DescriptorType::eSampler`) and sampled images (`vk::DescriptorType::eSampledImage`). Using separate descriptors allows you to reuse the same sampler with multiple images or access the same image with different sampling parameters. This can be more efficient in scenarios where you have many textures that use identical sampling configurations. However, the combined image sampler is often more convenient and can offer better performance on some hardware due to optimized cache usage. We'll start by modifying the descriptor set layout, descriptor pool and descriptor set to include such a combined image sampler descriptor. -After that, we're going to add texture coordinates to `Vertex` and modify the fragment shader to read colors from the texture instead of just interpolating the vertex colors. +After that, we're going to add texture coordinates to `Vertex`, replacing the color value, and modify the fragment shader to read colors from the texture instead of just interpolating the vertex colors. == Updating the descriptors @@ -20,36 +20,36 @@ We'll simply put it in the binding after the uniform buffer: [,c++] ---- -std::array bindings = { - vk::DescriptorSetLayoutBinding( 0, vk::DescriptorType::eUniformBuffer, 1, vk::ShaderStageFlagBits::eVertex, nullptr), - vk::DescriptorSetLayoutBinding( 1, vk::DescriptorType::eCombinedImageSampler, 1, vk::ShaderStageFlagBits::eFragment, nullptr) -}; +std::array bindings{ + {{.binding = 0, .descriptorType = vk::DescriptorType::eUniformBuffer, .descriptorCount = 1, .stageFlags = vk::ShaderStageFlagBits::eVertex}, + {.binding = 1, .descriptorType = vk::DescriptorType::eCombinedImageSampler, .descriptorCount = 1, .stageFlags = vk::ShaderStageFlagBits::eFragment}}}; -vk::DescriptorSetLayoutCreateInfo layoutInfo({}, bindings.size(), bindings.data()); +vk::DescriptorSetLayoutCreateInfo layoutInfo{.bindingCount = static_cast(bindings.size()), .pBindings = bindings.data()}; ---- Make sure to set the `stageFlags` to indicate that we intend to use the combined image sampler descriptor in the fragment shader. That's where the color of the fragment is going to be determined. It is possible to use texture sampling in the vertex shader, for example to dynamically deform a grid of vertices by a https://en.wikipedia.org/wiki/Heightmap[heightmap]. -We must also create a larger descriptor pool to make room for the allocation of the combined image sampler by adding another `VkPoolSize` of type `VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER` to the `VkDescriptorPoolCreateInfo`. -Go to the `createDescriptorPool` function and modify it to include a `VkDescriptorPoolSize` for this descriptor: +We must also create a larger descriptor pool to make room for the allocation of the combined image sampler by adding another `vk::PoolSize` of type `vk::DescriptorType::eCombinedImageSampler` to the `vk::DescriptorPoolCreateInfo`. +Go to the `createDescriptorPool` function and modify it to include a `vk::DescriptorPoolSize` for this descriptor: [,c++] ---- -std::array poolSize { - vk::DescriptorPoolSize( vk::DescriptorType::eUniformBuffer, MAX_FRAMES_IN_FLIGHT), - vk::DescriptorPoolSize( vk::DescriptorType::eCombinedImageSampler, MAX_FRAMES_IN_FLIGHT) -}; -vk::DescriptorPoolCreateInfo poolInfo(vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, MAX_FRAMES_IN_FLIGHT, poolSize); +std::array poolSize{{{.type = vk::DescriptorType::eUniformBuffer, .descriptorCount = MAX_FRAMES_IN_FLIGHT}, + {.type = vk::DescriptorType::eCombinedImageSampler, .descriptorCount = MAX_FRAMES_IN_FLIGHT}}}; +vk::DescriptorPoolCreateInfo poolInfo{.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet, + .maxSets = MAX_FRAMES_IN_FLIGHT, + .poolSizeCount = static_cast(poolSize.size()), + .pPoolSizes = poolSize.data()}; ---- -Inadequate descriptor pools are a good example of a problem that the validation layers will not catch: As of Vulkan 1.1, `vkAllocateDescriptorSets` may fail with the error code `VK_ERROR_POOL_OUT_OF_MEMORY` if the pool is not sufficiently large, but the driver may also try to solve the problem internally. +Inadequate descriptor pools are a good example of a problem that the validation layers will not catch: As of Vulkan 1.1, `vk::raii::Device::allocateDescriptorSets` may fail and throw a `vk::OutOfPoolMemoryError` exception if the pool is not sufficiently large, but the driver may also try to solve the problem internally. This means that sometimes (depending on hardware, pool size and allocation size) the driver will let us get away with an allocation that exceeds the limits of our descriptor pool. -Other times, `vkAllocateDescriptorSets` will fail and return `VK_ERROR_POOL_OUT_OF_MEMORY`. +Other times, `vk::raii::Device::allocateDescriptorSets` will fail and throw a `vk::OutOfPoolMemoryError` exception. This can be particularly frustrating if the allocation succeeds on some machines, but fails on others. -Since Vulkan shifts the responsibility for the allocation to the driver, it is no longer a strict requirement to only allocate as many descriptors of a certain type (`VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER`, etc.) as specified by the corresponding `descriptorCount` members for the creation of the descriptor pool. +Since Vulkan shifts the responsibility for the allocation to the driver, it is no longer a strict requirement to only allocate as many descriptors of a certain type (`vk::DescriptorType::eCombinedImageSampler`, etc.) as specified by the corresponding `descriptorCount` members for the creation of the descriptor pool. However, it remains best practice to do so, and in the future, `VK_LAYER_KHRONOS_validation` will warn about this type of problem if you enable https://vulkan.lunarg.com/doc/view/latest/windows/best_practices.html[Best Practice Validation]. The final step is to bind the actual image and sampler resources to the descriptors in the descriptor set. @@ -57,25 +57,32 @@ Go to the `createDescriptorSets` function. [,c++] ---- -for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { - vk::DescriptorBufferInfo bufferInfo{ .buffer = uniformBuffers[i], .offset = 0, .range = sizeof(UniformBufferObject) }; - vk::DescriptorImageInfo imageInfo{ .sampler = textureSampler, .imageView = textureImageView, .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}; +for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) +{ + vk::DescriptorBufferInfo bufferInfo{.buffer = uniformBuffers[i], .offset = 0, .range = sizeof(UniformBufferObject)}; + vk::DescriptorImageInfo imageInfo{.sampler = textureSampler, .imageView = textureImageView, .imageLayout = vk::ImageLayout::eShaderReadOnlyOptimal}; ... } ---- -The resources for a combined image sampler structure must be specified in a `VkDescriptorImageInfo` struct, just like the buffer resource for a uniform buffer descriptor is specified in a `VkDescriptorBufferInfo` struct. +The resources for a combined image sampler structure must be specified in a `vk::DescriptorImageInfo` struct, just like the buffer resource for a uniform buffer descriptor is specified in a `vk::DescriptorBufferInfo` struct. This is where the objects from the previous chapter come together. [,c++] ---- -std::array descriptorWrites{ - vk::WriteDescriptorSet{ .dstSet = descriptorSets[i], .dstBinding = 0, .dstArrayElement = 0, .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eUniformBuffer, .pBufferInfo = &bufferInfo }, - vk::WriteDescriptorSet{ .dstSet = descriptorSets[i], .dstBinding = 1, .dstArrayElement = 0, .descriptorCount = 1, - .descriptorType = vk::DescriptorType::eCombinedImageSampler, .pImageInfo = &imageInfo } -}; +std::array descriptorWrites{{{.dstSet = descriptorSets[i], + .dstBinding = 0, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eUniformBuffer, + .pBufferInfo = &bufferInfo}, + {.dstSet = descriptorSets[i], + .dstBinding = 1, + .dstArrayElement = 0, + .descriptorCount = 1, + .descriptorType = vk::DescriptorType::eCombinedImageSampler, + .pImageInfo = &imageInfo}}}; device.updateDescriptorSets(descriptorWrites, {}); ---- @@ -90,27 +97,26 @@ The texture coordinates determine how the image is actually mapped to the geomet [,c++] ---- -struct Vertex { +struct Vertex +{ glm::vec2 pos; - glm::vec3 color; glm::vec2 texCoord; - static vk::VertexInputBindingDescription getBindingDescription() { + static vk::VertexInputBindingDescription getBindingDescription() + { return { 0, sizeof(Vertex), vk::VertexInputRate::eVertex }; } - static std::array getAttributeDescriptions() { - return { - vk::VertexInputAttributeDescription( 0, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, pos) ), - vk::VertexInputAttributeDescription( 1, 0, vk::Format::eR32G32B32Sfloat, offsetof(Vertex, color) ), - vk::VertexInputAttributeDescription( 2, 0, vk::Format::eR32G32Sfloat, offsetof(Vertex, texCoord) ) - }; + static std::array getAttributeDescriptions() + { + return {{{.location = 0, .binding = 0, .format = vk::Format::eR32G32Sfloat, .offset = offsetof(Vertex, pos)}, + {.location = 1, .binding = 0, .format = vk::Format::eR32G32Sfloat, .offset = offsetof(Vertex, texCoord)}}}; } }; ---- -Modify the `Vertex` struct to include a `vec2` for texture coordinates. -Make sure to also add a `VkVertexInputAttributeDescription` so that we can use access texture coordinates as input in the vertex shader. +Modify the `Vertex` struct to include a `vec2` for texture coordinates. The color isn't used anymore and can be removed. +Make sure to also add a `vk::VertexInputAttributeDescription` so that we can access texture coordinates as input in the vertex shader. That is necessary to be able to pass them to the fragment shader for interpolation across the surface of the square. [,c++] @@ -163,11 +169,9 @@ VSOutput vertMain(VSInput input) { return output; } -Sampler2D texture; - [shader("fragment")] float4 fragMain(VSOutput vertIn) : SV_TARGET { - return texture.Sample(vertIn.fragTexCoord); + return float4(vertIn.fragTexCoord, 0.0, 1.0); } ----