feat: IBL 预积分 GPU compute 化 + 环境资产入 Assets 层(动态切换)

CPU 逐像素卷积(venice 4k 阻塞 7.36s)→ 4 个 compute shader 一次性
提交:equirect→cube / irradiance / prefilter×5mip / BRDF LUT。
Cubemap 瘦身为纯资源持有类,生成职责移交 IblGenerator;
环境经 AssetManager 缓存(EnvRef),Renderer::SetEnvironment
动态换绑(RenderPass::OnEnvironmentChanged 广播)。
调试视图 +4:EnvCube / Irradiance / Prefilter / BRDF LUT。

性能前后对比(RTX 5060 Laptop,venice_dawn_1_4k.hdr):
- 改造前 CPU:7.36 s(主线程阻塞)
- 改造后 GPU:HDR decode 127-134ms + 资源 22-35ms + compute 17-19ms
  ≈ 165-190ms(约 40×);Neutral 兜底 ~6ms
- 运行中动态切换环境同量级(ms 级 GPU,decode 占大头)

冒烟:两构建目录 0 警告;运行期 0 validation(含两次环境切换 +
调试视图 9-12 轮播自动化验证)。
This commit is contained in:
2026-09-24 13:23:59 +08:00
parent 705250df76
commit 1d690067bd
30 changed files with 1162 additions and 824 deletions
+527
View File
@@ -0,0 +1,527 @@
//
// 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; // 漫反射辐照度面边长(低频)
constexpr u32 kPrefilterFaceSize = 128; // pre-filter 面边长(mip0)
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/";
m_ToCubeShader = shaders.CreateFromFile(
dir + "ibl_equirect_to_cube.comp.spv",
vk::ShaderStageFlagBits::eCompute,
{m_IblSetLayout}).Handle;
m_IrrShader = shaders.CreateFromFile(
dir + "ibl_irradiance.comp.spv",
vk::ShaderStageFlagBits::eCompute,
{m_IblSetLayout}).Handle;
m_PrefShader = shaders.CreateFromFile(
dir + "ibl_prefilter.comp.spv",
vk::ShaderStageFlagBits::eCompute,
{m_IblSetLayout}, sizeof(f32)).Handle;
m_LutShader = shaders.CreateFromFile(
dir + "ibl_brdf_lut.comp.spv",
vk::ShaderStageFlagBits::eCompute,
{m_LutSetLayout}).Handle;
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();
for (const vk::ShaderEXT s : {m_ToCubeShader, m_IrrShader,
m_PrefShader, m_LutShader}) {
if (s) vkDevice.destroyShaderEXT(s);
}
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);
}
}
Scope<Cubemap> IblGenerator::FromHdrEquirect(const std::string& path,
u32 faceSize)
{
const auto t0 = std::chrono::steady_clock::now();
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 nullptr;
}
const f32 decodeMs = MsSince(t0);
auto cubemap = Generate(pixels, static_cast<u32>(w),
static_cast<u32>(h), faceSize, path);
stbi_image_free(pixels);
ENGINE_INFO("IblGenerator: HDR decode {:.2f} ms",
decodeMs);
return cubemap;
}
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};
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};
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};
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};
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