SleakEngine 1.0.0
C++23 multi-backend game engine
Loading...
Searching...
No Matches
VulkanCubemapTexture.cpp
Go to the documentation of this file.
2#include <Core/Logger.hpp>
3#include <stb_image.h>
4#include <cstring>
5#include <cmath>
6#include <algorithm>
7#include <vector>
8
9namespace Sleak {
10namespace RenderEngine {
11
13 VkPhysicalDevice physicalDevice,
14 VkCommandPool commandPool,
15 VkQueue graphicsQueue)
16 : m_device(device),
17 m_physicalDevice(physicalDevice),
18 m_commandPool(commandPool),
19 m_graphicsQueue(graphicsQueue) {}
20
24
26 const std::array<std::string, 6>& facePaths) {
27 Cleanup();
28
29 // Load all 6 faces and verify dimensions match
30 struct FaceData {
31 unsigned char* pixels = nullptr;
32 int w = 0, h = 0;
33 };
34 std::array<FaceData, 6> faces;
35
36 stbi_set_flip_vertically_on_load(false);
37
38 for (int i = 0; i < 6; i++) {
39 int channels;
40 faces[i].pixels =
41 stbi_load(facePaths[i].c_str(), &faces[i].w, &faces[i].h,
42 &channels, 4); // Force RGBA
43 if (!faces[i].pixels) {
44 SLEAK_ERROR("VulkanCubemapTexture: Failed to load face {}: {}",
45 i, facePaths[i]);
46 for (int j = 0; j < i; j++)
47 stbi_image_free(faces[j].pixels);
48 return false;
49 }
50 }
51
52 // All faces must be the same size
53 m_width = static_cast<uint32_t>(faces[0].w);
54 m_height = static_cast<uint32_t>(faces[0].h);
55 for (int i = 1; i < 6; i++) {
56 if (faces[i].w != faces[0].w || faces[i].h != faces[0].h) {
58 "VulkanCubemapTexture: Face {} size ({}x{}) doesn't match "
59 "face 0 ({}x{})",
60 i, faces[i].w, faces[i].h, faces[0].w, faces[0].h);
61 for (int j = 0; j < 6; j++)
62 stbi_image_free(faces[j].pixels);
63 return false;
64 }
65 }
66
67 VkDeviceSize faceSize =
68 static_cast<VkDeviceSize>(m_width) * m_height * 4;
69 VkDeviceSize totalSize = faceSize * 6;
70
71 // 1. Create staging buffer
72 VkBuffer stagingBuffer;
73 VkDeviceMemory stagingMemory;
74
75 VkBufferCreateInfo bufferInfo{};
76 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
77 bufferInfo.size = totalSize;
78 bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
79 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
80
81 if (vkCreateBuffer(m_device, &bufferInfo, nullptr, &stagingBuffer) !=
82 VK_SUCCESS) {
83 SLEAK_ERROR("VulkanCubemapTexture: Failed to create staging buffer");
84 for (int i = 0; i < 6; i++)
85 stbi_image_free(faces[i].pixels);
86 return false;
87 }
88
89 VkMemoryRequirements memReqs;
90 vkGetBufferMemoryRequirements(m_device, stagingBuffer, &memReqs);
91
92 VkMemoryAllocateInfo allocInfo{};
93 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
94 allocInfo.allocationSize = memReqs.size;
95 allocInfo.memoryTypeIndex = FindMemoryType(
96 memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
97 VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
98
99 if (vkAllocateMemory(m_device, &allocInfo, nullptr, &stagingMemory) !=
100 VK_SUCCESS) {
101 vkDestroyBuffer(m_device, stagingBuffer, nullptr);
103 "VulkanCubemapTexture: Failed to allocate staging memory");
104 for (int i = 0; i < 6; i++)
105 stbi_image_free(faces[i].pixels);
106 return false;
107 }
108
109 vkBindBufferMemory(m_device, stagingBuffer, stagingMemory, 0);
110
111 // 2. Copy all 6 faces to staging buffer sequentially
112 void* mapped;
113 vkMapMemory(m_device, stagingMemory, 0, totalSize, 0, &mapped);
114 for (int i = 0; i < 6; i++) {
115 memcpy(static_cast<char*>(mapped) + faceSize * i, faces[i].pixels,
116 static_cast<size_t>(faceSize));
117 }
118 vkUnmapMemory(m_device, stagingMemory);
119
120 // Free CPU pixel data
121 for (int i = 0; i < 6; i++)
122 stbi_image_free(faces[i].pixels);
123
124 // 3. Create VkImage with 6 array layers and CUBE_COMPATIBLE flag
125 VkImageCreateInfo imageInfo{};
126 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
127 imageInfo.imageType = VK_IMAGE_TYPE_2D;
128 imageInfo.extent.width = m_width;
129 imageInfo.extent.height = m_height;
130 imageInfo.extent.depth = 1;
131 imageInfo.mipLevels = 1;
132 imageInfo.arrayLayers = 6;
133 imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
134 imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
135 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
136 imageInfo.usage =
137 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
138 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
139 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
140 imageInfo.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
141
142 if (vkCreateImage(m_device, &imageInfo, nullptr, &m_image) !=
143 VK_SUCCESS) {
144 SLEAK_ERROR("VulkanCubemapTexture: Failed to create cubemap image");
145 vkDestroyBuffer(m_device, stagingBuffer, nullptr);
146 vkFreeMemory(m_device, stagingMemory, nullptr);
147 return false;
148 }
149
150 vkGetImageMemoryRequirements(m_device, m_image, &memReqs);
151
152 allocInfo.allocationSize = memReqs.size;
153 allocInfo.memoryTypeIndex = FindMemoryType(
154 memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
155
156 if (vkAllocateMemory(m_device, &allocInfo, nullptr, &m_imageMemory) !=
157 VK_SUCCESS) {
159 "VulkanCubemapTexture: Failed to allocate image memory");
160 vkDestroyBuffer(m_device, stagingBuffer, nullptr);
161 vkFreeMemory(m_device, stagingMemory, nullptr);
162 return false;
163 }
164
165 vkBindImageMemory(m_device, m_image, m_imageMemory, 0);
166
167 // 4. Transition to transfer dst, copy all 6 faces, transition to
168 // shader read
169 TransitionImageLayout(m_image, VK_IMAGE_LAYOUT_UNDEFINED,
170 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 6);
171
172 // Copy staging buffer to image (one region per face)
173 {
174 VkCommandBufferAllocateInfo cmdAllocInfo{};
175 cmdAllocInfo.sType =
176 VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
177 cmdAllocInfo.commandPool = m_commandPool;
178 cmdAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
179 cmdAllocInfo.commandBufferCount = 1;
180
181 VkCommandBuffer cmdBuffer;
182 vkAllocateCommandBuffers(m_device, &cmdAllocInfo, &cmdBuffer);
183
184 VkCommandBufferBeginInfo beginInfo{};
185 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
186 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
187 vkBeginCommandBuffer(cmdBuffer, &beginInfo);
188
189 std::array<VkBufferImageCopy, 6> regions{};
190 for (uint32_t i = 0; i < 6; i++) {
191 regions[i].bufferOffset = faceSize * i;
192 regions[i].bufferRowLength = 0;
193 regions[i].bufferImageHeight = 0;
194 regions[i].imageSubresource.aspectMask =
195 VK_IMAGE_ASPECT_COLOR_BIT;
196 regions[i].imageSubresource.mipLevel = 0;
197 regions[i].imageSubresource.baseArrayLayer = i;
198 regions[i].imageSubresource.layerCount = 1;
199 regions[i].imageOffset = {0, 0, 0};
200 regions[i].imageExtent = {m_width, m_height, 1};
201 }
202
203 vkCmdCopyBufferToImage(cmdBuffer, stagingBuffer, m_image,
204 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 6,
205 regions.data());
206
207 vkEndCommandBuffer(cmdBuffer);
208
209 VkSubmitInfo submitInfo{};
210 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
211 submitInfo.commandBufferCount = 1;
212 submitInfo.pCommandBuffers = &cmdBuffer;
213
214 VkFenceCreateInfo fenceInfo{};
215 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
216 VkFence copyFence;
217 vkCreateFence(m_device, &fenceInfo, nullptr, &copyFence);
218 vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, copyFence);
219 vkWaitForFences(m_device, 1, &copyFence, VK_TRUE, UINT64_MAX);
220 vkDestroyFence(m_device, copyFence, nullptr);
221 vkFreeCommandBuffers(m_device, m_commandPool, 1, &cmdBuffer);
222 }
223
224 TransitionImageLayout(m_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
225 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 6);
226
227 // 5. Cleanup staging
228 vkDestroyBuffer(m_device, stagingBuffer, nullptr);
229 vkFreeMemory(m_device, stagingMemory, nullptr);
230
231 // 6. Create cube image view
232 VkImageViewCreateInfo viewInfo{};
233 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
234 viewInfo.image = m_image;
235 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_CUBE;
236 viewInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
237 viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
238 viewInfo.subresourceRange.baseMipLevel = 0;
239 viewInfo.subresourceRange.levelCount = 1;
240 viewInfo.subresourceRange.baseArrayLayer = 0;
241 viewInfo.subresourceRange.layerCount = 6;
242
243 if (vkCreateImageView(m_device, &viewInfo, nullptr, &m_imageView) !=
244 VK_SUCCESS) {
245 SLEAK_ERROR("VulkanCubemapTexture: Failed to create image view");
246 return false;
247 }
248
249 // 7. Create sampler
250 VkSamplerCreateInfo samplerInfo{};
251 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
252 samplerInfo.magFilter = VK_FILTER_LINEAR;
253 samplerInfo.minFilter = VK_FILTER_LINEAR;
254 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
255 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
256 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
257 samplerInfo.anisotropyEnable = VK_FALSE;
258 samplerInfo.maxAnisotropy = 1.0f;
259 samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
260 samplerInfo.unnormalizedCoordinates = VK_FALSE;
261 samplerInfo.compareEnable = VK_FALSE;
262 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
263 samplerInfo.mipLodBias = 0.0f;
264 samplerInfo.minLod = 0.0f;
265 samplerInfo.maxLod = 0.0f;
266
267 if (vkCreateSampler(m_device, &samplerInfo, nullptr, &m_sampler) !=
268 VK_SUCCESS) {
269 SLEAK_ERROR("VulkanCubemapTexture: Failed to create sampler");
270 return false;
271 }
272
273 SLEAK_INFO("VulkanCubemapTexture: Loaded cubemap ({}x{}, 6 faces)",
274 m_width, m_height);
275 return true;
276}
277
278bool VulkanCubemapTexture::LoadEquirectangular(const std::string& path,
279 uint32_t faceSize) {
280 Cleanup();
281
282 stbi_set_flip_vertically_on_load(false);
283
284 int panW, panH, channels;
285 unsigned char* panorama = stbi_load(path.c_str(), &panW, &panH, &channels, 4);
286 if (!panorama) {
287 SLEAK_ERROR("VulkanCubemapTexture: Failed to load panorama: {}", path);
288 return false;
289 }
290
291 m_width = faceSize;
292 m_height = faceSize;
293
294 // Generate 6 face pixel arrays on CPU
295 VkDeviceSize faceBytes = static_cast<VkDeviceSize>(faceSize) * faceSize * 4;
296 VkDeviceSize totalSize = faceBytes * 6;
297
298 std::vector<unsigned char> allFaces(static_cast<size_t>(totalSize));
299
300 for (int face = 0; face < 6; ++face) {
301 unsigned char* faceData = allFaces.data() + face * faceBytes;
302
303 for (uint32_t y = 0; y < faceSize; ++y) {
304 for (uint32_t x = 0; x < faceSize; ++x) {
305 float u = (2.0f * (x + 0.5f) / faceSize) - 1.0f;
306 float v = (2.0f * (y + 0.5f) / faceSize) - 1.0f;
307
308 float dx, dy, dz;
309 switch (face) {
310 case 0: dx = 1.0f; dy = -v; dz = -u; break; // +X
311 case 1: dx = -1.0f; dy = -v; dz = u; break; // -X
312 case 2: dx = u; dy = 1.0f; dz = v; break; // +Y
313 case 3: dx = u; dy = -1.0f; dz = -v; break; // -Y
314 case 4: dx = u; dy = -v; dz = 1.0f; break; // +Z
315 case 5: dx = -u; dy = -v; dz = -1.0f; break; // -Z
316 default: dx = dy = dz = 0.0f; break;
317 }
318
319 float len = std::sqrt(dx * dx + dy * dy + dz * dz);
320 dx /= len; dy /= len; dz /= len;
321
322 float lon = std::atan2(dz, dx);
323 float lat = std::asin(std::clamp(dy, -1.0f, 1.0f));
324
325 float panU = 0.5f + lon / (2.0f * 3.14159265f);
326 float panV = 0.5f - lat / 3.14159265f;
327
328 float srcX = panU * (panW - 1);
329 float srcY = panV * (panH - 1);
330 int x0 = static_cast<int>(srcX);
331 int y0 = static_cast<int>(srcY);
332 int x1 = std::min(x0 + 1, panW - 1);
333 int y1 = std::min(y0 + 1, panH - 1);
334 x0 = std::clamp(x0, 0, panW - 1);
335 y0 = std::clamp(y0, 0, panH - 1);
336 float fx = srcX - x0;
337 float fy = srcY - y0;
338
339 size_t idx = (y * faceSize + x) * 4;
340 for (int c = 0; c < 4; ++c) {
341 float c00 = panorama[(y0 * panW + x0) * 4 + c];
342 float c10 = panorama[(y0 * panW + x1) * 4 + c];
343 float c01 = panorama[(y1 * panW + x0) * 4 + c];
344 float c11 = panorama[(y1 * panW + x1) * 4 + c];
345 float val = c00 * (1 - fx) * (1 - fy) + c10 * fx * (1 - fy)
346 + c01 * (1 - fx) * fy + c11 * fx * fy;
347 faceData[idx + c] = static_cast<unsigned char>(
348 std::clamp(val, 0.0f, 255.0f));
349 }
350 }
351 }
352 }
353
354 stbi_image_free(panorama);
355
356 // Upload via staging buffer — same pattern as LoadCubemap
357 VkBuffer stagingBuffer;
358 VkDeviceMemory stagingMemory;
359
360 VkBufferCreateInfo bufferInfo{};
361 bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
362 bufferInfo.size = totalSize;
363 bufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
364 bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
365
366 if (vkCreateBuffer(m_device, &bufferInfo, nullptr, &stagingBuffer) !=
367 VK_SUCCESS) {
368 SLEAK_ERROR("VulkanCubemapTexture: Failed to create staging buffer");
369 return false;
370 }
371
372 VkMemoryRequirements memReqs;
373 vkGetBufferMemoryRequirements(m_device, stagingBuffer, &memReqs);
374
375 VkMemoryAllocateInfo allocInfo{};
376 allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
377 allocInfo.allocationSize = memReqs.size;
378 allocInfo.memoryTypeIndex = FindMemoryType(
379 memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
380 VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
381
382 if (vkAllocateMemory(m_device, &allocInfo, nullptr, &stagingMemory) !=
383 VK_SUCCESS) {
384 vkDestroyBuffer(m_device, stagingBuffer, nullptr);
385 SLEAK_ERROR("VulkanCubemapTexture: Failed to allocate staging memory");
386 return false;
387 }
388
389 vkBindBufferMemory(m_device, stagingBuffer, stagingMemory, 0);
390
391 void* mapped;
392 vkMapMemory(m_device, stagingMemory, 0, totalSize, 0, &mapped);
393 memcpy(mapped, allFaces.data(), static_cast<size_t>(totalSize));
394 vkUnmapMemory(m_device, stagingMemory);
395
396 // Create VkImage with 6 array layers and CUBE_COMPATIBLE flag
397 VkImageCreateInfo imageInfo{};
398 imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
399 imageInfo.imageType = VK_IMAGE_TYPE_2D;
400 imageInfo.extent.width = faceSize;
401 imageInfo.extent.height = faceSize;
402 imageInfo.extent.depth = 1;
403 imageInfo.mipLevels = 1;
404 imageInfo.arrayLayers = 6;
405 imageInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
406 imageInfo.tiling = VK_IMAGE_TILING_OPTIMAL;
407 imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
408 imageInfo.usage =
409 VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
410 imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
411 imageInfo.samples = VK_SAMPLE_COUNT_1_BIT;
412 imageInfo.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
413
414 if (vkCreateImage(m_device, &imageInfo, nullptr, &m_image) !=
415 VK_SUCCESS) {
416 SLEAK_ERROR("VulkanCubemapTexture: Failed to create cubemap image");
417 vkDestroyBuffer(m_device, stagingBuffer, nullptr);
418 vkFreeMemory(m_device, stagingMemory, nullptr);
419 return false;
420 }
421
422 vkGetImageMemoryRequirements(m_device, m_image, &memReqs);
423
424 allocInfo.allocationSize = memReqs.size;
425 allocInfo.memoryTypeIndex = FindMemoryType(
426 memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
427
428 if (vkAllocateMemory(m_device, &allocInfo, nullptr, &m_imageMemory) !=
429 VK_SUCCESS) {
430 SLEAK_ERROR("VulkanCubemapTexture: Failed to allocate image memory");
431 vkDestroyBuffer(m_device, stagingBuffer, nullptr);
432 vkFreeMemory(m_device, stagingMemory, nullptr);
433 return false;
434 }
435
436 vkBindImageMemory(m_device, m_image, m_imageMemory, 0);
437
438 TransitionImageLayout(m_image, VK_IMAGE_LAYOUT_UNDEFINED,
439 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 6);
440
441 // Copy staging buffer to image (one region per face)
442 {
443 VkCommandBufferAllocateInfo cmdAllocInfo{};
444 cmdAllocInfo.sType =
445 VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
446 cmdAllocInfo.commandPool = m_commandPool;
447 cmdAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
448 cmdAllocInfo.commandBufferCount = 1;
449
450 VkCommandBuffer cmdBuffer;
451 vkAllocateCommandBuffers(m_device, &cmdAllocInfo, &cmdBuffer);
452
453 VkCommandBufferBeginInfo beginInfo{};
454 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
455 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
456 vkBeginCommandBuffer(cmdBuffer, &beginInfo);
457
458 std::array<VkBufferImageCopy, 6> regions{};
459 for (uint32_t i = 0; i < 6; i++) {
460 regions[i].bufferOffset = faceBytes * i;
461 regions[i].bufferRowLength = 0;
462 regions[i].bufferImageHeight = 0;
463 regions[i].imageSubresource.aspectMask =
464 VK_IMAGE_ASPECT_COLOR_BIT;
465 regions[i].imageSubresource.mipLevel = 0;
466 regions[i].imageSubresource.baseArrayLayer = i;
467 regions[i].imageSubresource.layerCount = 1;
468 regions[i].imageOffset = {0, 0, 0};
469 regions[i].imageExtent = {faceSize, faceSize, 1};
470 }
471
472 vkCmdCopyBufferToImage(cmdBuffer, stagingBuffer, m_image,
473 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 6,
474 regions.data());
475
476 vkEndCommandBuffer(cmdBuffer);
477
478 VkSubmitInfo submitInfo{};
479 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
480 submitInfo.commandBufferCount = 1;
481 submitInfo.pCommandBuffers = &cmdBuffer;
482
483 VkFenceCreateInfo fenceInfo{};
484 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
485 VkFence copyFence;
486 vkCreateFence(m_device, &fenceInfo, nullptr, &copyFence);
487 vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, copyFence);
488 vkWaitForFences(m_device, 1, &copyFence, VK_TRUE, UINT64_MAX);
489 vkDestroyFence(m_device, copyFence, nullptr);
490 vkFreeCommandBuffers(m_device, m_commandPool, 1, &cmdBuffer);
491 }
492
493 TransitionImageLayout(m_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
494 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 6);
495
496 // Cleanup staging
497 vkDestroyBuffer(m_device, stagingBuffer, nullptr);
498 vkFreeMemory(m_device, stagingMemory, nullptr);
499
500 // Create cube image view
501 VkImageViewCreateInfo viewInfo{};
502 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
503 viewInfo.image = m_image;
504 viewInfo.viewType = VK_IMAGE_VIEW_TYPE_CUBE;
505 viewInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
506 viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
507 viewInfo.subresourceRange.baseMipLevel = 0;
508 viewInfo.subresourceRange.levelCount = 1;
509 viewInfo.subresourceRange.baseArrayLayer = 0;
510 viewInfo.subresourceRange.layerCount = 6;
511
512 if (vkCreateImageView(m_device, &viewInfo, nullptr, &m_imageView) !=
513 VK_SUCCESS) {
514 SLEAK_ERROR("VulkanCubemapTexture: Failed to create image view");
515 return false;
516 }
517
518 // Create sampler
519 VkSamplerCreateInfo samplerInfo{};
520 samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
521 samplerInfo.magFilter = VK_FILTER_LINEAR;
522 samplerInfo.minFilter = VK_FILTER_LINEAR;
523 samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
524 samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
525 samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
526 samplerInfo.anisotropyEnable = VK_FALSE;
527 samplerInfo.maxAnisotropy = 1.0f;
528 samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK;
529 samplerInfo.unnormalizedCoordinates = VK_FALSE;
530 samplerInfo.compareEnable = VK_FALSE;
531 samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
532 samplerInfo.mipLodBias = 0.0f;
533 samplerInfo.minLod = 0.0f;
534 samplerInfo.maxLod = 0.0f;
535
536 if (vkCreateSampler(m_device, &samplerInfo, nullptr, &m_sampler) !=
537 VK_SUCCESS) {
538 SLEAK_ERROR("VulkanCubemapTexture: Failed to create sampler");
539 return false;
540 }
541
542 SLEAK_INFO("VulkanCubemapTexture: Loaded equirectangular panorama ({}x{} face)",
543 faceSize, faceSize);
544 return true;
545}
546
547bool VulkanCubemapTexture::LoadFromMemory(const void* data, uint32_t width,
548 uint32_t height,
549 TextureFormat format) {
550 (void)data;
551 (void)width;
552 (void)height;
553 (void)format;
554 return false;
555}
556
557bool VulkanCubemapTexture::LoadFromFile(const std::string& filePath) {
558 (void)filePath;
559 return false;
560}
561
562void VulkanCubemapTexture::Bind(uint32_t slot) const {
563 // Binding in Vulkan is handled through descriptor sets
564 (void)slot;
565}
566
568
570 (void)filter;
571}
572
574 (void)wrapMode;
575}
576
577void VulkanCubemapTexture::Cleanup() {
578 if (m_device == VK_NULL_HANDLE) return;
579
580 if (m_sampler != VK_NULL_HANDLE) {
581 vkDestroySampler(m_device, m_sampler, nullptr);
582 m_sampler = VK_NULL_HANDLE;
583 }
584 if (m_imageView != VK_NULL_HANDLE) {
585 vkDestroyImageView(m_device, m_imageView, nullptr);
586 m_imageView = VK_NULL_HANDLE;
587 }
588 if (m_image != VK_NULL_HANDLE) {
589 vkDestroyImage(m_device, m_image, nullptr);
590 m_image = VK_NULL_HANDLE;
591 }
592 if (m_imageMemory != VK_NULL_HANDLE) {
593 vkFreeMemory(m_device, m_imageMemory, nullptr);
594 m_imageMemory = VK_NULL_HANDLE;
595 }
596}
597
598uint32_t VulkanCubemapTexture::FindMemoryType(
599 uint32_t typeFilter, VkMemoryPropertyFlags properties) {
600 VkPhysicalDeviceMemoryProperties memProperties;
601 vkGetPhysicalDeviceMemoryProperties(m_physicalDevice, &memProperties);
602
603 for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
604 if ((typeFilter & (1 << i)) &&
605 (memProperties.memoryTypes[i].propertyFlags & properties) ==
606 properties) {
607 return i;
608 }
609 }
610
611 SLEAK_ERROR("VulkanCubemapTexture: Failed to find suitable memory type");
612 return 0;
613}
614
615void VulkanCubemapTexture::TransitionImageLayout(VkImage image,
616 VkImageLayout oldLayout,
617 VkImageLayout newLayout,
618 uint32_t layerCount) {
619 VkCommandBufferAllocateInfo cmdAllocInfo{};
620 cmdAllocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
621 cmdAllocInfo.commandPool = m_commandPool;
622 cmdAllocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
623 cmdAllocInfo.commandBufferCount = 1;
624
625 VkCommandBuffer cmdBuffer;
626 vkAllocateCommandBuffers(m_device, &cmdAllocInfo, &cmdBuffer);
627
628 VkCommandBufferBeginInfo beginInfo{};
629 beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
630 beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
631 vkBeginCommandBuffer(cmdBuffer, &beginInfo);
632
633 VkImageMemoryBarrier barrier{};
634 barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
635 barrier.oldLayout = oldLayout;
636 barrier.newLayout = newLayout;
637 barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
638 barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
639 barrier.image = image;
640 barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
641 barrier.subresourceRange.baseMipLevel = 0;
642 barrier.subresourceRange.levelCount = 1;
643 barrier.subresourceRange.baseArrayLayer = 0;
644 barrier.subresourceRange.layerCount = layerCount;
645
646 VkPipelineStageFlags srcStage;
647 VkPipelineStageFlags dstStage;
648
649 if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED &&
650 newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) {
651 barrier.srcAccessMask = 0;
652 barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
653 srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
654 dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
655 } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL &&
656 newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) {
657 barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
658 barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
659 srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT;
660 dstStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
661 } else {
662 barrier.srcAccessMask = 0;
663 barrier.dstAccessMask = 0;
664 srcStage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
665 dstStage = VK_PIPELINE_STAGE_ALL_COMMANDS_BIT;
666 }
667
668 vkCmdPipelineBarrier(cmdBuffer, srcStage, dstStage, 0, 0, nullptr, 0,
669 nullptr, 1, &barrier);
670
671 vkEndCommandBuffer(cmdBuffer);
672
673 VkSubmitInfo submitInfo{};
674 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
675 submitInfo.commandBufferCount = 1;
676 submitInfo.pCommandBuffers = &cmdBuffer;
677
678 VkFenceCreateInfo fenceInfo{};
679 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
680 VkFence copyFence;
681 vkCreateFence(m_device, &fenceInfo, nullptr, &copyFence);
682 vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, copyFence);
683 vkWaitForFences(m_device, 1, &copyFence, VK_TRUE, UINT64_MAX);
684 vkDestroyFence(m_device, copyFence, nullptr);
685 vkFreeCommandBuffers(m_device, m_commandPool, 1, &cmdBuffer);
686}
687
688} // namespace RenderEngine
689} // namespace Sleak
int width
int height
#define SLEAK_ERROR(...)
Definition Logger.hpp:22
#define SLEAK_INFO(...)
Definition Logger.hpp:20
bool LoadCubemap(const std::array< std::string, 6 > &facePaths)
Loads and uploads 6 face images: +X, -X, +Y, -Y, +Z, -Z.
bool LoadFromMemory(const void *data, uint32_t width, uint32_t height, TextureFormat format) override
Unused for cubemaps; load via LoadCubemap/LoadEquirectangular instead.
void SetFilter(TextureFilter filter) override
VulkanCubemapTexture(VkDevice device, VkPhysicalDevice physicalDevice, VkCommandPool commandPool, VkQueue graphicsQueue)
bool LoadEquirectangular(const std::string &path, uint32_t faceSize=512)
Load from a single equirectangular panorama and convert to cubemap.
void SetWrapMode(TextureWrapMode wrapMode) override
bool LoadFromFile(const std::string &filePath) override
Unused for cubemaps; load via LoadCubemap/LoadEquirectangular instead.
void Bind(uint32_t slot=0) const override
TextureFormat
Definition Texture.hpp:10
TextureFilter
Definition Texture.hpp:28
TextureWrapMode
Definition Texture.hpp:43
Backend-facing rendering layer shared by the four graphics backends.
Root namespace for everything the engine exposes.
Definition Camera.hpp:10