diff --git a/31_HLSLPathTracer/app_resources/hlsl/example_common.hlsl b/31_HLSLPathTracer/app_resources/hlsl/example_common.hlsl index c531088da..8cf657420 100644 --- a/31_HLSLPathTracer/app_resources/hlsl/example_common.hlsl +++ b/31_HLSLPathTracer/app_resources/hlsl/example_common.hlsl @@ -20,6 +20,14 @@ enum ProceduralShapeType : uint16_t PST_RECTANGLE }; +enum IntersectMode : uint32_t +{ + IM_RAY_QUERY, + IM_RAY_TRACING, + IM_PROCEDURAL, + IM_ENVMAP, +}; + template struct Payload { @@ -235,6 +243,24 @@ struct Light ObjectID objectID; }; +template +struct EnvmapLight +{ + using spectral_type = float32_t3; + using this_type = EnvmapLight; + + static this_type create(NBL_CONST_REF_ARG(EnvMapT) envMap, NBL_CONST_REF_ARG(HierarchicalImageT) hierarchicalImage) + { + this_type retval; + retval.envMap = envMap; + retval.hierarchicalImage = hierarchicalImage; + return retval; + } + + EnvMapT envMap; + HierarchicalImageT hierarchicalImage; +}; + template struct Tolerance { diff --git a/31_HLSLPathTracer/app_resources/hlsl/next_event_estimator.hlsl b/31_HLSLPathTracer/app_resources/hlsl/next_event_estimator.hlsl index 64a06b16f..ed5f45907 100644 --- a/31_HLSLPathTracer/app_resources/hlsl/next_event_estimator.hlsl +++ b/31_HLSLPathTracer/app_resources/hlsl/next_event_estimator.hlsl @@ -548,4 +548,80 @@ struct NextEventEstimator light_type lights[scene_type::SCENE_LIGHT_COUNT]; }; +template +struct NextEventEstimator +{ + using scalar_type = typename Ray::scalar_type; + using vector2_type = vector; + using vector3_type = vector; + using ray_type = Ray; + using scene_type = Scene; + using light_type = Light; + using spectral_type = typename light_type::spectral_type; + using interaction_type = Aniso; + using quotient_pdf_type = sampling::quotient_and_pdf; + using sample_type = LightSample; + using ray_dir_info_type = typename sample_type::ray_dir_info_type; + + // affected by https://github.com/microsoft/DirectXShaderCompiler/issues/7007 + // NBL_CONSTEXPR_STATIC_INLINE PTPolygonMethod PolygonMethod = PPM; + enum : uint16_t { PolygonMethod = PPM }; + + spectral_type deferredEvalAndPdf(NBL_REF_ARG(scalar_type) pdf, NBL_CONST_REF_ARG(scene_type) scene, uint32_t lightID, NBL_CONST_REF_ARG(ray_type) ray) + { + const light_type light = lights[lightID]; + + vector2_type envmapUv = light.hierarchicalImage.inverseWarp_and_deferredPdf(pdf, ray.direction); + pdf *= (1.0 / scalar_type(lightCount)); + + spectral_type radiance; + light.envMap.get(envmapUv, radiance); + + return radiance; + } + + sample_type generate_and_quotient_and_pdf(NBL_REF_ARG(quotient_pdf_type) quotient_pdf, NBL_REF_ARG(scalar_type) newRayMaxT, NBL_CONST_REF_ARG(scene_type) scene, uint32_t lightID, NBL_CONST_REF_ARG(vector3_type) origin, NBL_CONST_REF_ARG(interaction_type) interaction, bool isBSDF, NBL_CONST_REF_ARG(vector3_type) xi, uint32_t depth) + { + newRayMaxT = numeric_limits::max; + + const light_type light = lights[lightID]; + + scalar_type pdf; + vector2_type envmapUv; + const vector3_type sampleL = light.hierarchicalImage.generate_and_pdf(pdf, envmapUv, xi.xy); + + ray_dir_info_type rayL; + if (hlsl::isinf(pdf)) + { + quotient_pdf = quotient_pdf_type::create(hlsl::promote(0.0), 0.0); + return sample_type::createInvalid(); + } + + const vector3_type N = interaction.getN(); + const scalar_type NdotL = nbl::hlsl::dot(N, sampleL); + + rayL.setDirection(sampleL); + sample_type L = sample_type::create(rayL, interaction.getT(), interaction.getB(), NdotL); + + newRayMaxT *= path_tracing::Tolerance::getEnd(depth); + + // Ray ray; + // ray.origin = origin; + // ray.direction = sampleL; + // spectral_type radiance = deferredEvalAndPdf(pdf, scene, 0, ray); + + pdf *= 1.0 / scalar_type(lightCount); + spectral_type radiance; + light.envMap.get(envmapUv, radiance); + + spectral_type quo = radiance / pdf; + + quotient_pdf = quotient_pdf_type::create(quo, pdf); + + return L; + } + + light_type lights[scene_type::SCENE_LIGHT_COUNT]; + uint32_t lightCount; +}; #endif diff --git a/31_HLSLPathTracer/app_resources/hlsl/pathtracer.hlsl b/31_HLSLPathTracer/app_resources/hlsl/pathtracer.hlsl new file mode 100644 index 000000000..413eb9660 --- /dev/null +++ b/31_HLSLPathTracer/app_resources/hlsl/pathtracer.hlsl @@ -0,0 +1,268 @@ +#ifndef _NBL_HLSL_PATHTRACING_INCLUDED_ +#define _NBL_HLSL_PATHTRACING_INCLUDED_ + +#include +#include +#include +#include +#include +#include +#include +#include "concepts.hlsl" + +namespace nbl +{ +namespace hlsl +{ +namespace path_tracing +{ + +template && concepts::RayGenerator && + concepts::Intersector && concepts::MaterialSystem && + concepts::NextEventEstimator && concepts::Accumulator && + concepts::Scene) +struct Unidirectional +{ + using this_t = Unidirectional; + using randgen_type = RandGen; + using raygen_type = RayGen; + using intersector_type = Intersector; + using material_system_type = MaterialSystem; + using nee_type = NextEventEstimator; + using scene_type = Scene; + + using scalar_type = typename MaterialSystem::scalar_type; + using vector3_type = vector; + using monochrome_type = vector; + using measure_type = typename MaterialSystem::measure_type; + using output_storage_type = typename Accumulator::output_storage_type; // ? + using sample_type = typename NextEventEstimator::sample_type; + using ray_dir_info_type = typename sample_type::ray_dir_info_type; + using ray_type = typename RayGen::ray_type; + using id_type = typename Intersector::id_type; + using light_type = Light; + using bxdfnode_type = typename MaterialSystem::bxdfnode_type; + using anisotropic_interaction_type = typename MaterialSystem::anisotropic_interaction_type; + using isotropic_interaction_type = typename anisotropic_interaction_type::isotropic_interaction_type; + using anisocache_type = typename MaterialSystem::anisocache_type; + using isocache_type = typename anisocache_type::isocache_type; + using quotient_pdf_type = typename NextEventEstimator::quotient_pdf_type; + + using diffuse_op_type = typename MaterialSystem::diffuse_op_type; + using conductor_op_type = typename MaterialSystem::conductor_op_type; + using dielectric_op_type = typename MaterialSystem::dielectric_op_type; + + vector3_type rand3d(uint32_t protoDimension, uint32_t _sample, uint32_t i) + { + using sequence_type = sampling::QuantizedSequence; + uint32_t address = glsl::bitfieldInsert(protoDimension, _sample, MAX_DEPTH_LOG2, MAX_SAMPLES_LOG2); + sequence_type tmpSeq = vk::RawBufferLoad(pSampleBuffer + (address + i) * sizeof(sequence_type)); + return sampling::decode(tmpSeq, randGen()); + } + + scalar_type getLuma(NBL_CONST_REF_ARG(vector3_type) col) + { + return hlsl::dot(colorspace::scRGBtoXYZ[1], col); + } + + // TODO: probably will only work with isotropic surfaces, need to do aniso + bool closestHitProgram(uint32_t depth, uint32_t _sample, NBL_REF_ARG(ray_type) ray, NBL_CONST_REF_ARG(scene_type) scene) + { + const id_type objectID = ray.objectID; + const vector3_type intersection = ray.origin + ray.direction * ray.intersectionT; + + uint32_t bsdfLightIDs = scene.getBsdfLightIDs(objectID); + vector3_type N = scene.getNormal(objectID, intersection); + N = nbl::hlsl::normalize(N); + ray_dir_info_type V; + V.setDirection(-ray.direction); + isotropic_interaction_type iso_interaction = isotropic_interaction_type::create(V, N); + iso_interaction.luminosityContributionHint = colorspace::scRGBtoXYZ[1]; + anisotropic_interaction_type interaction = anisotropic_interaction_type::create(iso_interaction); + + vector3_type throughput = ray.payload.throughput; + + // emissive + const uint32_t lightID = glsl::bitfieldExtract(bsdfLightIDs, 16, 16); + if (lightID != light_type::INVALID_ID) + { + float _pdf; + ray.payload.accumulation += nee.deferredEvalAndPdf(_pdf, scene, lightID, ray) * throughput / (1.0 + _pdf * _pdf * ray.payload.otherTechniqueHeuristic); + } + + const uint32_t bsdfID = glsl::bitfieldExtract(bsdfLightIDs, 0, 16); + if (bsdfID == bxdfnode_type::INVALID_ID) + return false; + + bxdfnode_type bxdf = materialSystem.bxdfs[bsdfID]; + + // TODO: ifdef kill diffuse specular paths + + const bool isBSDF = material_system_type::isBSDF(bxdf.materialType); + + vector3_type eps0 = rand3d(depth, _sample, 0u); + vector3_type eps1 = rand3d(depth, _sample, 1u); + + // thresholds + const scalar_type bxdfPdfThreshold = 0.0001; + const scalar_type lumaContributionThreshold = getLuma(colorspace::eotf::sRGB((vector3_type)1.0 / 255.0)); // OETF smallest perceptible value + const vector3_type throughputCIE_Y = colorspace::sRGBtoXYZ[1] * throughput; // TODO: this only works if spectral_type is dim 3 + const measure_type eta = bxdf.params.ior1 / bxdf.params.ior0; + const scalar_type monochromeEta = hlsl::dot(throughputCIE_Y, eta) / (throughputCIE_Y.r + throughputCIE_Y.g + throughputCIE_Y.b); // TODO: imaginary eta? + + // sample lights + const scalar_type neeProbability = 1.0; // BSDFNode_getNEEProb(bsdf); + scalar_type rcpChoiceProb; + sampling::PartitionRandVariable partitionRandVariable; + partitionRandVariable.leftProb = neeProbability; + if (!partitionRandVariable(eps0.z, rcpChoiceProb) && depth < 2u) + { + uint32_t randLightID = uint32_t(float32_t(randGen.rng()) / numeric_limits::max) * nee.lightCount; + quotient_pdf_type neeContrib_pdf; + scalar_type t; + sample_type nee_sample = nee.generate_and_quotient_and_pdf( + neeContrib_pdf, t, + scene, randLightID, intersection, interaction, + isBSDF, eps0, depth + ); + + // We don't allow non watertight transmitters in this renderer + bool validPath = nee_sample.getNdotL() > numeric_limits::min && nee_sample.isValid(); + // but if we allowed non-watertight transmitters (single water surface), it would make sense just to apply this line by itself + bxdf::fresnel::OrientedEtas orientedEta = bxdf::fresnel::OrientedEtas::create(interaction.getNdotV(), hlsl::promote(monochromeEta)); + anisocache_type _cache = anisocache_type::template create(interaction, nee_sample, orientedEta); + validPath = validPath && _cache.getAbsNdotH() >= 0.0; + bxdf.params.eta = monochromeEta; + + if (neeContrib_pdf.pdf < numeric_limits::max) + { + if (nbl::hlsl::any(hlsl::isnan(nee_sample.getL().getDirection()))) + ray.payload.accumulation += vector3_type(1000.f, 0.f, 0.f); + else if (nbl::hlsl::all((vector3_type)69.f == nee_sample.getL().getDirection())) + ray.payload.accumulation += vector3_type(0.f, 1000.f, 0.f); + else if (validPath) + { + // example only uses isotropic bxdfs + quotient_pdf_type bsdf_quotient_pdf = materialSystem.quotient_and_pdf(bxdf.materialType, bxdf.params, nee_sample, interaction.isotropic, _cache.iso_cache); + neeContrib_pdf.quotient *= bxdf.albedo * throughput * bsdf_quotient_pdf.quotient; + const scalar_type otherGenOverChoice = bsdf_quotient_pdf.pdf * rcpChoiceProb; + const scalar_type otherGenOverLightAndChoice = otherGenOverChoice / bsdf_quotient_pdf.pdf; + neeContrib_pdf.quotient *= otherGenOverChoice / (1.f + otherGenOverLightAndChoice * otherGenOverLightAndChoice); // balance heuristic + + // TODO: ifdef NEE only + // neeContrib_pdf.quotient *= otherGenOverChoice; + + ray_type nee_ray; + nee_ray.origin = intersection + nee_sample.getL().getDirection() * t * Tolerance::getStart(depth); + nee_ray.direction = nee_sample.getL().getDirection(); + nee_ray.intersectionT = t; + if (bsdf_quotient_pdf.pdf < numeric_limits::max && getLuma(neeContrib_pdf.quotient) > lumaContributionThreshold && intersector_type::traceRay(nee_ray, scene).id == -1) + ray.payload.accumulation += neeContrib_pdf.quotient; + } + } + } + + // return false; // NEE only + + // sample BSDF + scalar_type bxdfPdf; + vector3_type bxdfSample; + { + anisocache_type _cache; + sample_type bsdf_sample = materialSystem.generate(bxdf.materialType, bxdf.params, interaction, eps1, _cache); + + if (!bsdf_sample.isValid()) + return false; + + // example only uses isotropic bxdfs + // the value of the bsdf divided by the probability of the sample being generated + quotient_pdf_type bsdf_quotient_pdf = materialSystem.quotient_and_pdf(bxdf.materialType, bxdf.params, bsdf_sample, interaction.isotropic, _cache.iso_cache); + throughput *= bxdf.albedo * bsdf_quotient_pdf.quotient; + bxdfPdf = bsdf_quotient_pdf.pdf; + bxdfSample = bsdf_sample.getL().getDirection(); + } + + // additional threshold + const float lumaThroughputThreshold = lumaContributionThreshold; + if (bxdfPdf > bxdfPdfThreshold && getLuma(throughput) > lumaThroughputThreshold) + { + ray.payload.throughput = throughput; + scalar_type otherTechniqueHeuristic = neeProbability / bxdfPdf; // numerically stable, don't touch + ray.payload.otherTechniqueHeuristic = otherTechniqueHeuristic * otherTechniqueHeuristic; + + // trace new ray + ray.origin = intersection + bxdfSample * (1.0/*kSceneSize*/) * Tolerance::getStart(depth); + ray.direction = bxdfSample; + if ((PTPolygonMethod)nee_type::PolygonMethod == PPM_APPROX_PROJECTED_SOLID_ANGLE) + { + ray.normalAtOrigin = interaction.getN(); + ray.wasBSDFAtOrigin = isBSDF; + } + return true; + } + + return false; + } + + void missProgram(NBL_REF_ARG(ray_type) ray, NBL_CONST_REF_ARG(scene_type) scene) + { + vector3_type finalContribution = ray.payload.throughput; +#ifdef ENVMAP_LIGHT + float _pdf; + ray.payload.accumulation += nee.deferredEvalAndPdf(_pdf, scene, + 0, ray) * ray.payload.throughput / (1.0 + _pdf * _pdf * ray.payload.otherTechniqueHeuristic); +#else + const vector3_type kConstantEnvLightRadiance = vector3_type(0.15, 0.21, 0.3); // TODO: match spectral_type + finalContribution *= kConstantEnvLightRadiance; + ray.payload.accumulation += finalContribution; +#endif + // #endif + } + + // Li + void sampleMeasure(uint32_t sampleIndex, uint32_t maxDepth, NBL_CONST_REF_ARG(scene_type) scene, NBL_REF_ARG(Accumulator) accumulator) + { + //scalar_type meanLumaSq = 0.0; + vector3_type uvw = rand3d(0u, sampleIndex, 0u); + ray_type ray = rayGen.generate(uvw); + + // bounces + bool hit = true; + bool rayAlive = true; + for (int d = 1; (d <= maxDepth) && hit && rayAlive; d += 2) + { + ray.intersectionT = numeric_limits::max; + ray.objectID = intersector_type::traceRay(ray, scene); + + hit = ray.objectID.id != -1; + if (hit) + rayAlive = closestHitProgram(1, sampleIndex, ray, scene); + } + if (!hit) + missProgram(ray, scene); + + const uint32_t sampleCount = sampleIndex + 1; + accumulator.addSample(sampleCount, ray.payload.accumulation); + + // TODO: visualize high variance + + // TODO: russian roulette early exit? + } + + NBL_CONSTEXPR_STATIC_INLINE uint32_t MAX_DEPTH_LOG2 = 4u; + NBL_CONSTEXPR_STATIC_INLINE uint32_t MAX_SAMPLES_LOG2 = 10u; + + randgen_type randGen; + raygen_type rayGen; + material_system_type materialSystem; + nee_type nee; + + uint64_t pSampleBuffer; +}; + +} +} +} + +#endif diff --git a/31_HLSLPathTracer/app_resources/hlsl/render.comp.hlsl b/31_HLSLPathTracer/app_resources/hlsl/render.comp.hlsl new file mode 100644 index 000000000..bdafd6ca3 --- /dev/null +++ b/31_HLSLPathTracer/app_resources/hlsl/render.comp.hlsl @@ -0,0 +1,335 @@ +#include "nbl/builtin/hlsl/cpp_compat.hlsl" +#include "nbl/builtin/hlsl/glsl_compat/core.hlsl" +#include "nbl/builtin/hlsl/random/pcg.hlsl" +#include "nbl/builtin/hlsl/random/xoroshiro.hlsl" +#include "nbl/builtin/hlsl/sampling/warps/spherical.hlsl" +#include "nbl/builtin/hlsl/sampling/hierarchical_image.hlsl" +#ifdef PERSISTENT_WORKGROUPS +#include "nbl/builtin/hlsl/math/morton.hlsl" +#endif + +#include "nbl/builtin/hlsl/bxdf/reflection.hlsl" +#include "nbl/builtin/hlsl/bxdf/transmission.hlsl" + +// add these defines (one at a time) using -D argument to dxc +// #define SPHERE_LIGHT +// #define TRIANGLE_LIGHT +// #define RECTANGLE_LIGHT + +#include +#include + +#ifdef RWMC_ENABLED +#include +#include +#endif + +#ifdef RWMC_ENABLED +[[vk::push_constant]] RenderRWMCPushConstants pc; +#else +[[vk::push_constant]] RenderPushConstants pc; +#endif + +[[vk::combinedImageSampler]] [[vk::binding(0, 2)]] Texture2D envMap; // unused +[[vk::combinedImageSampler]] [[vk::binding(0, 2)]] SamplerState envSampler; + +[[vk::combinedImageSampler]] [[vk::binding(2, 2)]] Texture2D scramblebuf; +[[vk::combinedImageSampler]] [[vk::binding(2, 2)]] SamplerState scrambleSampler; + +[[vk::combinedImageSampler]] [[vk::binding(3, 2)]] Texture2D lumaMap; +[[vk::combinedImageSampler]] [[vk::binding(3, 2)]] SamplerState lumaSampler; +[[vk::binding(4, 2)]] Texture2D warpMap; + +[[vk::image_format("rgba16f")]] [[vk::binding(0)]] RWTexture2DArray outImage; +[[vk::image_format("rgba16f")]] [[vk::binding(1)]] RWTexture2DArray cascade; + +#include "example_common.hlsl" +#include "scene.hlsl" +#include "rand_gen.hlsl" +#include "ray_gen.hlsl" +#include "intersector.hlsl" +#include "material_system.hlsl" +#include "next_event_estimator.hlsl" +#include "accumulator.hlsl" +#include "pathtracer.hlsl" + +using namespace nbl; +using namespace hlsl; + +#ifdef SPHERE_LIGHT +NBL_CONSTEXPR ProceduralShapeType LIGHT_TYPE = PST_SPHERE; +#endif +#ifdef TRIANGLE_LIGHT +NBL_CONSTEXPR ProceduralShapeType LIGHT_TYPE = PST_TRIANGLE; +#endif +#ifdef RECTANGLE_LIGHT +NBL_CONSTEXPR ProceduralShapeType LIGHT_TYPE = PST_RECTANGLE; +#endif +#ifdef ENVMAP_LIGHT +NBL_CONSTEXPR ProceduralShapeType LIGHT_TYPE = PST_NONE; +#endif + +NBL_CONSTEXPR path_tracing::PTPolygonMethod POLYGON_METHOD = path_tracing::PPM_SOLID_ANGLE; + +int32_t2 getCoordinates() +{ + uint32_t width, height, imageArraySize; + outImage.GetDimensions(width, height, imageArraySize); + return int32_t2(glsl::gl_GlobalInvocationID().x % width, glsl::gl_GlobalInvocationID().x / width); +} + +float32_t2 getTexCoords() +{ + uint32_t width, height, imageArraySize; + outImage.GetDimensions(width, height, imageArraySize); + int32_t2 iCoords = getCoordinates(); + return float32_t2(float(iCoords.x) / width, 1.0 - float(iCoords.y) / height); +} + +using spectral_t = vector; +using ray_dir_info_t = bxdf::ray_dir_info::SBasic; +using iso_interaction = bxdf::surface_interactions::SIsotropic; +using aniso_interaction = bxdf::surface_interactions::SAnisotropic; +using sample_t = bxdf::SLightSample; +using iso_cache = bxdf::SIsotropicMicrofacetCache; +using aniso_cache = bxdf::SAnisotropicMicrofacetCache; +using quotient_pdf_t = sampling::quotient_and_pdf; + +using iso_config_t = bxdf::SConfiguration; +using iso_microfacet_config_t = bxdf::SMicrofacetConfiguration; + +using diffuse_bxdf_type = bxdf::reflection::SOrenNayar; +using conductor_bxdf_type = bxdf::reflection::SGGXIsotropic; +using dielectric_bxdf_type = bxdf::transmission::SGGXDielectricIsotropic; +using iri_conductor_bxdf_type = bxdf::reflection::SIridescent; +using iri_dielectric_bxdf_type = bxdf::transmission::SIridescent; + +using ray_type = Ray; + +#ifdef ENVMAP_LIGHT +struct EnvmapAccessor +{ + template && + concepts::same_as + ) + void get(IndexT index, NBL_REF_ARG(ValT) val) + { + val = envMap.SampleLevel(envSampler, index, 0); + } +}; + +struct LuminanceAccessor +{ + template && + concepts::same_as + ) + void get(IndexT index, NBL_REF_ARG(ValT) val) + { + val = lumaMap.SampleLevel(lumaSampler, index, 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 + ); + } +}; + +using hierarchical_image_type = sampling::HierarchicalImage; +using light_type = EnvmapLight; +#else +using light_type = Light; +#endif + +using bxdfnode_type = BxDFNode; +using scene_type = Scene; +using randgen_type = RandGen::Uniform3D; +using raygen_type = RayGen::Basic; +using intersector_type = Intersector; +using material_system_type = MaterialSystem; + +#ifdef ENVMAP_LIGHT +using nee_type = NextEventEstimator; +#else +using nee_type = NextEventEstimator; +#endif + +#ifdef RWMC_ENABLED +using accumulator_type = rwmc::CascadeAccumulator; +#else +using accumulator_type = Accumulator::DefaultAccumulator; +#endif + +using pathtracer_type = path_tracing::Unidirectional; + +#ifdef SPHERE_LIGHT +static const Shape spheres[scene_type::SCENE_LIGHT_COUNT] = { + Shape::create(float3(-1.5, 1.5, 0.0), 0.3, bxdfnode_type::INVALID_ID, 0u) +}; +#endif + +#ifdef TRIANGLE_LIGHT +static const Shape triangles[scene_type::SCENE_LIGHT_COUNT] = { + Shape::create(float3(-1.8,0.35,0.3) * 10.0, float3(-1.2,0.35,0.0) * 10.0, float3(-1.5,0.8,-0.3) * 10.0, bxdfnode_type::INVALID_ID, 0u) +}; +#endif + +#ifdef RECTANGLE_LIGHT +static const Shape rectangles[scene_type::SCENE_LIGHT_COUNT] = { + Shape::create(float3(-3.8,0.35,1.3), normalize(float3(2,0,-1))*7.0, normalize(float3(2,-5,4))*0.1, bxdfnode_type::INVALID_ID, 0u) +}; +#endif + +#ifdef ENVMAP_LIGHT +static const EnvmapAccessor envmapAccessor; +static const LuminanceAccessor luminanceAccessor; +static const WarpAccessor warpAccessor; +static const hierarchical_image_type hierarchicalImage = hierarchical_image_type::create(luminanceAccessor, warpAccessor, uint32_t2(2048, 1024), pc.avgLuma); +static const light_type light = light_type::create(envmapAccessor, hierarchicalImage); +#else +static const light_type light = +light_type::create(LightEminence, +#ifdef SPHERE_LIGHT + scene_type::SCENE_SPHERE_COUNT, +#else + 0u, +#endif + IM_PROCEDURAL, LIGHT_TYPE); +#endif + +static const bxdfnode_type bxdfs[scene_type::SCENE_BXDF_COUNT] = { + bxdfnode_type::create(MaterialType::DIFFUSE, false, float2(0,0), spectral_t(0.8,0.8,0.8)), + bxdfnode_type::create(MaterialType::DIFFUSE, false, float2(0,0), spectral_t(0.8,0.4,0.4)), + bxdfnode_type::create(MaterialType::DIFFUSE, false, float2(0,0), spectral_t(0.4,0.8,0.4)), + bxdfnode_type::create(MaterialType::CONDUCTOR, false, float2(0,0), spectral_t(1.02,1.02,1.3), spectral_t(1.0,1.0,2.0)), + bxdfnode_type::create(MaterialType::CONDUCTOR, false, float2(0,0), spectral_t(1.02,1.3,1.02), spectral_t(1.0,2.0,1.0)), + bxdfnode_type::create(MaterialType::CONDUCTOR, false, float2(0.15,0.15), spectral_t(1.02,1.3,1.02), spectral_t(1.0,2.0,1.0)), + bxdfnode_type::create(MaterialType::DIELECTRIC, false, float2(0.0625,0.0625), spectral_t(1,1,1), spectral_t(1.4,1.45,1.5)), + bxdfnode_type::create(MaterialType::IRIDESCENT_CONDUCTOR, false, 0.0, 505.0, spectral_t(1.39,1.39,1.39), spectral_t(1.2,1.2,1.2), spectral_t(0.5,0.5,0.5)), + bxdfnode_type::create(MaterialType::IRIDESCENT_DIELECTRIC, false, 0.0, 400.0, spectral_t(1.7,1.7,1.7), spectral_t(1.0,1.0,1.0), spectral_t(0,0,0)) +}; + +RenderPushConstants retireveRenderPushConstants() +{ +#ifdef RWMC_ENABLED + return pc.renderPushConstants; +#else + return pc; +#endif +} + +[numthreads(RenderWorkgroupSize, 1, 1)] +void main(uint32_t3 threadID : SV_DispatchThreadID) +{ + const RenderPushConstants renderPushConstants = retireveRenderPushConstants(); + + uint32_t width, height, imageArraySize; + outImage.GetDimensions(width, height, imageArraySize); +#ifdef PERSISTENT_WORKGROUPS + uint32_t virtualThreadIndex; + [loop] + for (uint32_t virtualThreadBase = glsl::gl_WorkGroupID().x * RenderWorkgroupSize; virtualThreadBase < 1920*1080; virtualThreadBase += glsl::gl_NumWorkGroups().x * RenderWorkgroupSize) // not sure why 1280*720 doesn't cover draw surface + { + virtualThreadIndex = virtualThreadBase + glsl::gl_LocalInvocationIndex().x; + const int32_t2 coords = (int32_t2)math::Morton::decode2d(virtualThreadIndex); +#else + const int32_t2 coords = getCoordinates(); +#endif + float32_t2 texCoord = float32_t2(coords) / float32_t2(width, height); + texCoord.y = 1.0 - texCoord.y; + + if (false == (all((int32_t2)0 < coords)) && all(int32_t2(width, height) < coords)) { +#ifdef PERSISTENT_WORKGROUPS + continue; +#else + return; +#endif + } + + if (((renderPushConstants.depth - 1) >> MAX_DEPTH_LOG2) > 0 || ((renderPushConstants.sampleCount - 1) >> MAX_SAMPLES_LOG2) > 0) + { + float32_t4 pixelCol = float32_t4(1.0,0.0,0.0,1.0); + outImage[uint3(coords.x, coords.y, 0)] = pixelCol; +#ifdef PERSISTENT_WORKGROUPS + continue; +#else + return; +#endif + } + + int flatIdx = glsl::gl_GlobalInvocationID().y * glsl::gl_NumWorkGroups().x * RenderWorkgroupSize + glsl::gl_GlobalInvocationID().x; + + // set up scene + scene_type scene; +#ifdef SPHERE_LIGHT + scene.light_spheres[0] = spheres[0]; +#endif +#ifdef TRIANGLE_LIGHT + scene.light_triangles[0] = triangles[0]; +#endif +#ifdef RECTANGLE_LIGHT + scene.light_rectangles[0] = rectangles[0]; +#endif + + // set up path tracer + pathtracer_type pathtracer; + pathtracer.randGen = randgen_type::construct(scramblebuf[coords].rg); // TODO concept this create + + uint2 scrambleDim; + scramblebuf.GetDimensions(scrambleDim.x, scrambleDim.y); + float32_t2 pixOffsetParam = (float2)1.0 / float2(scrambleDim); + + float32_t4 NDC = float4(texCoord * float2(2.0, -2.0) + float2(-1.0, 1.0), 0.0, 1.0); + float32_t3 camPos; + { + float4 tmp = mul(renderPushConstants.invMVP, NDC); + camPos = tmp.xyz / tmp.w; + NDC.z = 1.0; + } + + scene.updateLight(renderPushConstants.generalPurposeLightMatrix); + pathtracer.rayGen = raygen_type::create(pixOffsetParam, camPos, NDC, renderPushConstants.invMVP); + pathtracer.nee.lights[0] = light; + pathtracer.nee.lightCount = scene_type::SCENE_LIGHT_COUNT; + pathtracer.materialSystem.bxdfs = bxdfs; + pathtracer.materialSystem.bxdfCount = scene_type::SCENE_BXDF_COUNT; + pathtracer.pSampleBuffer = renderPushConstants.pSampleSequence; + +#ifdef RWMC_ENABLED + const float32_t2 unpacked = hlsl::unpackHalf2x16(pc.packedSplattingParams); + rwmc::SplattingParameters splattingParameters = rwmc::SplattingParameters::create(unpacked[0], unpacked[1], CascadeCount); + accumulator_type accumulator = accumulator_type::create(splattingParameters); +#else + accumulator_type accumulator = accumulator_type::create(); +#endif + // path tracing loop + for(int i = 0; i < renderPushConstants.sampleCount; ++i) + pathtracer.sampleMeasure(i, renderPushConstants.depth, scene, accumulator); + +#ifdef RWMC_ENABLED + for (uint32_t i = 0; i < CascadeCount; ++i) + cascade[uint3(coords.x, coords.y, i)] = float32_t4(accumulator.accumulation.data[i], 1.0f); +#else + outImage[uint3(coords.x, coords.y, 0)] = float32_t4(accumulator.accumulation, 1.0); +#endif + +#ifdef PERSISTENT_WORKGROUPS + } +#endif +} \ No newline at end of file diff --git a/31_HLSLPathTracer/app_resources/hlsl/render_common.hlsl b/31_HLSLPathTracer/app_resources/hlsl/render_common.hlsl index 1c69ebe08..d2565fb0b 100644 --- a/31_HLSLPathTracer/app_resources/hlsl/render_common.hlsl +++ b/31_HLSLPathTracer/app_resources/hlsl/render_common.hlsl @@ -36,7 +36,8 @@ struct RenderPushConstants } uint64_t pSampleSequence; - float32_t4x4 invMVP; + float avgLuma; + float32_t4x4 invMVP; float32_t3 lightX; float32_t3 lightY; float32_t lightZscale; diff --git a/31_HLSLPathTracer/app_resources/hlsl/scene.hlsl b/31_HLSLPathTracer/app_resources/hlsl/scene.hlsl new file mode 100644 index 000000000..633857c6b --- /dev/null +++ b/31_HLSLPathTracer/app_resources/hlsl/scene.hlsl @@ -0,0 +1,252 @@ +#ifndef _NBL_HLSL_EXT_PATHTRACING_SCENE_INCLUDED_ +#define _NBL_HLSL_EXT_PATHTRACING_SCENE_INCLUDED_ + +#include "common.hlsl" +#include "example_common.hlsl" + +using namespace nbl; +using namespace hlsl; + +struct SceneBase +{ + using scalar_type = float; + using vector3_type = vector; + using light_type = Light; + + NBL_CONSTEXPR_STATIC_INLINE uint32_t SCENE_SPHERE_COUNT = 10u; + NBL_CONSTEXPR_STATIC_INLINE uint32_t SCENE_LIGHT_COUNT = 1u; + NBL_CONSTEXPR_STATIC_INLINE uint32_t SCENE_BXDF_COUNT = 9u; + + static const Shape scene_spheres[SCENE_SPHERE_COUNT]; +}; + +const Shape SceneBase::scene_spheres[SCENE_SPHERE_COUNT] = { + Shape::create(float3(0.0, -100.5, -1.0), 100.0, 0u, SceneBase::light_type::INVALID_ID), + Shape::create(float3(2.0, 0.0, -1.0), 0.5, 1u, SceneBase::light_type::INVALID_ID), + Shape::create(float3(0.0, 0.0, -1.0), 0.5, 2u, SceneBase::light_type::INVALID_ID), + Shape::create(float3(-2.0, 0.0, -1.0), 0.5, 3u, SceneBase::light_type::INVALID_ID), + Shape::create(float3(2.0, 0.0, 1.0), 0.5, 4u, SceneBase::light_type::INVALID_ID), + Shape::create(float3(0.0, 0.0, 1.0), 0.5, 4u, SceneBase::light_type::INVALID_ID), + Shape::create(float3(-2.0, 0.0, 1.0), 0.5, 5u, SceneBase::light_type::INVALID_ID), + Shape::create(float3(0.5, 1.0, 0.5), 0.5, 6u, SceneBase::light_type::INVALID_ID), + Shape::create(float3(-4.0, 0.0, 1.0), 0.5, 7u, SceneBase::light_type::INVALID_ID), + Shape::create(float3(-4.0, 0.0, -1.0), 0.5, 8u, SceneBase::light_type::INVALID_ID) +}; + +template +struct Scene; + +template<> +struct Scene : SceneBase +{ + using scalar_type = float; + using vector3_type = vector; + using this_t = Scene; + using base_t = SceneBase; + using id_type = ObjectID; + + NBL_CONSTEXPR_STATIC_INLINE uint32_t SphereCount = base_t::SCENE_SPHERE_COUNT + base_t::SCENE_LIGHT_COUNT; + NBL_CONSTEXPR_STATIC_INLINE uint32_t TriangleCount = 0u; + NBL_CONSTEXPR_STATIC_INLINE uint32_t RectangleCount = 0u; + + Shape light_spheres[1]; + Shape light_triangles[1]; + Shape light_rectangles[1]; + + Shape getSphere(uint32_t idx) + { + assert(idx < SphereCount); + if (idx < base_t::SCENE_SPHERE_COUNT) + return base_t::scene_spheres[idx]; + else + return light_spheres[idx-base_t::SCENE_SPHERE_COUNT]; + } + + Shape getTriangle(uint32_t idx) + { + assert(false); + return light_triangles[0]; + } + + Shape getRectangle(uint32_t idx) + { + assert(false); + return light_rectangles[0]; + } + + void updateLight(NBL_CONST_REF_ARG(float32_t3x4) generalPurposeLightMatrix) + { + } + + uint32_t getBsdfLightIDs(NBL_CONST_REF_ARG(id_type) objectID) + { + assert(false); + return getSphere(objectID.id).bsdfLightIDs; + } + + vector3_type getNormal(NBL_CONST_REF_ARG(id_type) objectID, NBL_CONST_REF_ARG(vector3_type) intersection) + { + assert(objectID.shapeType == PST_SPHERE); + return getSphere(objectID.id).getNormal(intersection); + } +}; + +template<> +struct Scene : SceneBase +{ + using scalar_type = float; + using vector3_type = vector; + using this_t = Scene; + using base_t = SceneBase; + using id_type = ObjectID; + + NBL_CONSTEXPR_STATIC_INLINE uint32_t SphereCount = base_t::SCENE_SPHERE_COUNT + base_t::SCENE_LIGHT_COUNT; + NBL_CONSTEXPR_STATIC_INLINE uint32_t TriangleCount = 0u; + NBL_CONSTEXPR_STATIC_INLINE uint32_t RectangleCount = 0u; + + Shape light_spheres[1]; + Shape light_triangles[1]; + Shape light_rectangles[1]; + + Shape getSphere(uint32_t idx) + { + assert(idx < SphereCount); + if (idx < base_t::SCENE_SPHERE_COUNT) + return base_t::scene_spheres[idx]; + else + return light_spheres[idx-base_t::SCENE_SPHERE_COUNT]; + } + + Shape getTriangle(uint32_t idx) + { + assert(false); + return light_triangles[0]; + } + + Shape getRectangle(uint32_t idx) + { + assert(false); + return light_rectangles[0]; + } + + void updateLight(NBL_CONST_REF_ARG(float32_t3x4) generalPurposeLightMatrix) + { + light_spheres[0].updateTransform(generalPurposeLightMatrix); + } + + uint32_t getBsdfLightIDs(NBL_CONST_REF_ARG(id_type) objectID) + { + assert(objectID.shapeType == PST_SPHERE); + return getSphere(objectID.id).bsdfLightIDs; + } + + vector3_type getNormal(NBL_CONST_REF_ARG(id_type) objectID, NBL_CONST_REF_ARG(vector3_type) intersection) + { + assert(objectID.shapeType == PST_SPHERE); + return getSphere(objectID.id).getNormal(intersection); + } +}; + +template<> +struct Scene : SceneBase +{ + using scalar_type = float; + using vector3_type = vector; + using this_t = Scene; + using base_t = SceneBase; + using id_type = ObjectID; + + NBL_CONSTEXPR_STATIC_INLINE uint32_t SphereCount = base_t::SCENE_SPHERE_COUNT; + NBL_CONSTEXPR_STATIC_INLINE uint32_t TriangleCount = base_t::SCENE_LIGHT_COUNT; + NBL_CONSTEXPR_STATIC_INLINE uint32_t RectangleCount = 0u; + + Shape light_spheres[1]; + Shape light_triangles[1]; + Shape light_rectangles[1]; + + Shape getSphere(uint32_t idx) + { + assert(idx < SphereCount); + return base_t::scene_spheres[idx]; + } + Shape getTriangle(uint32_t idx) + { + assert(idx < TriangleCount); + return light_triangles[idx]; + } + Shape getRectangle(uint32_t idx) + { + assert(false); + return light_rectangles[0]; + } + + void updateLight(NBL_CONST_REF_ARG(float32_t3x4) generalPurposeLightMatrix) + { + light_triangles[0].updateTransform(generalPurposeLightMatrix); + } + + uint32_t getBsdfLightIDs(NBL_CONST_REF_ARG(id_type) objectID) + { + assert(objectID.shapeType == PST_SPHERE || objectID.shapeType == PST_TRIANGLE); + return objectID.shapeType == PST_SPHERE ? getSphere(objectID.id).bsdfLightIDs : getTriangle(objectID.id).bsdfLightIDs; + } + + vector3_type getNormal(NBL_CONST_REF_ARG(id_type) objectID, NBL_CONST_REF_ARG(vector3_type) intersection) + { + assert(objectID.shapeType == PST_SPHERE || objectID.shapeType == PST_TRIANGLE); + return objectID.shapeType == PST_SPHERE ? getSphere(objectID.id).getNormal(intersection) : getTriangle(objectID.id).getNormalTimesArea(); + } +}; + +template<> +struct Scene : SceneBase +{ + using scalar_type = float; + using vector3_type = vector; + using this_t = Scene; + using base_t = SceneBase; + using id_type = ObjectID; + + NBL_CONSTEXPR_STATIC_INLINE uint32_t SphereCount = base_t::SCENE_SPHERE_COUNT; + NBL_CONSTEXPR_STATIC_INLINE uint32_t TriangleCount = 0u; + NBL_CONSTEXPR_STATIC_INLINE uint32_t RectangleCount = base_t::SCENE_LIGHT_COUNT; + + Shape light_spheres[1]; + Shape light_triangles[1]; + Shape light_rectangles[1]; + + Shape getSphere(uint32_t idx) + { + assert(idx < SphereCount); + return base_t::scene_spheres[idx]; + } + Shape getTriangle(uint32_t idx) + { + assert(false); + return light_triangles[0]; + } + Shape getRectangle(uint32_t idx) + { + assert(idx < RectangleCount); + return light_rectangles[idx]; + } + + void updateLight(NBL_CONST_REF_ARG(float32_t3x4) generalPurposeLightMatrix) + { + light_rectangles[0].updateTransform(generalPurposeLightMatrix); + } + + uint32_t getBsdfLightIDs(NBL_CONST_REF_ARG(id_type) objectID) + { + assert(objectID.shapeType == PST_SPHERE || objectID.shapeType == PST_RECTANGLE); + return objectID.shapeType == PST_SPHERE ? getSphere(objectID.id).bsdfLightIDs : getRectangle(objectID.id).bsdfLightIDs; + } + + vector3_type getNormal(NBL_CONST_REF_ARG(id_type) objectID, NBL_CONST_REF_ARG(vector3_type) intersection) + { + assert(objectID.shapeType == PST_SPHERE || objectID.shapeType == PST_RECTANGLE); + return objectID.shapeType == PST_SPHERE ? getSphere(objectID.id).getNormal(intersection) : getRectangle(objectID.id).getNormalTimesArea(); + } +}; + +#endif diff --git a/31_HLSLPathTracer/main.cpp b/31_HLSLPathTracer/main.cpp index 047900d41..4b0af2fe0 100644 --- a/31_HLSLPathTracer/main.cpp +++ b/31_HLSLPathTracer/main.cpp @@ -11,6 +11,8 @@ #include "nbl/this_example/transform.hpp" #include "nbl/this_example/render_variant_strings.hpp" #include "nbl/ext/FullScreenTriangle/FullScreenTriangle.h" +#include "nbl/ext/EnvmapImportanceSampling/CEnvmapImportanceSampling.h" +#include "nbl/builtin/hlsl/surface_transform.h" #include "nbl/ext/ScreenShot/ScreenShot.h" #include "nbl/builtin/hlsl/math/thin_lens_projection.hlsl" @@ -59,12 +61,55 @@ class HLSLComputePathtracer final : public SimpleWindowedApplication, public Bui using asset_base_t = BuiltinResourcesApplication; using clock_t = std::chrono::steady_clock; +<<<<<<< HEAD + enum E_LIGHT_GEOMETRY : uint8_t + { + ELG_SPHERE, + // ELG_TRIANGLE, + // ELG_RECTANGLE, + ELG_ENVMAP, + ELG_COUNT + }; + + enum E_RENDER_MODE : uint8_t + { + ERM_GLSL, + ERM_HLSL, + // ERM_CHECKERED, + ERM_COUNT + }; + + constexpr static inline uint32_t2 WindowDimensions = { 1280, 720 }; + constexpr static inline uint32_t MaxFramesInFlight = 5; + constexpr static inline uint32_t MaxDescriptorCount = 256u; + constexpr static inline uint8_t MaxUITextureCount = 1u; + static inline std::string DefaultImagePathsFile = "envmap/envmap_2.exr"; + static inline std::string OwenSamplerFilePath = "owen_sampler_buffer.bin"; + static inline std::string PTHLSLShaderPath = "app_resources/hlsl/render.comp.hlsl"; + static inline std::array PTHLSLShaderVariants = { + "SPHERE_LIGHT", + "ENVMAP_LIGHT", + }; + static inline std::string ResolveShaderPath = "app_resources/hlsl/resolve.comp.hlsl"; + static inline std::string PresentShaderPath = "app_resources/hlsl/present.frag.hlsl"; + + const char* shaderNames[E_LIGHT_GEOMETRY::ELG_COUNT] = { + "ELG_SPHERE", + "ELG_ENVMAP", + }; + + const char* shaderTypes[E_RENDER_MODE::ERM_COUNT] = { + "ERM_GLSL", + "ERM_HLSL" + }; +======= constexpr static inline uint32_t2 WindowDimensions = { 1280, 720 }; constexpr static inline uint32_t MaxFramesInFlight = 5; static constexpr std::string_view BuildConfigName = PATH_TRACER_BUILD_CONFIG_NAME; static constexpr uint32_t CiFramesBeforeCapture = 3u; static constexpr std::string_view RuntimeConfigFilename = "path_tracer.runtime.json"; static inline std::string DefaultImagePathsFile = "envmap/envmap_0.exr"; +>>>>>>> master public: inline HLSLComputePathtracer(const path& _localInputCWD, const path& _localOutputCWD, const path& _sharedInputCWD, const path& _sharedOutputCWD) @@ -262,7 +307,12 @@ class HLSLComputePathtracer final : public SimpleWindowedApplication, public Bui return gpuDS; }; +<<<<<<< HEAD + std::array descriptorSet0Bindings = {}; + std::array descriptorSet2Bindings = {}; +======= std::array descriptorSetBindings = {}; +>>>>>>> master std::array presentDescriptorSetBindings; descriptorSetBindings[0] = { @@ -289,9 +339,40 @@ class HLSLComputePathtracer final : public SimpleWindowedApplication, public Bui .count = 1u, .immutableSamplers = nullptr }; +<<<<<<< HEAD + + descriptorSet2Bindings[0] = { + .binding = 0u, + .type = nbl::asset::IDescriptor::E_TYPE::ET_COMBINED_IMAGE_SAMPLER, + .createFlags = ICPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS::ECF_NONE, + .stageFlags = IShader::E_SHADER_STAGE::ESS_COMPUTE, + .count = 1u, + .immutableSamplers = nullptr + }; + descriptorSet2Bindings[1] = { + .binding = 2u, + .type = nbl::asset::IDescriptor::E_TYPE::ET_COMBINED_IMAGE_SAMPLER, +======= descriptorSetBindings[3] = { .binding = 3u, .type = nbl::asset::IDescriptor::E_TYPE::ET_STORAGE_IMAGE, +>>>>>>> master + .createFlags = ICPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS::ECF_NONE, + .stageFlags = IShader::E_SHADER_STAGE::ESS_COMPUTE, + .count = 1u, + .immutableSamplers = nullptr + }; + descriptorSet2Bindings[2] = { + .binding = 3u, + .type = nbl::asset::IDescriptor::E_TYPE::ET_COMBINED_IMAGE_SAMPLER, + .createFlags = ICPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS::ECF_NONE, + .stageFlags = IShader::E_SHADER_STAGE::ESS_COMPUTE, + .count = 1u, + .immutableSamplers = nullptr + }; + descriptorSet2Bindings[3] = { + .binding = 4u, + .type = nbl::asset::IDescriptor::E_TYPE::ET_SAMPLED_IMAGE, .createFlags = ICPUDescriptorSetLayout::SBinding::E_CREATE_FLAGS::ECF_NONE, .stageFlags = IShader::E_SHADER_STAGE::ESS_COMPUTE, .count = 1u, @@ -307,7 +388,12 @@ class HLSLComputePathtracer final : public SimpleWindowedApplication, public Bui .immutableSamplers = &defaultSampler }; +<<<<<<< HEAD + auto cpuDescriptorSetLayout0 = make_smart_refctd_ptr(descriptorSet0Bindings); + auto cpuDescriptorSetLayout2 = make_smart_refctd_ptr(descriptorSet2Bindings); +======= auto cpuDescriptorSetLayout = make_smart_refctd_ptr(descriptorSetBindings); +>>>>>>> master auto gpuDescriptorSetLayout = convertDSLayoutCPU2GPU(cpuDescriptorSetLayout); auto gpuPresentDescriptorSetLayout = m_device->createDescriptorSetLayout(presentDescriptorSetBindings); @@ -327,6 +413,181 @@ class HLSLComputePathtracer final : public SimpleWindowedApplication, public Bui const uint32_t deviceMinSubgroupSize = m_device->getPhysicalDevice()->getLimits().minSubgroupSize; m_requiredSubgroupSize = static_cast(hlsl::log2(float(deviceMinSubgroupSize))); +<<<<<<< HEAD + auto source = smart_refctd_ptr_static_cast(assets[0]); + // The down-cast should not fail! + assert(source); + + auto compiler = make_smart_refctd_ptr(smart_refctd_ptr(m_system)); + CGLSLCompiler::SOptions options = {}; + options.stage = IShader::E_SHADER_STAGE::ESS_COMPUTE; // should be compute + options.preprocessorOptions.targetSpirvVersion = m_device->getPhysicalDevice()->getLimits().spirvVersion; + options.spirvOptimizer = nullptr; +#ifndef _NBL_DEBUG + ISPIRVOptimizer::E_OPTIMIZER_PASS optPasses = ISPIRVOptimizer::EOP_STRIP_DEBUG_INFO; + auto opt = make_smart_refctd_ptr(std::span(&optPasses, 1)); + options.spirvOptimizer = opt.get(); +#endif + options.debugInfoFlags |= IShaderCompiler::E_DEBUG_INFO_FLAGS::EDIF_LINE_BIT; + options.preprocessorOptions.sourceIdentifier = source->getFilepathHint(); + options.preprocessorOptions.logger = m_logger.get(); + options.preprocessorOptions.includeFinder = compiler->getDefaultIncludeFinder(); + + const IShaderCompiler::SMacroDefinition persistentDefine = { "PERSISTENT_WORKGROUPS", "1" }; + if (persistentWorkGroups) + options.preprocessorOptions.extraDefines = { &persistentDefine, &persistentDefine + 1 }; + + source = compiler->compileToSPIRV((const char*)source->getContent()->getPointer(), options); + + // this time we skip the use of the asset converter since the ICPUShader->IGPUShader path is quick and simple + auto shader = m_device->compileShader({ source.get(), nullptr, nullptr, nullptr }); + if (!shader) + { + m_logger->log("GLSL shader creationed failed: %s!", ILogger::ELL_ERROR, pathToShader); + std::exit(-1); + } + + return shader; + }; + + auto loadAndCompileHLSLShader = [&](const std::string& pathToShader, const std::string& defineMacro = "", bool persistentWorkGroups = false, bool rwmc = false) -> smart_refctd_ptr + { + IAssetLoader::SAssetLoadParams lp = {}; + lp.workingDirectory = localInputCWD; + auto assetBundle = m_assetMgr->getAsset(pathToShader, lp); + const auto assets = assetBundle.getContents(); + if (assets.empty()) + { + m_logger->log("Could not load shader: ", ILogger::ELL_ERROR, pathToShader); + std::exit(-1); + } + + auto source = smart_refctd_ptr_static_cast(assets[0]); + // The down-cast should not fail! + assert(source); + + auto compiler = make_smart_refctd_ptr(smart_refctd_ptr(m_system)); + CHLSLCompiler::SOptions options = {}; + options.stage = IShader::E_SHADER_STAGE::ESS_COMPUTE; + options.preprocessorOptions.targetSpirvVersion = m_device->getPhysicalDevice()->getLimits().spirvVersion; + options.spirvOptimizer = nullptr; +#ifndef _NBL_DEBUG + ISPIRVOptimizer::E_OPTIMIZER_PASS optPasses = ISPIRVOptimizer::EOP_STRIP_DEBUG_INFO; + auto opt = make_smart_refctd_ptr(std::span(&optPasses, 1)); + options.spirvOptimizer = opt.get(); +#endif + options.debugInfoFlags |= IShaderCompiler::E_DEBUG_INFO_FLAGS::EDIF_LINE_BIT; + options.preprocessorOptions.sourceIdentifier = source->getFilepathHint(); + options.preprocessorOptions.logger = m_logger.get(); + options.preprocessorOptions.includeFinder = compiler->getDefaultIncludeFinder(); + + core::vector defines; + defines.reserve(3); + if (!defineMacro.empty()) + defines.push_back({ defineMacro, "" }); + if(persistentWorkGroups) + defines.push_back({ "PERSISTENT_WORKGROUPS", "1" }); + if(rwmc) + defines.push_back({ "RWMC_ENABLED", "" }); + + options.preprocessorOptions.extraDefines = defines; + + source = compiler->compileToSPIRV((const char*)source->getContent()->getPointer(), options); + + auto shader = m_device->compileShader({ source.get(), nullptr, nullptr, nullptr }); + if (!shader) + { + m_logger->log("HLSL shader creationed failed: %s!", ILogger::ELL_ERROR, pathToShader); + std::exit(-1); + } + + return shader; + }; + + const auto deviceMinSubgroupSize = m_device->getPhysicalDevice()->getLimits().minSubgroupSize; + auto getComputePipelineCreationParams = [deviceMinSubgroupSize](IShader* shader, IGPUPipelineLayout* pipelineLayout) -> IGPUComputePipeline::SCreationParams + { + IGPUComputePipeline::SCreationParams params = {}; + params.layout = pipelineLayout; + params.shader.shader = shader; + params.shader.entryPoint = "main"; + params.shader.entries = nullptr; + params.cached.requireFullSubgroups = true; + params.shader.requiredSubgroupSize = static_cast(5); + + return params; + }; + + // Create compute pipelines + { + for (int index = 0; index < E_LIGHT_GEOMETRY::ELG_COUNT; index++) + { + const nbl::asset::SPushConstantRange pcRange = { + .stageFlags = IShader::E_SHADER_STAGE::ESS_COMPUTE, + .offset = 0, + .size = sizeof(RenderPushConstants) + }; + auto ptPipelineLayout = m_device->createPipelineLayout( + { &pcRange, 1 }, + core::smart_refctd_ptr(gpuDescriptorSetLayout0), + nullptr, + core::smart_refctd_ptr(gpuDescriptorSetLayout2), + nullptr + ); + if (!ptPipelineLayout) + return logFail("Failed to create Pathtracing pipeline layout"); + + const nbl::asset::SPushConstantRange rwmcPcRange = { + .stageFlags = IShader::E_SHADER_STAGE::ESS_COMPUTE, + .offset = 0, + .size = sizeof(RenderRWMCPushConstants) + }; + auto rwmcPtPipelineLayout = m_device->createPipelineLayout( + { &rwmcPcRange, 1 }, + core::smart_refctd_ptr(gpuDescriptorSetLayout0), + nullptr, + core::smart_refctd_ptr(gpuDescriptorSetLayout2), + nullptr + ); + if (!rwmcPtPipelineLayout) + return logFail("Failed to create RWMC Pathtracing pipeline layout"); + + { + auto ptShader = loadAndCompileHLSLShader(PTHLSLShaderPath, PTHLSLShaderVariants[index]); + auto params = getComputePipelineCreationParams(ptShader.get(), ptPipelineLayout.get()); + + if (!m_device->createComputePipelines(nullptr, { ¶ms, 1 }, m_PTHLSLPipelines.data() + index)) + return logFail("Failed to create HLSL compute pipeline!\n"); + } + // { + // auto ptShader = loadAndCompileHLSLShader(PTHLSLShaderPath, PTHLSLShaderVariants[index], true); + // auto params = getComputePipelineCreationParams(ptShader.get(), ptPipelineLayout.get()); + // + // if (!m_device->createComputePipelines(nullptr, { ¶ms, 1 }, m_PTHLSLPersistentWGPipelines.data() + index)) + // return logFail("Failed to create HLSL PersistentWG compute pipeline!\n"); + // } + // + // // rwmc pipelines + // { + // auto ptShader = loadAndCompileHLSLShader(PTHLSLShaderPath, PTHLSLShaderVariants[index], false, true); + // auto params = getComputePipelineCreationParams(ptShader.get(), rwmcPtPipelineLayout.get()); + // + // if (!m_device->createComputePipelines(nullptr, { ¶ms, 1 }, m_PTHLSLPipelinesRWMC.data() + index)) + // return logFail("Failed to create HLSL RWMC compute pipeline!\n"); + // } + // { + // auto ptShader = loadAndCompileHLSLShader(PTHLSLShaderPath, PTHLSLShaderVariants[index], true, true); + // auto params = getComputePipelineCreationParams(ptShader.get(), rwmcPtPipelineLayout.get()); + // + // if (!m_device->createComputePipelines(nullptr, { ¶ms, 1 }, m_PTHLSLPersistentWGPipelinesRWMC.data() + index)) + // return logFail("Failed to create HLSL RWMC PersistentWG compute pipeline!\n"); + // } + } + } + + // Create resolve pipelines +======= +>>>>>>> master { const nbl::asset::SPushConstantRange pcRange = { .stageFlags = IShader::E_SHADER_STAGE::ESS_COMPUTE, @@ -666,6 +927,114 @@ class HLSLComputePathtracer final : public SimpleWindowedApplication, public Bui cascade->setObjectDebugName("Cascade"); m_cascadeView = createHDRIImageView(cascade, CascadeCount, IGPUImageView::ET_2D_ARRAY); m_cascadeView->setObjectDebugName("Cascade View"); +<<<<<<< HEAD + + // Create resources related to envmap importance sampling + { + ext::envmap_importance_sampling::EnvmapSampler::SCreationParameters params = {}; + params.assetManager = m_assetMgr; + params.utilities = m_utils; + params.envMap = m_envMapView; + m_envmapImportanceSampling = nbl::ext::envmap_importance_sampling::EnvmapSampler::create(std::move(params)); + m_envmapImportanceSampling->computeWarpMap(getGraphicsQueue()); + } + + // TODO: change cascade layout to general + } + + // create sequence buffer view + { + // TODO: do this better use asset manager to get the ICPUBuffer from `.bin` + auto createBufferFromCacheFile = [this]( + system::path filename, + size_t bufferSize, + void *data, + smart_refctd_ptr& buffer + ) -> std::pair, bool> + { + ISystem::future_t> owenSamplerFileFuture; + ISystem::future_t owenSamplerFileReadFuture; + size_t owenSamplerFileBytesRead; + + m_system->createFile(owenSamplerFileFuture, localOutputCWD / filename, IFile::ECF_READ); + smart_refctd_ptr owenSamplerFile; + + if (owenSamplerFileFuture.wait()) + { + owenSamplerFileFuture.acquire().move_into(owenSamplerFile); + if (!owenSamplerFile) + return { nullptr, false }; + + owenSamplerFile->read(owenSamplerFileReadFuture, data, 0, bufferSize); + if (owenSamplerFileReadFuture.wait()) + { + owenSamplerFileReadFuture.acquire().move_into(owenSamplerFileBytesRead); + + if (owenSamplerFileBytesRead < bufferSize) + { + buffer = asset::ICPUBuffer::create({ sizeof(uint32_t) * bufferSize }); + return { owenSamplerFile, false }; + } + + buffer = asset::ICPUBuffer::create({ { sizeof(uint32_t) * bufferSize }, data }); + } + } + + return { owenSamplerFile, true }; + }; + auto writeBufferIntoCacheFile = [this](smart_refctd_ptr file, size_t bufferSize, void* data) + { + ISystem::future_t owenSamplerFileWriteFuture; + size_t owenSamplerFileBytesWritten; + + file->write(owenSamplerFileWriteFuture, data, 0, bufferSize); + if (owenSamplerFileWriteFuture.wait()) + owenSamplerFileWriteFuture.acquire().move_into(owenSamplerFileBytesWritten); + }; + + constexpr uint32_t quantizedDimensions = MaxBufferDimensions / 3u; + constexpr size_t bufferSize = quantizedDimensions * MaxBufferSamples; + using sequence_type = sampling::QuantizedSequence; + std::array data = {}; + smart_refctd_ptr sampleSeq; + + auto cacheBufferResult = createBufferFromCacheFile(sharedOutputCWD/OwenSamplerFilePath, bufferSize, data.data(), sampleSeq); + if (!cacheBufferResult.second) + { + core::OwenSampler sampler(MaxBufferDimensions, 0xdeadbeefu); + + ICPUBuffer::SCreationParams params = {}; + params.size = quantizedDimensions * MaxBufferSamples * sizeof(sequence_type); + sampleSeq = ICPUBuffer::create(std::move(params)); + + auto out = reinterpret_cast(sampleSeq->getPointer()); + for (auto dim = 0u; dim < MaxBufferDimensions; dim++) + for (uint32_t i = 0; i < MaxBufferSamples; i++) + { + const uint32_t quant_dim = dim / 3u; + const uint32_t offset = dim % 3u; + auto& seq = out[i * quantizedDimensions + quant_dim]; + const uint32_t sample = sampler.sample(dim, i); + seq.set(offset, sample); + } + if (cacheBufferResult.first) + writeBufferIntoCacheFile(cacheBufferResult.first, bufferSize, out); + } + + IGPUBuffer::SCreationParams params = {}; + params.usage = asset::IBuffer::EUF_TRANSFER_DST_BIT | asset::IBuffer::EUF_STORAGE_BUFFER_BIT | asset::IBuffer::EUF_SHADER_DEVICE_ADDRESS_BIT; + params.size = bufferSize; + + // we don't want to overcomplicate the example with multi-queue + m_utils->createFilledDeviceLocalBufferOnDedMem( + SIntendedSubmitInfo{ .queue = getGraphicsQueue() }, + std::move(params), + sampleSeq->getPointer() + ).move_into(m_sequenceBuffer); + + m_sequenceBuffer->setObjectDebugName("Sequence buffer"); +======= +>>>>>>> master } // Update Descriptors @@ -697,23 +1066,34 @@ class HLSLComputePathtracer final : public SimpleWindowedApplication, public Bui }; auto sampler1 = m_device->createSampler(samplerParams1); - std::array writeDSInfos = {}; + std::array writeDSInfos = {}; writeDSInfos[0].desc = m_outImgView; writeDSInfos[0].info.image.imageLayout = IImage::LAYOUT::GENERAL; + writeDSInfos[1].desc = m_cascadeView; writeDSInfos[1].info.image.imageLayout = IImage::LAYOUT::GENERAL; + writeDSInfos[2].desc = m_envMapView; // ISampler::SParams samplerParams = { ISampler::ETC_CLAMP_TO_EDGE, ISampler::ETC_CLAMP_TO_EDGE, ISampler::ETC_CLAMP_TO_EDGE, ISampler::ETBC_FLOAT_OPAQUE_BLACK, ISampler::ETF_LINEAR, ISampler::ETF_LINEAR, ISampler::ESMM_LINEAR, 0u, false, ECO_ALWAYS }; writeDSInfos[2].info.combinedImageSampler.sampler = sampler0; writeDSInfos[2].info.combinedImageSampler.imageLayout = asset::IImage::LAYOUT::READ_ONLY_OPTIMAL; + writeDSInfos[3].desc = m_scrambleView; // ISampler::SParams samplerParams = { ISampler::ETC_CLAMP_TO_EDGE, ISampler::ETC_CLAMP_TO_EDGE, ISampler::ETC_CLAMP_TO_EDGE, ISampler::ETBC_INT_OPAQUE_BLACK, ISampler::ETF_NEAREST, ISampler::ETF_NEAREST, ISampler::ESMM_NEAREST, 0u, false, ECO_ALWAYS }; writeDSInfos[3].info.combinedImageSampler.sampler = sampler1; writeDSInfos[3].info.combinedImageSampler.imageLayout = asset::IImage::LAYOUT::READ_ONLY_OPTIMAL; - writeDSInfos[4].desc = m_outImgView; - writeDSInfos[4].info.image.imageLayout = IImage::LAYOUT::READ_ONLY_OPTIMAL; - std::array writeDescriptorSets = {}; + writeDSInfos[4].desc = m_envmapImportanceSampling->getLumaMapView(); + writeDSInfos[4].info.combinedImageSampler.sampler = sampler0; + writeDSInfos[4].info.combinedImageSampler.imageLayout = asset::IImage::LAYOUT::READ_ONLY_OPTIMAL; + + writeDSInfos[5].desc = m_envmapImportanceSampling->getWarpMapView(); + writeDSInfos[5].info.combinedImageSampler.imageLayout = asset::IImage::LAYOUT::READ_ONLY_OPTIMAL; + + writeDSInfos[6].desc = m_outImgView; + writeDSInfos[6].info.image.imageLayout = IImage::LAYOUT::READ_ONLY_OPTIMAL; + + std::array writeDescriptorSets = {}; writeDescriptorSets[0] = { .dstSet = m_descriptorSet.get(), .binding = 2, @@ -743,11 +1123,25 @@ class HLSLComputePathtracer final : public SimpleWindowedApplication, public Bui .info = &writeDSInfos[3] }; writeDescriptorSets[4] = { + .dstSet = m_descriptorSet2.get(), + .binding = 3, + .arrayElement = 0u, + .count = 1u, + .info = &writeDSInfos[4] + }; + writeDescriptorSets[5] = { + .dstSet = m_descriptorSet2.get(), + .binding = 4, + .arrayElement = 0u, + .count = 1u, + .info = &writeDSInfos[5] + }; + writeDescriptorSets[6] = { .dstSet = m_presentDescriptorSet.get(), .binding = 0, .arrayElement = 0u, .count = 1u, - .info = &writeDSInfos[4] + .info = &writeDSInfos[6] }; m_device->updateDescriptorSets(writeDescriptorSets, {}); @@ -1083,7 +1477,11 @@ class HLSLComputePathtracer final : public SimpleWindowedApplication, public Bui ); } +<<<<<<< HEAD + if (E_LIGHT_GEOMETRY::ELG_ENVMAP == PTPipeline) +======= if (E_LIGHT_GEOMETRY::ELG_SPHERE == guiControlled.PTPipeline) +>>>>>>> master { m_transformParams.allowedOp = ImGuizmo::OPERATION::TRANSLATE | ImGuizmo::OPERATION::SCALEU; m_transformParams.isSphere = true; @@ -1095,7 +1493,11 @@ class HLSLComputePathtracer final : public SimpleWindowedApplication, public Bui } EditTransform(&imguizmoM16InOut.view[0][0], &imguizmoM16InOut.projection[0][0], &m_lightModelMatrix[0][0], m_transformParams); +<<<<<<< HEAD + if (E_LIGHT_GEOMETRY::ELG_ENVMAP == PTPipeline) +======= if (E_LIGHT_GEOMETRY::ELG_SPHERE == guiControlled.PTPipeline) +>>>>>>> master { // keep uniform scale for sphere float32_t uniformScale = (m_lightModelMatrix[0][0] + m_lightModelMatrix[1][1] + m_lightModelMatrix[2][2]) / 3.0f; @@ -1255,7 +1657,7 @@ class HLSLComputePathtracer final : public SimpleWindowedApplication, public Bui }, .oldLayout = IImage::LAYOUT::UNDEFINED, .newLayout = IImage::LAYOUT::GENERAL - } + }, }; cmdbuf->pipelineBarrier(E_DEPENDENCY_FLAGS::EDF_NONE, { .imgBarriers = imgBarriers }); } @@ -1644,8 +2046,19 @@ class HLSLComputePathtracer final : public SimpleWindowedApplication, public Bui const auto assets = assetBundle.getContents(); if (assets.empty()) { +<<<<<<< HEAD + memcpy(&rwmcPushConstants.renderPushConstants.invMVP, invMVP.pointer(), sizeof(rwmcPushConstants.renderPushConstants.invMVP)); + rwmcPushConstants.renderPushConstants.generalPurposeLightMatrix = hlsl::float32_t3x4(transpose(m_lightModelMatrix)); + rwmcPushConstants.renderPushConstants.depth = depth; + rwmcPushConstants.renderPushConstants.sampleCount = resolvePushConstants.sampleCount = spp; + rwmcPushConstants.renderPushConstants.pSampleSequence = m_sequenceBuffer->getDeviceAddress(); + rwmcPushConstants.renderPushConstants.avgLuma = m_envmapImportanceSampling->getAvgLuma(); + float32_t2 packParams = float32_t2(rwmcBase, rwmcStart); + rwmcPushConstants.packedSplattingParams = hlsl::packHalf2x16(packParams); +======= m_logger->log("Could not load precompiled shader: %s", ILogger::ELL_ERROR, key.c_str()); return nullptr; +>>>>>>> master } auto shader = IAsset::castDown(assets[0]); @@ -1936,8 +2349,17 @@ class HLSLComputePathtracer final : public SimpleWindowedApplication, public Bui if (m_sceneScreenshotExitAfterCapture) { +<<<<<<< HEAD + memcpy(&pc.invMVP, invMVP.pointer(), sizeof(pc.invMVP)); + pc.generalPurposeLightMatrix = hlsl::float32_t3x4(transpose(m_lightModelMatrix)); + pc.sampleCount = spp; + pc.depth = depth; + pc.pSampleSequence = m_sequenceBuffer->getDeviceAddress(); + pc.avgLuma = m_envmapImportanceSampling->getAvgLuma(); +======= m_ciScreenshotCaptured = true; requestExit(); +>>>>>>> master } m_sceneScreenshotExitAfterCapture = false; } @@ -2843,6 +3265,8 @@ class HLSLComputePathtracer final : public SimpleWindowedApplication, public Bui smart_refctd_ptr m_window; smart_refctd_ptr> m_surface; + smart_refctd_ptr m_envmapImportanceSampling; + // gpu resources smart_refctd_ptr m_cmdPool; SRenderPipelineStorage m_renderPipelines; @@ -2903,6 +3327,25 @@ class HLSLComputePathtracer final : public SimpleWindowedApplication, public Bui uint16_t gcIndex = {}; +<<<<<<< HEAD + float fov = 60.f, zNear = 0.1f, zFar = 10000.f, moveSpeed = 1.f, rotateSpeed = 1.f; + float viewWidth = 10.f; + float camYAngle = 165.f / 180.f * 3.14159f; + float camXAngle = 32.f / 180.f * 3.14159f; + int PTPipeline = E_LIGHT_GEOMETRY::ELG_ENVMAP; + int renderMode = E_RENDER_MODE::ERM_HLSL; + int spp = 32; + int depth = 3; + float rwmcMinReliableLuma; + float rwmcKappa; + float rwmcStart; + float rwmcBase; + bool usePersistentWorkGroups = false; + bool useRWMC = false; + RenderRWMCPushConstants rwmcPushConstants; + RenderPushConstants pc; + ResolvePushConstants resolvePushConstants; +======= struct GUIControllables { float fov = 60.f, zNear = 0.1f, zFar = 10000.f, moveSpeed = 1.f, rotateSpeed = 1.f; @@ -2917,6 +3360,7 @@ class HLSLComputePathtracer final : public SimpleWindowedApplication, public Bui bool useRWMC = false; }; GUIControllables guiControlled; +>>>>>>> master hlsl::float32_t4x4 m_lightModelMatrix = { 0.3f, 0.0f, 0.0f, 0.0f, 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 fc32b9ca2..4a5c09ede 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,7 +66,7 @@ if(NBL_BUILD_EXAMPLES) add_subdirectory(24_ColorSpaceTest) add_subdirectory(25_FilterTest EXCLUDE_FROM_ALL) add_subdirectory(26_Blur) - add_subdirectory(27_MPMCScheduler) + add_subdirectory(27_MPMCScheduler) add_subdirectory(28_FFTBloom) add_subdirectory(29_Arithmetic2Bench) # add_subdirectory(36_CUDAInterop) @@ -110,6 +110,7 @@ if(NBL_BUILD_EXAMPLES) endif() add_subdirectory(74_QuantizedSequenceTests) + add_subdirectory(75_EnvmapImportanceSamplingTest) add_subdirectory(75_ImageUploadBenchmark) if (NBL_COMPILE_WITH_CUDA) add_subdirectory(76_CudaInterop)