SleakEngine 1.0.0
C++23 multi-backend game engine
Loading...
Searching...
No Matches
VulkanIBL.cpp
Go to the documentation of this file.
3
4#include <array>
5#include <cstring>
6#include "Core/Logger.hpp"
7
8namespace Sleak {
9 namespace RenderEngine {
10
11/// Creates stub IBL irradiance, prefilter, and BRDF LUT images, samplers, and descriptor set.
12bool VulkanRenderer::CreateIBLResources() {
13 if (m_iblResourcesCreated) return true;
14
15 // --- Helper: create a 1×1 black 2D or cube image as a stub ---
16 auto createStubImage = [&](uint32_t layers, VkFormat fmt,
17 VkImage& img, VkDeviceMemory& mem, VkImageView& view) -> bool {
18 VkImageCreateInfo info{};
19 info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
20 info.imageType = VK_IMAGE_TYPE_2D;
21 info.extent = {1, 1, 1};
22 info.mipLevels = 1;
23 info.arrayLayers = layers;
24 info.format = fmt;
25 info.tiling = VK_IMAGE_TILING_OPTIMAL;
26 info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
27 info.usage = VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
28 | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
29 info.samples = VK_SAMPLE_COUNT_1_BIT;
30 info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
31 if (layers == 6) info.flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
32
33 if (vkCreateImage(device, &info, nullptr, &img) != VK_SUCCESS) return false;
34
35 VkMemoryRequirements memReqs;
36 vkGetImageMemoryRequirements(device, img, &memReqs);
37 VkMemoryAllocateInfo memInfo{};
38 memInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
39 memInfo.allocationSize = memReqs.size;
40 memInfo.memoryTypeIndex = FindMemoryType(memReqs.memoryTypeBits,
41 VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
42 if (vkAllocateMemory(device, &memInfo, nullptr, &mem) != VK_SUCCESS) return false;
43 vkBindImageMemory(device, img, mem, 0);
44
45 VkImageViewCreateInfo viewInfo{};
46 viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
47 viewInfo.image = img;
48 viewInfo.viewType = (layers == 6) ? VK_IMAGE_VIEW_TYPE_CUBE
49 : VK_IMAGE_VIEW_TYPE_2D;
50 viewInfo.format = fmt;
51 viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
52 viewInfo.subresourceRange.baseMipLevel = 0;
53 viewInfo.subresourceRange.levelCount = 1;
54 viewInfo.subresourceRange.baseArrayLayer = 0;
55 viewInfo.subresourceRange.layerCount = layers;
56 return vkCreateImageView(device, &viewInfo, nullptr, &view) == VK_SUCCESS;
57 };
58
59 auto createSampler = [&](bool mips, VkSampler& samp) -> bool {
60 VkSamplerCreateInfo si{};
61 si.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
62 si.magFilter = VK_FILTER_LINEAR;
63 si.minFilter = VK_FILTER_LINEAR;
64 si.mipmapMode = mips ? VK_SAMPLER_MIPMAP_MODE_LINEAR : VK_SAMPLER_MIPMAP_MODE_NEAREST;
65 si.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
66 si.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
67 si.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
68 si.minLod = 0.0f;
69 si.maxLod = mips ? static_cast<float>(IBL_PREFILTER_MIP_LEVELS) : 1.0f;
70 return vkCreateSampler(device, &si, nullptr, &samp) == VK_SUCCESS;
71 };
72
73 constexpr VkFormat hdrFmt = VK_FORMAT_R16G16B16A16_SFLOAT;
74 constexpr VkFormat lutFmt = VK_FORMAT_R16G16_SFLOAT;
75
76 if (!createStubImage(6, hdrFmt, m_iblIrradianceImage, m_iblIrradianceMemory, m_iblIrradianceView)) {
77 SLEAK_ERROR("IBL: Failed to create irradiance image!"); return false;
78 }
79 if (!createSampler(false, m_iblIrradianceSampler)) {
80 SLEAK_ERROR("IBL: Failed to create irradiance sampler!"); return false;
81 }
82
83 if (!createStubImage(6, hdrFmt, m_iblPrefilterImage, m_iblPrefilterMemory, m_iblPrefilterView)) {
84 SLEAK_ERROR("IBL: Failed to create prefilter image!"); return false;
85 }
86 if (!createSampler(true, m_iblPrefilterSampler)) {
87 SLEAK_ERROR("IBL: Failed to create prefilter sampler!"); return false;
88 }
89
90 if (!createStubImage(1, lutFmt, m_iblBrdfLutImage, m_iblBrdfLutMemory, m_iblBrdfLutView)) {
91 SLEAK_ERROR("IBL: Failed to create BRDF LUT image!"); return false;
92 }
93 if (!createSampler(false, m_iblBrdfLutSampler)) {
94 SLEAK_ERROR("IBL: Failed to create BRDF LUT sampler!"); return false;
95 }
96
97 // Transition stub images to SHADER_READ_ONLY_OPTIMAL so they can be sampled
98 {
99 VkCommandBufferAllocateInfo cmdAlloc{};
100 cmdAlloc.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
101 cmdAlloc.commandPool = commands;
102 cmdAlloc.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
103 cmdAlloc.commandBufferCount = 1;
104 VkCommandBuffer cmd;
105 vkAllocateCommandBuffers(device, &cmdAlloc, &cmd);
106
107 VkCommandBufferBeginInfo begin{};
108 begin.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
109 begin.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
110 vkBeginCommandBuffer(cmd, &begin);
111
112 auto transitionImage = [&](VkImage img, uint32_t layers) {
113 VkImageMemoryBarrier bar{};
114 bar.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
115 bar.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
116 bar.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
117 bar.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
118 bar.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
119 bar.image = img;
120 bar.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, layers};
121 bar.srcAccessMask = 0;
122 bar.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
123 vkCmdPipelineBarrier(cmd,
124 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
125 VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
126 0, 0, nullptr, 0, nullptr, 1, &bar);
127 };
128 transitionImage(m_iblIrradianceImage, 6);
129 transitionImage(m_iblPrefilterImage, 6);
130 transitionImage(m_iblBrdfLutImage, 1);
131
132 vkEndCommandBuffer(cmd);
133
134 VkSubmitInfo submit{};
135 submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
136 submit.commandBufferCount = 1;
137 submit.pCommandBuffers = &cmd;
138
139 VkFenceCreateInfo fenceInfo{};
140 fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
141 VkFence fence;
142 vkCreateFence(device, &fenceInfo, nullptr, &fence);
143 vkQueueSubmit(graphicsQueue, 1, &submit, fence);
144 vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX);
145 vkDestroyFence(device, fence, nullptr);
146 vkFreeCommandBuffers(device, commands, 1, &cmd);
147 }
148
149 // --- Descriptor Set Layout: 3 samplerCubes + 1 sampler2D + 1 UBO ---
150 std::array<VkDescriptorSetLayoutBinding, 4> iblBindings{};
151 // binding 0: irradiance cubemap
152 iblBindings[0] = {0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr};
153 // binding 1: prefilter cubemap
154 iblBindings[1] = {1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr};
155 // binding 2: BRDF LUT
156 iblBindings[2] = {2, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr};
157 // binding 3: IBLSettings UBO
158 iblBindings[3] = {3, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_FRAGMENT_BIT, nullptr};
159
160 VkDescriptorSetLayoutCreateInfo iblDSLInfo{};
161 iblDSLInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
162 iblDSLInfo.bindingCount = static_cast<uint32_t>(iblBindings.size());
163 iblDSLInfo.pBindings = iblBindings.data();
164 if (vkCreateDescriptorSetLayout(device, &iblDSLInfo, nullptr, &m_iblDSL) != VK_SUCCESS) {
165 SLEAK_ERROR("IBL: Failed to create IBL DSL!"); return false;
166 }
167
168 // --- IBL Descriptor Pool ---
169 std::array<VkDescriptorPoolSize, 2> iblPoolSizes{};
170 iblPoolSizes[0] = {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3 * MAX_FRAMES_IN_FLIGHT};
171 iblPoolSizes[1] = {VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1 * MAX_FRAMES_IN_FLIGHT};
172 VkDescriptorPoolCreateInfo iblPoolInfo{};
173 iblPoolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
174 iblPoolInfo.poolSizeCount = static_cast<uint32_t>(iblPoolSizes.size());
175 iblPoolInfo.pPoolSizes = iblPoolSizes.data();
176 iblPoolInfo.maxSets = MAX_FRAMES_IN_FLIGHT;
177 if (vkCreateDescriptorPool(device, &iblPoolInfo, nullptr, &m_iblPool) != VK_SUCCESS) {
178 SLEAK_ERROR("IBL: Failed to create IBL pool!"); return false;
179 }
180
181 // --- Allocate descriptor sets ---
182 std::array<VkDescriptorSetLayout, MAX_FRAMES_IN_FLIGHT> iblLayouts;
183 iblLayouts.fill(m_iblDSL);
184 VkDescriptorSetAllocateInfo iblAlloc{};
185 iblAlloc.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
186 iblAlloc.descriptorPool = m_iblPool;
187 iblAlloc.descriptorSetCount = MAX_FRAMES_IN_FLIGHT;
188 iblAlloc.pSetLayouts = iblLayouts.data();
189 if (vkAllocateDescriptorSets(device, &iblAlloc, m_iblSets.data()) != VK_SUCCESS) {
190 SLEAK_ERROR("IBL: Failed to allocate IBL descriptor sets!"); return false;
191 }
192
193 // --- Per-frame IBL settings UBO (IBLSettingsGPUData, 16 bytes) ---
194 constexpr VkDeviceSize settingsSize = sizeof(IBLSettingsGPUData);
195 for (uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) {
196 VkBufferCreateInfo bufInfo{};
197 bufInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
198 bufInfo.size = settingsSize;
199 bufInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT;
200 bufInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
201 if (vkCreateBuffer(device, &bufInfo, nullptr, &m_iblSettingsBuffers[i]) != VK_SUCCESS) {
202 SLEAK_ERROR("IBL: Failed to create settings buffer {}!", i); return false;
203 }
204 VkMemoryRequirements memReqs;
205 vkGetBufferMemoryRequirements(device, m_iblSettingsBuffers[i], &memReqs);
206 VkMemoryAllocateInfo memInfo{};
207 memInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
208 memInfo.allocationSize = memReqs.size;
209 memInfo.memoryTypeIndex = FindMemoryType(memReqs.memoryTypeBits,
210 VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
211 if (vkAllocateMemory(device, &memInfo, nullptr, &m_iblSettingsMemory[i]) != VK_SUCCESS) {
212 SLEAK_ERROR("IBL: Failed to allocate settings memory {}!", i); return false;
213 }
214 vkBindBufferMemory(device, m_iblSettingsBuffers[i], m_iblSettingsMemory[i], 0);
215 vkMapMemory(device, m_iblSettingsMemory[i], 0, settingsSize, 0, &m_iblSettingsMapped[i]);
216
217 // Default: IBL disabled — lighting shader falls back to hemisphere ambient
218 IBLSettingsGPUData settings{};
219 settings.IBLEnabled = 0;
220 settings.IBLIntensity = 1.0f;
221 settings.MaxReflectionLOD = static_cast<float>(IBL_PREFILTER_MIP_LEVELS - 1);
222 memcpy(m_iblSettingsMapped[i], &settings, sizeof(settings));
223 }
224
225 // --- Write initial descriptor sets ---
226 for (uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) {
227 VkDescriptorImageInfo irradianceInfo{};
228 irradianceInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
229 irradianceInfo.imageView = m_iblIrradianceView;
230 irradianceInfo.sampler = m_iblIrradianceSampler;
231
232 VkDescriptorImageInfo prefilterInfo{};
233 prefilterInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
234 prefilterInfo.imageView = m_iblPrefilterView;
235 prefilterInfo.sampler = m_iblPrefilterSampler;
236
237 VkDescriptorImageInfo lutInfo{};
238 lutInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
239 lutInfo.imageView = m_iblBrdfLutView;
240 lutInfo.sampler = m_iblBrdfLutSampler;
241
242 VkDescriptorBufferInfo settingsBufInfo{};
243 settingsBufInfo.buffer = m_iblSettingsBuffers[i];
244 settingsBufInfo.offset = 0;
245 settingsBufInfo.range = sizeof(IBLSettingsGPUData);
246
247 std::array<VkWriteDescriptorSet, 4> writes{};
248 writes[0] = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, nullptr,
249 m_iblSets[i], 0, 0, 1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
250 &irradianceInfo, nullptr, nullptr};
251 writes[1] = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, nullptr,
252 m_iblSets[i], 1, 0, 1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
253 &prefilterInfo, nullptr, nullptr};
254 writes[2] = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, nullptr,
255 m_iblSets[i], 2, 0, 1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,
256 &lutInfo, nullptr, nullptr};
257 writes[3] = {VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, nullptr,
258 m_iblSets[i], 3, 0, 1, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
259 nullptr, &settingsBufInfo, nullptr};
260 vkUpdateDescriptorSets(device, static_cast<uint32_t>(writes.size()),
261 writes.data(), 0, nullptr);
262 }
263
264 m_iblResourcesCreated = true;
265 m_iblReady = false; // not yet precomputed
266 SLEAK_INFO("VulkanRenderer: IBL resources created (IBL disabled until skybox precompute)");
267 return true;
268}
269
270/// Destroys the IBL images, samplers, and descriptor resources.
271void VulkanRenderer::CleanupIBLResources() {
272 if (!m_iblResourcesCreated) return;
273
274 for (uint32_t i = 0; i < MAX_FRAMES_IN_FLIGHT; ++i) {
275 if (m_iblSettingsMapped[i]) {
276 vkUnmapMemory(device, m_iblSettingsMemory[i]);
277 m_iblSettingsMapped[i] = nullptr;
278 }
279 if (m_iblSettingsBuffers[i]) {
280 vkDestroyBuffer(device, m_iblSettingsBuffers[i], nullptr);
281 m_iblSettingsBuffers[i] = VK_NULL_HANDLE;
282 }
283 if (m_iblSettingsMemory[i]) {
284 vkFreeMemory(device, m_iblSettingsMemory[i], nullptr);
285 m_iblSettingsMemory[i] = VK_NULL_HANDLE;
286 }
287 }
288
289 if (m_iblPool) { vkDestroyDescriptorPool(device, m_iblPool, nullptr); m_iblPool = VK_NULL_HANDLE; }
290 if (m_iblDSL) { vkDestroyDescriptorSetLayout(device, m_iblDSL, nullptr); m_iblDSL = VK_NULL_HANDLE; }
291
292 if (m_iblBrdfLutSampler) { vkDestroySampler(device, m_iblBrdfLutSampler, nullptr); m_iblBrdfLutSampler = VK_NULL_HANDLE; }
293 if (m_iblBrdfLutView) { vkDestroyImageView(device, m_iblBrdfLutView, nullptr); m_iblBrdfLutView = VK_NULL_HANDLE; }
294 if (m_iblBrdfLutImage) { vkDestroyImage(device, m_iblBrdfLutImage, nullptr); m_iblBrdfLutImage = VK_NULL_HANDLE; }
295 if (m_iblBrdfLutMemory) { vkFreeMemory(device, m_iblBrdfLutMemory, nullptr); m_iblBrdfLutMemory = VK_NULL_HANDLE; }
296
297 if (m_iblPrefilterSampler) { vkDestroySampler(device, m_iblPrefilterSampler, nullptr); m_iblPrefilterSampler = VK_NULL_HANDLE; }
298 if (m_iblPrefilterView) { vkDestroyImageView(device, m_iblPrefilterView, nullptr); m_iblPrefilterView = VK_NULL_HANDLE; }
299 if (m_iblPrefilterImage) { vkDestroyImage(device, m_iblPrefilterImage, nullptr); m_iblPrefilterImage = VK_NULL_HANDLE; }
300 if (m_iblPrefilterMemory) { vkFreeMemory(device, m_iblPrefilterMemory, nullptr); m_iblPrefilterMemory = VK_NULL_HANDLE; }
301
302 if (m_iblIrradianceSampler) { vkDestroySampler(device, m_iblIrradianceSampler, nullptr); m_iblIrradianceSampler = VK_NULL_HANDLE; }
303 if (m_iblIrradianceView) { vkDestroyImageView(device, m_iblIrradianceView, nullptr); m_iblIrradianceView = VK_NULL_HANDLE; }
304 if (m_iblIrradianceImage) { vkDestroyImage(device, m_iblIrradianceImage, nullptr); m_iblIrradianceImage = VK_NULL_HANDLE; }
305 if (m_iblIrradianceMemory) { vkFreeMemory(device, m_iblIrradianceMemory, nullptr); m_iblIrradianceMemory = VK_NULL_HANDLE; }
306
307 m_iblResourcesCreated = false;
308 m_iblReady = false;
309}
310
311} // namespace RenderEngine
312} // namespace Sleak
#define SLEAK_ERROR(...)
Definition Logger.hpp:22
#define SLEAK_INFO(...)
Definition Logger.hpp:20
Backend-facing rendering layer shared by the four graphics backends.
Root namespace for everything the engine exposes.
Definition Camera.hpp:10