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:
@@ -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
|
||||
Reference in New Issue
Block a user