SleakEngine 1.0.0
C++23 multi-backend game engine
Loading...
Searching...
No Matches
VulkanBloom.cpp
Go to the documentation of this file.
2
3#include <algorithm>
4#include <array>
5#include <cstring>
6#include <vector>
7#include "Core/Logger.hpp"
8
9namespace Sleak {
10 namespace RenderEngine {
11
12// FillFullscreenViewportScissor moved here: of its 8 call sites across the
13// four post-effect TUs, bloom has the most (4 of 8; SSAO 2, TAA 1, SSR 1).
14/// Sets the dynamic viewport and scissor to fill the given extent.
15void VulkanRenderer::FillFullscreenViewportScissor(VkCommandBuffer cmd, VkExtent2D ext) {
16 VkViewport vp{};
17 vp.x = 0.0f;
18 vp.y = 0.0f;
19 vp.width = static_cast<float>(ext.width);
20 vp.height = static_cast<float>(ext.height);
21 vp.minDepth = 0.0f;
22 vp.maxDepth = 1.0f;
23 vkCmdSetViewport(cmd, 0, 1, &vp);
24
25 VkRect2D sc{};
26 sc.offset = {0, 0};
27 sc.extent = ext;
28 vkCmdSetScissor(cmd, 0, 1, &sc);
29}
30
31// ==================================================================
32// ==================== Bloom (UE4-style pyramid) ===================
33// ==================================================================
34
35/// Creates the HDR scene color image, view, and the shared bloom-source sampler.
36bool VulkanRenderer::CreateHDRSceneResources() {
37 VkImageCreateInfo info{};
38 info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
39 info.imageType = VK_IMAGE_TYPE_2D;
40 info.extent.width = scExtent.width;
41 info.extent.height = scExtent.height;
42 info.extent.depth = 1;
43 info.mipLevels = 1;
44 info.arrayLayers = 1;
45 info.format = m_hdrSceneFormat;
46 info.tiling = VK_IMAGE_TILING_OPTIMAL;
47 info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
48 // TRANSFER_DST_BIT: TAA copies its resolved result back into hdrScene.
49 // TRANSFER_SRC_BIT: MSAA resolve from the forward render pass.
50 info.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
51 | VK_IMAGE_USAGE_SAMPLED_BIT
52 | VK_IMAGE_USAGE_TRANSFER_DST_BIT
53 | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
54 info.samples = VK_SAMPLE_COUNT_1_BIT;
55 info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
56 if (vkCreateImage(device, &info, nullptr, &m_hdrSceneImage) != VK_SUCCESS) return false;
57
58 VkMemoryRequirements req;
59 vkGetImageMemoryRequirements(device, m_hdrSceneImage, &req);
60 VkMemoryAllocateInfo alloc{};
61 alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
62 alloc.allocationSize = req.size;
63 alloc.memoryTypeIndex = FindMemoryType(req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
64 if (vkAllocateMemory(device, &alloc, nullptr, &m_hdrSceneMemory) != VK_SUCCESS) return false;
65 vkBindImageMemory(device, m_hdrSceneImage, m_hdrSceneMemory, 0);
66
67 VkImageViewCreateInfo vinfo{};
68 vinfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
69 vinfo.image = m_hdrSceneImage;
70 vinfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
71 vinfo.format = m_hdrSceneFormat;
72 vinfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
73 if (vkCreateImageView(device, &vinfo, nullptr, &m_hdrSceneView) != VK_SUCCESS) return false;
74
75 // Shared linear-clamp sampler for every bloom source sample.
76 VkSamplerCreateInfo sinfo{};
77 sinfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
78 sinfo.magFilter = VK_FILTER_LINEAR;
79 sinfo.minFilter = VK_FILTER_LINEAR;
80 sinfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_NEAREST;
81 sinfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
82 sinfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
83 sinfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE;
84 sinfo.minLod = 0.0f;
85 sinfo.maxLod = 0.0f;
86 if (vkCreateSampler(device, &sinfo, nullptr, &m_bloomSampler) != VK_SUCCESS) return false;
87
88 return true;
89}
90
91/// Creates the multi-mip bloom image and a per-mip image view.
92bool VulkanRenderer::CreateBloomImages() {
93 // Single image with BLOOM_MIP_COUNT mip levels, each starting at
94 // (scExtent / 2) and halving. HDR float format preserves bloom energy.
95 uint32_t w = std::max(1u, scExtent.width / 2);
96 uint32_t h = std::max(1u, scExtent.height / 2);
97
98 VkImageCreateInfo info{};
99 info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
100 info.imageType = VK_IMAGE_TYPE_2D;
101 info.extent.width = w;
102 info.extent.height = h;
103 info.extent.depth = 1;
104 info.mipLevels = BLOOM_MIP_COUNT;
105 info.arrayLayers = 1;
106 info.format = m_hdrSceneFormat;
107 info.tiling = VK_IMAGE_TILING_OPTIMAL;
108 info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
109 // TRANSFER_DST_BIT: when bloom is disabled, mip[0] is cleared-to-black so the
110 // composite pass samples a defined SHADER_READ_ONLY image (no full mip chain).
111 info.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT |
112 VK_IMAGE_USAGE_TRANSFER_DST_BIT;
113 info.samples = VK_SAMPLE_COUNT_1_BIT;
114 info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
115 if (vkCreateImage(device, &info, nullptr, &m_bloomImage) != VK_SUCCESS) return false;
116
117 VkMemoryRequirements req;
118 vkGetImageMemoryRequirements(device, m_bloomImage, &req);
119 VkMemoryAllocateInfo alloc{};
120 alloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
121 alloc.allocationSize = req.size;
122 alloc.memoryTypeIndex = FindMemoryType(req.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
123 if (vkAllocateMemory(device, &alloc, nullptr, &m_bloomMemory) != VK_SUCCESS) return false;
124 vkBindImageMemory(device, m_bloomImage, m_bloomMemory, 0);
125
126 // Per-mip views — each is a single-mip view so framebuffers can target it.
127 for (uint32_t m = 0; m < BLOOM_MIP_COUNT; ++m) {
128 m_bloomMipExtents[m].width = std::max(1u, w >> m);
129 m_bloomMipExtents[m].height = std::max(1u, h >> m);
130
131 VkImageViewCreateInfo vinfo{};
132 vinfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
133 vinfo.image = m_bloomImage;
134 vinfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
135 vinfo.format = m_hdrSceneFormat;
136 vinfo.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, m, 1, 0, 1};
137 if (vkCreateImageView(device, &vinfo, nullptr, &m_bloomMipViews[m]) != VK_SUCCESS) return false;
138 }
139
140 return true;
141}
142
143/// Creates the bloom threshold/downsample, additive-upsample, and composite render passes.
144bool VulkanRenderer::CreateBloomRenderPasses() {
145 // Common HDR color attachment.
146 auto makeRP = [&](VkAttachmentLoadOp loadOp, VkImageLayout initial,
147 VkFormat fmt, VkImageLayout finalLayout,
148 VkRenderPass& out) -> bool {
149 VkAttachmentDescription att{};
150 att.format = fmt;
151 att.samples = VK_SAMPLE_COUNT_1_BIT;
152 att.loadOp = loadOp;
153 att.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
154 att.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
155 att.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
156 att.initialLayout = initial;
157 att.finalLayout = finalLayout;
158
159 VkAttachmentReference ref{};
160 ref.attachment = 0;
161 ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
162
163 VkSubpassDescription sp{};
164 sp.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
165 sp.colorAttachmentCount = 1;
166 sp.pColorAttachments = &ref;
167
168 std::array<VkSubpassDependency, 2> d{};
169 d[0].srcSubpass = VK_SUBPASS_EXTERNAL;
170 d[0].dstSubpass = 0;
171 d[0].srcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
172 d[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
173 d[0].srcAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
174 d[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT;
175 d[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
176
177 d[1].srcSubpass = 0;
178 d[1].dstSubpass = VK_SUBPASS_EXTERNAL;
179 d[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
180 d[1].dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
181 d[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
182 d[1].dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
183 d[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
184
185 VkRenderPassCreateInfo rp{};
186 rp.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
187 rp.attachmentCount = 1;
188 rp.pAttachments = &att;
189 rp.subpassCount = 1;
190 rp.pSubpasses = &sp;
191 rp.dependencyCount = static_cast<uint32_t>(d.size());
192 rp.pDependencies = d.data();
193
194 return vkCreateRenderPass(device, &rp, nullptr, &out) == VK_SUCCESS;
195 };
196
197 // Downsample / threshold: DONT_CARE → STORE → SHADER_READ_ONLY.
198 if (!makeRP(VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_IMAGE_LAYOUT_UNDEFINED,
199 m_hdrSceneFormat, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
200 m_bloomRenderPass)) return false;
201
202 // Upsample (blend-add): LOAD → STORE → SHADER_READ_ONLY.
203 if (!makeRP(VK_ATTACHMENT_LOAD_OP_LOAD, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
204 m_hdrSceneFormat, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
205 m_bloomAddRenderPass)) return false;
206
207 // Composite: writes swapchain image, ends in PRESENT_SRC_KHR. ImGui draws here.
208 if (!makeRP(VK_ATTACHMENT_LOAD_OP_DONT_CARE, VK_IMAGE_LAYOUT_UNDEFINED,
209 scImageFormat, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
210 m_bloomCompositeRenderPass)) return false;
211
212 return true;
213}
214
215/// Creates the per-mip bloom framebuffers and one composite framebuffer per swapchain image.
216bool VulkanRenderer::CreateBloomFramebuffers() {
217 // One framebuffer per bloom mip (reused between threshold/downsample/upsample).
218 for (uint32_t m = 0; m < BLOOM_MIP_COUNT; ++m) {
219 VkFramebufferCreateInfo fb{};
220 fb.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
221 fb.renderPass = m_bloomRenderPass;
222 fb.attachmentCount = 1;
223 fb.pAttachments = &m_bloomMipViews[m];
224 fb.width = m_bloomMipExtents[m].width;
225 fb.height = m_bloomMipExtents[m].height;
226 fb.layers = 1;
227 if (vkCreateFramebuffer(device, &fb, nullptr, &m_bloomMipFramebuffers[m]) != VK_SUCCESS) return false;
228 }
229
230 // One framebuffer per swapchain image for the composite pass.
231 m_bloomCompositeFramebuffers.resize(swapChainImageViews.size());
232 for (size_t i = 0; i < swapChainImageViews.size(); ++i) {
233 VkFramebufferCreateInfo fb{};
234 fb.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
235 fb.renderPass = m_bloomCompositeRenderPass;
236 fb.attachmentCount = 1;
237 fb.pAttachments = &swapChainImageViews[i];
238 fb.width = scExtent.width;
239 fb.height = scExtent.height;
240 fb.layers = 1;
241 if (vkCreateFramebuffer(device, &fb, nullptr, &m_bloomCompositeFramebuffers[i]) != VK_SUCCESS) return false;
242 }
243
244 return true;
245}
246
247/// Creates the bloom filter and composite descriptor layouts, pool, and sets.
248bool VulkanRenderer::CreateBloomDescriptorResources() {
249 // Filter DSL: single combined image sampler (binding 0).
250 {
251 VkDescriptorSetLayoutBinding b{};
252 b.binding = 0;
253 b.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
254 b.descriptorCount = 1;
255 b.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
256 VkDescriptorSetLayoutCreateInfo info{};
257 info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
258 info.bindingCount = 1;
259 info.pBindings = &b;
260 if (vkCreateDescriptorSetLayout(device, &info, nullptr, &m_bloomFilterDSL) != VK_SUCCESS) return false;
261 }
262 // Composite DSL: three combined image samplers (sceneHDR + bloom + SSR).
263 {
264 std::array<VkDescriptorSetLayoutBinding, 3> binds{};
265 for (uint32_t i = 0; i < binds.size(); ++i) {
266 binds[i].binding = i;
267 binds[i].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
268 binds[i].descriptorCount = 1;
269 binds[i].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
270 }
271 VkDescriptorSetLayoutCreateInfo info{};
272 info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
273 info.bindingCount = static_cast<uint32_t>(binds.size());
274 info.pBindings = binds.data();
275 if (vkCreateDescriptorSetLayout(device, &info, nullptr, &m_bloomCompositeDSL) != VK_SUCCESS) return false;
276 }
277
278 // Pool sized for BLOOM_TRANSITION_COUNT filter sets per frame slot + 1 composite set per frame.
279 // Composite set now binds 3 samplers (HDR + bloom + SSR).
280 VkDescriptorPoolSize sz{};
281 sz.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
282 sz.descriptorCount = (BLOOM_TRANSITION_COUNT + 3) * MAX_FRAMES_IN_FLIGHT;
283
284 VkDescriptorPoolCreateInfo pool{};
285 pool.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
286 pool.poolSizeCount = 1;
287 pool.pPoolSizes = &sz;
288 pool.maxSets = (BLOOM_TRANSITION_COUNT + 1) * MAX_FRAMES_IN_FLIGHT;
289 if (vkCreateDescriptorPool(device, &pool, nullptr, &m_bloomDescriptorPool) != VK_SUCCESS) return false;
290
291 // Allocate filter sets: MAX_FRAMES_IN_FLIGHT frames × BLOOM_TRANSITION_COUNT transitions.
292 for (uint32_t f = 0; f < MAX_FRAMES_IN_FLIGHT; ++f) {
293 std::array<VkDescriptorSetLayout, BLOOM_TRANSITION_COUNT> layouts;
294 layouts.fill(m_bloomFilterDSL);
295 VkDescriptorSetAllocateInfo a{};
296 a.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
297 a.descriptorPool = m_bloomDescriptorPool;
298 a.descriptorSetCount = BLOOM_TRANSITION_COUNT;
299 a.pSetLayouts = layouts.data();
300 if (vkAllocateDescriptorSets(device, &a, m_bloomFilterSets[f].data()) != VK_SUCCESS) return false;
301 }
302
303 // Allocate composite sets (one per frame).
304 std::array<VkDescriptorSetLayout, MAX_FRAMES_IN_FLIGHT> layouts;
305 layouts.fill(m_bloomCompositeDSL);
306 VkDescriptorSetAllocateInfo a{};
307 a.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
308 a.descriptorPool = m_bloomDescriptorPool;
309 a.descriptorSetCount = MAX_FRAMES_IN_FLIGHT;
310 a.pSetLayouts = layouts.data();
311 if (vkAllocateDescriptorSets(device, &a, m_bloomCompositeSets.data()) != VK_SUCCESS) return false;
312
313 return true;
314}
315
316/// Compiles the bloom threshold/downsample/upsample/composite shaders and creates their pipelines.
317bool VulkanRenderer::CreateBloomPipelines() {
318 // ---- Pipeline layouts ----
319 VkPushConstantRange filterPushRange{};
320 filterPushRange.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
321 filterPushRange.offset = 0;
322 filterPushRange.size = 16; // 4 floats, all filter PCs are 16 bytes
323
324 VkPipelineLayoutCreateInfo fliInfo{};
325 fliInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
326 fliInfo.setLayoutCount = 1;
327 fliInfo.pSetLayouts = &m_bloomFilterDSL;
328 fliInfo.pushConstantRangeCount = 1;
329 fliInfo.pPushConstantRanges = &filterPushRange;
330 if (vkCreatePipelineLayout(device, &fliInfo, nullptr, &m_bloomFilterPipelineLayout) != VK_SUCCESS) return false;
331
332 VkPushConstantRange compPushRange{};
333 compPushRange.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
334 compPushRange.offset = 0;
335 compPushRange.size = 16;
336
337 VkPipelineLayoutCreateInfo cliInfo{};
338 cliInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
339 cliInfo.setLayoutCount = 1;
340 cliInfo.pSetLayouts = &m_bloomCompositeDSL;
341 cliInfo.pushConstantRangeCount = 1;
342 cliInfo.pPushConstantRanges = &compPushRange;
343 if (vkCreatePipelineLayout(device, &cliInfo, nullptr, &m_bloomCompositePipelineLayout) != VK_SUCCESS) return false;
344
345 // ---- Compile shaders ----
346 m_bloomThresholdShader = new VulkanShader(device);
347 if (!m_bloomThresholdShader->compile("assets/shaders/bloom.vert.spv",
348 "assets/shaders/bloom_threshold.frag.spv")) return false;
349 m_bloomDownsampleShader = new VulkanShader(device);
350 if (!m_bloomDownsampleShader->compile("assets/shaders/bloom.vert.spv",
351 "assets/shaders/bloom_downsample.frag.spv")) return false;
352 m_bloomUpsampleShader = new VulkanShader(device);
353 if (!m_bloomUpsampleShader->compile("assets/shaders/bloom.vert.spv",
354 "assets/shaders/bloom_upsample.frag.spv")) return false;
355 m_bloomCompositeShader = new VulkanShader(device);
356 if (!m_bloomCompositeShader->compile("assets/shaders/bloom.vert.spv",
357 "assets/shaders/bloom_composite.frag.spv")) return false;
358
359 // ---- Common pipeline state (fullscreen triangle) ----
360 VkPipelineVertexInputStateCreateInfo vin{};
361 vin.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
362
363 VkPipelineInputAssemblyStateCreateInfo ia{};
364 ia.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
365 ia.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
366
367 std::vector<VkDynamicState> dynStates = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR };
368 VkPipelineDynamicStateCreateInfo ds{};
369 ds.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
370 ds.dynamicStateCount = static_cast<uint32_t>(dynStates.size());
371 ds.pDynamicStates = dynStates.data();
372
373 VkPipelineViewportStateCreateInfo vps{};
374 vps.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
375 vps.viewportCount = 1;
376 vps.scissorCount = 1;
377
378 VkPipelineRasterizationStateCreateInfo rs{};
379 rs.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
380 rs.polygonMode = VK_POLYGON_MODE_FILL;
381 rs.cullMode = VK_CULL_MODE_NONE;
382 rs.lineWidth = 1.0f;
383
384 VkPipelineMultisampleStateCreateInfo ms{};
385 ms.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
386 ms.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
387
388 VkPipelineDepthStencilStateCreateInfo dss{};
389 dss.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
390
391 // Opaque blend (default).
392 VkPipelineColorBlendAttachmentState blendOpaque{};
393 blendOpaque.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
394 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
395 VkPipelineColorBlendStateCreateInfo cbOpaque{};
396 cbOpaque.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
397 cbOpaque.attachmentCount = 1;
398 cbOpaque.pAttachments = &blendOpaque;
399
400 // Additive blend for bloom upsample.
401 VkPipelineColorBlendAttachmentState blendAdd{};
402 blendAdd.blendEnable = VK_TRUE;
403 blendAdd.srcColorBlendFactor = VK_BLEND_FACTOR_ONE;
404 blendAdd.dstColorBlendFactor = VK_BLEND_FACTOR_ONE;
405 blendAdd.colorBlendOp = VK_BLEND_OP_ADD;
406 blendAdd.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
407 blendAdd.dstAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
408 blendAdd.alphaBlendOp = VK_BLEND_OP_ADD;
409 blendAdd.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
410 VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
411 VkPipelineColorBlendStateCreateInfo cbAdd{};
412 cbAdd.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
413 cbAdd.attachmentCount = 1;
414 cbAdd.pAttachments = &blendAdd;
415
416 // ---- Threshold pipeline (writes mip 0 of bloom image) ----
417 VkPipelineShaderStageCreateInfo threshStages[] = {
418 m_bloomThresholdShader->GetVertexInfo(),
419 m_bloomThresholdShader->GetFragInfo()
420 };
421 VkGraphicsPipelineCreateInfo gpi{};
422 gpi.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
423 gpi.stageCount = 2;
424 gpi.pStages = threshStages;
425 gpi.pVertexInputState = &vin;
426 gpi.pInputAssemblyState = &ia;
427 gpi.pViewportState = &vps;
428 gpi.pRasterizationState = &rs;
429 gpi.pMultisampleState = &ms;
430 gpi.pDepthStencilState = &dss;
431 gpi.pColorBlendState = &cbOpaque;
432 gpi.pDynamicState = &ds;
433 gpi.layout = m_bloomFilterPipelineLayout;
434 gpi.renderPass = m_bloomRenderPass;
435 gpi.subpass = 0;
436 if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &gpi, nullptr, &m_bloomThresholdPipeline) != VK_SUCCESS) return false;
437
438 // ---- Downsample pipeline ----
439 VkPipelineShaderStageCreateInfo downStages[] = {
440 m_bloomDownsampleShader->GetVertexInfo(),
441 m_bloomDownsampleShader->GetFragInfo()
442 };
443 gpi.pStages = downStages;
444 if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &gpi, nullptr, &m_bloomDownsamplePipeline) != VK_SUCCESS) return false;
445
446 // ---- Upsample pipeline (additive blend, LOAD render pass) ----
447 VkPipelineShaderStageCreateInfo upStages[] = {
448 m_bloomUpsampleShader->GetVertexInfo(),
449 m_bloomUpsampleShader->GetFragInfo()
450 };
451 gpi.pStages = upStages;
452 gpi.pColorBlendState = &cbAdd;
453 gpi.renderPass = m_bloomAddRenderPass;
454 if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &gpi, nullptr, &m_bloomUpsamplePipeline) != VK_SUCCESS) return false;
455
456 // ---- Composite pipeline ----
457 VkPipelineShaderStageCreateInfo compStages[] = {
458 m_bloomCompositeShader->GetVertexInfo(),
459 m_bloomCompositeShader->GetFragInfo()
460 };
461 gpi.pStages = compStages;
462 gpi.pColorBlendState = &cbOpaque;
463 gpi.layout = m_bloomCompositePipelineLayout;
464 gpi.renderPass = m_bloomCompositeRenderPass;
465 if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &gpi, nullptr, &m_bloomCompositePipeline) != VK_SUCCESS) return false;
466
467 return true;
468}
469
470/// Creates the HDR scene, bloom mip chain, passes, descriptors, pipelines, and TAA resources.
471bool VulkanRenderer::CreateBloomResources() {
472 if (m_bloomResourcesCreated) return true;
473
474 if (!CreateHDRSceneResources()) { SLEAK_ERROR("Bloom: HDR scene resources failed"); return false; }
475 if (!CreateBloomImages()) { SLEAK_ERROR("Bloom: images failed"); return false; }
476 if (!CreateBloomRenderPasses()) { SLEAK_ERROR("Bloom: render passes failed"); return false; }
477 if (!CreateBloomFramebuffers()) { SLEAK_ERROR("Bloom: framebuffers failed"); return false; }
478 if (!CreateBloomDescriptorResources()) { SLEAK_ERROR("Bloom: descriptor resources failed"); return false; }
479 if (!CreateBloomPipelines()) { SLEAK_ERROR("Bloom: pipelines failed"); return false; }
480 if (!CreateTAAResources()) { SLEAK_ERROR("Bloom: TAA resources failed"); return false; }
481
482 m_bloomResourcesCreated = true;
483 SLEAK_INFO("Bloom + HDR scene resources created ({}x{}, {} mips)",
484 scExtent.width, scExtent.height, BLOOM_MIP_COUNT);
485 return true;
486}
487
488/// Destroys all bloom and HDR scene resources, then cleans up TAA resources.
489void VulkanRenderer::CleanupBloomResources() {
490 if (!m_bloomResourcesCreated) return;
491
492 if (m_bloomThresholdPipeline) { vkDestroyPipeline(device, m_bloomThresholdPipeline, nullptr); m_bloomThresholdPipeline = VK_NULL_HANDLE; }
493 if (m_bloomDownsamplePipeline) { vkDestroyPipeline(device, m_bloomDownsamplePipeline, nullptr); m_bloomDownsamplePipeline = VK_NULL_HANDLE; }
494 if (m_bloomUpsamplePipeline) { vkDestroyPipeline(device, m_bloomUpsamplePipeline, nullptr); m_bloomUpsamplePipeline = VK_NULL_HANDLE; }
495 if (m_bloomCompositePipeline) { vkDestroyPipeline(device, m_bloomCompositePipeline, nullptr); m_bloomCompositePipeline = VK_NULL_HANDLE; }
496
497 if (m_bloomFilterPipelineLayout) { vkDestroyPipelineLayout(device, m_bloomFilterPipelineLayout, nullptr); m_bloomFilterPipelineLayout = VK_NULL_HANDLE; }
498 if (m_bloomCompositePipelineLayout) { vkDestroyPipelineLayout(device, m_bloomCompositePipelineLayout, nullptr); m_bloomCompositePipelineLayout = VK_NULL_HANDLE; }
499
500 delete m_bloomThresholdShader; m_bloomThresholdShader = nullptr;
501 delete m_bloomDownsampleShader; m_bloomDownsampleShader = nullptr;
502 delete m_bloomUpsampleShader; m_bloomUpsampleShader = nullptr;
503 delete m_bloomCompositeShader; m_bloomCompositeShader = nullptr;
504
505 if (m_bloomDescriptorPool) { vkDestroyDescriptorPool(device, m_bloomDescriptorPool, nullptr); m_bloomDescriptorPool = VK_NULL_HANDLE; }
506 if (m_bloomFilterDSL) { vkDestroyDescriptorSetLayout(device, m_bloomFilterDSL, nullptr); m_bloomFilterDSL = VK_NULL_HANDLE; }
507 if (m_bloomCompositeDSL) { vkDestroyDescriptorSetLayout(device, m_bloomCompositeDSL, nullptr); m_bloomCompositeDSL = VK_NULL_HANDLE; }
508
509 for (auto& fb : m_bloomCompositeFramebuffers) {
510 if (fb) vkDestroyFramebuffer(device, fb, nullptr);
511 }
512 m_bloomCompositeFramebuffers.clear();
513
514 for (uint32_t m = 0; m < BLOOM_MIP_COUNT; ++m) {
515 if (m_bloomMipFramebuffers[m]) { vkDestroyFramebuffer(device, m_bloomMipFramebuffers[m], nullptr); m_bloomMipFramebuffers[m] = VK_NULL_HANDLE; }
516 if (m_bloomMipViews[m]) { vkDestroyImageView(device, m_bloomMipViews[m], nullptr); m_bloomMipViews[m] = VK_NULL_HANDLE; }
517 }
518
519 if (m_bloomRenderPass) { vkDestroyRenderPass(device, m_bloomRenderPass, nullptr); m_bloomRenderPass = VK_NULL_HANDLE; }
520 if (m_bloomAddRenderPass) { vkDestroyRenderPass(device, m_bloomAddRenderPass, nullptr); m_bloomAddRenderPass = VK_NULL_HANDLE; }
521 if (m_bloomCompositeRenderPass) { vkDestroyRenderPass(device, m_bloomCompositeRenderPass, nullptr); m_bloomCompositeRenderPass = VK_NULL_HANDLE; }
522
523 if (m_bloomImage) { vkDestroyImage(device, m_bloomImage, nullptr); m_bloomImage = VK_NULL_HANDLE; }
524 if (m_bloomMemory) { vkFreeMemory(device, m_bloomMemory, nullptr); m_bloomMemory = VK_NULL_HANDLE; }
525
526 if (m_hdrSceneView) { vkDestroyImageView(device, m_hdrSceneView, nullptr); m_hdrSceneView = VK_NULL_HANDLE; }
527 if (m_hdrSceneImage) { vkDestroyImage(device, m_hdrSceneImage, nullptr); m_hdrSceneImage = VK_NULL_HANDLE; }
528 if (m_hdrSceneMemory) { vkFreeMemory(device, m_hdrSceneMemory, nullptr); m_hdrSceneMemory = VK_NULL_HANDLE; }
529
530 if (m_bloomSampler) { vkDestroySampler(device, m_bloomSampler, nullptr); m_bloomSampler = VK_NULL_HANDLE; }
531
532 CleanupTAAResources();
533 m_bloomResourcesCreated = false;
534}
535
536/// Writes a filter-DSL descriptor set for a given source view.
537static void WriteSingleImageSampler(VkDevice dev, VkDescriptorSet set,
538 VkImageView view, VkSampler sampler,
539 VkImageLayout layout) {
540 VkDescriptorImageInfo ii{};
541 ii.imageLayout = layout;
542 ii.imageView = view;
543 ii.sampler = sampler;
544
545 VkWriteDescriptorSet w{};
546 w.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
547 w.dstSet = set;
548 w.dstBinding = 0;
549 w.dstArrayElement = 0;
550 w.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
551 w.descriptorCount = 1;
552 w.pImageInfo = &ii;
553 vkUpdateDescriptorSets(dev, 1, &w, 0, nullptr);
554}
555
556/// Runs the threshold, downsample, and additive-upsample bloom mip chain, or clears mip 0 when bloom is disabled.
557void VulkanRenderer::RenderBloomPass() {
558 if (!m_bloomResourcesCreated) return;
559
560 // Bloom disabled: skip the expensive 6-mip threshold/downsample/upsample
561 // chain. The composite still samples bloomMip[0] (binding 1), so leave it in
562 // a defined SHADER_READ_ONLY state by clearing only that one mip to black.
563 // Composite also pushes bloomStrength=0 so even a stale mip adds nothing.
564 // Mirrors the SSAO/SSR disabled-fallback pattern.
565 if (!m_bloomEnabled) {
566 // Already primed to black SHADER_READ_ONLY — composite also pushes
567 // bloomStrength=0, so skip the redundant per-frame clear. Re-prime only
568 // if the enabled path dirtied it.
569 if (m_bloomFallbackPrimed) return;
570 VkImageMemoryBarrier toClear{};
571 toClear.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER;
572 toClear.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
573 toClear.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
574 toClear.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
575 toClear.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
576 toClear.srcAccessMask = 0;
577 toClear.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
578 toClear.image = m_bloomImage;
579 toClear.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
580 vkCmdPipelineBarrier(command,
581 VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
582 0, 0, nullptr, 0, nullptr, 1, &toClear);
583
584 VkClearColorValue black{};
585 VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
586 vkCmdClearColorImage(command, m_bloomImage,
587 VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &black, 1, &range);
588
589 VkImageMemoryBarrier toRead = toClear;
590 toRead.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
591 toRead.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
592 toRead.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT;
593 toRead.dstAccessMask = VK_ACCESS_SHADER_READ_BIT;
594 vkCmdPipelineBarrier(command,
595 VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT,
596 0, 0, nullptr, 0, nullptr, 1, &toRead);
597 m_bloomFallbackPrimed = true;
598 return;
599 }
600
601 // Enabled path dirties bloom mip0; force a re-prime if bloom is later
602 // disabled (composite's bloomStrength=0 already neutralizes stale content,
603 // but keep the buffer well-defined for consistency).
604 m_bloomFallbackPrimed = false;
605
606 // Per-frame transition set index layout:
607 // [0] : threshold (sceneHDR -> bloomMip0)
608 // [1..MIP-1] : downsamples (bloomMip[i-1] -> bloomMip[i])
609 // [MIP..MIP*2-2] : upsamples (bloomMip[MIP-1-k] -> bloomMip[MIP-2-k])
610 // BLOOM_TRANSITION_COUNT = 1 + (MIP-1) + (MIP-1) = 11 for MIP=6.
611 auto& setArray = m_bloomFilterSets[currentFrame];
612
613 // ---------- 1. Threshold / prefilter pass ----------
614 // Input: sceneHDR (already SHADER_READ_ONLY_OPTIMAL via forward RP finalLayout)
615 // Output: bloomMip[0]
616 WriteSingleImageSampler(device, setArray[0], m_hdrSceneView, m_bloomSampler,
617 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
618
619 {
620 VkRenderPassBeginInfo rp{};
621 rp.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
622 rp.renderPass = m_bloomRenderPass;
623 rp.framebuffer = m_bloomMipFramebuffers[0];
624 rp.renderArea.offset = {0, 0};
625 rp.renderArea.extent = m_bloomMipExtents[0];
626 rp.clearValueCount = 0;
627 vkCmdBeginRenderPass(command, &rp, VK_SUBPASS_CONTENTS_INLINE);
628 FillFullscreenViewportScissor(command, m_bloomMipExtents[0]);
629 vkCmdBindPipeline(command, VK_PIPELINE_BIND_POINT_GRAPHICS, m_bloomThresholdPipeline);
630 vkCmdBindDescriptorSets(command, VK_PIPELINE_BIND_POINT_GRAPHICS,
631 m_bloomFilterPipelineLayout, 0, 1, &setArray[0], 0, nullptr);
632 struct { float threshold, knee, p0, p1; } thresh{ 1.0f, 0.5f, 0.0f, 0.0f };
633 vkCmdPushConstants(command, m_bloomFilterPipelineLayout,
634 VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(thresh), &thresh);
635 vkCmdDraw(command, 3, 1, 0, 0);
636 vkCmdEndRenderPass(command);
637 }
638
639 // ---------- 2. Downsample chain ----------
640 for (uint32_t m = 1; m < BLOOM_MIP_COUNT; ++m) {
641 uint32_t setIdx = m; // [1..MIP-1]
642 WriteSingleImageSampler(device, setArray[setIdx], m_bloomMipViews[m - 1], m_bloomSampler,
643 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
644
645 VkRenderPassBeginInfo rp{};
646 rp.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
647 rp.renderPass = m_bloomRenderPass;
648 rp.framebuffer = m_bloomMipFramebuffers[m];
649 rp.renderArea.offset = {0, 0};
650 rp.renderArea.extent = m_bloomMipExtents[m];
651 rp.clearValueCount = 0;
652 vkCmdBeginRenderPass(command, &rp, VK_SUBPASS_CONTENTS_INLINE);
653 FillFullscreenViewportScissor(command, m_bloomMipExtents[m]);
654 vkCmdBindPipeline(command, VK_PIPELINE_BIND_POINT_GRAPHICS, m_bloomDownsamplePipeline);
655 vkCmdBindDescriptorSets(command, VK_PIPELINE_BIND_POINT_GRAPHICS,
656 m_bloomFilterPipelineLayout, 0, 1, &setArray[setIdx], 0, nullptr);
657
658 struct { float tsx, tsy, karis, p; } pc;
659 pc.tsx = 1.0f / float(m_bloomMipExtents[m - 1].width);
660 pc.tsy = 1.0f / float(m_bloomMipExtents[m - 1].height);
661 pc.karis = (m == 1) ? 1.0f : 0.0f; // Karis only on first downsample (firefly kill).
662 pc.p = 0.0f;
663 vkCmdPushConstants(command, m_bloomFilterPipelineLayout,
664 VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(pc), &pc);
665 vkCmdDraw(command, 3, 1, 0, 0);
666 vkCmdEndRenderPass(command);
667 }
668
669 // ---------- 3. Upsample chain (additive blend onto next-larger mip) ----------
670 for (uint32_t m = BLOOM_MIP_COUNT - 1; m > 0; --m) {
671 uint32_t setIdx = BLOOM_MIP_COUNT + (BLOOM_MIP_COUNT - 1 - m); // [MIP..MIP*2-2]
672 WriteSingleImageSampler(device, setArray[setIdx], m_bloomMipViews[m], m_bloomSampler,
673 VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
674
675 VkRenderPassBeginInfo rp{};
676 rp.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
677 rp.renderPass = m_bloomAddRenderPass;
678 rp.framebuffer = m_bloomMipFramebuffers[m - 1];
679 rp.renderArea.offset = {0, 0};
680 rp.renderArea.extent = m_bloomMipExtents[m - 1];
681 rp.clearValueCount = 0;
682 vkCmdBeginRenderPass(command, &rp, VK_SUBPASS_CONTENTS_INLINE);
683 FillFullscreenViewportScissor(command, m_bloomMipExtents[m - 1]);
684 vkCmdBindPipeline(command, VK_PIPELINE_BIND_POINT_GRAPHICS, m_bloomUpsamplePipeline);
685 vkCmdBindDescriptorSets(command, VK_PIPELINE_BIND_POINT_GRAPHICS,
686 m_bloomFilterPipelineLayout, 0, 1, &setArray[setIdx], 0, nullptr);
687
688 struct { float tsx, tsy, radius, intensity; } pc;
689 pc.tsx = 1.0f / float(m_bloomMipExtents[m].width);
690 pc.tsy = 1.0f / float(m_bloomMipExtents[m].height);
691 pc.radius = 1.0f;
692 pc.intensity = 1.0f;
693 vkCmdPushConstants(command, m_bloomFilterPipelineLayout,
694 VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(pc), &pc);
695 vkCmdDraw(command, 3, 1, 0, 0);
696 vkCmdEndRenderPass(command);
697 }
698}
699
700/// Tonemaps and composites the HDR scene, bloom, and SSR into the swapchain image, then draws ImGui.
701void VulkanRenderer::RenderBloomCompositePass() {
702 if (!m_bloomResourcesCreated) return;
703
704 // Bind sceneHDR (binding 0) + bloomMip[0] (binding 1) + SSR (binding 2).
705 // When SSR is disabled/unavailable, fall back to the 1x1 default texture
706 // which the shader reads as rgb=0 (multiplied by premultiplied alpha it
707 // adds nothing) — safe to leave always bound.
708 VkDescriptorSet compSet = m_bloomCompositeSets[currentFrame];
709 std::array<VkDescriptorImageInfo, 3> infos{};
710 infos[0].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
711 infos[0].imageView = m_hdrSceneView;
712 infos[0].sampler = m_bloomSampler;
713
714 infos[1].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
715 infos[1].imageView = m_bloomMipViews[0];
716 infos[1].sampler = m_bloomSampler;
717
718 // RenderSSRPass() guarantees m_ssrImage ends in SHADER_READ_ONLY_OPTIMAL
719 // (either via pipeline output or a cleared-to-black fallback when SSR
720 // is disabled). Fall back to the bloom mip view only when SSR resources
721 // haven't been created yet (should never happen once init completes).
722 infos[2].imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
723 if (m_ssrResourcesCreated && m_ssrView != VK_NULL_HANDLE) {
724 infos[2].imageView = m_ssrView;
725 infos[2].sampler = m_ssrSampler ? m_ssrSampler : m_bloomSampler;
726 } else {
727 infos[2].imageView = m_bloomMipViews[0];
728 infos[2].sampler = m_bloomSampler;
729 }
730
731 std::array<VkWriteDescriptorSet, 3> writes{};
732 for (uint32_t i = 0; i < writes.size(); ++i) {
733 writes[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
734 writes[i].dstSet = compSet;
735 writes[i].dstBinding = i;
736 writes[i].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
737 writes[i].descriptorCount = 1;
738 writes[i].pImageInfo = &infos[i];
739 }
740 vkUpdateDescriptorSets(device, static_cast<uint32_t>(writes.size()),
741 writes.data(), 0, nullptr);
742
743 VkRenderPassBeginInfo rp{};
744 rp.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
745 rp.renderPass = m_bloomCompositeRenderPass;
746 rp.framebuffer = m_bloomCompositeFramebuffers[CurrentFrameIndex];
747 rp.renderArea.offset = {0, 0};
748 rp.renderArea.extent = scExtent;
749 rp.clearValueCount = 0;
750
751 vkCmdBeginRenderPass(command, &rp, VK_SUBPASS_CONTENTS_INLINE);
752 FillFullscreenViewportScissor(command, scExtent);
753 vkCmdBindPipeline(command, VK_PIPELINE_BIND_POINT_GRAPHICS, m_bloomCompositePipeline);
754 vkCmdBindDescriptorSets(command, VK_PIPELINE_BIND_POINT_GRAPHICS,
755 m_bloomCompositePipelineLayout, 0, 1, &compSet, 0, nullptr);
756
757 struct { float bloomStrength, exposure, p0, p1; } pc;
758 // bloom disabled -> 0 so the (black) mip contributes nothing
759 pc.bloomStrength = m_bloomEnabled ? 0.06f : 0.0f; // UE4-style soft bloom
760 pc.exposure = m_exposure;
761 pc.p0 = 0.0f;
762 pc.p1 = 0.0f;
763 vkCmdPushConstants(command, m_bloomCompositePipelineLayout,
764 VK_SHADER_STAGE_FRAGMENT_BIT, 0, sizeof(pc), &pc);
765 vkCmdDraw(command, 3, 1, 0, 0);
766
767 // ImGui draws inside the composite pass, AFTER the HDR → LDR tonemap.
768 if (bImFrameActive) {
769 ImGui::Render();
770 ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), command);
771 }
772
773 vkCmdEndRenderPass(command);
774}
775
776} // namespace RenderEngine
777} // namespace Sleak
int width
int height
#define SLEAK_ERROR(...)
Definition Logger.hpp:22
#define SLEAK_INFO(...)
Definition Logger.hpp:20
Backend-facing rendering layer shared by the four graphics backends.
static void WriteSingleImageSampler(VkDevice dev, VkDescriptorSet set, VkImageView view, VkSampler sampler, VkImageLayout layout)
Writes a filter-DSL descriptor set for a given source view.
Root namespace for everything the engine exposes.
Definition Camera.hpp:10