11/*
2- * Vulkan texture loader
2+ * Vulkan texture loader for KTX files
33*
4- * Copyright(C) by Sascha Willems - www.saschawillems.de
4+ * Copyright (C) 2016-2025 by Sascha Willems - www.saschawillems.de
55*
66* This code is licensed under the MIT license(MIT) (http://opensource.org/licenses/MIT)
77*/
@@ -61,10 +61,9 @@ namespace vks
6161 * @param copyQueue Queue used for the texture staging copy commands (must support transfer)
6262 * @param (Optional) imageUsageFlags Usage flags for the texture's image (defaults to VK_IMAGE_USAGE_SAMPLED_BIT)
6363 * @param (Optional) imageLayout Usage layout for the texture (defaults VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL)
64- * @param (Optional) forceLinear Force linear tiling (not advised, defaults to false)
6564 *
6665 */
67- void Texture2D::loadFromFile (std::string filename, VkFormat format, vks::VulkanDevice *device, VkQueue copyQueue, VkImageUsageFlags imageUsageFlags, VkImageLayout imageLayout, bool forceLinear )
66+ void Texture2D::loadFromFile (std::string filename, VkFormat format, vks::VulkanDevice *device, VkQueue copyQueue, VkImageUsageFlags imageUsageFlags, VkImageLayout imageLayout)
6867 {
6968 ktxTexture* ktxTexture;
7069 ktxResult result = loadKTXFile (filename, &ktxTexture);
@@ -82,212 +81,128 @@ namespace vks
8281 VkFormatProperties formatProperties;
8382 vkGetPhysicalDeviceFormatProperties (device->physicalDevice , format, &formatProperties);
8483
85- // Only use linear tiling if requested (and supported by the device)
86- // Support for linear tiling is mostly limited, so prefer to use
87- // optimal tiling instead
88- // On most implementations linear tiling will only support a very
89- // limited amount of formats and features (mip maps, cubemaps, arrays, etc.)
90- VkBool32 useStaging = !forceLinear;
91-
9284 VkMemoryAllocateInfo memAllocInfo = vks::initializers::memoryAllocateInfo ();
9385 VkMemoryRequirements memReqs;
9486
9587 // Use a separate command buffer for texture loading
9688 VkCommandBuffer copyCmd = device->createCommandBuffer (VK_COMMAND_BUFFER_LEVEL_PRIMARY, true );
9789
98- if (useStaging)
99- {
100- // Create a host-visible staging buffer that contains the raw image data
101- VkBuffer stagingBuffer;
102- VkDeviceMemory stagingMemory;
103-
104- VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo ();
105- bufferCreateInfo.size = ktxTextureSize;
106- // This buffer is used as a transfer source for the buffer copy
107- bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
108- bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
109-
110- VK_CHECK_RESULT (vkCreateBuffer (device->logicalDevice , &bufferCreateInfo, nullptr , &stagingBuffer));
111-
112- // Get memory requirements for the staging buffer (alignment, memory type bits)
113- vkGetBufferMemoryRequirements (device->logicalDevice , stagingBuffer, &memReqs);
90+ // Create a host-visible staging buffer that contains the raw image data
91+ VkBuffer stagingBuffer;
92+ VkDeviceMemory stagingMemory;
11493
115- memAllocInfo.allocationSize = memReqs.size ;
116- // Get memory type index for a host visible buffer
117- memAllocInfo.memoryTypeIndex = device->getMemoryType (memReqs.memoryTypeBits , VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
94+ VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo ();
95+ bufferCreateInfo.size = ktxTextureSize;
96+ // This buffer is used as a transfer source for the buffer copy
97+ bufferCreateInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
98+ bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
11899
119- VK_CHECK_RESULT (vkAllocateMemory (device->logicalDevice , &memAllocInfo, nullptr , &stagingMemory));
120- VK_CHECK_RESULT (vkBindBufferMemory (device->logicalDevice , stagingBuffer, stagingMemory, 0 ));
100+ VK_CHECK_RESULT (vkCreateBuffer (device->logicalDevice , &bufferCreateInfo, nullptr , &stagingBuffer));
121101
122- // Copy texture data into staging buffer
123- uint8_t *data;
124- VK_CHECK_RESULT (vkMapMemory (device->logicalDevice , stagingMemory, 0 , memReqs.size , 0 , (void **)&data));
125- memcpy (data, ktxTextureData, ktxTextureSize);
126- vkUnmapMemory (device->logicalDevice , stagingMemory);
102+ // Get memory requirements for the staging buffer (alignment, memory type bits)
103+ vkGetBufferMemoryRequirements (device->logicalDevice , stagingBuffer, &memReqs);
127104
128- // Setup buffer copy regions for each mip level
129- std::vector<VkBufferImageCopy> bufferCopyRegions;
105+ memAllocInfo.allocationSize = memReqs.size ;
106+ // Get memory type index for a host visible buffer
107+ memAllocInfo.memoryTypeIndex = device->getMemoryType (memReqs.memoryTypeBits , VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
130108
131- for (uint32_t i = 0 ; i < mipLevels; i++)
132- {
133- ktx_size_t offset;
134- KTX_error_code result = ktxTexture_GetImageOffset (ktxTexture, i, 0 , 0 , &offset);
135- assert (result == KTX_SUCCESS);
109+ VK_CHECK_RESULT (vkAllocateMemory (device->logicalDevice , &memAllocInfo, nullptr , &stagingMemory));
110+ VK_CHECK_RESULT (vkBindBufferMemory (device->logicalDevice , stagingBuffer, stagingMemory, 0 ));
136111
137- VkBufferImageCopy bufferCopyRegion = {};
138- bufferCopyRegion.imageSubresource .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
139- bufferCopyRegion.imageSubresource .mipLevel = i;
140- bufferCopyRegion.imageSubresource .baseArrayLayer = 0 ;
141- bufferCopyRegion.imageSubresource .layerCount = 1 ;
142- bufferCopyRegion.imageExtent .width = std::max (1u , ktxTexture->baseWidth >> i);
143- bufferCopyRegion.imageExtent .height = std::max (1u , ktxTexture->baseHeight >> i);
144- bufferCopyRegion.imageExtent .depth = 1 ;
145- bufferCopyRegion.bufferOffset = offset;
112+ // Copy texture data into staging buffer
113+ uint8_t * data{ nullptr };
114+ VK_CHECK_RESULT (vkMapMemory (device->logicalDevice , stagingMemory, 0 , memReqs.size , 0 , (void **)&data));
115+ memcpy (data, ktxTextureData, ktxTextureSize);
116+ vkUnmapMemory (device->logicalDevice , stagingMemory);
146117
147- bufferCopyRegions. push_back (bufferCopyRegion);
148- }
118+ // Setup buffer copy regions for each mip level
119+ std::vector<VkBufferImageCopy> bufferCopyRegions;
149120
150- // Create optimal tiled target image
151- VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo ();
152- imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
153- imageCreateInfo.format = format;
154- imageCreateInfo.mipLevels = mipLevels;
155- imageCreateInfo.arrayLayers = 1 ;
156- imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
157- imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
158- imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
159- imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
160- imageCreateInfo.extent = { width, height, 1 };
161- imageCreateInfo.usage = imageUsageFlags;
162- // Ensure that the TRANSFER_DST bit is set for staging
163- if (!(imageCreateInfo.usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT))
164- {
165- imageCreateInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
166- }
167- VK_CHECK_RESULT (vkCreateImage (device->logicalDevice , &imageCreateInfo, nullptr , &image));
168-
169- vkGetImageMemoryRequirements (device->logicalDevice , image, &memReqs);
170-
171- memAllocInfo.allocationSize = memReqs.size ;
172-
173- memAllocInfo.memoryTypeIndex = device->getMemoryType (memReqs.memoryTypeBits , VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
174- VK_CHECK_RESULT (vkAllocateMemory (device->logicalDevice , &memAllocInfo, nullptr , &deviceMemory));
175- VK_CHECK_RESULT (vkBindImageMemory (device->logicalDevice , image, deviceMemory, 0 ));
176-
177- VkImageSubresourceRange subresourceRange = {};
178- subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
179- subresourceRange.baseMipLevel = 0 ;
180- subresourceRange.levelCount = mipLevels;
181- subresourceRange.layerCount = 1 ;
182-
183- // Image barrier for optimal image (target)
184- // Optimal image will be used as destination for the copy
185- vks::tools::setImageLayout (
186- copyCmd,
187- image,
188- VK_IMAGE_LAYOUT_UNDEFINED,
189- VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
190- subresourceRange);
191-
192- // Copy mip levels from staging buffer
193- vkCmdCopyBufferToImage (
194- copyCmd,
195- stagingBuffer,
196- image,
197- VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
198- static_cast <uint32_t >(bufferCopyRegions.size ()),
199- bufferCopyRegions.data ()
200- );
201-
202- // Change texture image layout to shader read after all mip levels have been copied
203- this ->imageLayout = imageLayout;
204- vks::tools::setImageLayout (
205- copyCmd,
206- image,
207- VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
208- imageLayout,
209- subresourceRange);
210-
211- device->flushCommandBuffer (copyCmd, copyQueue);
212-
213- // Clean up staging resources
214- vkDestroyBuffer (device->logicalDevice , stagingBuffer, nullptr );
215- vkFreeMemory (device->logicalDevice , stagingMemory, nullptr );
216- }
217- else
121+ for (uint32_t i = 0 ; i < mipLevels; i++)
218122 {
219- // Prefer using optimal tiling, as linear tiling
220- // may support only a small set of features
221- // depending on implementation (e.g. no mip maps, only one layer, etc.)
222-
223- // Check if this support is supported for linear tiling
224- assert (formatProperties.linearTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_BIT);
225-
226- VkImage mappableImage;
227- VkDeviceMemory mappableMemory;
228-
229- VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo ();
230- imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
231- imageCreateInfo.format = format;
232- imageCreateInfo.extent = { width, height, 1 };
233- imageCreateInfo.mipLevels = 1 ;
234- imageCreateInfo.arrayLayers = 1 ;
235- imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
236- imageCreateInfo.tiling = VK_IMAGE_TILING_LINEAR;
237- imageCreateInfo.usage = imageUsageFlags;
238- imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
239- imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
240-
241- // Load mip map level 0 to linear tiling image
242- VK_CHECK_RESULT (vkCreateImage (device->logicalDevice , &imageCreateInfo, nullptr , &mappableImage));
243-
244- // Get memory requirements for this image
245- // like size and alignment
246- vkGetImageMemoryRequirements (device->logicalDevice , mappableImage, &memReqs);
247- // Set memory allocation size to required memory size
248- memAllocInfo.allocationSize = memReqs.size ;
249-
250- // Get memory type that can be mapped to host memory
251- memAllocInfo.memoryTypeIndex = device->getMemoryType (memReqs.memoryTypeBits , VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
252-
253- // Allocate host memory
254- VK_CHECK_RESULT (vkAllocateMemory (device->logicalDevice , &memAllocInfo, nullptr , &mappableMemory));
123+ ktx_size_t offset;
124+ KTX_error_code result = ktxTexture_GetImageOffset (ktxTexture, i, 0 , 0 , &offset);
125+ assert (result == KTX_SUCCESS);
126+
127+ VkBufferImageCopy bufferCopyRegion = {};
128+ bufferCopyRegion.imageSubresource .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
129+ bufferCopyRegion.imageSubresource .mipLevel = i;
130+ bufferCopyRegion.imageSubresource .baseArrayLayer = 0 ;
131+ bufferCopyRegion.imageSubresource .layerCount = 1 ;
132+ bufferCopyRegion.imageExtent .width = std::max (1u , ktxTexture->baseWidth >> i);
133+ bufferCopyRegion.imageExtent .height = std::max (1u , ktxTexture->baseHeight >> i);
134+ bufferCopyRegion.imageExtent .depth = 1 ;
135+ bufferCopyRegion.bufferOffset = offset;
136+
137+ bufferCopyRegions.push_back (bufferCopyRegion);
138+ }
255139
256- // Bind allocated image for use
257- VK_CHECK_RESULT (vkBindImageMemory (device->logicalDevice , mappableImage, mappableMemory, 0 ));
140+ // Create optimal tiled target image
141+ VkImageCreateInfo imageCreateInfo = vks::initializers::imageCreateInfo ();
142+ imageCreateInfo.imageType = VK_IMAGE_TYPE_2D;
143+ imageCreateInfo.format = format;
144+ imageCreateInfo.mipLevels = mipLevels;
145+ imageCreateInfo.arrayLayers = 1 ;
146+ imageCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
147+ imageCreateInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
148+ imageCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
149+ imageCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
150+ imageCreateInfo.extent = { width, height, 1 };
151+ imageCreateInfo.usage = imageUsageFlags;
152+ // Ensure that the TRANSFER_DST bit is set for staging
153+ if (!(imageCreateInfo.usage & VK_IMAGE_USAGE_TRANSFER_DST_BIT))
154+ {
155+ imageCreateInfo.usage |= VK_IMAGE_USAGE_TRANSFER_DST_BIT;
156+ }
157+ VK_CHECK_RESULT (vkCreateImage (device->logicalDevice , &imageCreateInfo, nullptr , &image));
258158
259- // Get sub resource layout
260- // Mip map count, array layer, etc.
261- VkImageSubresource subRes = {};
262- subRes.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
263- subRes.mipLevel = 0 ;
159+ vkGetImageMemoryRequirements (device->logicalDevice , image, &memReqs);
264160
265- VkSubresourceLayout subResLayout;
266- void *data;
161+ memAllocInfo.allocationSize = memReqs.size ;
267162
268- // Get sub resources layout
269- // Includes row pitch, size offsets, etc.
270- vkGetImageSubresourceLayout ( device->logicalDevice , mappableImage, &subRes, &subResLayout );
163+ memAllocInfo. memoryTypeIndex = device-> getMemoryType (memReqs. memoryTypeBits , VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
164+ VK_CHECK_RESULT ( vkAllocateMemory (device-> logicalDevice , &memAllocInfo, nullptr , &deviceMemory));
165+ VK_CHECK_RESULT ( vkBindImageMemory ( device->logicalDevice , image, deviceMemory, 0 ) );
271166
272- // Map image memory
273- VK_CHECK_RESULT (vkMapMemory (device->logicalDevice , mappableMemory, 0 , memReqs.size , 0 , &data));
167+ VkImageSubresourceRange subresourceRange = {};
168+ subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
169+ subresourceRange.baseMipLevel = 0 ;
170+ subresourceRange.levelCount = mipLevels;
171+ subresourceRange.layerCount = 1 ;
274172
275- // Copy image data into memory
276- memcpy (data, ktxTextureData, memReqs.size );
173+ // Image barrier for optimal image (target)
174+ // Optimal image will be used as destination for the copy
175+ vks::tools::setImageLayout (
176+ copyCmd,
177+ image,
178+ VK_IMAGE_LAYOUT_UNDEFINED,
179+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
180+ subresourceRange);
277181
278- vkUnmapMemory (device->logicalDevice , mappableMemory);
182+ // Copy mip levels from staging buffer
183+ vkCmdCopyBufferToImage (
184+ copyCmd,
185+ stagingBuffer,
186+ image,
187+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
188+ static_cast <uint32_t >(bufferCopyRegions.size ()),
189+ bufferCopyRegions.data ()
190+ );
279191
280- // Linear tiled images don't need to be staged
281- // and can be directly used as textures
282- image = mappableImage;
283- deviceMemory = mappableMemory;
284- this ->imageLayout = imageLayout;
192+ // Change texture image layout to shader read after all mip levels have been copied
193+ this ->imageLayout = imageLayout;
194+ vks::tools::setImageLayout (
195+ copyCmd,
196+ image,
197+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
198+ imageLayout,
199+ subresourceRange);
285200
286- // Setup image memory barrier
287- vks::tools::setImageLayout (copyCmd, image, VK_IMAGE_ASPECT_COLOR_BIT, VK_IMAGE_LAYOUT_UNDEFINED, imageLayout);
201+ device->flushCommandBuffer (copyCmd, copyQueue);
288202
289- device->flushCommandBuffer (copyCmd, copyQueue);
290- }
203+ // Clean up staging resources
204+ vkDestroyBuffer (device->logicalDevice , stagingBuffer, nullptr );
205+ vkFreeMemory (device->logicalDevice , stagingMemory, nullptr );
291206
292207 ktxTexture_Destroy (ktxTexture);
293208
@@ -304,7 +219,7 @@ namespace vks
304219 samplerCreateInfo.compareOp = VK_COMPARE_OP_NEVER;
305220 samplerCreateInfo.minLod = 0 .0f ;
306221 // Max level-of-detail should match mip level count
307- samplerCreateInfo.maxLod = (useStaging) ? ( float )mipLevels : 0 . 0f ;
222+ samplerCreateInfo.maxLod = (float )mipLevels;
308223 // Only enable anisotropic filtering if enabled on the device
309224 samplerCreateInfo.maxAnisotropy = device->enabledFeatures .samplerAnisotropy ? device->properties .limits .maxSamplerAnisotropy : 1 .0f ;
310225 samplerCreateInfo.anisotropyEnable = device->enabledFeatures .samplerAnisotropy ;
@@ -322,7 +237,7 @@ namespace vks
322237 viewCreateInfo.subresourceRange = { VK_IMAGE_ASPECT_COLOR_BIT, 0 , 1 , 0 , 1 };
323238 // Linear tiling usually won't support mip maps
324239 // Only set mip map count if optimal tiling is used
325- viewCreateInfo.subresourceRange .levelCount = (useStaging) ? mipLevels : 1 ;
240+ viewCreateInfo.subresourceRange .levelCount = mipLevels;
326241 viewCreateInfo.image = image;
327242 VK_CHECK_RESULT (vkCreateImageView (device->logicalDevice , &viewCreateInfo, nullptr , &view));
328243
@@ -382,7 +297,7 @@ namespace vks
382297 VK_CHECK_RESULT (vkBindBufferMemory (device->logicalDevice , stagingBuffer, stagingMemory, 0 ));
383298
384299 // Copy texture data into staging buffer
385- uint8_t *data;
300+ uint8_t *data{ nullptr } ;
386301 VK_CHECK_RESULT (vkMapMemory (device->logicalDevice , stagingMemory, 0 , memReqs.size , 0 , (void **)&data));
387302 memcpy (data, buffer, bufferSize);
388303 vkUnmapMemory (device->logicalDevice , stagingMemory);
@@ -547,7 +462,7 @@ namespace vks
547462 VK_CHECK_RESULT (vkBindBufferMemory (device->logicalDevice , stagingBuffer, stagingMemory, 0 ));
548463
549464 // Copy texture data into staging buffer
550- uint8_t *data;
465+ uint8_t *data{ nullptr } ;
551466 VK_CHECK_RESULT (vkMapMemory (device->logicalDevice , stagingMemory, 0 , memReqs.size , 0 , (void **)&data));
552467 memcpy (data, ktxTextureData, ktxTextureSize);
553468 vkUnmapMemory (device->logicalDevice , stagingMemory);
@@ -730,7 +645,7 @@ namespace vks
730645 VK_CHECK_RESULT (vkBindBufferMemory (device->logicalDevice , stagingBuffer, stagingMemory, 0 ));
731646
732647 // Copy texture data into staging buffer
733- uint8_t *data;
648+ uint8_t *data{ nullptr } ;
734649 VK_CHECK_RESULT (vkMapMemory (device->logicalDevice , stagingMemory, 0 , memReqs.size , 0 , (void **)&data));
735650 memcpy (data, ktxTextureData, ktxTextureSize);
736651 vkUnmapMemory (device->logicalDevice , stagingMemory);
0 commit comments