Files
fs2open.github.com/code/graphics/vulkan/VulkanPostProcessingBloom.cpp

419 lines
13 KiB
C++

#include "VulkanPostProcessing.h"
#include <array>
#include "gr_vulkan.h"
#include "VulkanBarrier.h"
#include "VulkanRenderer.h"
#include "VulkanDescriptorManager.h"
#include "graphics/util/uniform_structs.h"
#include "graphics/post_processing.h"
#include "graphics/grinternal.h"
#include "graphics/2d.h"
namespace graphics::vulkan {
// ===== Bloom Pipeline Implementation =====
// Local UBO struct for blur shader (matches OpenGL's blur_data layout).
struct BlurUBOData {
float texSize;
int level;
int pad[2];
};
bool VulkanBloom::init(PostProcessContext& ctx, const RenderTarget& sceneColor)
{
m_ctx = &ctx;
m_sceneColor = &sceneColor;
// Create bloom render pass (color-only RGBA16F, loadOp=eDontCare for overwriting)
{
vk::AttachmentDescription att;
att.format = HDR_COLOR_FORMAT;
att.samples = vk::SampleCountFlagBits::e1;
att.loadOp = vk::AttachmentLoadOp::eDontCare;
att.storeOp = vk::AttachmentStoreOp::eStore;
att.stencilLoadOp = vk::AttachmentLoadOp::eDontCare;
att.stencilStoreOp = vk::AttachmentStoreOp::eDontCare;
att.initialLayout = vk::ImageLayout::eUndefined;
att.finalLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
vk::AttachmentReference colorRef;
colorRef.attachment = 0;
colorRef.layout = vk::ImageLayout::eColorAttachmentOptimal;
vk::SubpassDescription subpass;
subpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorRef;
vk::SubpassDependency dep;
dep.srcSubpass = VK_SUBPASS_EXTERNAL;
dep.dstSubpass = 0;
dep.srcStageMask = vk::PipelineStageFlagBits::eFragmentShader
| vk::PipelineStageFlagBits::eColorAttachmentOutput;
dep.dstStageMask = vk::PipelineStageFlagBits::eFragmentShader
| vk::PipelineStageFlagBits::eColorAttachmentOutput;
dep.srcAccessMask = vk::AccessFlagBits::eShaderRead
| vk::AccessFlagBits::eColorAttachmentWrite;
dep.dstAccessMask = vk::AccessFlagBits::eShaderRead
| vk::AccessFlagBits::eColorAttachmentWrite;
vk::RenderPassCreateInfo rpInfo;
rpInfo.attachmentCount = 1;
rpInfo.pAttachments = &att;
rpInfo.subpassCount = 1;
rpInfo.pSubpasses = &subpass;
rpInfo.dependencyCount = 1;
rpInfo.pDependencies = &dep;
try {
m_renderPass = m_ctx->device.createRenderPass(rpInfo);
} catch (const vk::SystemError& e) {
nprintf(("vulkan", "VulkanBloom: Failed to create bloom render pass: %s\n", e.what()));
return false;
}
}
// Create bloom composite render pass (loadOp=eLoad for additive compositing onto scene color)
{
vk::AttachmentDescription att;
att.format = HDR_COLOR_FORMAT;
att.samples = vk::SampleCountFlagBits::e1;
att.loadOp = vk::AttachmentLoadOp::eLoad;
att.storeOp = vk::AttachmentStoreOp::eStore;
att.stencilLoadOp = vk::AttachmentLoadOp::eDontCare;
att.stencilStoreOp = vk::AttachmentStoreOp::eDontCare;
att.initialLayout = vk::ImageLayout::eColorAttachmentOptimal;
att.finalLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
vk::AttachmentReference colorRef;
colorRef.attachment = 0;
colorRef.layout = vk::ImageLayout::eColorAttachmentOptimal;
vk::SubpassDescription subpass;
subpass.pipelineBindPoint = vk::PipelineBindPoint::eGraphics;
subpass.colorAttachmentCount = 1;
subpass.pColorAttachments = &colorRef;
vk::SubpassDependency dep;
dep.srcSubpass = VK_SUBPASS_EXTERNAL;
dep.dstSubpass = 0;
dep.srcStageMask = vk::PipelineStageFlagBits::eFragmentShader
| vk::PipelineStageFlagBits::eColorAttachmentOutput;
dep.dstStageMask = vk::PipelineStageFlagBits::eFragmentShader
| vk::PipelineStageFlagBits::eColorAttachmentOutput;
dep.srcAccessMask = vk::AccessFlagBits::eShaderRead
| vk::AccessFlagBits::eColorAttachmentWrite;
dep.dstAccessMask = vk::AccessFlagBits::eColorAttachmentRead
| vk::AccessFlagBits::eColorAttachmentWrite;
vk::RenderPassCreateInfo rpInfo;
rpInfo.attachmentCount = 1;
rpInfo.pAttachments = &att;
rpInfo.subpassCount = 1;
rpInfo.pSubpasses = &subpass;
rpInfo.dependencyCount = 1;
rpInfo.pDependencies = &dep;
try {
m_compositeRenderPass = m_ctx->device.createRenderPass(rpInfo);
} catch (const vk::SystemError& e) {
nprintf(("vulkan", "VulkanBloom: Failed to create bloom composite render pass: %s\n", e.what()));
return false;
}
}
if (!createTargets()) {
return false;
}
m_initialized = true;
nprintf(("vulkan", "VulkanBloom: Bloom initialized (%ux%u, %d mip levels)\n",
m_width, m_height, MAX_MIP_BLUR_LEVELS));
return true;
}
bool VulkanBloom::createTargets()
{
m_width = m_ctx->sceneExtent.width / 2;
m_height = m_ctx->sceneExtent.height / 2;
const uint32_t mipLevels = MAX_MIP_BLUR_LEVELS;
// Create 2 bloom textures (RGBA16F, half-res, 4 mip levels each)
for (size_t i = 0; i < m_tex.size(); i++) {
vk::ImageCreateInfo imageInfo;
imageInfo.imageType = vk::ImageType::e2D;
imageInfo.format = HDR_COLOR_FORMAT;
imageInfo.extent.width = m_width;
imageInfo.extent.height = m_height;
imageInfo.extent.depth = 1;
imageInfo.mipLevels = mipLevels;
imageInfo.arrayLayers = 1;
imageInfo.samples = vk::SampleCountFlagBits::e1;
imageInfo.tiling = vk::ImageTiling::eOptimal;
imageInfo.usage = vk::ImageUsageFlagBits::eColorAttachment
| vk::ImageUsageFlagBits::eSampled
| vk::ImageUsageFlagBits::eTransferSrc
| vk::ImageUsageFlagBits::eTransferDst;
imageInfo.sharingMode = vk::SharingMode::eExclusive;
imageInfo.initialLayout = vk::ImageLayout::eUndefined;
try {
m_tex[i].image = m_ctx->device.createImage(imageInfo);
} catch (const vk::SystemError& e) {
nprintf(("vulkan", "VulkanBloom: Failed to create bloom image %zu: %s\n", i, e.what()));
return false;
}
if (!m_ctx->memoryManager->allocateImageMemory(
m_tex[i].image, MemoryUsage::GpuOnly, m_tex[i].allocation, MemoryPurpose::RenderTarget)) {
nprintf(("vulkan", "VulkanBloom: Failed to allocate bloom image %zu memory!\n", i));
return false;
}
// Full image view (all mip levels, for textureLod sampling)
vk::ImageViewCreateInfo fullViewInfo;
fullViewInfo.image = m_tex[i].image;
fullViewInfo.viewType = vk::ImageViewType::e2D;
fullViewInfo.format = HDR_COLOR_FORMAT;
fullViewInfo.subresourceRange.aspectMask = vk::ImageAspectFlagBits::eColor;
fullViewInfo.subresourceRange.baseMipLevel = 0;
fullViewInfo.subresourceRange.levelCount = mipLevels;
fullViewInfo.subresourceRange.baseArrayLayer = 0;
fullViewInfo.subresourceRange.layerCount = 1;
try {
m_tex[i].fullView = m_ctx->device.createImageView(fullViewInfo);
} catch (const vk::SystemError& e) {
nprintf(("vulkan", "VulkanBloom: Failed to create bloom %zu full view: %s\n", i, e.what()));
return false;
}
// Per-mip image views (for framebuffer attachment)
for (uint32_t mip = 0; mip < mipLevels; mip++) {
vk::ImageViewCreateInfo mipViewInfo = fullViewInfo;
mipViewInfo.subresourceRange.baseMipLevel = mip;
mipViewInfo.subresourceRange.levelCount = 1;
try {
m_tex[i].mipViews[mip] = m_ctx->device.createImageView(mipViewInfo);
} catch (const vk::SystemError& e) {
nprintf(("vulkan", "VulkanBloom: Failed to create bloom %zu mip %u view: %s\n", i, mip, e.what()));
return false;
}
}
}
// Create per-mip framebuffers for bloom textures
for (size_t i = 0; i < m_tex.size(); i++) {
for (uint32_t mip = 0; mip < mipLevels; mip++) {
uint32_t mipW = std::max(1u, m_width >> mip);
uint32_t mipH = std::max(1u, m_height >> mip);
vk::FramebufferCreateInfo fbInfo;
fbInfo.renderPass = m_renderPass;
fbInfo.attachmentCount = 1;
fbInfo.pAttachments = &m_tex[i].mipViews[mip];
fbInfo.width = mipW;
fbInfo.height = mipH;
fbInfo.layers = 1;
try {
m_tex[i].mipFramebuffers[mip] = m_ctx->device.createFramebuffer(fbInfo);
} catch (const vk::SystemError& e) {
nprintf(("vulkan", "VulkanBloom: Failed to create bloom %zu mip %u framebuffer: %s\n", i, mip, e.what()));
return false;
}
}
}
// Create scene color framebuffer for bloom composite (wraps the scene color as attachment)
{
vk::FramebufferCreateInfo fbInfo;
fbInfo.renderPass = m_compositeRenderPass;
fbInfo.attachmentCount = 1;
fbInfo.pAttachments = &m_sceneColor->view;
fbInfo.width = m_ctx->sceneExtent.width;
fbInfo.height = m_ctx->sceneExtent.height;
fbInfo.layers = 1;
try {
m_sceneColorFB = m_ctx->device.createFramebuffer(fbInfo);
} catch (const vk::SystemError& e) {
nprintf(("vulkan", "VulkanBloom: Failed to create scene color bloom framebuffer: %s\n", e.what()));
return false;
}
}
return true;
}
void VulkanBloom::destroyTargets()
{
if (m_sceneColorFB) {
m_ctx->device.destroyFramebuffer(m_sceneColorFB);
m_sceneColorFB = nullptr;
}
for (auto& bt : m_tex) {
for (size_t mip = 0; mip < bt.mipFramebuffers.size(); mip++) {
if (bt.mipFramebuffers[mip]) {
m_ctx->device.destroyFramebuffer(bt.mipFramebuffers[mip]);
bt.mipFramebuffers[mip] = nullptr;
}
if (bt.mipViews[mip]) {
m_ctx->device.destroyImageView(bt.mipViews[mip]);
bt.mipViews[mip] = nullptr;
}
}
if (bt.fullView) {
m_ctx->device.destroyImageView(bt.fullView);
bt.fullView = nullptr;
}
if (bt.image) {
m_ctx->device.destroyImage(bt.image);
bt.image = nullptr;
}
if (bt.allocation.isValid()) {
m_ctx->memoryManager->freeAllocation(bt.allocation);
bt.allocation = {};
}
}
}
bool VulkanBloom::resize()
{
if (!m_initialized) {
return true;
}
destroyTargets();
return createTargets();
}
void VulkanBloom::shutdown()
{
if (!m_initialized) {
return;
}
destroyTargets();
if (m_compositeRenderPass) {
m_ctx->device.destroyRenderPass(m_compositeRenderPass);
m_compositeRenderPass = nullptr;
}
if (m_renderPass) {
m_ctx->device.destroyRenderPass(m_renderPass);
m_renderPass = nullptr;
}
m_initialized = false;
}
void VulkanBloom::execute(vk::CommandBuffer cmd)
{
if (!m_initialized || gr_bloom_intensity() <= 0) {
return;
}
GR_DEBUG_SCOPE("Bloom");
// Per-draw UBO slots come from the persistently-mapped scratch ring
// (cursor reset once per frame in VulkanPostProcessor::beginFrame()).
// 1. Bright pass: extract pixels brighter than 1.0 from scene color → bloom_tex[0] mip 0
{
GR_DEBUG_SCOPE("Bloom bright pass");
m_ctx->drawFullscreenTriangle(cmd, m_renderPass,
m_tex[0].mipFramebuffers[0],
vk::Extent2D(m_width, m_height),
SDR_TYPE_POST_PROCESS_BRIGHTPASS,
m_sceneColor->view, m_ctx->linearSampler,
nullptr, 0, // Brightpass has no UBO
ALPHA_BLEND_NONE);
}
// 2. Generate mipmaps for bloom_tex[0] (fill mips 1-3 from mip 0)
PostProcessContext::generateMipmaps(cmd, m_tex[0].image, m_width, m_height, MAX_MIP_BLUR_LEVELS);
// 3. Blur iterations (2 iterations of vertical + horizontal ping-pong)
for (int iteration = 0; iteration < 2; iteration++) {
for (int pass = 0; pass < 2; pass++) {
// pass 0 = vertical (tex[0] → tex[1]), pass 1 = horizontal (tex[1] → tex[0])
GR_DEBUG_SCOPE("Bloom iteration step");
int srcIdx = pass;
int dstIdx = 1 - pass;
bool isVertical = (pass == 0);
unsigned int blurFlags = isVertical ? SDR_FLAG_BLUR_VERTICAL : SDR_FLAG_BLUR_HORIZONTAL;
for (int mip = 0; mip < MAX_MIP_BLUR_LEVELS; mip++) {
uint32_t mipW = std::max(1u, m_width >> mip);
uint32_t mipH = std::max(1u, m_height >> mip);
BlurUBOData blurData;
blurData.texSize = isVertical ? 1.0f / static_cast<float>(mipH)
: 1.0f / static_cast<float>(mipW);
blurData.level = mip;
blurData.pad[0] = 0;
blurData.pad[1] = 0;
m_ctx->drawFullscreenTriangle(cmd, m_renderPass,
m_tex[dstIdx].mipFramebuffers[mip],
vk::Extent2D(mipW, mipH),
SDR_TYPE_POST_PROCESS_BLUR,
m_tex[srcIdx].fullView, m_ctx->mipmapSampler,
&blurData, sizeof(blurData),
ALPHA_BLEND_NONE,
blurFlags);
}
}
}
// 4. Transition scene color for bloom composite (eShaderReadOnlyOptimal → eColorAttachmentOptimal)
{
ImageBarrier2 barrier;
barrier.image = m_sceneColor->image;
barrier.levelCount = 1;
barrier.layerCount = 1;
barrier.oldLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
barrier.newLayout = vk::ImageLayout::eColorAttachmentOptimal;
barrier.srcStage = vk::PipelineStageFlagBits2::eFragmentShader;
barrier.srcAccess = vk::AccessFlagBits2::eShaderSampledRead;
barrier.dstStage = vk::PipelineStageFlagBits2::eColorAttachmentOutput;
barrier.dstAccess = vk::AccessFlagBits2::eColorAttachmentRead
| vk::AccessFlagBits2::eColorAttachmentWrite;
cmdImageBarrier(cmd, barrier);
}
// 5. Bloom composite: additively blend blurred bloom onto scene color
{
GR_DEBUG_SCOPE("Bloom composite step");
graphics::generic_data::bloom_composition_data compData;
compData.bloom_intensity = gr_bloom_intensity() / 100.0f;
compData.levels = MAX_MIP_BLUR_LEVELS;
compData.pad[0] = 0.0f;
compData.pad[1] = 0.0f;
m_ctx->drawFullscreenTriangle(cmd, m_compositeRenderPass,
m_sceneColorFB,
m_ctx->sceneExtent,
SDR_TYPE_POST_PROCESS_BLOOM_COMP,
m_tex[0].fullView, m_ctx->mipmapSampler,
&compData, sizeof(compData),
ALPHA_BLEND_ADDITIVE);
}
// Scene color is now in eShaderReadOnlyOptimal (from bloom composite render pass finalLayout)
}
} // namespace graphics::vulkan