// // 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 #include #include #include #include #include #include #include #include #include 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( 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 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 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}); 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(w); decoded.h = static_cast(h); decoded.pixels.assign(pixels, pixels + static_cast(w) * h * 4); stbi_image_free(pixels); return decoded; } Scope 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 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 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 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(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(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 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 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 computeStage = { vk::ShaderStageFlagBits::eCompute}; std::array 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 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 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(m) / static_cast(kPrefilterMips - 1); m_Cmd.bindDescriptorSets(vk::PipelineBindPoint::eCompute, m_IblLayoutPush, 0, setPref[m], {}); m_Cmd.pushConstants(m_IblLayoutPush, vk::ShaderStageFlagBits::eCompute, 0, roughness); m_Cmd.dispatch((dim + kGroupSize - 1) / kGroupSize, (dim + kGroupSize - 1) / kGroupSize, kFaceCount); } // 4f. ④ BRDF LUT(独立纯数学) std::array 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 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(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(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