mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 08:15:59 -10:00
The Vulkan light shaft pass reuses OpenGL's sun position formula, whose texture V grows upward. The Vulkan fullscreen triangle in post-v.sdr has V = 0 at the top of the screen, and ls-f.sdr steps from fragTexCoord toward sun_pos in that space, so the shafts radiated from a point mirrored vertically from the sun. Flip the vertical coordinate before upload. Co-authored-by: Saga port <saga-port@invalid> Co-authored-by: Claude Opus 5.5 <[email protected]>
604 lines
20 KiB
C++
604 lines
20 KiB
C++
#include "VulkanPostProcessing.h"
|
|
|
|
#include <array>
|
|
|
|
#include "VulkanRenderer.h"
|
|
#include "VulkanBarrier.h"
|
|
#include "VulkanDescriptorManager.h"
|
|
#include "graphics/util/uniform_structs.h"
|
|
#include "graphics/post_processing.h"
|
|
#include "graphics/grinternal.h"
|
|
#include "graphics/2d.h"
|
|
#include "io/timer.h"
|
|
#include "lighting/lighting_profiles.h"
|
|
#include "lighting/lighting.h"
|
|
#include "math/floating.h"
|
|
#include "math/vecmat.h"
|
|
#include "render/3d.h"
|
|
#include "nebula/neb.h"
|
|
#include "nebula/volumetrics.h"
|
|
#include "mission/missionparse.h"
|
|
|
|
extern float Sun_spot;
|
|
extern int Game_subspace_effect;
|
|
extern SCP_vector<light> Lights;
|
|
extern int Num_lights;
|
|
|
|
|
|
namespace graphics::vulkan {
|
|
|
|
|
|
// ===== LDR Targets + FXAA Pipeline Implementation =====
|
|
|
|
bool VulkanLDR::init(PostProcessContext& ctx, const RenderTarget& sceneColor, const RenderTarget& sceneDepth,
|
|
const VulkanBloom& bloom)
|
|
{
|
|
m_ctx = &ctx;
|
|
m_sceneColor = &sceneColor;
|
|
m_sceneDepth = &sceneDepth;
|
|
m_bloom = &bloom;
|
|
|
|
const vk::Format ldrFormat = m_ctx->ldrFormat;
|
|
|
|
// Create LDR render pass (color-only, loadOp=eDontCare, finalLayout=eShaderReadOnlyOptimal)
|
|
{
|
|
vk::AttachmentDescription att;
|
|
att.format = ldrFormat;
|
|
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::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_ldrRenderPass = m_ctx->device.createRenderPass(rpInfo);
|
|
} catch (const vk::SystemError& e) {
|
|
nprintf(("vulkan", "VulkanLDR: Failed to create LDR render pass: %s\n", e.what()));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Create LDR load render pass (loadOp=eLoad for additive blending onto existing content)
|
|
{
|
|
vk::AttachmentDescription att;
|
|
att.format = ldrFormat;
|
|
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_ldrLoadRenderPass = m_ctx->device.createRenderPass(rpInfo);
|
|
} catch (const vk::SystemError& e) {
|
|
nprintf(("vulkan", "VulkanLDR: Failed to create LDR load render pass: %s\n", e.what()));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!createTargets()) {
|
|
return false;
|
|
}
|
|
|
|
m_initialized = true;
|
|
nprintf(("vulkan", "VulkanLDR: LDR targets initialized (%ux%u, RGBA8)\n",
|
|
m_ctx->sceneExtent.width, m_ctx->sceneExtent.height));
|
|
return true;
|
|
}
|
|
|
|
bool VulkanLDR::createTargets()
|
|
{
|
|
const vk::Format ldrFormat = m_ctx->ldrFormat;
|
|
|
|
// Create Scene_ldr (full resolution) — tonemapped display-referred output.
|
|
// fp16 in HDR (carries values above paper white), 8-bit UNORM in SDR.
|
|
if (!m_ctx->createImage(m_ctx->sceneExtent.width, m_ctx->sceneExtent.height, ldrFormat,
|
|
vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled,
|
|
vk::ImageAspectFlagBits::eColor,
|
|
m_sceneLdr.image, m_sceneLdr.view, m_sceneLdr.allocation)) {
|
|
nprintf(("vulkan", "VulkanLDR: Failed to create Scene_ldr image!\n"));
|
|
return false;
|
|
}
|
|
m_sceneLdr.format = ldrFormat;
|
|
m_sceneLdr.width = m_ctx->sceneExtent.width;
|
|
m_sceneLdr.height = m_ctx->sceneExtent.height;
|
|
|
|
// Create Scene_luminance (full resolution) — LDR with luma in alpha for FXAA
|
|
if (!m_ctx->createImage(m_ctx->sceneExtent.width, m_ctx->sceneExtent.height, ldrFormat,
|
|
vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled,
|
|
vk::ImageAspectFlagBits::eColor,
|
|
m_sceneLuminance.image, m_sceneLuminance.view, m_sceneLuminance.allocation)) {
|
|
nprintf(("vulkan", "VulkanLDR: Failed to create Scene_luminance image!\n"));
|
|
return false;
|
|
}
|
|
m_sceneLuminance.format = ldrFormat;
|
|
m_sceneLuminance.width = m_ctx->sceneExtent.width;
|
|
m_sceneLuminance.height = m_ctx->sceneExtent.height;
|
|
|
|
// Create Scene_ldr_compressed (full resolution) — tonemapped to [0,1] using the
|
|
// real tone curve, purely for FXAA/SMAA edge detection while HDR output is
|
|
// active (see executeTonemap/getAADetectionView).
|
|
if (!m_ctx->createImage(m_ctx->sceneExtent.width, m_ctx->sceneExtent.height, ldrFormat,
|
|
vk::ImageUsageFlagBits::eColorAttachment | vk::ImageUsageFlagBits::eSampled,
|
|
vk::ImageAspectFlagBits::eColor,
|
|
m_sceneLdrCompressed.image, m_sceneLdrCompressed.view, m_sceneLdrCompressed.allocation)) {
|
|
nprintf(("vulkan", "VulkanLDR: Failed to create Scene_ldr_compressed image!\n"));
|
|
return false;
|
|
}
|
|
m_sceneLdrCompressed.format = ldrFormat;
|
|
m_sceneLdrCompressed.width = m_ctx->sceneExtent.width;
|
|
m_sceneLdrCompressed.height = m_ctx->sceneExtent.height;
|
|
|
|
// Create framebuffers
|
|
{
|
|
vk::FramebufferCreateInfo fbInfo;
|
|
fbInfo.renderPass = m_ldrRenderPass;
|
|
fbInfo.attachmentCount = 1;
|
|
fbInfo.pAttachments = &m_sceneLdr.view;
|
|
fbInfo.width = m_ctx->sceneExtent.width;
|
|
fbInfo.height = m_ctx->sceneExtent.height;
|
|
fbInfo.layers = 1;
|
|
|
|
try {
|
|
m_sceneLdrFB = m_ctx->device.createFramebuffer(fbInfo);
|
|
} catch (const vk::SystemError& e) {
|
|
nprintf(("vulkan", "VulkanLDR: Failed to create Scene_ldr framebuffer: %s\n", e.what()));
|
|
return false;
|
|
}
|
|
|
|
fbInfo.pAttachments = &m_sceneLuminance.view;
|
|
try {
|
|
m_sceneLuminanceFB = m_ctx->device.createFramebuffer(fbInfo);
|
|
} catch (const vk::SystemError& e) {
|
|
nprintf(("vulkan", "VulkanLDR: Failed to create Scene_luminance framebuffer: %s\n", e.what()));
|
|
return false;
|
|
}
|
|
|
|
fbInfo.pAttachments = &m_sceneLdrCompressed.view;
|
|
try {
|
|
m_sceneLdrCompressedFB = m_ctx->device.createFramebuffer(fbInfo);
|
|
} catch (const vk::SystemError& e) {
|
|
nprintf(("vulkan", "VulkanLDR: Failed to create Scene_ldr_compressed framebuffer: %s\n", e.what()));
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void VulkanLDR::destroyTargets()
|
|
{
|
|
if (m_sceneLdrCompressedFB) {
|
|
m_ctx->device.destroyFramebuffer(m_sceneLdrCompressedFB);
|
|
m_sceneLdrCompressedFB = nullptr;
|
|
}
|
|
if (m_sceneLuminanceFB) {
|
|
m_ctx->device.destroyFramebuffer(m_sceneLuminanceFB);
|
|
m_sceneLuminanceFB = nullptr;
|
|
}
|
|
if (m_sceneLdrFB) {
|
|
m_ctx->device.destroyFramebuffer(m_sceneLdrFB);
|
|
m_sceneLdrFB = nullptr;
|
|
}
|
|
|
|
m_ctx->destroyTarget(m_sceneLdrCompressed);
|
|
m_ctx->destroyTarget(m_sceneLuminance);
|
|
m_ctx->destroyTarget(m_sceneLdr);
|
|
}
|
|
|
|
bool VulkanLDR::resize()
|
|
{
|
|
if (!m_initialized) {
|
|
return true;
|
|
}
|
|
destroyTargets();
|
|
return createTargets();
|
|
}
|
|
|
|
void VulkanLDR::shutdown()
|
|
{
|
|
if (!m_initialized) {
|
|
return;
|
|
}
|
|
|
|
destroyTargets();
|
|
|
|
if (m_ldrLoadRenderPass) {
|
|
m_ctx->device.destroyRenderPass(m_ldrLoadRenderPass);
|
|
m_ldrLoadRenderPass = nullptr;
|
|
}
|
|
if (m_ldrRenderPass) {
|
|
m_ctx->device.destroyRenderPass(m_ldrRenderPass);
|
|
m_ldrRenderPass = nullptr;
|
|
}
|
|
|
|
m_initialized = false;
|
|
}
|
|
|
|
void VulkanLDR::executeTonemap(vk::CommandBuffer cmd)
|
|
{
|
|
if (!m_initialized) {
|
|
return;
|
|
}
|
|
|
|
GR_DEBUG_SCOPE("Tonemapping");
|
|
|
|
namespace ltp = lighting_profiles;
|
|
|
|
// Build tonemapping data directly from lighting profiles
|
|
graphics::generic_data::tonemapping_data tmData;
|
|
memset(&tmData, 0, sizeof(tmData));
|
|
auto ppc = ltp::current_piecewise_intermediates();
|
|
tmData.exposure = ltp::current_exposure();
|
|
tmData.tonemapper = static_cast<int>(ltp::current_tonemapper());
|
|
tmData.x0 = ppc.x0;
|
|
tmData.y0 = ppc.y0;
|
|
tmData.x1 = ppc.x1;
|
|
tmData.toe_B = ppc.toe_B;
|
|
tmData.toe_lnA = ppc.toe_lnA;
|
|
tmData.sh_B = ppc.sh_B;
|
|
tmData.sh_lnA = ppc.sh_lnA;
|
|
tmData.sh_offsetX = ppc.sh_offsetX;
|
|
tmData.sh_offsetY = ppc.sh_offsetY;
|
|
|
|
// current_tonemapper() (used above) forces TonemapperAlgorithm::HdrScene
|
|
// whenever HDR10 output is active, so tmData.tonemapper already selects
|
|
// the right shader case; just supply the nits it needs.
|
|
if (m_ctx->hdrActive) {
|
|
tmData.hdr_paperwhite_nits = Gr_hdr_paperwhite_nits;
|
|
tmData.hdr_peak_nits = Gr_hdr_peak_nits;
|
|
}
|
|
|
|
// HDR scene → Scene_ldr via tonemapping shader
|
|
m_ctx->drawFullscreenTriangle(cmd, m_ldrRenderPass,
|
|
m_sceneLdrFB, m_ctx->sceneExtent,
|
|
SDR_TYPE_POST_PROCESS_TONEMAPPING,
|
|
m_sceneColor->view, m_ctx->linearSampler,
|
|
&tmData, sizeof(tmData),
|
|
ALPHA_BLEND_NONE);
|
|
|
|
// While HDR is active, Scene_ldr carries extended-range values (the
|
|
// HdrScene tonemapper only clamps to headroom, not [0,1]), which breaks
|
|
// the fixed [0,1] luma thresholds FXAA/SMAA edge detection rely on. So
|
|
// also derive a cheap [0,1]-bounded proxy for those passes to detect
|
|
// edges from — they still blend the real extended-range Scene_ldr for
|
|
// the actual output. This just clamps the already-tonemapped Scene_ldr,
|
|
// rather than re-running exposure+tonemap from Scene_color a second time.
|
|
if (m_ctx->hdrActive) {
|
|
m_ctx->drawFullscreenTriangle(cmd, m_ldrRenderPass,
|
|
m_sceneLdrCompressedFB, m_ctx->sceneExtent,
|
|
SDR_TYPE_COPY,
|
|
m_sceneLdr.view, m_ctx->linearSampler,
|
|
nullptr, 0,
|
|
ALPHA_BLEND_NONE,
|
|
SDR_FLAG_COPY_CLAMP01);
|
|
}
|
|
}
|
|
|
|
// Returns the buffer FXAA/SMAA edge detection should read for luma computation:
|
|
// the real Scene_ldr when it's already bounded to [0,1], or the compressed
|
|
// tonemap proxy when HDR output has left it in extended range.
|
|
vk::ImageView VulkanLDR::getAADetectionView() const
|
|
{
|
|
return m_ctx->hdrActive ? m_sceneLdrCompressed.view : m_sceneLdr.view;
|
|
}
|
|
|
|
void VulkanLDR::executeFXAA(vk::CommandBuffer cmd)
|
|
{
|
|
if (!m_initialized || !gr_is_fxaa_mode(Gr_aa_mode)) {
|
|
return;
|
|
}
|
|
|
|
GR_DEBUG_SCOPE("FXAA");
|
|
|
|
// FXAA prepass: Scene_ldr → Scene_luminance (real color passthrough + luma in
|
|
// alpha computed from the detection proxy, see getAADetectionView)
|
|
{
|
|
std::array<vk::ImageView, 2> views = {m_sceneLdr.view, getAADetectionView()};
|
|
m_ctx->drawFullscreenTriangleMulti(cmd, m_ldrRenderPass, m_sceneLuminanceFB, m_ctx->sceneExtent,
|
|
SDR_TYPE_POST_PROCESS_FXAA_PREPASS, views.data(), static_cast<uint32_t>(views.size()),
|
|
nullptr, 0);
|
|
}
|
|
|
|
// The FXAA main pass discards non-edge fragments (FXAA_DISCARD), so those
|
|
// pixels must keep the tonemapped image already sitting in Scene_ldr. That
|
|
// requires a load-preserving render pass (loadOp=eLoad) — with the default
|
|
// eDontCare/eUndefined pass the discarded pixels are left undefined, which
|
|
// on IMR GPUs (NVIDIA) happens to preserve the old contents but on tiled /
|
|
// AMD Vega / MoltenVK gets scrambled, leaving only the FXAA-written edge
|
|
// pixels visible. Transition Scene_ldr to a color attachment first (mirrors
|
|
// executeLightshafts). This also orders the main pass's color writes after
|
|
// the prepass's sampled read of Scene_ldr (WAR hazard).
|
|
{
|
|
ImageBarrier2 barrier;
|
|
barrier.image = m_sceneLdr.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);
|
|
}
|
|
|
|
// FXAA main pass: Scene_luminance → Scene_ldr (loadOp=eLoad preserves non-edge pixels)
|
|
graphics::generic_data::fxaa_data fxaaData;
|
|
fxaaData.rt_w = static_cast<float>(m_ctx->sceneExtent.width);
|
|
fxaaData.rt_h = static_cast<float>(m_ctx->sceneExtent.height);
|
|
fxaaData.pad[0] = 0.0f;
|
|
fxaaData.pad[1] = 0.0f;
|
|
|
|
m_ctx->drawFullscreenTriangle(cmd, m_ldrLoadRenderPass,
|
|
m_sceneLdrFB, m_ctx->sceneExtent,
|
|
SDR_TYPE_POST_PROCESS_FXAA,
|
|
m_sceneLuminance.view, m_ctx->linearSampler,
|
|
&fxaaData, sizeof(fxaaData),
|
|
ALPHA_BLEND_NONE);
|
|
}
|
|
|
|
bool VulkanLDR::executePostEffects(vk::CommandBuffer cmd)
|
|
{
|
|
m_postEffectsApplied = false;
|
|
|
|
if (!m_initialized || !graphics::Post_processing_manager) {
|
|
return false;
|
|
}
|
|
|
|
const auto& postEffects = graphics::Post_processing_manager->getPostEffects();
|
|
if (postEffects.empty()) {
|
|
return false;
|
|
}
|
|
|
|
// Compute effect flags from current state
|
|
int effectFlags = 0;
|
|
for (size_t idx = 0; idx < postEffects.size(); idx++) {
|
|
if (postEffects[idx].always_on || (postEffects[idx].intensity != postEffects[idx].default_intensity)) {
|
|
effectFlags |= (1 << idx);
|
|
}
|
|
}
|
|
|
|
if (effectFlags == 0) {
|
|
return false;
|
|
}
|
|
|
|
GR_DEBUG_SCOPE("Draw post effects");
|
|
|
|
// Build the extended post_data UBO with effectFlags appended
|
|
struct PostEffectsUBOData {
|
|
graphics::generic_data::post_data base;
|
|
int effectFlags;
|
|
int pad[3];
|
|
};
|
|
|
|
PostEffectsUBOData uboData;
|
|
memset(&uboData, 0, sizeof(uboData));
|
|
uboData.base.timer = static_cast<float>((timer_get_milliseconds() % 100) + 1);
|
|
uboData.effectFlags = effectFlags;
|
|
|
|
// Fill effect parameters
|
|
for (size_t idx = 0; idx < postEffects.size(); idx++) {
|
|
if (!(effectFlags & (1 << idx))) {
|
|
continue;
|
|
}
|
|
float value = postEffects[idx].intensity;
|
|
switch (postEffects[idx].uniform_type) {
|
|
case graphics::PostEffectUniformType::NoiseAmount:
|
|
uboData.base.noise_amount = value;
|
|
break;
|
|
case graphics::PostEffectUniformType::Saturation:
|
|
uboData.base.saturation = value;
|
|
break;
|
|
case graphics::PostEffectUniformType::Brightness:
|
|
uboData.base.brightness = value;
|
|
break;
|
|
case graphics::PostEffectUniformType::Contrast:
|
|
uboData.base.contrast = value;
|
|
break;
|
|
case graphics::PostEffectUniformType::FilmGrain:
|
|
uboData.base.film_grain = value;
|
|
break;
|
|
case graphics::PostEffectUniformType::TvStripes:
|
|
uboData.base.tv_stripes = value;
|
|
break;
|
|
case graphics::PostEffectUniformType::Cutoff:
|
|
uboData.base.cutoff = value;
|
|
break;
|
|
case graphics::PostEffectUniformType::Dither:
|
|
uboData.base.dither = value;
|
|
break;
|
|
case graphics::PostEffectUniformType::Tint:
|
|
uboData.base.tint = postEffects[idx].rgb;
|
|
break;
|
|
case graphics::PostEffectUniformType::CustomEffectVEC3A:
|
|
uboData.base.custom_effect_vec3_a = postEffects[idx].rgb;
|
|
break;
|
|
case graphics::PostEffectUniformType::CustomEffectFloatA:
|
|
uboData.base.custom_effect_float_a = value;
|
|
break;
|
|
case graphics::PostEffectUniformType::CustomEffectVEC3B:
|
|
uboData.base.custom_effect_vec3_b = postEffects[idx].rgb;
|
|
break;
|
|
case graphics::PostEffectUniformType::CustomEffectFloatB:
|
|
uboData.base.custom_effect_float_b = value;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Post-effects: Scene_ldr → Scene_luminance (reusing luminance target as temp)
|
|
m_ctx->drawFullscreenTriangle(cmd, m_ldrRenderPass,
|
|
m_sceneLuminanceFB, m_ctx->sceneExtent,
|
|
SDR_TYPE_POST_PROCESS_MAIN,
|
|
m_sceneLdr.view, m_ctx->linearSampler,
|
|
&uboData, sizeof(uboData),
|
|
ALPHA_BLEND_NONE,
|
|
static_cast<unsigned int>(effectFlags));
|
|
|
|
m_postEffectsApplied = true;
|
|
return true;
|
|
}
|
|
|
|
void VulkanLDR::executeLightshafts(vk::CommandBuffer cmd)
|
|
{
|
|
if (!m_initialized || !graphics::Post_processing_manager) {
|
|
return;
|
|
}
|
|
|
|
if (Game_subspace_effect || !gr_sunglare_enabled() || !gr_lightshafts_enabled()) {
|
|
return;
|
|
}
|
|
|
|
// Find a global light with glare facing the camera
|
|
int n_lights = light_get_global_count();
|
|
float sun_x = 0.0f, sun_y = 0.0f;
|
|
bool found = false;
|
|
|
|
for (int idx = 0; idx < n_lights; idx++) {
|
|
vec3d light_dir;
|
|
light_get_global_dir(&light_dir, idx);
|
|
|
|
if (!light_has_glare(idx)) {
|
|
continue;
|
|
}
|
|
|
|
float dot = vm_vec_dot(&light_dir, &Eye_matrix.vec.fvec);
|
|
if (dot > 0.7f) {
|
|
sun_x = asinf_safe(vm_vec_dot(&light_dir, &Eye_matrix.vec.rvec)) / PI * 1.5f + 0.5f;
|
|
sun_y = asinf_safe(vm_vec_dot(&light_dir, &Eye_matrix.vec.uvec)) / PI * 1.5f * gr_screen.clip_aspect + 0.5f;
|
|
// The formula above is OpenGL's, whose texture V grows upward. The Vulkan fullscreen triangle in
|
|
// post-v.sdr has V = 0 at the top of the screen, so flip to the space ls-f.sdr's fragTexCoord is in.
|
|
sun_y = 1.0f - sun_y;
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
return;
|
|
}
|
|
|
|
GR_DEBUG_SCOPE("Lightshafts");
|
|
|
|
// Transition scene depth from eDepthStencilAttachmentOptimal to eShaderReadOnlyOptimal for sampling
|
|
{
|
|
ImageBarrier2 barrier;
|
|
barrier.image = m_sceneDepth->image;
|
|
barrier.aspectMask = imageAspectFromFormat(m_ctx->depthFormat);
|
|
barrier.levelCount = 1;
|
|
barrier.layerCount = 1;
|
|
barrier.oldLayout = vk::ImageLayout::eDepthStencilAttachmentOptimal;
|
|
barrier.newLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
|
|
barrier.srcStage = vk::PipelineStageFlagBits2::eLateFragmentTests;
|
|
barrier.srcAccess = vk::AccessFlagBits2::eDepthStencilAttachmentWrite;
|
|
barrier.dstStage = vk::PipelineStageFlagBits2::eFragmentShader;
|
|
barrier.dstAccess = vk::AccessFlagBits2::eShaderSampledRead;
|
|
|
|
cmdImageBarrier(cmd, barrier);
|
|
}
|
|
|
|
// Transition Scene_ldr to eColorAttachmentOptimal for loadOp=eLoad render pass
|
|
{
|
|
ImageBarrier2 barrier;
|
|
barrier.image = m_sceneLdr.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);
|
|
}
|
|
|
|
// Build lightshaft UBO data
|
|
auto& ls_params = graphics::Post_processing_manager->getLightshaftParams();
|
|
|
|
graphics::generic_data::lightshaft_data lsData;
|
|
lsData.sun_pos.x = sun_x;
|
|
lsData.sun_pos.y = sun_y;
|
|
lsData.density = ls_params.density;
|
|
lsData.weight = ls_params.weight;
|
|
lsData.falloff = ls_params.falloff;
|
|
lsData.intensity = Sun_spot * ls_params.intensity;
|
|
lsData.cp_intensity = Sun_spot * ls_params.cpintensity;
|
|
lsData.pad[0] = 0.0f;
|
|
|
|
// Additive blend lightshafts onto Scene_ldr
|
|
m_ctx->drawFullscreenTriangle(cmd, m_ldrLoadRenderPass,
|
|
m_sceneLdrFB, m_ctx->sceneExtent,
|
|
SDR_TYPE_POST_PROCESS_LIGHTSHAFTS,
|
|
m_sceneDepth->view, m_ctx->linearSampler,
|
|
&lsData, sizeof(lsData),
|
|
ALPHA_BLEND_ADDITIVE);
|
|
}
|
|
|
|
} // namespace graphics::vulkan
|