Files
HelloVulkan/Engine/src/Renderer/IblGenerator.cpp
T

549 lines
25 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// Created by atdunbg on 2026/9/24.
//
// IblGenerator 实现:一次性 compute 提交完成全部预积分。
//
// 同步与生命周期模型:
// - 提交用独立 command buffer + fence,与帧管线(RG 提交)互不干扰;
// - fence 等待后所有产物 layout 恒为 eShaderReadOnlyOptimal,后续
// 帧提交经队列序自动可见(无需额外 barrier);
// - 中间资源(equirect 纹理 / staging / storage view)用完即毁;
// - 描述符集不回收(growable pool 吸收;环境加载是低频事件)。
//
// 计时打点输出各段耗时,作为 CPU→GPU 迁移的前后对比依据。
//
#include <Engine/Renderer/IblGenerator.h>
#include <Engine/Renderer/Cubemap.h>
#include <Engine/Vulkan/Device.h>
#include <Engine/Vulkan/DescriptorManager.h>
#include <Engine/Vulkan/ShaderManager.h>
#include <Engine/Core/Log.h>
#include <stb/stb_image.h>
#include <chrono>
#include <cstring>
#include <filesystem>
namespace Engine
{
namespace
{
constexpr u32 kFaceCount = 6;
constexpr u32 kIrradianceFaceSize = 32; // 漫反射辐照度面边长(低频)
// pre-filter 面边长(mip0):roughness→0 的镜面反射采 mip0,
// 128²/面放大到全屏 = 块状马赛克;256 = 无 SSR 时的清晰度底线
constexpr u32 kPrefilterFaceSize = 256;
constexpr u32 kPrefilterMips = 5; // mip 分级 roughness 数
constexpr u32 kBrdfLutSize = 128; // BRDF LUT 边长
constexpr u32 kGroupSize = 8; // compute local_size(shader 同步)
f32 MsSince(const std::chrono::steady_clock::time_point& t0)
{
return std::chrono::duration<f32, std::milli>(
std::chrono::steady_clock::now() - t0).count();
}
}
IblGenerator::IblGenerator(Device& device, MemoryAllocator& allocator,
DescriptorManager& descriptors,
ShaderManager& shaders)
: m_Device(device), m_Allocator(allocator),
m_Descriptors(descriptors), m_Shaders(shaders)
{
// Set A:{0 采样输入(cube/2D 共用 combined 槽),1 storage 输出}
const std::vector<DescriptorBinding> iblBindings = {
{0, vk::DescriptorType::eCombinedImageSampler,
vk::ShaderStageFlagBits::eCompute},
{1, vk::DescriptorType::eStorageImage,
vk::ShaderStageFlagBits::eCompute},
};
m_IblSetLayout = descriptors.CreateLayout(iblBindings);
// Set B:LUT 只有 storage 输出
const std::vector<DescriptorBinding> lutBindings = {
{0, vk::DescriptorType::eStorageImage,
vk::ShaderStageFlagBits::eCompute},
};
m_LutSetLayout = descriptors.CreateLayout(lutBindings);
// 管线布局:push 仅 prefilter 用(compute 单独按 eCompute 声明)
m_IblLayout = descriptors.CreatePipelineLayout(
{m_IblSetLayout}, 0, vk::ShaderStageFlagBits::eCompute);
m_IblLayoutPush = descriptors.CreatePipelineLayout(
{m_IblSetLayout}, sizeof(f32), vk::ShaderStageFlagBits::eCompute);
m_LutLayout = descriptors.CreatePipelineLayout(
{m_LutSetLayout}, 0, vk::ShaderStageFlagBits::eCompute);
const std::string dir = "shaders/env/";
m_ToCubeShader = shaders.CreateFromFile(
dir + "ibl_equirect_to_cube.comp.spv",
vk::ShaderStageFlagBits::eCompute,
{m_IblSetLayout});
m_IrrShader = shaders.CreateFromFile(
dir + "ibl_irradiance.comp.spv",
vk::ShaderStageFlagBits::eCompute,
{m_IblSetLayout});
m_PrefShader = shaders.CreateFromFile(
dir + "ibl_prefilter.comp.spv",
vk::ShaderStageFlagBits::eCompute,
{m_IblSetLayout}, sizeof(f32));
m_LutShader = shaders.CreateFromFile(
dir + "ibl_brdf_lut.comp.spv",
vk::ShaderStageFlagBits::eCompute,
{m_LutSetLayout});
if (!m_ToCubeShader || !m_IrrShader || !m_PrefShader || !m_LutShader) {
ENGINE_FATAL("IblGenerator: failed to create compute shaders");
}
// equirect 中间纹理采样器:硬件双线性,S=repeat 吃掉经度接缝
vk::SamplerCreateInfo sInfo;
sInfo.setMagFilter(vk::Filter::eLinear)
.setMinFilter(vk::Filter::eLinear)
.setMipmapMode(vk::SamplerMipmapMode::eNearest)
.setAddressModeU(vk::SamplerAddressMode::eRepeat)
.setAddressModeV(vk::SamplerAddressMode::eClampToEdge)
.setAddressModeW(vk::SamplerAddressMode::eClampToEdge);
m_EquirectSampler = device.GetDevice().createSampler(sInfo);
// 持久命令设施:每次生成 reset 复用(避免反复建池)
vk::CommandPoolCreateInfo poolInfo;
poolInfo.setQueueFamilyIndex(device.GetGraphicsFamily())
.setFlags(vk::CommandPoolCreateFlagBits::eTransient |
vk::CommandPoolCreateFlagBits::eResetCommandBuffer);
m_Pool = device.GetDevice().createCommandPool(poolInfo);
m_Cmd = device.GetDevice().allocateCommandBuffers(
vk::CommandBufferAllocateInfo{}
.setCommandPool(m_Pool)
.setLevel(vk::CommandBufferLevel::ePrimary)
.setCommandBufferCount(1))[0];
m_Fence = device.GetDevice().createFence(
vk::FenceCreateInfo{vk::FenceCreateFlagBits::eSignaled});
}
IblGenerator::~IblGenerator()
{
const auto& vkDevice = m_Device.GetDevice();
// shader 随 ShaderRef 引用计数自动销毁,此处只清其余原生句柄
vkDevice.destroySampler(m_EquirectSampler);
vkDevice.destroyPipelineLayout(m_IblLayout);
vkDevice.destroyPipelineLayout(m_IblLayoutPush);
vkDevice.destroyPipelineLayout(m_LutLayout);
vkDevice.destroyDescriptorSetLayout(m_IblSetLayout);
vkDevice.destroyDescriptorSetLayout(m_LutSetLayout);
vkDevice.destroyFence(m_Fence);
vkDevice.freeCommandBuffers(m_Pool, m_Cmd);
vkDevice.destroyCommandPool(m_Pool);
}
// ---- cube 兼容目标:storage(2DArray per-mip view)+ 采样(eCube view)----
IblGenerator::CubeTarget IblGenerator::CreateCubeTarget(u32 size, u32 mips) const
{
// fp16 storage 支持(桌面 GPU 普遍具备,显式校验防退化平台)
const vk::Format format = m_Device.FindSupportedFormat(
{vk::Format::eR16G16B16A16Sfloat},
vk::ImageTiling::eOptimal,
vk::FormatFeatureFlagBits::eStorageImage);
vk::ImageCreateInfo info;
info.setImageType(vk::ImageType::e2D)
.setFormat(format)
.setExtent({size, size, 1})
.setMipLevels(mips)
.setArrayLayers(kFaceCount)
.setSamples(vk::SampleCountFlagBits::e1)
.setTiling(vk::ImageTiling::eOptimal)
.setFlags(vk::ImageCreateFlagBits::eCubeCompatible)
.setUsage(vk::ImageUsageFlagBits::eStorage |
vk::ImageUsageFlagBits::eSampled)
.setInitialLayout(vk::ImageLayout::eUndefined);
CubeTarget t;
t.allocation = m_Allocator.CreateImage(info,
vk::MemoryPropertyFlagBits::eDeviceLocal);
t.image = t.allocation.Image;
// storage 写:2DArray view,每 mip 一个(storage 必须单 mip 层)
for (u32 mip = 0; mip < mips; ++mip) {
t.storageViews.push_back(m_Device.GetDevice().createImageView(
vk::ImageViewCreateInfo{}
.setImage(t.image)
.setViewType(vk::ImageViewType::e2DArray)
.setFormat(format)
.setSubresourceRange({vk::ImageAspectFlagBits::eColor,
mip, 1, 0, kFaceCount})));
}
// 采样读:eCube view 全 mip 链(消费端契约与原 CPU 路径一致)
t.cubeView = m_Device.GetDevice().createImageView(
vk::ImageViewCreateInfo{}
.setImage(t.image)
.setViewType(vk::ImageViewType::eCube)
.setFormat(format)
.setSubresourceRange({vk::ImageAspectFlagBits::eColor,
0, mips, 0, kFaceCount}));
return t;
}
// 只释放 storage 中转 view(image/cubeView/显存交给 Cubemap 接管)
void IblGenerator::ReleaseStorageViews(const CubeTarget& t) const
{
for (const vk::ImageView v : t.storageViews) {
m_Device.GetDevice().destroyImageView(v);
}
}
DecodedEquirect IblGenerator::DecodeHdr(const std::string& path)
{
// stbi_loadf 无共享可变状态(失败原因每次调用独立),可并发调用
int w = 0, h = 0, channels = 0;
f32* pixels = stbi_loadf(path.c_str(), &w, &h, &channels, STBI_rgb_alpha);
if (!pixels) {
ENGINE_ERROR("IblGenerator: failed to load HDR: {}", path);
return {};
}
DecodedEquirect decoded;
decoded.w = static_cast<u32>(w);
decoded.h = static_cast<u32>(h);
decoded.pixels.assign(pixels,
pixels + static_cast<size_t>(w) * h * 4);
stbi_image_free(pixels);
return decoded;
}
Scope<Cubemap> IblGenerator::FromDecoded(const DecodedEquirect& decoded,
u32 faceSize,
const std::string& debugName)
{
if (!decoded.Valid()) {
ENGINE_ERROR("IblGenerator: invalid decoded equirect ({})",
debugName);
return nullptr;
}
return Generate(decoded.pixels.data(), decoded.w, decoded.h,
faceSize, debugName);
}
Scope<Cubemap> IblGenerator::FromHdrEquirect(const std::string& path,
u32 faceSize)
{
const auto t0 = std::chrono::steady_clock::now();
DecodedEquirect decoded = DecodeHdr(path);
if (!decoded.Valid()) {
return nullptr;
}
ENGINE_INFO("IblGenerator: HDR decode {:.2f} ms", MsSince(t0));
return FromDecoded(decoded, faceSize, path);
}
Scope<Cubemap> IblGenerator::Neutral(const glm::vec3& rgb)
{
// 1×1 常数 equirect:全链复用(LUT 仍是真实积分,纯数学)
const f32 pixel[4] = {rgb.r, rgb.g, rgb.b, 1.0f};
return Generate(pixel, 1, 1, 1, "neutral");
}
Scope<Cubemap> IblGenerator::Generate(const f32* pixels, u32 w, u32 h,
u32 faceSize,
const std::string& debugName)
{
const auto t0 = std::chrono::steady_clock::now();
const auto& vkDevice = m_Device.GetDevice();
// ---- 1. equirect 中间纹理(R32G32B32A32,免 CPU fp16 转换)----
const vk::DeviceSize eqBytes =
static_cast<vk::DeviceSize>(w) * h * 16;
auto staging = m_Allocator.CreateBuffer(
eqBytes,
vk::BufferUsageFlagBits::eTransferSrc,
vk::MemoryPropertyFlagBits::eHostVisible |
vk::MemoryPropertyFlagBits::eHostCoherent);
std::memcpy(staging.MappedPtr, pixels, static_cast<size_t>(eqBytes));
vk::ImageCreateInfo eqInfo;
eqInfo.setImageType(vk::ImageType::e2D)
.setFormat(vk::Format::eR32G32B32A32Sfloat)
.setExtent({w, h, 1})
.setMipLevels(1)
.setArrayLayers(1)
.setSamples(vk::SampleCountFlagBits::e1)
.setTiling(vk::ImageTiling::eOptimal)
.setUsage(vk::ImageUsageFlagBits::eTransferDst |
vk::ImageUsageFlagBits::eSampled)
.setInitialLayout(vk::ImageLayout::eUndefined);
auto eqAlloc = m_Allocator.CreateImage(eqInfo,
vk::MemoryPropertyFlagBits::eDeviceLocal);
vk::ImageView eqView = vkDevice.createImageView(
vk::ImageViewCreateInfo{}
.setImage(eqAlloc.Image)
.setViewType(vk::ImageViewType::e2D)
.setFormat(vk::Format::eR32G32B32A32Sfloat)
.setSubresourceRange({vk::ImageAspectFlagBits::eColor,
0, 1, 0, 1}));
// ---- 2. 输出资源 + 消费端采样器 ----
CubeTarget env = CreateCubeTarget(faceSize, 1);
CubeTarget irr = CreateCubeTarget(kIrradianceFaceSize, 1);
CubeTarget pref = CreateCubeTarget(kPrefilterFaceSize, kPrefilterMips);
const vk::Sampler envSampler = CreateEnvSampler(kPrefilterMips - 1);
const vk::Sampler lutSampler = CreateLutSampler();
const vk::Format lutFormat = m_Device.FindSupportedFormat(
{vk::Format::eR16G16Sfloat},
vk::ImageTiling::eOptimal,
vk::FormatFeatureFlagBits::eStorageImage);
vk::ImageCreateInfo lutInfo;
lutInfo.setImageType(vk::ImageType::e2D)
.setFormat(lutFormat)
.setExtent({kBrdfLutSize, kBrdfLutSize, 1})
.setMipLevels(1)
.setArrayLayers(1)
.setSamples(vk::SampleCountFlagBits::e1)
.setTiling(vk::ImageTiling::eOptimal)
.setUsage(vk::ImageUsageFlagBits::eStorage |
vk::ImageUsageFlagBits::eSampled)
.setInitialLayout(vk::ImageLayout::eUndefined);
auto lutAlloc = m_Allocator.CreateImage(lutInfo,
vk::MemoryPropertyFlagBits::eDeviceLocal);
vk::ImageView lutView = vkDevice.createImageView(
vk::ImageViewCreateInfo{}
.setImage(lutAlloc.Image)
.setViewType(vk::ImageViewType::e2D)
.setFormat(lutFormat)
.setSubresourceRange({vk::ImageAspectFlagBits::eColor,
0, 1, 0, 1}));
const auto resourceMs = MsSince(t0);
// ---- 3. 描述符(低频事件,growable pool 吸收,不回收)----
vk::DescriptorSet setToCube =
m_Descriptors.AllocateDescriptorSet(m_IblSetLayout);
vk::DescriptorSet setIrr =
m_Descriptors.AllocateDescriptorSet(m_IblSetLayout);
vk::DescriptorSet setLut =
m_Descriptors.AllocateDescriptorSet(m_LutSetLayout);
std::array<vk::DescriptorSet, kPrefilterMips> setPref{};
for (u32 m = 0; m < kPrefilterMips; ++m) {
setPref[m] = m_Descriptors.AllocateDescriptorSet(m_IblSetLayout);
}
m_Descriptors.UpdateTexture(setToCube, 0, m_EquirectSampler, eqView);
m_Descriptors.UpdateStorageImage(setToCube, 1, env.storageViews[0]);
// cube 采样必须用带 mip 的 env 采样器(textureLod 被 clamp 到 maxLod)
m_Descriptors.UpdateTexture(setIrr, 0, envSampler, env.cubeView);
m_Descriptors.UpdateStorageImage(setIrr, 1, irr.storageViews[0]);
for (u32 m = 0; m < kPrefilterMips; ++m) {
m_Descriptors.UpdateTexture(setPref[m], 0, envSampler, env.cubeView);
m_Descriptors.UpdateStorageImage(setPref[m], 1, pref.storageViews[m]);
}
m_Descriptors.UpdateStorageImage(setLut, 0, lutView);
// ---- 4. 录制 + 一次性提交 ----
vkDevice.resetFences(m_Fence);
vkDevice.resetCommandPool(m_Pool);
m_Cmd.begin(vk::CommandBufferBeginInfo{
vk::CommandBufferUsageFlagBits::eOneTimeSubmit});
// 4a. equirect staging → image → shader 读 layout
vk::ImageMemoryBarrier2 eqToTransfer;
eqToTransfer.setSrcStageMask(vk::PipelineStageFlagBits2::eNone)
.setSrcAccessMask(vk::AccessFlagBits2::eNone)
.setDstStageMask(vk::PipelineStageFlagBits2::eTransfer)
.setDstAccessMask(vk::AccessFlagBits2::eTransferWrite)
.setOldLayout(vk::ImageLayout::eUndefined)
.setNewLayout(vk::ImageLayout::eTransferDstOptimal)
.setImage(eqAlloc.Image)
.setSubresourceRange({vk::ImageAspectFlagBits::eColor,
0, 1, 0, 1});
m_Cmd.pipelineBarrier2(
vk::DependencyInfo{}.setImageMemoryBarriers(eqToTransfer));
vk::BufferImageCopy eqCopy;
eqCopy.setBufferOffset(0)
.setBufferRowLength(0)
.setBufferImageHeight(0)
.setImageSubresource({vk::ImageAspectFlagBits::eColor, 0, 0, 1})
.setImageOffset({0, 0, 0})
.setImageExtent({w, h, 1});
m_Cmd.copyBufferToImage(staging.Buffer, eqAlloc.Image,
vk::ImageLayout::eTransferDstOptimal, eqCopy);
vk::ImageMemoryBarrier2 eqToShader;
eqToShader.setSrcStageMask(vk::PipelineStageFlagBits2::eTransfer)
.setSrcAccessMask(vk::AccessFlagBits2::eTransferWrite)
.setDstStageMask(vk::PipelineStageFlagBits2::eComputeShader)
.setDstAccessMask(vk::AccessFlagBits2::eShaderRead)
.setOldLayout(vk::ImageLayout::eTransferDstOptimal)
.setNewLayout(vk::ImageLayout::eShaderReadOnlyOptimal)
.setImage(eqAlloc.Image)
.setSubresourceRange({vk::ImageAspectFlagBits::eColor,
0, 1, 0, 1});
m_Cmd.pipelineBarrier2(
vk::DependencyInfo{}.setImageMemoryBarriers(eqToShader));
// 4b. 输出资源 Undefined → General(compute storage 写的要求)
std::array<vk::ImageMemoryBarrier2, 4> toGeneral{};
const vk::Image images[4] = {env.image, irr.image, pref.image,
lutAlloc.Image};
const u32 mipCounts[4] = {1, 1, kPrefilterMips, 1};
for (u32 i = 0; i < 4; ++i) {
toGeneral[i]
.setSrcStageMask(vk::PipelineStageFlagBits2::eNone)
.setSrcAccessMask(vk::AccessFlagBits2::eNone)
.setDstStageMask(vk::PipelineStageFlagBits2::eComputeShader)
.setDstAccessMask(vk::AccessFlagBits2::eShaderWrite)
.setOldLayout(vk::ImageLayout::eUndefined)
.setNewLayout(vk::ImageLayout::eGeneral)
.setImage(images[i])
.setSubresourceRange({vk::ImageAspectFlagBits::eColor,
0, mipCounts[i], 0, kFaceCount});
}
toGeneral[3].setSubresourceRange({vk::ImageAspectFlagBits::eColor,
0, 1, 0, 1}); // LUT 是 2D
m_Cmd.pipelineBarrier2(
vk::DependencyInfo{}.setImageMemoryBarriers(toGeneral));
// 4c. ① equirect → env cube
std::array<vk::ShaderStageFlagBits, 1> computeStage = {
vk::ShaderStageFlagBits::eCompute};
std::array<vk::ShaderEXT, 1> toCubeShaders = {m_ToCubeShader.GetHandle()};
m_Cmd.bindShadersEXT(computeStage, toCubeShaders);
m_Cmd.bindDescriptorSets(vk::PipelineBindPoint::eCompute,
m_IblLayout, 0, setToCube, {});
m_Cmd.dispatch((faceSize + kGroupSize - 1) / kGroupSize,
(faceSize + kGroupSize - 1) / kGroupSize, kFaceCount);
// env 转采样读(②③的输入)
vk::ImageMemoryBarrier2 envToRead;
envToRead.setSrcStageMask(vk::PipelineStageFlagBits2::eComputeShader)
.setSrcAccessMask(vk::AccessFlagBits2::eShaderWrite)
.setDstStageMask(vk::PipelineStageFlagBits2::eComputeShader)
.setDstAccessMask(vk::AccessFlagBits2::eShaderRead)
.setOldLayout(vk::ImageLayout::eGeneral)
.setNewLayout(vk::ImageLayout::eShaderReadOnlyOptimal)
.setImage(env.image)
.setSubresourceRange({vk::ImageAspectFlagBits::eColor,
0, 1, 0, kFaceCount});
m_Cmd.pipelineBarrier2(
vk::DependencyInfo{}.setImageMemoryBarriers(envToRead));
// 4d. ② irradiance(env → irr)
std::array<vk::ShaderEXT, 1> irrShaders = {m_IrrShader.GetHandle()};
m_Cmd.bindShadersEXT(computeStage, irrShaders);
m_Cmd.bindDescriptorSets(vk::PipelineBindPoint::eCompute,
m_IblLayout, 0, setIrr, {});
m_Cmd.dispatch((kIrradianceFaceSize + kGroupSize - 1) / kGroupSize,
(kIrradianceFaceSize + kGroupSize - 1) / kGroupSize,
kFaceCount);
// 4e. ③ prefilter(env → pref,每 mip 一次 dispatch + push roughness)
std::array<vk::ShaderEXT, 1> prefShaders = {m_PrefShader.GetHandle()};
m_Cmd.bindShadersEXT(computeStage, prefShaders);
for (u32 m = 0; m < kPrefilterMips; ++m) {
const u32 dim = kPrefilterFaceSize >> m;
const f32 roughness =
static_cast<f32>(m) / static_cast<f32>(kPrefilterMips - 1);
m_Cmd.bindDescriptorSets(vk::PipelineBindPoint::eCompute,
m_IblLayoutPush, 0, setPref[m], {});
m_Cmd.pushConstants<f32>(m_IblLayoutPush,
vk::ShaderStageFlagBits::eCompute,
0, roughness);
m_Cmd.dispatch((dim + kGroupSize - 1) / kGroupSize,
(dim + kGroupSize - 1) / kGroupSize, kFaceCount);
}
// 4f. ④ BRDF LUT(独立纯数学)
std::array<vk::ShaderEXT, 1> lutShaders = {m_LutShader.GetHandle()};
m_Cmd.bindShadersEXT(computeStage, lutShaders);
m_Cmd.bindDescriptorSets(vk::PipelineBindPoint::eCompute,
m_LutLayout, 0, setLut, {});
m_Cmd.dispatch((kBrdfLutSize + kGroupSize - 1) / kGroupSize,
(kBrdfLutSize + kGroupSize - 1) / kGroupSize, 1);
// 4g. irr/pref/lut 收尾:General → ShaderReadOnly(消费端采样 layout;
// fence 等待后对后续队列提交自动可见)
std::array<vk::ImageMemoryBarrier2, 3> toRead{};
const vk::Image readImages[3] = {irr.image, pref.image,
lutAlloc.Image};
const u32 readMips[3] = {1, kPrefilterMips, 1};
const u32 readLayers[3] = {kFaceCount, kFaceCount, 1}; // LUT 是 2D
for (u32 i = 0; i < 3; ++i) {
toRead[i]
.setSrcStageMask(vk::PipelineStageFlagBits2::eComputeShader)
.setSrcAccessMask(vk::AccessFlagBits2::eShaderWrite)
.setDstStageMask(vk::PipelineStageFlagBits2::eComputeShader)
.setDstAccessMask(vk::AccessFlagBits2::eShaderRead)
.setOldLayout(vk::ImageLayout::eGeneral)
.setNewLayout(vk::ImageLayout::eShaderReadOnlyOptimal)
.setImage(readImages[i])
.setSubresourceRange({vk::ImageAspectFlagBits::eColor,
0, readMips[i], 0, readLayers[i]});
}
m_Cmd.pipelineBarrier2(
vk::DependencyInfo{}.setImageMemoryBarriers(toRead));
m_Cmd.end();
m_Device.GetGraphicsQueue().submit(
vk::SubmitInfo{}.setCommandBuffers(m_Cmd), m_Fence);
if (vkDevice.waitForFences(m_Fence, VK_TRUE, UINT64_MAX) !=
vk::Result::eSuccess) {
ENGINE_ERROR("IblGenerator: waitForFences failed ({})", debugName);
}
// ---- 5. 中间资源即毁;image/cubeView/显存交棒 Cubemap ----
vkDevice.destroyImageView(eqView);
m_Allocator.Destroy(eqAlloc);
m_Allocator.Destroy(staging);
ReleaseStorageViews(env);
ReleaseStorageViews(irr);
ReleaseStorageViews(pref);
const f32 computeMs = MsSince(t0) - resourceMs;
ENGINE_INFO("IblGenerator[{}]: {}x6 env, {}x6 irradiance, "
"{}x6 prefilter {} mips, {}² BRDF LUT — "
"resources {:.2f} ms, compute+wait {:.2f} ms",
debugName, faceSize, kIrradianceFaceSize,
kPrefilterFaceSize, kPrefilterMips, kBrdfLutSize,
resourceMs, computeMs);
return Scope<Cubemap>(new Cubemap(
m_Device, m_Allocator,
EnvTexture{env.image, env.cubeView, env.allocation},
EnvTexture{irr.image, irr.cubeView, irr.allocation},
EnvTexture{pref.image, pref.cubeView, pref.allocation},
EnvTexture{lutAlloc.Image, lutView, lutAlloc},
envSampler, lutSampler));
}
vk::Sampler IblGenerator::CreateEnvSampler(u32 maxLod) const
{
vk::SamplerCreateInfo sInfo;
sInfo.setMagFilter(vk::Filter::eLinear)
.setMinFilter(vk::Filter::eLinear)
.setMipmapMode(vk::SamplerMipmapMode::eNearest)
.setAddressModeU(vk::SamplerAddressMode::eClampToEdge)
.setAddressModeV(vk::SamplerAddressMode::eClampToEdge)
.setAddressModeW(vk::SamplerAddressMode::eClampToEdge)
.setAnisotropyEnable(VK_FALSE)
.setBorderColor(vk::BorderColor::eIntOpaqueBlack)
.setUnnormalizedCoordinates(VK_FALSE)
.setCompareEnable(VK_FALSE)
.setMinLod(0.0f)
.setMaxLod(static_cast<f32>(maxLod));
return m_Device.GetDevice().createSampler(sInfo);
}
vk::Sampler IblGenerator::CreateLutSampler() const
{
// clamp-to-edge:共享 border 白采样器会在 uv=1.0 处混入白色 border
return CreateEnvSampler(0);
}
} // namespace Engine