26 const std::array<std::string, 6>& facePaths) {
31 unsigned char* pixels =
nullptr;
34 std::array<FaceData, 6> faces;
36 stbi_set_flip_vertically_on_load(
false);
38 for (
int i = 0; i < 6; i++) {
41 stbi_load(facePaths[i].c_str(), &faces[i].w, &faces[i].h,
43 if (!faces[i].pixels) {
44 SLEAK_ERROR(
"VulkanCubemapTexture: Failed to load face {}: {}",
46 for (
int j = 0; j < i; j++)
47 stbi_image_free(faces[j].pixels);
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 "
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);
67 VkDeviceSize faceSize =
68 static_cast<VkDeviceSize
>(m_width) * m_height * 4;
69 VkDeviceSize totalSize = faceSize * 6;
72 VkBuffer stagingBuffer;
73 VkDeviceMemory stagingMemory;
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;
81 if (vkCreateBuffer(m_device, &bufferInfo,
nullptr, &stagingBuffer) !=
83 SLEAK_ERROR(
"VulkanCubemapTexture: Failed to create staging buffer");
84 for (
int i = 0; i < 6; i++)
85 stbi_image_free(faces[i].pixels);
89 VkMemoryRequirements memReqs;
90 vkGetBufferMemoryRequirements(m_device, stagingBuffer, &memReqs);
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);
99 if (vkAllocateMemory(m_device, &allocInfo,
nullptr, &stagingMemory) !=
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);
109 vkBindBufferMemory(m_device, stagingBuffer, stagingMemory, 0);
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));
118 vkUnmapMemory(m_device, stagingMemory);
121 for (
int i = 0; i < 6; i++)
122 stbi_image_free(faces[i].pixels);
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;
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;
142 if (vkCreateImage(m_device, &imageInfo,
nullptr, &m_image) !=
144 SLEAK_ERROR(
"VulkanCubemapTexture: Failed to create cubemap image");
145 vkDestroyBuffer(m_device, stagingBuffer,
nullptr);
146 vkFreeMemory(m_device, stagingMemory,
nullptr);
150 vkGetImageMemoryRequirements(m_device, m_image, &memReqs);
152 allocInfo.allocationSize = memReqs.size;
153 allocInfo.memoryTypeIndex = FindMemoryType(
154 memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
156 if (vkAllocateMemory(m_device, &allocInfo,
nullptr, &m_imageMemory) !=
159 "VulkanCubemapTexture: Failed to allocate image memory");
160 vkDestroyBuffer(m_device, stagingBuffer,
nullptr);
161 vkFreeMemory(m_device, stagingMemory,
nullptr);
165 vkBindImageMemory(m_device, m_image, m_imageMemory, 0);
169 TransitionImageLayout(m_image, VK_IMAGE_LAYOUT_UNDEFINED,
170 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 6);
174 VkCommandBufferAllocateInfo cmdAllocInfo{};
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;
181 VkCommandBuffer cmdBuffer;
182 vkAllocateCommandBuffers(m_device, &cmdAllocInfo, &cmdBuffer);
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);
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};
203 vkCmdCopyBufferToImage(cmdBuffer, stagingBuffer, m_image,
204 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 6,
207 vkEndCommandBuffer(cmdBuffer);
209 VkSubmitInfo submitInfo{};
210 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
211 submitInfo.commandBufferCount = 1;
212 submitInfo.pCommandBuffers = &cmdBuffer;
214 VkFenceCreateInfo fenceInfo{};
215 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
217 vkCreateFence(m_device, &fenceInfo,
nullptr, ©Fence);
218 vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, copyFence);
219 vkWaitForFences(m_device, 1, ©Fence, VK_TRUE, UINT64_MAX);
220 vkDestroyFence(m_device, copyFence,
nullptr);
221 vkFreeCommandBuffers(m_device, m_commandPool, 1, &cmdBuffer);
224 TransitionImageLayout(m_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
225 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 6);
228 vkDestroyBuffer(m_device, stagingBuffer,
nullptr);
229 vkFreeMemory(m_device, stagingMemory,
nullptr);
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;
243 if (vkCreateImageView(m_device, &viewInfo,
nullptr, &m_imageView) !=
245 SLEAK_ERROR(
"VulkanCubemapTexture: Failed to create image view");
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;
267 if (vkCreateSampler(m_device, &samplerInfo,
nullptr, &m_sampler) !=
269 SLEAK_ERROR(
"VulkanCubemapTexture: Failed to create sampler");
273 SLEAK_INFO(
"VulkanCubemapTexture: Loaded cubemap ({}x{}, 6 faces)",
282 stbi_set_flip_vertically_on_load(
false);
284 int panW, panH, channels;
285 unsigned char* panorama = stbi_load(path.c_str(), &panW, &panH, &channels, 4);
287 SLEAK_ERROR(
"VulkanCubemapTexture: Failed to load panorama: {}", path);
295 VkDeviceSize faceBytes =
static_cast<VkDeviceSize
>(faceSize) * faceSize * 4;
296 VkDeviceSize totalSize = faceBytes * 6;
298 std::vector<unsigned char> allFaces(
static_cast<size_t>(totalSize));
300 for (
int face = 0; face < 6; ++face) {
301 unsigned char* faceData = allFaces.data() + face * faceBytes;
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;
310 case 0: dx = 1.0f; dy = -v; dz = -u;
break;
311 case 1: dx = -1.0f; dy = -v; dz = u;
break;
312 case 2: dx = u; dy = 1.0f; dz = v;
break;
313 case 3: dx = u; dy = -1.0f; dz = -v;
break;
314 case 4: dx = u; dy = -v; dz = 1.0f;
break;
315 case 5: dx = -u; dy = -v; dz = -1.0f;
break;
316 default: dx = dy = dz = 0.0f;
break;
319 float len = std::sqrt(dx * dx + dy * dy + dz * dz);
320 dx /= len; dy /= len; dz /= len;
322 float lon = std::atan2(dz, dx);
323 float lat = std::asin(std::clamp(dy, -1.0f, 1.0f));
325 float panU = 0.5f + lon / (2.0f * 3.14159265f);
326 float panV = 0.5f - lat / 3.14159265f;
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;
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));
354 stbi_image_free(panorama);
357 VkBuffer stagingBuffer;
358 VkDeviceMemory stagingMemory;
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;
366 if (vkCreateBuffer(m_device, &bufferInfo,
nullptr, &stagingBuffer) !=
368 SLEAK_ERROR(
"VulkanCubemapTexture: Failed to create staging buffer");
372 VkMemoryRequirements memReqs;
373 vkGetBufferMemoryRequirements(m_device, stagingBuffer, &memReqs);
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);
382 if (vkAllocateMemory(m_device, &allocInfo,
nullptr, &stagingMemory) !=
384 vkDestroyBuffer(m_device, stagingBuffer,
nullptr);
385 SLEAK_ERROR(
"VulkanCubemapTexture: Failed to allocate staging memory");
389 vkBindBufferMemory(m_device, stagingBuffer, stagingMemory, 0);
392 vkMapMemory(m_device, stagingMemory, 0, totalSize, 0, &mapped);
393 memcpy(mapped, allFaces.data(),
static_cast<size_t>(totalSize));
394 vkUnmapMemory(m_device, stagingMemory);
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;
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;
414 if (vkCreateImage(m_device, &imageInfo,
nullptr, &m_image) !=
416 SLEAK_ERROR(
"VulkanCubemapTexture: Failed to create cubemap image");
417 vkDestroyBuffer(m_device, stagingBuffer,
nullptr);
418 vkFreeMemory(m_device, stagingMemory,
nullptr);
422 vkGetImageMemoryRequirements(m_device, m_image, &memReqs);
424 allocInfo.allocationSize = memReqs.size;
425 allocInfo.memoryTypeIndex = FindMemoryType(
426 memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
428 if (vkAllocateMemory(m_device, &allocInfo,
nullptr, &m_imageMemory) !=
430 SLEAK_ERROR(
"VulkanCubemapTexture: Failed to allocate image memory");
431 vkDestroyBuffer(m_device, stagingBuffer,
nullptr);
432 vkFreeMemory(m_device, stagingMemory,
nullptr);
436 vkBindImageMemory(m_device, m_image, m_imageMemory, 0);
438 TransitionImageLayout(m_image, VK_IMAGE_LAYOUT_UNDEFINED,
439 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 6);
443 VkCommandBufferAllocateInfo cmdAllocInfo{};
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;
450 VkCommandBuffer cmdBuffer;
451 vkAllocateCommandBuffers(m_device, &cmdAllocInfo, &cmdBuffer);
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);
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};
472 vkCmdCopyBufferToImage(cmdBuffer, stagingBuffer, m_image,
473 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 6,
476 vkEndCommandBuffer(cmdBuffer);
478 VkSubmitInfo submitInfo{};
479 submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
480 submitInfo.commandBufferCount = 1;
481 submitInfo.pCommandBuffers = &cmdBuffer;
483 VkFenceCreateInfo fenceInfo{};
484 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
486 vkCreateFence(m_device, &fenceInfo,
nullptr, ©Fence);
487 vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, copyFence);
488 vkWaitForFences(m_device, 1, ©Fence, VK_TRUE, UINT64_MAX);
489 vkDestroyFence(m_device, copyFence,
nullptr);
490 vkFreeCommandBuffers(m_device, m_commandPool, 1, &cmdBuffer);
493 TransitionImageLayout(m_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
494 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, 6);
497 vkDestroyBuffer(m_device, stagingBuffer,
nullptr);
498 vkFreeMemory(m_device, stagingMemory,
nullptr);
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;
512 if (vkCreateImageView(m_device, &viewInfo,
nullptr, &m_imageView) !=
514 SLEAK_ERROR(
"VulkanCubemapTexture: Failed to create image view");
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;
536 if (vkCreateSampler(m_device, &samplerInfo,
nullptr, &m_sampler) !=
538 SLEAK_ERROR(
"VulkanCubemapTexture: Failed to create sampler");
542 SLEAK_INFO(
"VulkanCubemapTexture: Loaded equirectangular panorama ({}x{} face)",