From 2758c47b8622a5f1aedebc5cb86f3b33bda2f675 Mon Sep 17 00:00:00 2001 From: saschawillems Date: Mon, 15 Aug 2016 15:17:01 +0200 Subject: [PATCH] Added runtime mip map generation examples (wip) (refs #178) --- data/models/tunnel.dae | 209 +++++ data/shaders/texturemipmapgen/texture.frag | 19 + .../shaders/texturemipmapgen/texture.frag.spv | Bin 0 -> 1140 bytes data/shaders/texturemipmapgen/texture.vert | 35 + .../shaders/texturemipmapgen/texture.vert.spv | Bin 0 -> 1912 bytes data/textures/checkerboard_nomips_rgba.ktx | Bin 0 -> 65636 bytes texturemipmapgen/texturemipmapgen.cpp | 758 ++++++++++++++++++ texturemipmapgen/texturemipmapgen.vcxproj | 99 +++ .../texturemipmapgen.vcxproj.filters | 53 ++ vulkanExamples.sln | 8 +- 10 files changed, 1180 insertions(+), 1 deletion(-) create mode 100644 data/models/tunnel.dae create mode 100644 data/shaders/texturemipmapgen/texture.frag create mode 100644 data/shaders/texturemipmapgen/texture.frag.spv create mode 100644 data/shaders/texturemipmapgen/texture.vert create mode 100644 data/shaders/texturemipmapgen/texture.vert.spv create mode 100644 data/textures/checkerboard_nomips_rgba.ktx create mode 100644 texturemipmapgen/texturemipmapgen.cpp create mode 100644 texturemipmapgen/texturemipmapgen.vcxproj create mode 100644 texturemipmapgen/texturemipmapgen.vcxproj.filters diff --git a/data/models/tunnel.dae b/data/models/tunnel.dae new file mode 100644 index 00000000..92b14ad3 --- /dev/null +++ b/data/models/tunnel.dae @@ -0,0 +1,209 @@ + + + + + Blender User + Blender 2.77.0 commit date:2016-03-18, commit time:12:34, hash:22a2853 + + 2016-08-14T23:02:23 + 2016-08-14T23:02:23 + + Z_UP + + + + + + + 49.13434 + 1.777778 + 0.1 + 100 + + + + + + 0 + 0 + 0 + + + + + + + + + 1 1 1 + 1 + 0 + 0.00111109 + + + + + 0.000999987 + 1 + 0.1 + 0.1 + 1 + 1 + 1 + 2 + 0 + 1 + 1 + 1 + 1 + 1 + 0 + 2880 + 2 + 30.002 + 1.000799 + 0.04999995 + 29.99998 + 1 + 2 + 0 + 0 + 1 + 1 + 1 + 1 + 8192 + 1 + 1 + 0 + 1 + 1 + 1 + 3 + 0 + 0 + 0 + 0 + 0 + 1 + 1 + 1 + 3 + 0.15 + 75 + 1 + 1 + 0 + 1 + 1 + 0 + + + + + + + + + + + + 0 0 0 1 + + + 0 0 0 1 + + + 0.64 0.64 0.64 1 + + + 0.5 0.5 0.5 1 + + + 50 + + + 1 + + + + + + + + + + + + + + + + -1 -1 -1 -1 -1 1 -1 1 -1 -1 1 1 1 -1 -1 1 -1 1 1 1 -1 1 1 1 + + + + + + + + + + 0 1 0 0 -1 0 0 0 -1 0 0 1 + + + + + + + + + + 0.9998999 9.998e-5 9.998e-5 0.9999001 1.0001e-4 1.00099e-4 0.9999999 0 0 1 0 0 1 1 0 0 1 0 0.9999001 0.9999001 9.998e-5 9.998e-5 0.9999001 9.998e-5 0.9998999 9.998e-5 0.9998999 0.9999001 9.998e-5 0.9999001 0.9999999 0 1 0.9999999 0 1 1 1 0 1 0 0 0.9999001 0.9999001 1.0004e-4 0.9999001 9.998e-5 9.998e-5 + + + + + + + + + + + + + + + 3 3 3 3 3 3 3 3 +

7 0 0 2 0 1 3 0 2 1 1 3 4 1 4 5 1 5 2 2 6 4 2 7 0 2 8 7 3 9 1 3 10 5 3 11 7 0 12 6 0 13 2 0 14 1 1 15 0 1 16 4 1 17 2 2 18 6 2 19 4 2 20 7 3 21 3 3 22 1 3 23

+
+
+
+
+ + + + + 0.6858805 -0.3173701 0.6548619 7.481132 0.7276338 0.3124686 -0.6106656 -6.50764 -0.01081678 0.8953432 0.4452454 5.343665 0 0 0 1 + + + + -0.2908646 -0.7711008 0.5663932 4.076245 0.9551712 -0.1998834 0.2183912 1.005454 -0.05518906 0.6045247 0.7946723 5.903862 0 0 0 1 + + + + 10 0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/data/shaders/texturemipmapgen/texture.frag b/data/shaders/texturemipmapgen/texture.frag new file mode 100644 index 00000000..0d24666a --- /dev/null +++ b/data/shaders/texturemipmapgen/texture.frag @@ -0,0 +1,19 @@ +#version 450 + +#extension GL_ARB_separate_shader_objects : enable +#extension GL_ARB_shading_language_420pack : enable + +layout (set = 0, binding = 1) uniform texture2D textureColor; +layout (set = 0, binding = 2) uniform sampler samplers[3]; + +layout (location = 0) in vec2 inUV; +layout (location = 1) in float inLodBias; +layout (location = 2) in float inSamplerIndex; + +layout (location = 0) out vec4 outFragColor; + +void main() +{ + highp int samplerIndex = int(inSamplerIndex); + outFragColor = texture(sampler2D(textureColor, samplers[samplerIndex]), inUV, inLodBias); +} \ No newline at end of file diff --git a/data/shaders/texturemipmapgen/texture.frag.spv b/data/shaders/texturemipmapgen/texture.frag.spv new file mode 100644 index 0000000000000000000000000000000000000000..b6238f312980fdc6bcb5437c0f731db3ff057224 GIT binary patch literal 1140 zcmY+CZA;uh6op53v(;*~zSq}gwXK36QmhDKK?N6KKPlReVRVOW+BI2{EcpBStNKOg zc_zCwPPp7T=bpK9@14}fcitE?rfivQ0p3oh+ed_mIcAa$Gv!HYhUt65q zzKXz8Z#pQhGTXgOducY(FKAP^=VFk#3Lls?@x%P8lH7IQZ=BrSmpJgf$4NWwT*MYp zOB!agC1VC)?gg{J+_&w~f7}#i*2KBD8V3^}Tz|4uYrjC~6>0?j@WXg7~)^=+PS*O0Ryt0U5 z*z{Q;nh|r(uKV@f3ZfWrMm8iHmwB=|*(2GytWTd_&A;W`FJ-mnZ@%C1v#=ecK_~RH z4?#Ul{p8?dc-YBKYqS-`4Zj)0jiaCu`pZj;?cnf}De0u{`@zJJ9Ch~gHG_#KiY*i_ z+1RvSOX^!upnFP+Yf75Et+&d$Y}bOOohC#QCCaQU6&mtxCH1hW*>U9bISRk*B-sf^ zm72u`Wiw!Xi8BjY?Pi$1j_cu9L3{TV%|{1`)@55W#kSimgTE3*1Ya$jPF zXRw!Kkqwr7s5^uFsecp`9k^tB5yfwkv=xwssTMVx255k(8JK**^wZ&OVQRrB$+q?F z@T_G|b9!Xvg4N}IoZ0OM#_yHPodx&bFF&6;qdS&49d(BL-o;U8xL>kr(OZ1UgIdqX z7!Fr*XtAd?d7|eIS6}ME;5;~Y9<}YR8}Hotk4c5DA|K*pJ)u=uvnQKWJmd&w&l4Wh zj23>qz`SQT_RnhWk-2>^vBAx2pR>-(q~07C9%cp9U>`hU5q!Vx3uoiUee{W5!x7(v zX7;G*l)fi5!-1z21$~EeJX(yiFKZ?*G@K2l{&2*0RkJ7Skr5mCn#|>TU2C-6v3bS5 z(^rJ^Sn|FQ8+YF-bo7e5!E*)Yd~XXg(BIL_UF6~R=d~s_>hMrAak$v{Gh`2*^TE6Y zIQ-E*mKk%r!TC?M!k74*p0|R2Rqu?u$%BFJj~IR)c&3f-f8Y@jfP%0%>%9Bf9f237G(IrqoF5DGWzkZ$bY%uG@WoO y1xF8@Znfa($Brsbzn;kON6$We{h@Q&Ta$4=8e*o;&t!8l@*;0Ay1(l7n(QB_I)Kms literal 0 HcmV?d00001 diff --git a/data/textures/checkerboard_nomips_rgba.ktx b/data/textures/checkerboard_nomips_rgba.ktx new file mode 100644 index 0000000000000000000000000000000000000000..a8d010fa63193ffb582533a7a49a1204c07339af GIT binary patch literal 65636 zcmeI5K`sPg7)1ZULL_!J*f@ZV1I!65Bv{x=Sj>h*9Kd}X!Qnj=7uvaBQdi8G)92bj+RSKhM$5rJrAaMxcG~(J^_9<`ER*LCx1pD9NWp<&40^1 zI{7mK;n+^*zI^_F`}4Q&Ejm9xBM^@5WbVuFPy4x}^Yb$T;n+^*ZvI;C(aE0?2*-9Z zck|zJk52xKKsdIOxtsr%dvx+=1j4bM%-#IA+@q5}BM^@5WbWp_gBpG0*hkw5i_MV^1pP%NR5itMu56J*apZ!DF z+duY?`^N~lfA$~A085|!N7&nc_MiL52)KXtAIShqpZ!PJ+kf_-`^N~lfA$~A085|! zN7(;`{xd&}z<8DQIIe-ZYxmR|b1|0DzEj}aj3-G9$NmOlH}{j>i_2HZbJ ufUvj!>_3)1`_KKe|40VhKSqGCxBu)vmOlH>{j>i_2HZbJfUvj!#`Y7;0C%nc literal 0 HcmV?d00001 diff --git a/texturemipmapgen/texturemipmapgen.cpp b/texturemipmapgen/texturemipmapgen.cpp new file mode 100644 index 00000000..e2a7f6b4 --- /dev/null +++ b/texturemipmapgen/texturemipmapgen.cpp @@ -0,0 +1,758 @@ +/* +* Vulkan Example - Runtime mip map generation +* +* Copyright (C) 2016 by Sascha Willems - www.saschawillems.de +* +* This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT) +*/ + +// todo: Fallback for sampler selection on devices that don't support shaderSampledImageArrayDynamicIndexing + +#include +#include +#include +#include +#include +#include + +#define GLM_FORCE_RADIANS +#define GLM_FORCE_DEPTH_ZERO_TO_ONE +#include +#include + +#include +#include "vulkanexamplebase.h" +#include "vulkandevice.hpp" +#include "vulkanbuffer.hpp" + +#define VERTEX_BUFFER_BIND_ID 0 +#define ENABLE_VALIDATION false + +// Vertex layout for this example +struct Vertex { + float pos[3]; + float uv[2]; + float normal[3]; +}; + +std::vector vertexLayout = +{ + vkMeshLoader::VERTEX_LAYOUT_POSITION, + vkMeshLoader::VERTEX_LAYOUT_UV, + vkMeshLoader::VERTEX_LAYOUT_NORMAL +}; +class VulkanExample : public VulkanExampleBase +{ +public: + struct Texture { + VkImage image; + VkImageLayout imageLayout; + VkDeviceMemory deviceMemory; + VkImageView view; + uint32_t width, height; + uint32_t mipLevels; + } texture; + + // To demonstrate mip mapping and filtering this example uses separate samplers + std::vector samplerNames{ "No mip maps" , "With mip maps (bilinear)" , "With mip maps (anisotropic)" }; + std::vector samplers; + + struct { + vkMeshLoader::MeshBuffer tunnel; + } meshes; + + struct { + VkPipelineVertexInputStateCreateInfo inputState; + std::vector bindingDescriptions; + std::vector attributeDescriptions; + } vertices; + + vk::Buffer uniformBufferVS; + + struct uboVS { + glm::mat4 projection; + glm::mat4 view; + glm::vec4 viewPos; + float lodBias = 0.0f; + float samplerIndex = 0.0f; + } uboVS; + + struct { + VkPipeline solid; + } pipelines; + + VkPipelineLayout pipelineLayout; + VkDescriptorSet descriptorSet; + VkDescriptorSetLayout descriptorSetLayout; + + VulkanExample() : VulkanExampleBase(ENABLE_VALIDATION) + { + title = "Vulkan Example - Texturing"; + enableTextOverlay = true; + camera.type = Camera::CameraType::firstperson; + camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 512.0f); + camera.setRotation(glm::vec3(0.0f, 90.0f, 0.0f)); + camera.setTranslation(glm::vec3(10.0f, 0.0f, 0.0f)); + camera.movementSpeed = 2.5f; + camera.rotationSpeed = 0.5f; + } + + ~VulkanExample() + { + destroyTextureImage(texture); + vkDestroyPipeline(device, pipelines.solid, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + uniformBufferVS.destroy(); + for (auto sampler : samplers) + { + vkDestroySampler(device, sampler, nullptr); + } + vkMeshLoader::freeMeshBufferResources(device, &meshes.tunnel); + } + + void loadTexture(std::string fileName, VkFormat format, bool forceLinearTiling) + { +#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); + assert(asset); + size_t size = AAsset_getLength(asset); + assert(size > 0); + + void *textureData = malloc(size); + AAsset_read(asset, textureData, size); + AAsset_close(asset); + + gli::texture2D tex2D(gli::load((const char*)textureData, size)); +#else + gli::texture2D tex2D(gli::load(fileName)); +#endif + + assert(!tex2D.empty()); + + VkFormatProperties formatProperties; + + texture.width = static_cast(tex2D[0].dimensions().x); + texture.height = static_cast(tex2D[0].dimensions().y); + + // calculate num of mip maps + // numLevels = 1 + floor(log2(max(w, h, d))) + // Calculated as log2(max(width, height, depth))c + 1 (see specs) + texture.mipLevels = floor(log2(std::max(texture.width, texture.height))) + 1; + + // Get device properites for the requested texture format + vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &formatProperties); + + // todo check blit flags + + VkMemoryAllocateInfo memAllocInfo = vkTools::initializers::memoryAllocateInfo(); + VkMemoryRequirements memReqs = {}; + + // Create a host-visible staging buffer that contains the raw image data + VkBuffer stagingBuffer; + VkDeviceMemory stagingMemory; + + VkBufferCreateInfo bufferCreateInfo = vkTools::initializers::bufferCreateInfo(); + bufferCreateInfo.size = tex2D.size(); + // This buffer is used as a transfer source for the buffer copy + bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; + bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + VK_CHECK_RESULT(vkCreateBuffer(device, &bufferCreateInfo, nullptr, &stagingBuffer)); + vkGetBufferMemoryRequirements(device, stagingBuffer, &memReqs); + memAllocInfo.allocationSize = memReqs.size; + memAllocInfo.memoryTypeIndex = vulkanDevice->getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT); + VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &stagingMemory)); + VK_CHECK_RESULT(vkBindBufferMemory(device, stagingBuffer, stagingMemory, 0)); + + // Copy texture data into staging buffer + uint8_t *data; + VK_CHECK_RESULT(vkMapMemory(device, stagingMemory, 0, memReqs.size, 0, (void **)&data)); + memcpy(data, tex2D.data(), tex2D.size()); + vkUnmapMemory(device, stagingMemory); + + // Create optimal tiled target image + VkImageCreateInfo imageCreateInfo = vkTools::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.usage = VK_IMAGE_USAGE_SAMPLED_BIT; + imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + imageCreateInfo.extent = { texture.width, texture.height, 1 }; + imageCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT; + VK_CHECK_RESULT(vkCreateImage(device, &imageCreateInfo, nullptr, &texture.image)); + 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)); + + VkCommandBuffer copyCmd = VulkanExampleBase::createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); + + VkImageSubresourceRange subresourceRange = {}; + subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + subresourceRange.levelCount = 1; + subresourceRange.layerCount = 1; + + // Optimal image will be used as destination for the copy, so we must transfer from our initial undefined image layout to the transfer destination layout + vkTools::setImageLayout(copyCmd, texture.image, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, subresourceRange); + + // Copy the first mip of the chain, remaining mips will be generated + VkBufferImageCopy bufferCopyRegion = {}; + bufferCopyRegion.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + bufferCopyRegion.imageSubresource.mipLevel = 0; + bufferCopyRegion.imageSubresource.baseArrayLayer = 0; + bufferCopyRegion.imageSubresource.layerCount = 1; + bufferCopyRegion.imageExtent.width = texture.width; + bufferCopyRegion.imageExtent.height = texture.height; + bufferCopyRegion.imageExtent.depth = 1; + + vkCmdCopyBufferToImage(copyCmd, stagingBuffer, texture.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &bufferCopyRegion); + + // Transition first mip level to transfer source for read during blit + texture.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + vkTools::setImageLayout( + copyCmd, + texture.image, + VK_IMAGE_ASPECT_COLOR_BIT, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + subresourceRange); + + VulkanExampleBase::flushCommandBuffer(copyCmd, queue, true); + + // Clean up staging resources + vkFreeMemory(device, stagingMemory, nullptr); + vkDestroyBuffer(device, stagingBuffer, nullptr); + + // Generate the mip chain + // --------------------------------------------------------------- + // We copy down the whole mip chain doint a blit from mip-1 to mip + // An alternative way would be to always blit from the first mip level and sample that one down + // todo: comment + + VkCommandBuffer blitCmd = VulkanExampleBase::createCommandBuffer(VK_COMMAND_BUFFER_LEVEL_PRIMARY, true); + + // Copy down mips from n-1 to n + for (int32_t i = 1; i < texture.mipLevels; i++) + { + int32_t mipWidth = texture.width >> i; + int32_t mipHeight = texture.height >> i; + + VkImageBlit imageBlit{}; + + // Source + imageBlit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageBlit.srcSubresource.layerCount = 1; + imageBlit.srcSubresource.mipLevel = i-1; + imageBlit.srcOffsets[1].x = int32_t(texture.width >> (i - 1)); + imageBlit.srcOffsets[1].y = int32_t(texture.height >> (i - 1)); + imageBlit.srcOffsets[1].z = 1; + + // Destination + imageBlit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + imageBlit.dstSubresource.layerCount = 1; + imageBlit.dstSubresource.mipLevel = i; + imageBlit.dstOffsets[1].x = int32_t(texture.width >> i); + imageBlit.dstOffsets[1].y = int32_t(texture.height >> i); + imageBlit.dstOffsets[1].z = 1; + + VkImageSubresourceRange mipSubRange = {}; + mipSubRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + mipSubRange.baseMipLevel = i; + mipSubRange.levelCount = 1; + mipSubRange.layerCount = 1; + + // Transiton current mip level to transfer dest + vkTools::setImageLayout( + blitCmd, + texture.image, + VK_IMAGE_ASPECT_COLOR_BIT, + VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + mipSubRange); + + // Blit from previous level + vkCmdBlitImage( + blitCmd, + texture.image, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + texture.image, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + 1, + &imageBlit, + VK_FILTER_LINEAR); + + // Transiton current mip level to transfer source for read in next iteration + vkTools::setImageLayout( + blitCmd, + texture.image, + VK_IMAGE_ASPECT_COLOR_BIT, + VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + mipSubRange); + } + + // After the loop, all mip layers are in TRANSFER_SRC layout, so transition all to SHADER_READ + subresourceRange.levelCount = texture.mipLevels; + vkTools::setImageLayout( + blitCmd, + texture.image, + VK_IMAGE_ASPECT_COLOR_BIT, + VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, + texture.imageLayout, + subresourceRange); + + VulkanExampleBase::flushCommandBuffer(blitCmd, queue, true); + // --------------------------------------------------------------- + + // Create samplers + samplers.resize(3); + VkSamplerCreateInfo sampler = vkTools::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 = 0.0f; + sampler.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; + sampler.maxAnisotropy = 1.0; + sampler.anisotropyEnable = VK_FALSE; + + // Without mip mapping + VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &samplers[0])); + + // With mip mapping + sampler.maxLod = (float)texture.mipLevels; + VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &samplers[1])); + + // With mip mapping and anisotropic filtering + if (vulkanDevice->features.samplerAnisotropy) + { + sampler.maxAnisotropy = vulkanDevice->properties.limits.maxSamplerAnisotropy; + sampler.anisotropyEnable = VK_TRUE; + } + VK_CHECK_RESULT(vkCreateSampler(device, &sampler, nullptr, &samplers[2])); + + // Create image view + VkImageViewCreateInfo view = vkTools::initializers::imageViewCreateInfo(); + view.image = texture.image; + view.viewType = VK_IMAGE_VIEW_TYPE_2D; + view.format = format; + view.components = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A }; + 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; + VK_CHECK_RESULT(vkCreateImageView(device, &view, nullptr, &texture.view)); + } + + // Free all Vulkan resources used a texture object + void destroyTextureImage(Texture texture) + { + vkDestroyImageView(device, texture.view, nullptr); + vkDestroyImage(device, texture.image, nullptr); + vkFreeMemory(device, texture.deviceMemory, nullptr); + } + + void buildCommandBuffers() + { + VkCommandBufferBeginInfo cmdBufInfo = vkTools::initializers::commandBufferBeginInfo(); + + VkClearValue clearValues[2]; + clearValues[0].color = defaultClearColor; + clearValues[1].depthStencil = { 1.0f, 0 }; + + VkRenderPassBeginInfo renderPassBeginInfo = vkTools::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 = vkTools::initializers::viewport((float)width, (float)height, 0.0f, 1.0f); + vkCmdSetViewport(drawCmdBuffers[i], 0, 1, &viewport); + + VkRect2D scissor = vkTools::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, NULL); + vkCmdBindPipeline(drawCmdBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelines.solid); + + VkDeviceSize offsets[1] = { 0 }; + vkCmdBindVertexBuffers(drawCmdBuffers[i], VERTEX_BUFFER_BIND_ID, 1, &meshes.tunnel.vertices.buf, offsets); + vkCmdBindIndexBuffer(drawCmdBuffers[i], meshes.tunnel.indices.buf, 0, VK_INDEX_TYPE_UINT32); + + vkCmdDrawIndexed(drawCmdBuffers[i], meshes.tunnel.indexCount, 1, 0, 0, 0); + + vkCmdEndRenderPass(drawCmdBuffers[i]); + + VK_CHECK_RESULT(vkEndCommandBuffer(drawCmdBuffers[i])); + } + } + + void draw() + { + VulkanExampleBase::prepareFrame(); + + // Command buffer to be sumitted to the queue + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; + + // Submit to queue + VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); + + VulkanExampleBase::submitFrame(); + } + + void loadAssets() + { + loadMesh(getAssetPath() + "models/tunnel.dae", &meshes.tunnel, vertexLayout, 1.0f); + loadTexture(getAssetPath() + "textures/checkerboard_nomips_rgba.ktx", VK_FORMAT_R8G8B8A8_UNORM, false); + } + + void setupVertexDescriptions() + { + // Binding description + vertices.bindingDescriptions.resize(1); + vertices.bindingDescriptions[0] = + vkTools::initializers::vertexInputBindingDescription( + VERTEX_BUFFER_BIND_ID, + sizeof(Vertex), + VK_VERTEX_INPUT_RATE_VERTEX); + + // Attribute descriptions + // Describes memory layout and shader positions + vertices.attributeDescriptions.resize(3); + // Location 0 : Position + vertices.attributeDescriptions[0] = + vkTools::initializers::vertexInputAttributeDescription( + VERTEX_BUFFER_BIND_ID, + 0, + VK_FORMAT_R32G32B32_SFLOAT, + offsetof(Vertex, pos)); + // Location 1 : Texture coordinates + vertices.attributeDescriptions[1] = + vkTools::initializers::vertexInputAttributeDescription( + VERTEX_BUFFER_BIND_ID, + 1, + VK_FORMAT_R32G32_SFLOAT, + offsetof(Vertex, uv)); + // Location 1 : Vertex normal + vertices.attributeDescriptions[2] = + vkTools::initializers::vertexInputAttributeDescription( + VERTEX_BUFFER_BIND_ID, + 2, + VK_FORMAT_R32G32B32_SFLOAT, + offsetof(Vertex, normal)); + + vertices.inputState = vkTools::initializers::pipelineVertexInputStateCreateInfo(); + vertices.inputState.vertexBindingDescriptionCount = static_cast(vertices.bindingDescriptions.size()); + vertices.inputState.pVertexBindingDescriptions = vertices.bindingDescriptions.data(); + vertices.inputState.vertexAttributeDescriptionCount = static_cast(vertices.attributeDescriptions.size()); + vertices.inputState.pVertexAttributeDescriptions = vertices.attributeDescriptions.data(); + } + + void setupDescriptorPool() + { + std::vector poolSizes = + { + vkTools::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1), // Vertex shader UBO + vkTools::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1), // Sampled image + vkTools::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_SAMPLER, 3), // 3 samplers (array) + }; + + VkDescriptorPoolCreateInfo descriptorPoolInfo = + vkTools::initializers::descriptorPoolCreateInfo( + static_cast(poolSizes.size()), + poolSizes.data(), + 1); + + VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); + } + + void setupDescriptorSetLayout() + { + std::vector setLayoutBindings; + + // Binding 0: Vertex shader uniform buffer + setLayoutBindings.push_back(vkTools::initializers::descriptorSetLayoutBinding( + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + VK_SHADER_STAGE_VERTEX_BIT, + 0)); + + // Binding 1: Sampled image + setLayoutBindings.push_back(vkTools::initializers::descriptorSetLayoutBinding( + VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, + VK_SHADER_STAGE_FRAGMENT_BIT, + 1)); + + // Binding 2: Sampler array (3 descriptors) + setLayoutBindings.push_back(vkTools::initializers::descriptorSetLayoutBinding( + VK_DESCRIPTOR_TYPE_SAMPLER, + VK_SHADER_STAGE_FRAGMENT_BIT, + 2, + 3)); + + VkDescriptorSetLayoutCreateInfo descriptorLayout = + vkTools::initializers::descriptorSetLayoutCreateInfo( + setLayoutBindings.data(), + static_cast(setLayoutBindings.size())); + + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); + + VkPipelineLayoutCreateInfo pPipelineLayoutCreateInfo = + vkTools::initializers::pipelineLayoutCreateInfo( + &descriptorSetLayout, + 1); + + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pPipelineLayoutCreateInfo, nullptr, &pipelineLayout)); + } + + void setupDescriptorSet() + { + VkDescriptorSetAllocateInfo allocInfo = + vkTools::initializers::descriptorSetAllocateInfo( + descriptorPool, + &descriptorSetLayout, + 1); + + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); + + std::vector writeDescriptorSets; + + // Binding 0: Vertex shader uniform buffer + writeDescriptorSets.push_back(vkTools::initializers::writeDescriptorSet( + descriptorSet, + VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, + 0, + &uniformBufferVS.descriptor)); + + // Binding 1: Sampled image + VkDescriptorImageInfo texDescriptor = vkTools::initializers::descriptorImageInfo(VK_NULL_HANDLE, texture.view, VK_IMAGE_LAYOUT_GENERAL); + writeDescriptorSets.push_back(vkTools::initializers::writeDescriptorSet( + descriptorSet, + VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, + 1, + &texDescriptor)); + + // Binding 2: Sampler array + std::vector samplerDescriptors; + for (auto i = 0; i < samplers.size(); i++) + { + samplerDescriptors.push_back(vkTools::initializers::descriptorImageInfo(samplers[i], VK_NULL_HANDLE, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)); + } + VkWriteDescriptorSet samplerDescriptorWrite{}; + samplerDescriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; + samplerDescriptorWrite.dstSet = descriptorSet; + samplerDescriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_SAMPLER; + samplerDescriptorWrite.descriptorCount = static_cast(samplerDescriptors.size()); + samplerDescriptorWrite.pImageInfo = samplerDescriptors.data(); + samplerDescriptorWrite.dstBinding = 2; + samplerDescriptorWrite.dstArrayElement = 0; + writeDescriptorSets.push_back(samplerDescriptorWrite); + + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL); + } + + void preparePipelines() + { + VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = + vkTools::initializers::pipelineInputAssemblyStateCreateInfo( + VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, + 0, + VK_FALSE); + + VkPipelineRasterizationStateCreateInfo rasterizationState = + vkTools::initializers::pipelineRasterizationStateCreateInfo( + VK_POLYGON_MODE_FILL, + VK_CULL_MODE_BACK_BIT, + VK_FRONT_FACE_COUNTER_CLOCKWISE, + 0); + + VkPipelineColorBlendAttachmentState blendAttachmentState = + vkTools::initializers::pipelineColorBlendAttachmentState( + 0xf, + VK_FALSE); + + VkPipelineColorBlendStateCreateInfo colorBlendState = + vkTools::initializers::pipelineColorBlendStateCreateInfo( + 1, + &blendAttachmentState); + + VkPipelineDepthStencilStateCreateInfo depthStencilState = + vkTools::initializers::pipelineDepthStencilStateCreateInfo( + VK_TRUE, + VK_TRUE, + VK_COMPARE_OP_LESS_OR_EQUAL); + + VkPipelineViewportStateCreateInfo viewportState = + vkTools::initializers::pipelineViewportStateCreateInfo(1, 1, 0); + + VkPipelineMultisampleStateCreateInfo multisampleState = + vkTools::initializers::pipelineMultisampleStateCreateInfo( + VK_SAMPLE_COUNT_1_BIT, + 0); + + std::vector dynamicStateEnables = { + VK_DYNAMIC_STATE_VIEWPORT, + VK_DYNAMIC_STATE_SCISSOR + }; + VkPipelineDynamicStateCreateInfo dynamicState = + vkTools::initializers::pipelineDynamicStateCreateInfo( + dynamicStateEnables.data(), + static_cast(dynamicStateEnables.size()), + 0); + + // Load shaders + std::array shaderStages; + + shaderStages[0] = loadShader(getAssetPath() + "shaders/texturemipmapgen/texture.vert.spv", VK_SHADER_STAGE_VERTEX_BIT); + shaderStages[1] = loadShader(getAssetPath() + "shaders/texturemipmapgen/texture.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT); + + VkGraphicsPipelineCreateInfo pipelineCreateInfo = + vkTools::initializers::pipelineCreateInfo( + pipelineLayout, + renderPass, + 0); + + pipelineCreateInfo.pVertexInputState = &vertices.inputState; + 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(shaderStages.size()); + pipelineCreateInfo.pStages = shaderStages.data(); + + VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipelines.solid)); + } + + // 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, + &uniformBufferVS, + sizeof(uboVS), + &uboVS)); + + updateUniformBuffers(); + } + + void updateUniformBuffers() + { + uboVS.projection = camera.matrices.perspective; + uboVS.view = camera.matrices.view; + + VK_CHECK_RESULT(uniformBufferVS.map()); + memcpy(uniformBufferVS.mapped, &uboVS, sizeof(uboVS)); + uniformBufferVS.unmap(); + } + + void prepare() + { + VulkanExampleBase::prepare(); + loadAssets(); + setupVertexDescriptions(); + prepareUniformBuffers(); + setupDescriptorSetLayout(); + preparePipelines(); + setupDescriptorPool(); + setupDescriptorSet(); + buildCommandBuffers(); + prepared = true; + } + + virtual void render() + { + if (!prepared) + return; + draw(); + } + + virtual void viewChanged() + { + updateUniformBuffers(); + } + + void changeLodBias(float delta) + { + uboVS.lodBias += delta; + if (uboVS.lodBias < 0.0f) + { + uboVS.lodBias = 0.0f; + } + if (uboVS.lodBias > texture.mipLevels) + { + uboVS.lodBias = (float)texture.mipLevels; + } + updateUniformBuffers(); + updateTextOverlay(); + } + + void toggleSampler() + { + uboVS.samplerIndex = (uboVS.samplerIndex < static_cast(samplers.size()) - 1) ? uboVS.samplerIndex + 1 : 0; + updateUniformBuffers(); + } + + virtual void keyPressed(uint32_t keyCode) + { + switch (keyCode) + { + case KEY_KPADD: + case GAMEPAD_BUTTON_R1: + changeLodBias(0.1f); + break; + case KEY_KPSUB: + case GAMEPAD_BUTTON_L1: + changeLodBias(-0.1f); + break; + case KEY_F: + case GAMEPAD_BUTTON_A: + toggleSampler(); + break; + } + } + + virtual void getOverlayText(VulkanTextOverlay *textOverlay) + { + std::stringstream ss; + ss << std::setprecision(2) << std::fixed << uboVS.lodBias; +#if defined(__ANDROID__) + textOverlay->addText("LOD bias: " + ss.str() + " (Buttons L1/R1 to change)", 5.0f, 85.0f, VulkanTextOverlay::alignLeft); + textOverlay->addText("Sampler: " + samplerNames[uboVS.samplerIndex] + " (\"Button A\" to toggle)", 5.0f, 105.0f, VulkanTextOverlay::alignLeft); +#else + textOverlay->addText("LOD bias: " + ss.str() + " (numpad +/- to change)", 5.0f, 85.0f, VulkanTextOverlay::alignLeft); + textOverlay->addText("Sampler: " + samplerNames[uboVS.samplerIndex] + " (\"f\" to toggle)", 5.0f, 105.0f, VulkanTextOverlay::alignLeft); +#endif + } +}; + +VULKAN_EXAMPLE_MAIN() diff --git a/texturemipmapgen/texturemipmapgen.vcxproj b/texturemipmapgen/texturemipmapgen.vcxproj new file mode 100644 index 00000000..a12f35af --- /dev/null +++ b/texturemipmapgen/texturemipmapgen.vcxproj @@ -0,0 +1,99 @@ + + + + + Debug + x64 + + + Release + x64 + + + + {24AD09B4-6ABE-4A82-83E7-6A7799A88762} + Win32Proj + 8.1 + + + + Application + true + v140 + + + Application + false + v140 + + + + + + + + + + + + + true + $(SolutionDir)\bin\ + $(SolutionDir)\bin\intermediate\$(ProjectName)\$(ConfigurationName) + + + true + $(SolutionDir)\bin\ + $(SolutionDir)\bin\intermediate\$(ProjectName)\$(ConfigurationName) + + + + WIN32;_DEBUG;_WINDOWS;VK_USE_PLATFORM_WIN32_KHR;_USE_MATH_DEFINES;NOMINMAX;%(PreprocessorDefinitions) + MultiThreadedDebugDLL + Level3 + ProgramDatabase + Disabled + ..\base;..\external\glm;..\external\gli;..\external\assimp;..\external;%(AdditionalIncludeDirectories) + /FS %(AdditionalOptions) + + + true + Windows + ..\libs\vulkan\vulkan-1.lib;..\libs\assimp\assimp.lib;%(AdditionalDependencies) + + + + + WIN32;NDEBUG;_WINDOWS;VK_USE_PLATFORM_WIN32_KHR;_USE_MATH_DEFINES;NOMINMAX;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + MultiThreadedDLL + Level3 + ProgramDatabase + ..\base;..\external\glm;..\external\gli;..\external\assimp;..\external;%(AdditionalIncludeDirectories) + + + true + Windows + true + true + ..\libs\vulkan\vulkan-1.lib;..\libs\assimp\assimp.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/texturemipmapgen/texturemipmapgen.vcxproj.filters b/texturemipmapgen/texturemipmapgen.vcxproj.filters new file mode 100644 index 00000000..ae852223 --- /dev/null +++ b/texturemipmapgen/texturemipmapgen.vcxproj.filters @@ -0,0 +1,53 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + {abe06c5e-339d-4f7b-b3c6-186a7240f658} + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + + + Shaders + + + Shaders + + + \ No newline at end of file diff --git a/vulkanExamples.sln b/vulkanExamples.sln index 73463d53..bca9e871 100644 --- a/vulkanExamples.sln +++ b/vulkanExamples.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 14 -VisualStudioVersion = 14.0.24720.0 +VisualStudioVersion = 14.0.25420.1 MinimumVisualStudioVersion = 10.0.40219.1 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tessellation", "tessellation\tessellation.vcxproj", "{015C3F70-FFCE-45DA-80E0-5DED015868D7}" EndProject @@ -97,6 +97,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "indirectdraw", "indirectdraw\indirectdraw.vcxproj", "{2BBDD10F-2C9D-4BEA-8C7B-1C510A2CE08B}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "texturemipmapgen", "texturemipmapgen\texturemipmapgen.vcxproj", "{24AD09B4-6ABE-4A82-83E7-6A7799A88762}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -247,6 +249,10 @@ Global {2BBDD10F-2C9D-4BEA-8C7B-1C510A2CE08B}.Debug|x64.Build.0 = Debug|x64 {2BBDD10F-2C9D-4BEA-8C7B-1C510A2CE08B}.Release|x64.ActiveCfg = Release|x64 {2BBDD10F-2C9D-4BEA-8C7B-1C510A2CE08B}.Release|x64.Build.0 = Release|x64 + {24AD09B4-6ABE-4A82-83E7-6A7799A88762}.Debug|x64.ActiveCfg = Debug|x64 + {24AD09B4-6ABE-4A82-83E7-6A7799A88762}.Debug|x64.Build.0 = Debug|x64 + {24AD09B4-6ABE-4A82-83E7-6A7799A88762}.Release|x64.ActiveCfg = Release|x64 + {24AD09B4-6ABE-4A82-83E7-6A7799A88762}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE