From 766d96518d70ac186fe7ee2c25a4f9bffda785aa Mon Sep 17 00:00:00 2001 From: kevyuu Date: Mon, 23 Feb 2026 21:07:34 +0700 Subject: [PATCH 1/2] Add example 75 --- .../CMakeLists.txt | 72 +++ .../app_resources/common.hlsl | 38 ++ .../app_resources/present.frag.hlsl | 24 + .../app_resources/test.comp.hlsl | 134 +++++ .../config.json.template | 28 + .../imagesTestList.txt | 7 + 75_EnvmapImportanceSamplingTest/main.cpp | 485 ++++++++++++++++++ CMakeLists.txt | 1 + 8 files changed, 789 insertions(+) create mode 100644 75_EnvmapImportanceSamplingTest/CMakeLists.txt create mode 100644 75_EnvmapImportanceSamplingTest/app_resources/common.hlsl create mode 100644 75_EnvmapImportanceSamplingTest/app_resources/present.frag.hlsl create mode 100644 75_EnvmapImportanceSamplingTest/app_resources/test.comp.hlsl create mode 100644 75_EnvmapImportanceSamplingTest/config.json.template create mode 100644 75_EnvmapImportanceSamplingTest/imagesTestList.txt create mode 100644 75_EnvmapImportanceSamplingTest/main.cpp diff --git a/75_EnvmapImportanceSamplingTest/CMakeLists.txt b/75_EnvmapImportanceSamplingTest/CMakeLists.txt new file mode 100644 index 000000000..997d42fc7 --- /dev/null +++ b/75_EnvmapImportanceSamplingTest/CMakeLists.txt @@ -0,0 +1,72 @@ +include(common RESULT_VARIABLE RES) +if(NOT RES) + message(FATAL_ERROR "common.cmake not found. Should be in {repo_root}/cmake directory") +endif() + +nbl_create_executable_project("" "" "" "" "${NBL_EXECUTABLE_PROJECT_CREATION_PCH_TARGET}") + +if(NBL_EMBED_BUILTIN_RESOURCES) + set(_BR_TARGET_ ${EXECUTABLE_NAME}_builtinResourceData) + set(RESOURCE_DIR "app_resources") + + get_filename_component(_SEARCH_DIRECTORIES_ "${CMAKE_CURRENT_SOURCE_DIR}" ABSOLUTE) + get_filename_component(_OUTPUT_DIRECTORY_SOURCE_ "${CMAKE_CURRENT_BINARY_DIR}/src" ABSOLUTE) + get_filename_component(_OUTPUT_DIRECTORY_HEADER_ "${CMAKE_CURRENT_BINARY_DIR}/include" ABSOLUTE) + + file(GLOB_RECURSE BUILTIN_RESOURCE_FILES RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/${RESOURCE_DIR}" CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/${RESOURCE_DIR}/*") + foreach(RES_FILE ${BUILTIN_RESOURCE_FILES}) + LIST_BUILTIN_RESOURCE(RESOURCES_TO_EMBED "${RES_FILE}") + endforeach() + + ADD_CUSTOM_BUILTIN_RESOURCES(${_BR_TARGET_} RESOURCES_TO_EMBED "${_SEARCH_DIRECTORIES_}" "${RESOURCE_DIR}" "nbl::this_example::builtin" "${_OUTPUT_DIRECTORY_HEADER_}" "${_OUTPUT_DIRECTORY_SOURCE_}") + + LINK_BUILTIN_RESOURCES_TO_TARGET(${EXECUTABLE_NAME} ${_BR_TARGET_}) +endif() + +add_dependencies(${EXECUTABLE_NAME} argparse) +target_include_directories(${EXECUTABLE_NAME} PUBLIC $) + +enable_testing() + +add_test(NAME NBL_IMAGE_HASH_RUN_TESTS + COMMAND "$" --test hash + WORKING_DIRECTORY "$" + COMMAND_EXPAND_LISTS +) + +set(OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/auto-gen") + +set(SM 6_8) +set(JSON [=[ +[ + { + "INPUT": "app_resources/test.comp.hlsl", + "KEY": "test", + }] +]=]) +string(CONFIGURE "${JSON}" JSON) + +set(COMPILE_OPTIONS + -I "${CMAKE_CURRENT_SOURCE_DIR}" + -T lib_${SM} +) + +NBL_CREATE_NSC_COMPILE_RULES( + TARGET ${EXECUTABLE_NAME}SPIRV + LINK_TO ${EXECUTABLE_NAME} + BINARY_DIR ${OUTPUT_DIRECTORY} + MOUNT_POINT_DEFINE NBL_THIS_EXAMPLE_BUILD_MOUNT_POINT + COMMON_OPTIONS ${COMPILE_OPTIONS} + OUTPUT_VAR KEYS + INCLUDE nbl/this_example/builtin/build/spirv/keys.hpp + NAMESPACE nbl::this_example::builtin::build + INPUTS ${JSON} +) + +NBL_CREATE_RESOURCE_ARCHIVE( + NAMESPACE nbl::this_example::builtin::build + TARGET ${EXECUTABLE_NAME}_builtinsBuild + LINK_TO ${EXECUTABLE_NAME} + BIND ${OUTPUT_DIRECTORY} + BUILTINS ${KEYS} +) diff --git a/75_EnvmapImportanceSamplingTest/app_resources/common.hlsl b/75_EnvmapImportanceSamplingTest/app_resources/common.hlsl new file mode 100644 index 000000000..3a06547fa --- /dev/null +++ b/75_EnvmapImportanceSamplingTest/app_resources/common.hlsl @@ -0,0 +1,38 @@ +#ifndef _ENVMAP_IMPORTANCE_SAMPLING_SEARCH_H_INCLUDED_ +#define _ENVMAP_IMPORTANCE_SAMPLING_SEARCH_H_INCLUDED_ + +#include +#include + +using namespace nbl; +using namespace nbl::hlsl; + +NBL_CONSTEXPR uint32_t WorkgroupSize = 128; + +struct STestPushConstants +{ + float32_t eps; + uint64_t outputAddress; + uint32_t2 warpResolution; + float32_t avgLuma; +}; + +struct TestOutput +{ + float32_t3 L; + float32_t2 uv; + float32_t jacobian; + float32_t pdf; + float32_t deferredPdf; +}; + +struct TestSample +{ + TestOutput directOutput; + TestOutput cachedOutput; + float32_t2 xi; +}; + +using test_sample_t = TestSample; + +#endif // _COOPERATIVE_BINARY_SEARCH_H_INCLUDED_ diff --git a/75_EnvmapImportanceSamplingTest/app_resources/present.frag.hlsl b/75_EnvmapImportanceSamplingTest/app_resources/present.frag.hlsl new file mode 100644 index 000000000..0ce9eac3d --- /dev/null +++ b/75_EnvmapImportanceSamplingTest/app_resources/present.frag.hlsl @@ -0,0 +1,24 @@ +// Copyright (C) 2024-2024 - DevSH Graphics Programming Sp. z O.O. +// This file is part of the "Nabla Engine". +// For conditions of distribution and use, see copyright notice in nabla.h + +#pragma wave shader_stage(fragment) + +// vertex shader is provided by the fullScreenTriangle extension +#include +using namespace nbl::hlsl::ext::FullScreenTriangle; + +[[vk::binding(0, 3)]] Texture2D warpMap; +[[vk::combinedImageSampler]][[vk::binding(1, 3)]] Texture2D envMap; +[[vk::combinedImageSampler]][[vk::binding(1, 3)]] SamplerState envMapSampler; + +[[vk::location(0)]] float32_t4 main(SVertexAttributes vxAttr) : SV_Target0 +{ + uint width; + uint height; + warpMap.GetDimensions(width, height); + float32_t2 uv = warpMap.Load(uint32_t3(width * vxAttr.uv.x, height * vxAttr.uv.y, 0)); + float32_t4 color = envMap.Sample(envMapSampler, uv); + + return float32_t4(color.xyz, 1.0); +} diff --git a/75_EnvmapImportanceSamplingTest/app_resources/test.comp.hlsl b/75_EnvmapImportanceSamplingTest/app_resources/test.comp.hlsl new file mode 100644 index 000000000..1ef6a0564 --- /dev/null +++ b/75_EnvmapImportanceSamplingTest/app_resources/test.comp.hlsl @@ -0,0 +1,134 @@ +#include "common.hlsl" + +#include +#include +#include +#include +#include +#include + +[[vk::push_constant]] STestPushConstants pc; + +[[vk::combinedImageSampler]][[vk::binding(0, 0)]] Texture2D lumaMap; +[[vk::combinedImageSampler]][[vk::binding(0, 0)]] SamplerState lumaSampler; + +[[vk::combinedImageSampler]][[vk::binding(1, 0)]] Texture2D warpMap; +[[vk::combinedImageSampler]][[vk::binding(1, 0)]] SamplerState warpSampler; + +using namespace nbl::hlsl::sampling::hierarchical_image; + + +struct LuminanceAccessor +{ + template && + concepts::same_as + ) + void get(IndexT index, NBL_REF_ARG(ValT) val) + { + val = lumaMap.SampleLevel(lumaSampler, index, 0); + } + + float32_t texelFetch(uint32_t2 coord, uint32_t level) + { + return lumaMap.Load(uint32_t3(coord, level)); + } + + float32_t4 texelGather(uint32_t2 coord, uint32_t level) + { + return float32_t4( + lumaMap.Load(uint32_t3(coord, level), uint32_t2(0, 1)), + lumaMap.Load(uint32_t3(coord, level), uint32_t2(1, 1)), + lumaMap.Load(uint32_t3(coord, level), uint32_t2(1, 0)), + lumaMap.Load(uint32_t3(coord, level), uint32_t2(0, 0)) + ); + } +}; + +struct WarpAccessor +{ + matrix sampleUvs(uint32_t2 sampleCoord) NBL_CONST_MEMBER_FUNC + { + const float32_t2 dir0 = warpMap.Load(int32_t3(sampleCoord + uint32_t2(0, 1), 0)); + const float32_t2 dir1 = warpMap.Load(int32_t3(sampleCoord + uint32_t2(1, 1), 0)); + const float32_t2 dir2 = warpMap.Load(int32_t3(sampleCoord + uint32_t2(1, 0), 0)); + const float32_t2 dir3 = warpMap.Load(int32_t3(sampleCoord, 0)); + return matrix( + dir0, + dir1, + dir2, + dir3 + ); + } +}; + + +template +TestOutput GenerateTestOutput(NBL_CONST_REF_ARG(HierarchicalImageT) hImage, float32_t2 xi) +{ + float pdf; + float32_t2 uv; + + const float3 L = hImage.generate_and_pdf(pdf, uv, xi); + + float eps_x = pc.eps; + float eps_y = pc.eps; + + float32_t2 d_uv; + float32_t d_pdf; + const float3 L_plus_du = hImage.generate_and_pdf(d_pdf, d_uv, xi + float32_t2(0.5f * eps_x, 0)); + const float3 L_plus_dv = hImage.generate_and_pdf(d_pdf, d_uv, xi + float32_t2(0, 0.5f * eps_y)); + + const float3 L_minus_du = hImage.generate_and_pdf(d_pdf, d_uv, xi - float32_t2(0.5f * eps_x, 0)); + const float3 L_minus_dv = hImage.generate_and_pdf(d_pdf, d_uv, xi - float32_t2(0, 0.5f * eps_y)); + + float jacobian = length(cross(L_plus_du - L_minus_du, L_plus_dv - L_minus_dv)) / (eps_x * eps_y); + + TestOutput testOutput; + testOutput.uv = uv; + testOutput.L = L; + testOutput.jacobian = jacobian; + testOutput.pdf = pdf; + testOutput.deferredPdf = hImage.deferredPdf(L); + return testOutput; +} + +float32_t2 convertToFloat01(uint32_t2 xi_uint) +{ + return float32_t2(xi_uint) / promote(float32_t(numeric_limits::max)); +} + +[numthreads(WorkgroupSize, 1, 1)] +[shader("compute")] +void main(uint32_t3 threadID : SV_DispatchThreadID) +{ + const LuminanceAccessor luminanceAccessor; + const WarpAccessor warpAccessor; + using luminance_sampler_type = nbl::hlsl::sampling::LuminanceMapSampler; + + using direct_hierarchical_image_type = sampling::HierarchicalImage >; + + const luminance_sampler_type luminanceSampler = luminance_sampler_type::create(luminanceAccessor, pc.warpResolution, true, pc.warpResolution); + + float32_t eps = pc.eps; + + random::PCG32 pcg = random::PCG32::construct(threadID.x); + uint32_t2 xi_uint = random::DimAdaptorRecursive::__call(pcg); + + float32_t2 xi = convertToFloat01(xi_uint); + + xi.x = hlsl::clamp(xi.x, eps, 1.f - eps); + xi.y = hlsl::clamp(xi.y, eps, 1.f - eps); + + test_sample_t testSample; + testSample.xi = xi; + + const direct_hierarchical_image_type directHImage = direct_hierarchical_image_type::create(luminanceAccessor, luminanceSampler, pc.warpResolution, pc.avgLuma); + testSample.directOutput = GenerateTestOutput(directHImage, xi); + + using cached_hierarchical_image_type = sampling::HierarchicalImage >; + const cached_hierarchical_image_type cachedHImage = cached_hierarchical_image_type::create(luminanceAccessor, warpAccessor, pc.warpResolution, pc.avgLuma); + testSample.cachedOutput = GenerateTestOutput(cachedHImage, xi); + vk::RawBufferStore(pc.outputAddress + threadID.x * sizeof(test_sample_t), testSample); +} diff --git a/75_EnvmapImportanceSamplingTest/config.json.template b/75_EnvmapImportanceSamplingTest/config.json.template new file mode 100644 index 000000000..24adf54fb --- /dev/null +++ b/75_EnvmapImportanceSamplingTest/config.json.template @@ -0,0 +1,28 @@ +{ + "enableParallelBuild": true, + "threadsPerBuildProcess" : 2, + "isExecuted": false, + "scriptPath": "", + "cmake": { + "configurations": [ "Release", "Debug", "RelWithDebInfo" ], + "buildModes": [], + "requiredOptions": [] + }, + "profiles": [ + { + "backend": "vulkan", + "platform": "windows", + "buildModes": [], + "runConfiguration": "Release", + "gpuArchitectures": [] + } + ], + "dependencies": [], + "data": [ + { + "dependencies": [], + "command": [""], + "outputs": [] + } + ] +} diff --git a/75_EnvmapImportanceSamplingTest/imagesTestList.txt b/75_EnvmapImportanceSamplingTest/imagesTestList.txt new file mode 100644 index 000000000..34a40079c --- /dev/null +++ b/75_EnvmapImportanceSamplingTest/imagesTestList.txt @@ -0,0 +1,7 @@ +; This is the testing suite for various Nabla loaders/writers (JPG/PNG/TGA/BMP/DDS/KTX). +; BMP is currently unsupported for now. +; 16-bit PNG & 8-bit RLE (compressed) TGA is not supported. +; For licensing attribution, see LICENSE. + +; JPG, colored & 8-bit grayscale +../../media/envmap/envmap_1.exr diff --git a/75_EnvmapImportanceSamplingTest/main.cpp b/75_EnvmapImportanceSamplingTest/main.cpp new file mode 100644 index 000000000..b5a9a1f16 --- /dev/null +++ b/75_EnvmapImportanceSamplingTest/main.cpp @@ -0,0 +1,485 @@ +// Copyright (C) 2018-2024 - DevSH Graphics Programming Sp. z O.O. +// This file is part of the "Nabla Engine". +// For conditions of distribution and use, see copyright notice in nabla.h +#include "nbl/this_example/builtin/build/spirv/keys.hpp" +#include "nbl/examples/examples.hpp" + +#include "nbl/core/sampling/EnvmapSampler.h" + +#include "nlohmann/json.hpp" +#include "argparse/argparse.hpp" + +#include "app_resources/common.hlsl" + +using namespace nbl; +using namespace core; +using namespace hlsl; +using namespace system; +using namespace asset; +using namespace ui; +using namespace video; +using namespace nbl::examples; + +namespace +{ + template + smart_refctd_ptr loadPrecompiledShader(ILogicalDevice* device, IAssetManager* assetManager, ILogger* logger) + { + IAssetLoader::SAssetLoadParams lp = {}; + lp.logger = logger; + lp.workingDirectory = "app_resources"; + + auto key = nbl::this_example::builtin::build::get_spirv_key(device); + auto assetBundle = assetManager->getAsset(key.data(), lp); + const auto assets = assetBundle.getContents(); + if (assets.empty()) + return nullptr; + + auto shader = IAsset::castDown(assets[0]); + return shader; + }; + + template + bool checkEq(T a, T b, float32_t eps = 1e-4) + { + if constexpr (!is_vector_v) + { + return abs(a - b) <= eps; + } + else + { + T _a = hlsl::max(hlsl::abs(a), hlsl::promote(1e-5)); + T _b = hlsl::max(hlsl::abs(b), hlsl::promote(1e-5)); + return nbl::hlsl::all::Dimension> >(nbl::hlsl::max(_a / _b, _b / _a) <= hlsl::promote(1 + eps)); + } + } +} + +class EnvmapImportanceSamplingTest final : public application_templates::BasicMultiQueueApplication, public BuiltinResourcesApplication +{ + using device_base_t = application_templates::BasicMultiQueueApplication; + using asset_base_t = BuiltinResourcesApplication; + using clock_t = std::chrono::steady_clock; + using perf_clock_resolution_t = std::chrono::milliseconds; + + constexpr static inline clock_t::duration DisplayImageDuration = std::chrono::milliseconds(900); + constexpr static inline std::string_view DefaultImagePathsFile = "../imagesTestList.txt"; + + public: + // Yay thanks to multiple inheritance we cannot forward ctors anymore + inline EnvmapImportanceSamplingTest(const path& _localInputCWD, const path& _localOutputCWD, const path& _sharedInputCWD, const path& _sharedOutputCWD) : + IApplicationFramework(_localInputCWD,_localOutputCWD,_sharedInputCWD,_sharedOutputCWD) {} + + virtual bool isComputeOnly() const {return false;} + + inline bool onAppInitialized(smart_refctd_ptr&& system) override + { + argparse::ArgumentParser program("Envmap Importance Sampling Test"); + program.add_argument("--input-list") + .help("File path to override input list with image file paths to execute this program with."); + program.add_argument("--sample-count") + .default_value(static_cast(1000)) + .help("Sample count for each input (Default : 1000)"); + try + { + program.parse_args({ argv.data(), argv.data() + argv.size() }); + } + catch (const std::exception& err) + { + std::cerr << err.what() << std::endl << program; + return 1; + } + + m_sampleCount = program.get("--sample-count"); + + if (!device_base_t::onAppInitialized(smart_refctd_ptr(system))) + return false; + if (!asset_base_t::onAppInitialized(std::move(system))) + return false; + + // get custom input list of files to execute the program with + system::path m_loadCWD = DefaultImagePathsFile; + { + const auto hook = program.present("--input-list"); + + if (hook) + { + const auto inputList = *hook; + + m_testPathsFile = std::ifstream(inputList); + if (m_testPathsFile.is_open()) + m_loadCWD = inputList; + else + m_logger->log("Couldn't open test file given by argument --input-list \"%s\", falling back to default list!", ILogger::ELL_ERROR, inputList.c_str()); + } + } + + if (!m_testPathsFile.is_open()) + m_testPathsFile = std::ifstream(m_loadCWD); + + if (!m_testPathsFile.is_open()) + return logFail("Could not open the test paths file"); + + m_logger->log("Connected \"%s\" input test list!", ILogger::ELL_INFO, m_loadCWD.string().c_str()); + m_loadCWD = m_loadCWD.parent_path(); + + + const auto* queue = getGraphicsQueue(); + + { + smart_refctd_ptr cmdpool = m_device->createCommandPool(queue->getFamilyIndex(),IGPUCommandPool::CREATE_FLAGS::RESET_COMMAND_BUFFER_BIT); + if (!cmdpool->createCommandBuffers(IGPUCommandPool::BUFFER_LEVEL::PRIMARY,{&m_cmdbuf,1})) + { + m_logger->log("Failed to create command buffer", ILogger::ELL_ERROR); + return false; + } + } + + smart_refctd_ptr dsLayout; + { + auto defaultSampler = m_device->createSampler({ + .TextureWrapU = ETC_CLAMP_TO_EDGE, + .TextureWrapV = ETC_CLAMP_TO_EDGE, + .TextureWrapW = ETC_CLAMP_TO_EDGE, + .MinFilter = ISampler::ETF_NEAREST, + .MaxFilter = ISampler::ETF_NEAREST, + .AnisotropicFilter = 0 + }); + + const IGPUDescriptorSetLayout::SBinding bindings[] = { + { + .binding = 0, + .type = IDescriptor::E_TYPE::ET_COMBINED_IMAGE_SAMPLER, + .createFlags = IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS::ECF_NONE, + .stageFlags = IShader::E_SHADER_STAGE::ESS_COMPUTE, + .count = 1, + .immutableSamplers = &defaultSampler + }, + { + .binding = 1, + .type = IDescriptor::E_TYPE::ET_COMBINED_IMAGE_SAMPLER, + .createFlags = IGPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS::ECF_NONE, + .stageFlags = IShader::E_SHADER_STAGE::ESS_COMPUTE, + .count = 1, + .immutableSamplers = &defaultSampler + }, + }; + dsLayout = m_device->createDescriptorSetLayout(bindings); + if (!dsLayout) + { + m_logger->log("Failed to Create Descriptor Layout", ILogger::ELL_ERROR); + return false; + } + asset::SPushConstantRange pcRange = { + .stageFlags = hlsl::ESS_COMPUTE, + .offset = 0, + .size = sizeof(STestPushConstants) + }; + const auto pipelineLayout = m_device->createPipelineLayout({ &pcRange, 1 }, dsLayout); + + const auto shader = loadPrecompiledShader<"test">(m_device.get(), m_assetMgr.get(), m_logger.get()); + + video::IGPUComputePipeline::SCreationParams pipelineParams = { + .layout = pipelineLayout.get(), + .shader = { + .shader = shader.get(), + .entryPoint = "main", + } + }; + + if (!m_device->createComputePipelines(nullptr, { &pipelineParams, 1 }, &m_pipeline)) + { + m_logger->log("Fail to create test pipeline", ILogger::ELL_ERROR); + return false; + } + + const auto dsPool = m_device->createDescriptorPoolForDSLayouts(IDescriptorPool::ECF_UPDATE_AFTER_BIND_BIT, pipelineLayout->getDescriptorSetLayouts()); + + m_descriptorSet = dsPool->createDescriptorSet(core::smart_refctd_ptr(pipelineLayout->getDescriptorSetLayouts()[0])); + + auto downStreamingBuffer = m_utils->getDefaultDownStreamingBuffer(); + std::chrono::steady_clock::time_point waitTill(std::chrono::years(45)); + uint32_t outputSize = sizeof(test_sample_t) * m_sampleCount; + m_outputOffset = downStreamingBuffer->invalid_value; + const auto& deviceLimits = m_device->getPhysicalDevice()->getLimits(); + const uint32_t alignment = core::max(deviceLimits.nonCoherentAtomSize,alignof(float)); + downStreamingBuffer->multi_allocate(waitTill, 1, &m_outputOffset, &outputSize, &alignment); + + m_scratchSemaphore = m_device->createSemaphore(0); + if (!m_scratchSemaphore) + { + logFail("Could not create Scratch Semaphore"); + return false; + } + m_scratchSemaphore->setObjectDebugName("Scratch Semaphore"); + + m_semaphore = m_device->createSemaphore(0); + if (!m_semaphore) + { + logFail("Could not create Scratch Semaphore"); + return false; + } + m_semaphore->setObjectDebugName("Semaphore"); + m_timelineValue = 0; + + // now convert + m_intendedSubmit.queue = getGraphicsQueue(); + // wait for nothing before upload + m_intendedSubmit.waitSemaphores = {}; + m_intendedSubmit.prevCommandBuffers = {}; + // fill later + m_intendedSubmit.scratchCommandBuffers = {}; + m_intendedSubmit.scratchSemaphore = { + .semaphore = m_scratchSemaphore.get(), + .value = 0, + .stageMask = PIPELINE_STAGE_FLAGS::ALL_TRANSFER_BITS + }; + + std::string nextPath; + while (std::getline(m_testPathsFile,nextPath)) + { + if (nextPath!="" && nextPath[0]!=';') + { + m_cmdbuf->begin(IGPUCommandBuffer::USAGE::ONE_TIME_SUBMIT_BIT); + + // load the image view + system::path filename, extension; + const core::smart_refctd_ptr imgView = getImageView(nextPath, filename, extension, m_cmdbuf.get()); + + { + EnvmapSampler::SCreationParameters params; + params.utilities = m_utils; + params.assetManager = m_assetMgr; + params.envMap = imgView; + m_envmapImportanceSampling = EnvmapSampler::create(std::move(params)); + m_envmapImportanceSampling->computeWarpMap(getGraphicsQueue()); + } + + const auto lumaMap = m_envmapImportanceSampling->getLumaMapView(); + const auto warpMap = m_envmapImportanceSampling->getWarpMapView(); + + auto downStreamingBuffer = m_utils->getDefaultDownStreamingBuffer(); + + + IGPUDescriptorSet::SDescriptorInfo lumaMapDescriptorInfo = {}; + lumaMapDescriptorInfo.desc = lumaMap; + lumaMapDescriptorInfo.info.image.imageLayout = IImage::LAYOUT::READ_ONLY_OPTIMAL; + + IGPUDescriptorSet::SDescriptorInfo warpMapDescriptorInfo = {}; + warpMapDescriptorInfo.desc = warpMap; + warpMapDescriptorInfo.info.image.imageLayout = IImage::LAYOUT::READ_ONLY_OPTIMAL; + + const IGPUDescriptorSet::SWriteDescriptorSet writes[] = { + { + .dstSet = m_descriptorSet.get(), .binding = 0, .count = 1, .info = &lumaMapDescriptorInfo + }, + { + .dstSet = m_descriptorSet.get(), .binding = 1, .count = 1, .info = &warpMapDescriptorInfo + }, + }; + + m_utils->getLogicalDevice()->updateDescriptorSets(writes, {}); + + const auto warpExtent = warpMap->getCreationParameters().image->getCreationParameters().extent; + const STestPushConstants pc = { + .eps = 5 * 1e-5, + .outputAddress = downStreamingBuffer->getBuffer()->getDeviceAddress() + m_outputOffset, + .warpResolution = uint32_t2(warpExtent.width, warpExtent.height), + .avgLuma = m_envmapImportanceSampling->getAvgLuma(), + }; + + m_cmdbuf->bindComputePipeline(m_pipeline.get()); + m_cmdbuf->bindDescriptorSets(EPBP_COMPUTE, m_pipeline->getLayout(), 0, 1, &m_descriptorSet.get()); + m_cmdbuf->pushConstants(m_pipeline->getLayout(), ESS_COMPUTE, 0, sizeof(STestPushConstants), &pc); + m_cmdbuf->dispatch(m_sampleCount / WorkgroupSize, 1, 1); + + m_cmdbuf->end(); + + const IQueue::SSubmitInfo::SSemaphoreInfo signal[1] = {{.semaphore = m_semaphore.get(),.value=++m_timelineValue}}; + const IQueue::SSubmitInfo::SCommandBufferInfo cmdbufs[1] = {{.cmdbuf=m_cmdbuf.get()}}; + const IQueue::SSubmitInfo submits[1] = {{.commandBuffers=cmdbufs,.signalSemaphores=signal}}; + getGraphicsQueue()->submit(submits); + const ISemaphore::SWaitInfo wait[1] = {{.semaphore=m_semaphore.get(),.value=m_timelineValue}}; + m_device->blockForSemaphores(wait); + + auto* gpuDownstreamingBuffer = downStreamingBuffer->getBuffer(); + if (downStreamingBuffer->needsManualFlushOrInvalidate()) + { + const auto nonCoherentAtomSize = m_device->getPhysicalDevice()->getLimits().nonCoherentAtomSize; + auto flushRange = ILogicalDevice::MappedMemoryRange(gpuDownstreamingBuffer->getBoundMemory().memory,m_outputOffset,m_sampleCount * sizeof(test_sample_t),ILogicalDevice::MappedMemoryRange::align_non_coherent_tag); + m_device->invalidateMappedMemoryRanges(1u,&flushRange); + } + + // Call the function + const uint8_t* bufSrc = reinterpret_cast(downStreamingBuffer->getBufferPointer()) + m_outputOffset; + const auto* testSamples = reinterpret_cast(bufSrc); + + for (uint32_t sample_i = 0; sample_i < m_sampleCount; sample_i++) + { + const auto& testSample = testSamples[sample_i]; + const auto& directOutput = testSample.directOutput; + const auto& cachedOutput = testSample.cachedOutput; + + if (!checkEq(cachedOutput.L, directOutput.L) || !checkEq(cachedOutput.uv, directOutput.uv) || !checkEq(cachedOutput.pdf, directOutput.pdf) || !checkEq(cachedOutput.deferredPdf, directOutput.deferredPdf)) + { + logFail("Failed similarity test between direct sampling and cached sampling. Direct Sampling = {uv = (%f, %f), L = (%f, %f %f), pdf = %f, deferredPdf = %f}, Cached Sampling = {uv = (%f, %f), L = (%f, %f %f), pdf = %f, deferredPdf = %f}", directOutput.uv.x, directOutput.uv.y, directOutput.L.x, directOutput.L.y, directOutput.L.z, directOutput.pdf, directOutput.deferredPdf, cachedOutput.uv.x, cachedOutput.uv.y, cachedOutput.L.x, cachedOutput.L.y, cachedOutput.L.z, cachedOutput.pdf, cachedOutput.pdf); + } + + const auto& testOutput = directOutput; + if (testOutput.jacobian < 0.05) continue; + if (const auto diff = abs(1.0f - (testOutput.jacobian * testOutput.pdf)); diff > 0.05) + { + m_logger->log("Failed similarity test of jacobian and pdf for image %s for sample number %d. xi = (%f, %f), uv = (%f, %f), Jacobian = %f, pdf = %f, difference = %f", ILogger::ELL_ERROR, "dummy", sample_i, testSample.xi.x, testSample.xi.y, testOutput.uv.x, testOutput.uv.y, testOutput.jacobian, testOutput.pdf, diff); + continue; + } + + if (const auto diff = abs(1.0f - (testOutput.jacobian * testOutput.deferredPdf)); diff > 0.05) + { + m_logger->log("Failed similarity test of jacobian and pdf for image %s for sample number %d. xi = (%f, %f), uv = (%f, %f), Jacobian = %f, deferredPdf = %f, difference = %f", ILogger::ELL_ERROR, "dummy", sample_i, testSample.xi.x, testSample.xi.y, testOutput.uv.x, testOutput.uv.y, testOutput.jacobian, testOutput.deferredPdf, diff); + } + } + } + } + + return true; + } + } + + inline void workLoopBody() override {} + + inline bool keepRunning() override { return false; } + + inline bool onAppTerminated() override + { + return true; + } + + protected: + + private: + smart_refctd_ptr m_envmapImportanceSampling; + + smart_refctd_ptr getImageView(std::string inAssetPath, system::path& outFilename, system::path& outExtension, IGPUCommandBuffer* cmdbuf) + { + smart_refctd_ptr cpuView; + + m_logger->log("Loading image from path %s", ILogger::ELL_DEBUG, inAssetPath.c_str()); + + constexpr auto cachingFlags = static_cast(IAssetLoader::ECF_DONT_CACHE_REFERENCES & IAssetLoader::ECF_DONT_CACHE_TOP_LEVEL); + const IAssetLoader::SAssetLoadParams loadParams(0ull, nullptr, cachingFlags, IAssetLoader::ELPF_NONE, m_logger.get(), m_loadCWD); + + auto bundle = m_assetMgr->getAsset(inAssetPath, loadParams); + + auto contents = bundle.getContents(); + if (contents.empty()) + { + logFail("Failed to load image with path %s, skipping!", (m_loadCWD / inAssetPath).c_str()); + return nullptr; + } + + core::splitFilename(inAssetPath.c_str(), nullptr, &outFilename, &outExtension); + + const auto& asset = contents[0]; + switch (asset->getAssetType()) + { + case IAsset::ET_IMAGE: + { + auto image = smart_refctd_ptr_static_cast(asset); + const auto format = image->getCreationParameters().format; + + ICPUImageView::SCreationParams viewParams = + { + .flags = ICPUImageView::E_CREATE_FLAGS::ECF_NONE, + .image = std::move(image), + .viewType = IImageView::E_TYPE::ET_2D, + .format = format, + .subresourceRange = { + .aspectMask = IImage::E_ASPECT_FLAGS::EAF_COLOR_BIT, + .baseMipLevel = 0u, + .levelCount = ICPUImageView::remaining_mip_levels, + .baseArrayLayer = 0u, + .layerCount = ICPUImageView::remaining_array_layers + } + }; + + cpuView = ICPUImageView::create(std::move(viewParams)); + } break; + + case IAsset::ET_IMAGE_VIEW: + cpuView = smart_refctd_ptr_static_cast(asset); + break; + default: + logFail("Failed to load ICPUImage or ICPUImageView got some other Asset Type, skipping!"); + return nullptr; + } + + auto converter = CAssetConverter::create({ .device = m_device.get() }); + + // Test the provision of a custom patch this time + CAssetConverter::patch_t patch(cpuView.get(), IImage::E_USAGE_FLAGS::EUF_SAMPLED_BIT); + + // We don't want to generate mip-maps for these images (YET), to ensure that we must override the default callbacks. + struct SInputs final : CAssetConverter::SInputs + { + inline uint8_t getMipLevelCount(const size_t groupCopyID, const ICPUImage* image, const CAssetConverter::patch_t& patch) const override + { + return image->getCreationParameters().mipLevels; + } + inline uint16_t needToRecomputeMips(const size_t groupCopyID, const ICPUImage* image, const CAssetConverter::patch_t& patch) const override + { + return 0b0u; + } + } inputs = {}; + std::get>(inputs.assets) = { &cpuView.get(),1 }; + std::get>(inputs.patches) = { &patch,1 }; + inputs.logger = m_logger.get(); + + // + auto reservation = converter->reserve(inputs); + + // get the created image view + auto gpuView = reservation.getGPUObjects().front().value; + + if (!gpuView) + return nullptr; + + gpuView->getCreationParameters().image->setObjectDebugName(inAssetPath.c_str()); + + // we should multi-buffer to not stall before renderpass recording but oh well + IQueue::SSubmitInfo::SCommandBufferInfo cmdbufInfo = { cmdbuf }; + + m_intendedSubmit.scratchCommandBuffers = { &cmdbufInfo,1 }; + CAssetConverter::SConvertParams params = {}; + params.transfer = &m_intendedSubmit; + params.utilities = m_utils.get(); + auto result = reservation.convert(params); + + if (result.copy() != IQueue::RESULT::SUCCESS) + return nullptr; + + return gpuView; + + } + + std::ifstream m_testPathsFile; + system::path m_loadCWD; + + smart_refctd_ptr m_scratchSemaphore; + smart_refctd_ptr m_semaphore; + uint64_t m_timelineValue; + + smart_refctd_ptr m_cmdPool; + SIntendedSubmitInfo m_intendedSubmit; + + smart_refctd_ptr m_cmdbuf; + core::smart_refctd_ptr m_pipeline; + core::smart_refctd_ptr m_descriptorSet; + core::smart_refctd_ptr m_outputBuffer; + + + uint32_t m_sampleCount = 10000; + uint32_t m_outputOffset; + +}; + +NBL_MAIN_FUNC(EnvmapImportanceSamplingTest) diff --git a/CMakeLists.txt b/CMakeLists.txt index d945c547a..76f07c94b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -111,6 +111,7 @@ if(NBL_BUILD_EXAMPLES) endif() add_subdirectory(74_QuantizedSequenceTests) + add_subdirectory(75_EnvmapImportanceSamplingTest) # add new examples *before* NBL_GET_ALL_TARGETS invocation, it gathers recursively all targets created so far in this subdirectory NBL_GET_ALL_TARGETS(TARGETS) From b3e27cb339b97214dab7939b265ac899ffdd4d03 Mon Sep 17 00:00:00 2001 From: kevyuu Date: Mon, 23 Feb 2026 22:01:46 +0700 Subject: [PATCH 2/2] Fix filename logging --- 75_EnvmapImportanceSamplingTest/main.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/75_EnvmapImportanceSamplingTest/main.cpp b/75_EnvmapImportanceSamplingTest/main.cpp index b5a9a1f16..4e006901f 100644 --- a/75_EnvmapImportanceSamplingTest/main.cpp +++ b/75_EnvmapImportanceSamplingTest/main.cpp @@ -322,20 +322,20 @@ class EnvmapImportanceSamplingTest final : public application_templates::BasicMu if (!checkEq(cachedOutput.L, directOutput.L) || !checkEq(cachedOutput.uv, directOutput.uv) || !checkEq(cachedOutput.pdf, directOutput.pdf) || !checkEq(cachedOutput.deferredPdf, directOutput.deferredPdf)) { - logFail("Failed similarity test between direct sampling and cached sampling. Direct Sampling = {uv = (%f, %f), L = (%f, %f %f), pdf = %f, deferredPdf = %f}, Cached Sampling = {uv = (%f, %f), L = (%f, %f %f), pdf = %f, deferredPdf = %f}", directOutput.uv.x, directOutput.uv.y, directOutput.L.x, directOutput.L.y, directOutput.L.z, directOutput.pdf, directOutput.deferredPdf, cachedOutput.uv.x, cachedOutput.uv.y, cachedOutput.L.x, cachedOutput.L.y, cachedOutput.L.z, cachedOutput.pdf, cachedOutput.pdf); + logFail("Failed similarity test between direct sampling and cached sampling for image %s. Direct Sampling = {uv = (%f, %f), L = (%f, %f %f), pdf = %f, deferredPdf = %f}, Cached Sampling = {uv = (%f, %f), L = (%f, %f %f), pdf = %f, deferredPdf = %f}", nextPath.c_str(), directOutput.uv.x, directOutput.uv.y, directOutput.L.x, directOutput.L.y, directOutput.L.z, directOutput.pdf, directOutput.deferredPdf, cachedOutput.uv.x, cachedOutput.uv.y, cachedOutput.L.x, cachedOutput.L.y, cachedOutput.L.z, cachedOutput.pdf, cachedOutput.pdf); } const auto& testOutput = directOutput; if (testOutput.jacobian < 0.05) continue; if (const auto diff = abs(1.0f - (testOutput.jacobian * testOutput.pdf)); diff > 0.05) { - m_logger->log("Failed similarity test of jacobian and pdf for image %s for sample number %d. xi = (%f, %f), uv = (%f, %f), Jacobian = %f, pdf = %f, difference = %f", ILogger::ELL_ERROR, "dummy", sample_i, testSample.xi.x, testSample.xi.y, testOutput.uv.x, testOutput.uv.y, testOutput.jacobian, testOutput.pdf, diff); + m_logger->log("Failed similarity test of jacobian and pdf for image %s for sample number %d. xi = (%f, %f), uv = (%f, %f), Jacobian = %f, pdf = %f, difference = %f", ILogger::ELL_ERROR, nextPath.c_str(), sample_i, testSample.xi.x, testSample.xi.y, testOutput.uv.x, testOutput.uv.y, testOutput.jacobian, testOutput.pdf, diff); continue; } if (const auto diff = abs(1.0f - (testOutput.jacobian * testOutput.deferredPdf)); diff > 0.05) { - m_logger->log("Failed similarity test of jacobian and pdf for image %s for sample number %d. xi = (%f, %f), uv = (%f, %f), Jacobian = %f, deferredPdf = %f, difference = %f", ILogger::ELL_ERROR, "dummy", sample_i, testSample.xi.x, testSample.xi.y, testOutput.uv.x, testOutput.uv.y, testOutput.jacobian, testOutput.deferredPdf, diff); + m_logger->log("Failed similarity test of jacobian and pdf for image %s for sample number %d. xi = (%f, %f), uv = (%f, %f), Jacobian = %f, deferredPdf = %f, difference = %f", ILogger::ELL_ERROR, nextPath.c_str(), sample_i, testSample.xi.x, testSample.xi.y, testOutput.uv.x, testOutput.uv.y, testOutput.jacobian, testOutput.deferredPdf, diff); } } }