Vulkan Schnee 0.0.1
High-performance rendering engine
Loading...
Searching...
No Matches
Texture.cpp
Go to the documentation of this file.
8#include "vulkan/vulkan_core.h"
9
10namespace EngineCore
11{
12
13void Texture::setupImageFormat(const tinygltf::Image& imageData)
14{
15 if (imageData.bits == 8)
16 {
17 if (imageData.component == 1) format = VK_FORMAT_R8_SRGB; // Grayscale
18 else if (imageData.component == 2) format = VK_FORMAT_R8G8_SRGB; // Grayscale + Alpha, or other 2-channel
19 else if (imageData.component == 3) format = VK_FORMAT_R8G8B8_SRGB; // RGB (Less common, better to pad)
20 else if (imageData.component == 4) format = VK_FORMAT_R8G8B8A8_SRGB; // RGBA
21 }
22 else if (imageData.bits == 16)
23 {
24 if (imageData.component == 1) format = VK_FORMAT_R16_SFLOAT;
25 else if (imageData.component == 2) format = VK_FORMAT_R16G16_SFLOAT;
26 else if (imageData.component == 3) format = VK_FORMAT_R16G16B16_SFLOAT;
27 else if (imageData.component == 4) format = VK_FORMAT_R16G16B16A16_SFLOAT;
28 }
29 else if (imageData.bits == 32)
30 {
31 if (imageData.component == 1) format = VK_FORMAT_R32_SFLOAT;
32 else if (imageData.component == 2) format = VK_FORMAT_R32G32_SFLOAT;
33 else if (imageData.component == 3) format = VK_FORMAT_R32G32B32_SFLOAT;
34 else if (imageData.component == 4) format = VK_FORMAT_R32G32B32A32_SFLOAT;
35 }
36
37 if (format == VK_FORMAT_UNDEFINED)
38 {
39 PLOGE << "Unsupported image format: bits=" << imageData.bits << ", components=" << imageData.component << ". Falling back to VK_FORMAT_R8G8B8A8_SRGB.";
40 format = VK_FORMAT_R8G8B8A8_SRGB; // As a last resort fallback
41 }
42 PLOGI << "Image format: " << format;
43}
46{
47 // imagedata needs to be padded
49
50 // Use type-aware format selection instead of setupImageFormat()
51 format = getVkFormat(type, this->imageData.bits, this->imageData.component);
52 PLOGI << "Texture format (type-aware): " << format << " for type " << static_cast<int>(type)
53 << " bits=" << this->imageData.bits << " components=" << this->imageData.component;
54}
55
58{
59 // imagedata needs to be padded
61
62 // Legacy constructor - uses old format selection (defaults to SRGB for 8-bit)
63 setupImageFormat(this->imageData);
64}
65
66#include <limits>
67
68// Move constructor
70 : // Copy the reference; note that you cannot "move" a reference
71 context(other.context),
72 vkTexture(other.vkTexture),
73 textureAllocation_(other.textureAllocation_),
74 vkTextureView(other.vkTextureView),
75 vkTextureSampler(other.vkTextureSampler),
76 descriptorIndex(other.descriptorIndex),
77 format(other.format),
78 textureType_(other.textureType_)
79{
80 imageData = std::move(other.imageData);
81 // Invalidate the moved-from object's Vulkan handles so its destructor doesn't release them
82 other.vkTexture = VK_NULL_HANDLE;
83 other.textureAllocation_ = VK_NULL_HANDLE;
84 other.vkTextureView = VK_NULL_HANDLE;
85 other.vkTextureSampler = VK_NULL_HANDLE;
86 other.imageData = tinygltf::Image();
87 other.descriptorIndex = std::numeric_limits<uint32_t>::max();
88 other.textureType_ = TextureType::BaseColor;
89
91 // Format was already set by the moved-from object, no need to recalculate
92}
93
94// Move assignment operator
96{
97 if (this != &other)
98 {
99 // Clean up current resources before taking ownership of new ones.
100 cleanup();
101
102 // Move Vulkan objects
103 vkTexture = other.vkTexture;
104 textureAllocation_ = other.textureAllocation_;
105 vkTextureView = other.vkTextureView;
106 vkTextureSampler = other.vkTextureSampler;
107 descriptorIndex = other.descriptorIndex;
108 format = other.format;
109 textureType_ = other.textureType_;
110 imageData = other.imageData;
111
112 // context is a raw pointer so we can just copy it.
113 context = other.context;
114
115 // Invalidate the moved-from object
116 other.vkTexture = VK_NULL_HANDLE;
117 other.textureAllocation_ = VK_NULL_HANDLE;
118 other.vkTextureView = VK_NULL_HANDLE;
119 other.vkTextureSampler = VK_NULL_HANDLE;
120 other.imageData = tinygltf::Image();
121 other.descriptorIndex = std::numeric_limits<uint32_t>::max();
122 other.textureType_ = TextureType::BaseColor;
123 }
124 return *this;
125}
126
128{
129 if (imageData.uri.empty()) throw std::runtime_error("Cannot create resources for a texture with no contents");
130
131 #ifdef ENABLE_TRACY
132 std::stringstream tracyZoneName;
133 tracyZoneName << __FUNCTION__ << ": " << imageData.uri;
135 ZoneName(tracyZoneName.str().c_str(), tracyZoneName.str().size());
136#endif
137
138 // Create image using VMA
139 VkImageCreateInfo imageInfo{VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO};
140 imageInfo.imageType = VK_IMAGE_TYPE_2D;
141 imageInfo.extent.width = imageData.width;
142 imageInfo.extent.height = imageData.height;
143 imageInfo.extent.depth = 1;
144 imageInfo.mipLevels = 1;
145 imageInfo.arrayLayers = 1;
146 imageInfo.format = format;
147 imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
148 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
149 imageInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
150 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
151 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
152 imageInfo.flags = 0;
153
154 VmaAllocationCreateInfo allocCI{};
155 allocCI.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
156
157 VmaAllocator allocator = context->getVmaAllocator();
158 if (vmaCreateImage(allocator, &imageInfo, &allocCI, &vkTexture, &textureAllocation_, nullptr) != VK_SUCCESS)
159 {
160 throw std::runtime_error("Failed to create texture image with VMA: " + imageData.uri);
161 }
162
163 // Create an image view for the texture image so it can be used in shaders.
164 VkImageViewCreateInfo viewInfo{};
165 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
166 viewInfo.image = vkTexture;
167 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
168 viewInfo.format = format;
169 viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
170 viewInfo.subresourceRange.baseMipLevel = 0;
171 viewInfo.subresourceRange.levelCount = 1;
172 viewInfo.subresourceRange.baseArrayLayer = 0;
173 viewInfo.subresourceRange.layerCount = 1;
174
175 if(vkCreateImageView(context->getVkDevice(), &viewInfo, nullptr, &vkTextureView) != VK_SUCCESS)
176 {
177 throw std::runtime_error("failed to create texture image view!");
178 }
179
180 // Create a sampler for the texture image.
181 VkSamplerCreateInfo samplerCreateInfo{};
182 samplerCreateInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
183 samplerCreateInfo.magFilter = VK_FILTER_LINEAR;
184 samplerCreateInfo.minFilter = VK_FILTER_LINEAR;
185 samplerCreateInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT;
186 samplerCreateInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT;
187 samplerCreateInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT;
188 samplerCreateInfo.anisotropyEnable = VK_TRUE;
189 samplerCreateInfo.maxAnisotropy = 16.0f;
190 samplerCreateInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
191 samplerCreateInfo.unnormalizedCoordinates = VK_FALSE;
192 samplerCreateInfo.compareEnable = VK_FALSE;
193 samplerCreateInfo.compareOp = VK_COMPARE_OP_ALWAYS;
194 samplerCreateInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
195 samplerCreateInfo.mipLodBias = 0.0f;
196 samplerCreateInfo.minLod = 0.0f;
197 samplerCreateInfo.maxLod = 0.0f; // For now, no mipmapping
198
199 if(vkCreateSampler(context->getVkDevice(), &samplerCreateInfo, nullptr, &vkTextureSampler) != VK_SUCCESS)
200 {
201 throw std::runtime_error("failed to create texture sampler!");
202 } // Create an image view for the texture image so it can be used in shaders.
203
205}
206
207void Texture::setDebugName(const std::string &debugName) {
208 if(vkTexture) VulkanHelper::setObjectName(context->getVkDevice(), vkTexture, debugName.c_str());
209 if(vkTextureView) VulkanHelper::setObjectName(context->getVkDevice(), vkTextureView, (debugName + " image view").c_str());
210 if(vkTextureSampler) VulkanHelper::setObjectName(context->getVkDevice(), vkTextureSampler, (debugName + " image sampler").c_str());
211 // Set VMA allocation name for debugging
213 {
214 vmaSetAllocationName(context->getVmaAllocator(), textureAllocation_, (debugName + " allocation").c_str());
215 }
216}
217
219std::string Texture::createTexturePath(const std::filesystem::path& assetPath,
220 const GltfLoader::GltfMeshPrimitiveData& primitiveData)
221{
222 std::stringstream texturePathString {};
223 texturePathString << assetPath.parent_path() << "/" << primitiveData.getMaterialData().getTextureData().getImage().uri;
224 return texturePathString.str();
225}
226
227void Texture::cleanup() const {
229 vkDestroySampler(context->getVkDevice(), vkTextureSampler, nullptr);
230 if(vkTextureView)
231 vkDestroyImageView(context->getVkDevice(), vkTextureView, nullptr);
232 // Use VMA to destroy image and free memory together
234 {
235 vmaDestroyImage(context->getVmaAllocator(), vkTexture, textureAllocation_);
236 }
237}
238
240{
241 cleanup();
242 vkTexture = VK_NULL_HANDLE;
243 textureAllocation_ = VK_NULL_HANDLE;
244 vkTextureView = VK_NULL_HANDLE;
245 vkTextureSampler = VK_NULL_HANDLE;
246 descriptorIndex = std::numeric_limits<uint32_t>::max();
247}
248
249VkImage Texture::getVkImage() const
250{
251 return vkTexture;
252}
253
254VkDeviceMemory Texture::getVkImageMemory() const
255{
257 {
258 VmaAllocationInfo info{};
259 vmaGetAllocationInfo(context->getVmaAllocator(), textureAllocation_, &info);
260 return info.deviceMemory;
261 }
262 return VK_NULL_HANDLE;
263}
264
265VkImageView Texture::getVkImageView() const
266{
267 return vkTextureView;
268}
269
271{
272 return vkTextureSampler;
273}
275{
276 return descriptorIndex != std::numeric_limits<uint32_t>::max();
277}
279{
280 return descriptorIndex;
281}
282void Texture::createDataUploadCommand(VkCommandBuffer commandBuffer, Renderer* renderer) const
283{
284 PLOGI << "Updating texture with " << imageData.width << "x" << imageData.height << " and URI: " << imageData.uri;
285 const VkDeviceSize imageDataSize = imageData.width * imageData.height * (imageData.bits/8u) * imageData.component;
286 assert(imageDataSize == imageData.image.size() && "Calculated image data size mismatch with tinygltf::Image::image vector size!");
287
288 if(imageDataSize == 0)
289 {
290 PLOGE << "Image data size of " << imageData.uri << " is zero!";
291 return;
292 }
293
294 // Create the staging buffer with the CORRECT size
295 VulkanBuffer stagingBuffer;
296 new (&stagingBuffer) VulkanBuffer(context);
297
298 stagingBuffer.create(imageDataSize, // <-- Use the calculated size of the pixel data
299 VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
300 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
301 std::string bufferName = "Image upload staging buffer for " + imageData.uri;
302 stagingBuffer.setDebugName(bufferName);
303
304 // Map the memory (now the requested size matches the allocated size)
305 void* mappedData = stagingBuffer.map();
306 if(stagingBuffer.isMapped())
307 {
308 memcpy(mappedData, imageData.image.data(), static_cast<size_t>(imageData.image.size()));
309 stagingBuffer.unmap();
310
311 // --- Image Layout Transition and Copy ---
312 VkImageMemoryBarrier2 imageMemoryBarrier1{};
313 imageMemoryBarrier1.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
314 imageMemoryBarrier1.srcStageMask = VK_PIPELINE_STAGE_2_NONE;
315 imageMemoryBarrier1.srcAccessMask = VK_ACCESS_2_NONE;
316 imageMemoryBarrier1.dstStageMask = VK_PIPELINE_STAGE_2_COPY_BIT;
317 imageMemoryBarrier1.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
318 imageMemoryBarrier1.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
319 imageMemoryBarrier1.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
320 imageMemoryBarrier1.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
321 imageMemoryBarrier1.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
322 imageMemoryBarrier1.image = vkTexture;
323 imageMemoryBarrier1.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
324 imageMemoryBarrier1.subresourceRange.baseMipLevel = 0;
325 imageMemoryBarrier1.subresourceRange.levelCount = 1;
326 imageMemoryBarrier1.subresourceRange.baseArrayLayer = 0;
327 imageMemoryBarrier1.subresourceRange.layerCount = 1;
328
329 VkDependencyInfo dependencyInfo1{VK_STRUCTURE_TYPE_DEPENDENCY_INFO};
330 dependencyInfo1.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
331 dependencyInfo1.pImageMemoryBarriers = &imageMemoryBarrier1;
332 dependencyInfo1.imageMemoryBarrierCount = 1;
333 vkCmdPipelineBarrier2(commandBuffer, &dependencyInfo1);
334
335 VkBufferImageCopy region{};
336 region.bufferOffset = 0;
337 region.bufferRowLength = 0; // Tightly packed
338 region.bufferImageHeight = 0; // Tightly packed
339 region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
340 region.imageSubresource.mipLevel = 0;
341 region.imageSubresource.baseArrayLayer = 0;
342 region.imageSubresource.layerCount = 1;
343 region.imageOffset = {0, 0, 0};
344 region.imageExtent = {static_cast<uint32_t>(imageData.width), static_cast<uint32_t>(imageData.height), 1};
345
346 vkCmdCopyBufferToImage(commandBuffer, stagingBuffer.getBuffer(), vkTexture,
347 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
348
349 // For transfer queue, we only transition to a transfer-complete state
350 // The final transition to shader-readable will happen on the graphics queue
351 VkImageMemoryBarrier2 imageMemoryBarrier2{};
352 imageMemoryBarrier2.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
353 imageMemoryBarrier2.srcStageMask = VK_PIPELINE_STAGE_2_COPY_BIT;
354 imageMemoryBarrier2.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
355 imageMemoryBarrier2.dstStageMask = VK_PIPELINE_STAGE_2_BOTTOM_OF_PIPE_BIT; // Transfer-compatible stage
356 imageMemoryBarrier2.dstAccessMask = VK_ACCESS_2_NONE; // No access needed at end of transfer
357 imageMemoryBarrier2.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
358 imageMemoryBarrier2.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; // Keep in transfer layout for graphics queue
359 imageMemoryBarrier2.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
360 imageMemoryBarrier2.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
361 imageMemoryBarrier2.image = vkTexture;
362 imageMemoryBarrier2.subresourceRange = imageMemoryBarrier1.subresourceRange;
363
364 VkDependencyInfo dependencyInfo2{VK_STRUCTURE_TYPE_DEPENDENCY_INFO};
365 dependencyInfo2.imageMemoryBarrierCount = 1;
366 dependencyInfo2.pImageMemoryBarriers = &imageMemoryBarrier2;
367 vkCmdPipelineBarrier2(commandBuffer, &dependencyInfo2);
368
369 renderer->getCurrentRenderProcess()->addStagingBufferForCleanup(std::move(stagingBuffer));
370 }
371 else
372 {
373 PLOGE << "Failed to map staging buffer memory for texture " << imageData.uri;
374 stagingBuffer.destroy();
375 }
376
377 // If map failed, stagingBuffer might still exist but shouldn't be used.
378 // Proper cleanup still needs to happen eventually, potentially immediately if map failed.
379 // How you handle this depends on your VulkanBuffer destructor/cleanup logic.
380 // If map failed, maybe: stagingBuffer.destroy();
381}
382
384{
385 return format;
386}
387
389{
390 // Only pad if it currently has 3 components.
391 // If it already has 4 components, or 1/2, no padding is needed to make it RGBA.
392 if (imageData.component == 3)
393 {
394 if (imageData.bits == 8)
395 {
397 }
398 else if (imageData.bits == 16)
399 {
401 }
402 else if (imageData.bits == 32)
403 {
404 addPaddingHelper<float>(); // 32-bit components are typically float for images
405 }
406 else
407 {
408 PLOGE << "Unsupported bit depth for padding (3-component image): " << imageData.bits << ". Skipping padding for " << imageData.uri;
409 }
410 }
411}
412
413} // namespace EngineCore
#define TRACY_ZONE_SCOPED_FUNCTION
The application context is the core class which stores the basic openxr and vulkan objects.
void addStagingBufferForCleanup(VulkanBuffer &&stagingBuffer)
RenderProcess * getCurrentRenderProcess() const
Gets the render process of the current frame.
TextureType textureType_
Definition Texture.h:102
VmaAllocation textureAllocation_
VMA allocation for texture memory.
Definition Texture.h:95
VkDeviceMemory getVkImageMemory() const
Definition Texture.cpp:254
VkImage vkTexture
Definition Texture.h:94
uint32_t descriptorIndex
Definition Texture.h:99
void setupImageFormat(const tinygltf::Image &imageData)
Definition Texture.cpp:13
tinygltf::Image imageData
Definition Texture.h:104
void cleanup() const
Definition Texture.cpp:227
Texture & operator=(const Texture &)=delete
void setDebugName(const std::string &debugName)
Definition Texture.cpp:207
VkImageView vkTextureView
Definition Texture.h:96
VkSampler getVkImageSampler() const
Definition Texture.cpp:270
static std::string createTexturePath(const std::filesystem::path &assetPath, const GltfLoader::GltfMeshPrimitiveData &primitiveData)
Definition Texture.cpp:219
VkSampler vkTextureSampler
Definition Texture.h:97
bool isDescriptorIndexInitialized() const
Definition Texture.cpp:274
VkImage getVkImage() const
Definition Texture.cpp:249
void addPaddingHelper()
Definition Texture.h:109
VkImageView getVkImageView() const
Definition Texture.cpp:265
void createDataUploadCommand(VkCommandBuffer commandBuffer, Renderer *renderer) const
Definition Texture.cpp:282
uint32_t getDescriptorIndex() const
Definition Texture.cpp:278
VkFormat getTextureFormat() const
Definition Texture.cpp:383
ApplicationContext * context
Definition Texture.h:105
RAII wrapper for Vulkan buffer and device memory.
void setDebugName(const std::string &name)
Sets the name shown in vulkan validation layer.
VkBuffer getBuffer() const
void * map()
Maps the buffer to be writable by the cpu. The mapped memory range is also stored in the object.
void unmap()
Unmaps the memory if it is mapped.
bool isMapped() const
Checks whether the buffer is already mapped to a memory range.
void destroy()
Destroys all vulkan resources of the buffer.
void create(size_t size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties)
Creates a buffer with a certain size, usage flags and memory properties.
static void setObjectName(VkDevice device, VulkanObjectType objectHandle, const std::string &name)
Log category system implementation.
constexpr VkFormat getVkFormat(TextureType type, int bits, int components)
Get the correct VkFormat based on texture type and image properties.
Definition TextureType.h:43
TextureType
Represents the semantic type of a texture, determining its Vulkan format.
Definition TextureType.h:18
@ BaseColor
Color data with gamma (SRGB format for 8-bit)
Definition TextureType.h:19
Stores intermediate data from loading the asset from disk. These are the vertices,...
Definition GltfLoader.h:350
tinygltf::Image getImage() const
Definition GltfLoader.h:64