Skip to content

Commit 83a83a4

Browse files
committed
update ProfileProperties to tightly pack version, type and symmetry, add sample function overload to sampler.hlsl, create include/nbl/builtin/hlsl/ies/texture.hlsl to share texel write method between C++ & HLSL (the correct one including blending and corner sampling)
TODO: need to update IES viewer example to compile again and clean bindings a bit (keep only octahedral image to write and sample from + add new ies::Texture::SInfo)
1 parent 5ce6fa2 commit 83a83a4

File tree

6 files changed

+186
-74
lines changed

6 files changed

+186
-74
lines changed

include/nbl/builtin/hlsl/ies/profile.hlsl

Lines changed: 55 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,22 +26,33 @@ struct ProfileProperties
2626
NBL_CONSTEXPR_STATIC_INLINE float32_t MAX_VANGLE = 180.f;
2727
NBL_CONSTEXPR_STATIC_INLINE float32_t MAX_HANGLE = 360.f;
2828

29-
enum Version : uint16_t
29+
// TODO: could change to uint8_t once we get implemented
30+
// https://github.com/microsoft/hlsl-specs/pull/538
31+
using packed_flags_t = uint16_t;
32+
33+
NBL_CONSTEXPR_STATIC_INLINE packed_flags_t VERSION_BITS = 2u;
34+
NBL_CONSTEXPR_STATIC_INLINE packed_flags_t TYPE_BITS = 2u;
35+
NBL_CONSTEXPR_STATIC_INLINE packed_flags_t SYMM_BITS = 3u;
36+
NBL_CONSTEXPR_STATIC_INLINE packed_flags_t VERSION_MASK = (packed_flags_t(1u) << VERSION_BITS) - packed_flags_t(1u);
37+
NBL_CONSTEXPR_STATIC_INLINE packed_flags_t TYPE_MASK = (packed_flags_t(1u) << TYPE_BITS) - packed_flags_t(1u);
38+
NBL_CONSTEXPR_STATIC_INLINE packed_flags_t SYMM_MASK = (packed_flags_t(1u) << SYMM_BITS) - packed_flags_t(1u);
39+
40+
enum Version : packed_flags_t
3041
{
3142
V_1995,
3243
V_2002,
3344
V_SIZE
3445
};
3546

36-
enum PhotometricType : uint16_t
47+
enum PhotometricType : packed_flags_t
3748
{
3849
TYPE_NONE,
3950
TYPE_C,
4051
TYPE_B,
4152
TYPE_A
4253
};
4354

44-
enum LuminairePlanesSymmetry : uint16_t
55+
enum LuminairePlanesSymmetry : packed_flags_t
4556
{
4657
ISOTROPIC, //! Only one horizontal angle present and a luminaire is assumed to be laterally axial symmetric
4758
QUAD_SYMETRIC, //! The luminaire is assumed to be symmetric in each quadrant
@@ -50,13 +61,48 @@ struct ProfileProperties
5061
NO_LATERAL_SYMMET //! The luminaire is assumed to exhibit no lateral symmet
5162
};
5263

53-
PhotometricType type;
54-
Version version;
55-
LuminairePlanesSymmetry symmetry;
64+
Version getVersion() const
65+
{
66+
return static_cast<Version>( packed & VERSION_MASK );
67+
}
68+
69+
PhotometricType getType() const
70+
{
71+
const packed_flags_t shift = VERSION_BITS;
72+
return static_cast<PhotometricType>( (packed >> shift) & TYPE_MASK );
73+
}
74+
75+
LuminairePlanesSymmetry getSymmetry() const
76+
{
77+
const packed_flags_t shift = VERSION_BITS + TYPE_BITS;
78+
return static_cast<LuminairePlanesSymmetry>( (packed >> shift) & SYMM_MASK );
79+
}
80+
81+
void setVersion(Version v)
82+
{
83+
packed_flags_t vBits = static_cast<packed_flags_t>(v) & VERSION_MASK;
84+
packed = (packed & ~VERSION_MASK) | vBits;
85+
}
86+
87+
void setType(PhotometricType t)
88+
{
89+
const packed_flags_t shift = VERSION_BITS;
90+
packed_flags_t tBits = (static_cast<packed_flags_t>(t) & TYPE_MASK) << shift;
91+
packed = (packed & ~(TYPE_MASK << shift)) | tBits;
92+
}
93+
94+
void setSymmetry(LuminairePlanesSymmetry s)
95+
{
96+
const packed_flags_t shift = VERSION_BITS + TYPE_BITS;
97+
packed_flags_t sBits = (static_cast<packed_flags_t>(s) & SYMM_MASK) << shift;
98+
packed = (packed & ~(SYMM_MASK << shift)) | sBits;
99+
}
56100

57-
float32_t maxCandelaValue; //! Max scalar value from candela data vector
58-
float32_t totalEmissionIntegral; //! Total energy emitted
59-
float32_t avgEmmision; //! totalEmissionIntegral / <size of the emission domain where non zero emission values>
101+
float32_t maxCandelaValue; //! Max candela sample value
102+
float32_t totalEmissionIntegral; //! Total emitted intensity (integral over full angular domain)
103+
float32_t fullDomainAvgEmission; //! Mean intensity over full angular domain (including I == 0)
104+
float32_t avgEmmision; //! Mean intensity over emitting solid angle (I > 0)
105+
packed_flags_t packed = 0u; //! Packed version, type and symmetry flags
60106
};
61107

62108
}

include/nbl/builtin/hlsl/ies/sampler.hlsl

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#include "nbl/builtin/hlsl/cpp_compat.hlsl"
99
#include "nbl/builtin/hlsl/math/polar.hlsl"
10+
#include "nbl/builtin/hlsl/math/octahedral.hlsl"
1011
#include "nbl/builtin/hlsl/concepts.hlsl"
1112
#include "nbl/builtin/hlsl/ies/profile.hlsl"
1213

@@ -37,7 +38,7 @@ NBL_CONCEPT_END(
3738

3839
((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((accessor.vAnglesCount()), is_same_v, req_key_t))
3940
((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((accessor.hAnglesCount()), is_same_v, req_key_t))
40-
((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((accessor.symmetry()), is_same_v, ProfileProperties::LuminairePlanesSymmetry))
41+
((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((accessor.getProperties()), is_same_v, ProfileProperties))
4142
((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((accessor.template vAngle<req_key_t>((req_key_t)0)), is_same_v, req_value_t))
4243
((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((accessor.template hAngle<req_key_t>((req_key_t)0)), is_same_v, req_value_t))
4344
((NBL_CONCEPT_REQ_EXPR_RET_TYPE)((accessor.template value<req_key_t2>((req_key_t2)0)), is_same_v, req_value_t))
@@ -58,11 +59,13 @@ struct CandelaSampler
5859
using accessor_t = Accessor;
5960
using value_t = typename accessor_t::value_t;
6061
using symmetry_t = ProfileProperties::LuminairePlanesSymmetry;
62+
using polar_t = math::Polar<float32_t>;
63+
using octahedral_t = math::OctahedralTransform<float32_t>;
6164

6265
static value_t sample(NBL_CONST_REF_ARG(accessor_t) accessor, NBL_CONST_REF_ARG(math::Polar<float32_t>) polar)
6366
{
6467
// TODO: DXC seems to have a bug and cannot use symmetry_t directly with == operator https://godbolt.devsh.eu/z/P9Kc5x
65-
const ProfileProperties::LuminairePlanesSymmetry symmetry = accessor.symmetry();
68+
const ProfileProperties::LuminairePlanesSymmetry symmetry = accessor.getProperties().getSymmetry();
6669
const float32_t vAngle = degrees(polar.theta);
6770
const float32_t hAngle = degrees(wrapPhi(polar.phi, symmetry));
6871

@@ -87,6 +90,13 @@ struct CandelaSampler
8790
return s0 * (1.f - u) + s1 * u;
8891
}
8992

93+
static value_t sample(NBL_CONST_REF_ARG(accessor_t) accessor, NBL_CONST_REF_ARG(float32_t2) uv)
94+
{
95+
const float32_t3 dir = octahedral_t::uvToDir(uv);
96+
const polar_t polar = polar_t::createFromCartesian(dir);
97+
return sample(accessor, polar);
98+
}
99+
90100
static float32_t wrapPhi(const float32_t phi, const symmetry_t symmetry)
91101
{
92102
switch (symmetry)
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// Copyright (C) 2018-2025 - DevSH Graphics Programming Sp. z O.O.
2+
// This file is part of the "Nabla Engine".
3+
// For conditions of distribution and use, see copyright notice in nabla.h
4+
5+
#ifndef _NBL_BUILTIN_HLSL_IES_TEXTURE_INCLUDED_
6+
#define _NBL_BUILTIN_HLSL_IES_TEXTURE_INCLUDED_
7+
8+
#include "nbl/builtin/hlsl/ies/sampler.hlsl"
9+
10+
namespace nbl
11+
{
12+
namespace hlsl
13+
{
14+
namespace ies
15+
{
16+
17+
template<typename Accessor NBL_FUNC_REQUIRES(concepts::IsIESAccessor<Accessor>)
18+
struct Texture
19+
{
20+
using accessor_t = Accessor;
21+
using value_t = typename accessor_t::value_t;
22+
using sampler_t = CandelaSampler<accessor_t>;
23+
using polar_t = math::Polar<float32_t>;
24+
using octahedral_t = math::OctahedralTransform<float32_t>;
25+
26+
struct SInfo
27+
{
28+
float32_t2 inv;
29+
float32_t flatten;
30+
float32_t maxValueRecip;
31+
float32_t flattenTarget;
32+
float32_t domainLo;
33+
float32_t domainHi;
34+
bool fullDomainFlatten;
35+
};
36+
37+
static SInfo createInfo(NBL_CONST_REF_ARG(accessor_t) accessor, NBL_CONST_REF_ARG(uint32_t2) size, float32_t flatten, bool fullDomainFlatten)
38+
{
39+
SInfo retval;
40+
const ProfileProperties props = accessor.getProperties();
41+
42+
// There is one huge issue, the IES files love to give us values for degrees 0, 90, 180 an 360
43+
// So standard octahedral mapping won't work, because for above data points you need corner sampled images.
44+
45+
retval.inv = float32_t2(1.f, 1.f) / float32_t2(size - 1u);
46+
retval.flatten = flatten;
47+
retval.maxValueRecip = 1.0f / props.maxCandelaValue; // Late Optimization TODO: Modify the Max Value for the UNORM texture to be the Max Value after flatten blending
48+
retval.domainLo = radians(accessor.vAngle(0u));
49+
retval.domainHi = radians(accessor.vAngle(accessor.vAnglesCount() - 1u));
50+
retval.fullDomainFlatten = fullDomainFlatten;
51+
52+
if(fullDomainFlatten)
53+
retval.flattenTarget = props.fullDomainAvgEmission;
54+
else
55+
retval.flattenTarget = props.avgEmmision;
56+
57+
return retval;
58+
}
59+
60+
static float32_t eval(NBL_CONST_REF_ARG(accessor_t) accessor, NBL_CONST_REF_ARG(SInfo) info, NBL_CONST_REF_ARG(uint32_t2) position)
61+
{
62+
// We don't currently support generating IES images that exploit symmetries or reduced domains, all are full octahederal mappings of a sphere.
63+
// If we did, we'd rely on MIRROR and CLAMP samplers to do some of the work for us while handling the discontinuity due to corner sampling.
64+
65+
const float32_t2 uv = float32_t2(position) * info.inv;
66+
const float32_t3 dir = octahedral_t::uvToDir(uv);
67+
const polar_t polar = polar_t::createFromCartesian(dir);
68+
69+
sampler_t sampler;
70+
const float32_t intensity = sampler.sample(accessor, polar);
71+
72+
//! blend the IES texture with "flatten"
73+
float32_t blendV = intensity * (1.f - info.flatten);
74+
75+
const bool inDomain = (info.domainLo <= polar.theta) && (polar.theta <= info.domainHi);
76+
77+
if ((info.fullDomainFlatten && inDomain) || intensity > 0.0f)
78+
blendV += info.flattenTarget * info.flatten;
79+
80+
blendV *= info.maxValueRecip;
81+
82+
return blendV;
83+
}
84+
};
85+
86+
}
87+
}
88+
}
89+
90+
#endif // _NBL_BUILTIN_HLSL_IES_TEXTURE_INCLUDED_

src/nbl/asset/utils/CIESProfile.cpp

Lines changed: 3 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
#include <atomic>
44
#include "nbl/asset/filters/CBasicImageFilterCommon.h"
5-
#include "nbl/builtin/hlsl/math/octahedral.hlsl"
6-
#include "nbl/builtin/hlsl/math/polar.hlsl"
75

86
using namespace nbl;
97
using namespace asset;
@@ -70,42 +68,15 @@ core::smart_refctd_ptr<asset::ICPUImageView> CIESProfile::createIESTexture(Execu
7068
state.outRange.extent = creationParams.extent;
7169

7270
const IImageFilter::IState::ColorValue::WriteMemoryInfo wInfo(creationParams.format, outImg->getBuffer()->getPointer());
71+
const auto tInfo = texture_t::createInfo(accessor, hlsl::uint32_t2(width, height), flatten, fullDomainFlatten);
7372

74-
// Late Optimization TODO: Modify the Max Value for the UNORM texture to be the Max Value after flatten blending
75-
const auto maxValue = accessor.properties.maxCandelaValue;
76-
const auto maxValueRecip = 1.f / maxValue;
77-
78-
// There is one huge issue, the IES files love to give us values for degrees 0, 90, 180 an 360
79-
// So standard octahedral mapping won't work, because for above data points you need corner sampled images.
80-
const float vertInv = 1.0 / (height-1);
81-
const float horiInv = 1.0 / (width-1);
82-
83-
const double flattenTarget = getAvgEmmision(fullDomainFlatten);
84-
const double domainLo = core::radians(accessor.vAngles.front());
85-
const double domainHi = core::radians(accessor.vAngles.back());
8673
auto fill = [&](uint32_t blockArrayOffset, core::vectorSIMDu32 position) -> void
8774
{
88-
// We don't currently support generating IES images that exploit symmetries or reduced domains, all are full octahederal mappings of a sphere.
89-
// If we did, we'd rely on MIRROR and CLAMP samplers to do some of the work for us while handling the discontinuity due to corner sampling.
90-
91-
using Octahedral = hlsl::math::OctahedralTransform<hlsl::float32_t>;
92-
using Polar = hlsl::math::Polar<hlsl::float32_t>;
93-
const auto uv = Octahedral::vector2_type(position.x * vertInv, position.y * horiInv);
94-
const auto dir = Octahedral::uvToDir(uv);
95-
const auto polar = Polar::createFromCartesian(dir);
96-
const auto intensity = sampler_t::sample(accessor, polar);
97-
98-
//! blend the IES texture with "flatten"
99-
float blendV = intensity * (1.f - flatten);
100-
if (fullDomainFlatten && domainLo<= polar.theta && polar.theta<=domainHi || intensity >0.0)
101-
blendV += flattenTarget * flatten;
102-
103-
blendV *= maxValueRecip;
75+
auto texel = texture_t::eval(accessor, tInfo, hlsl::uint32_t2(position.x, position.y));
10476

10577
asset::IImageFilter::IState::ColorValue color;
106-
//asset::encodePixels<CIESProfile::IES_TEXTURE_STORAGE_FORMAT>(color.asDouble, &blendV); TODO: FIX THIS ENCODE, GIVES ARTIFACTS
10778
constexpr float UI16_MAX_D = static_cast<float>(std::numeric_limits<std::uint16_t>::max());
108-
const uint16_t encodeV = static_cast<uint16_t>(std::clamp(blendV * UI16_MAX_D + 0.5f, 0.f, UI16_MAX_D));
79+
const uint16_t encodeV = static_cast<uint16_t>(std::clamp(texel * UI16_MAX_D + 0.5f, 0.f, UI16_MAX_D)); // TODO: use asset::encodePixels when its fixed (no artifacts)
10980
*color.asUShort = encodeV;
11081
color.writeMemory(wInfo, blockArrayOffset);
11182
};

src/nbl/asset/utils/CIESProfile.h

Lines changed: 8 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
#define __NBL_ASSET_C_IES_PROFILE_H_INCLUDED__
77

88
#include "nbl/asset/metadata/CIESProfileMetadata.h"
9-
#include "nbl/builtin/hlsl/ies/sampler.hlsl"
9+
#include "nbl/builtin/hlsl/ies/texture.hlsl"
1010

1111
namespace nbl
1212
{
@@ -15,10 +15,14 @@ namespace asset
1515
class CIESProfile
1616
{
1717
public:
18+
CIESProfile() = default;
19+
~CIESProfile() = default;
20+
1821
struct properties_t : public nbl::hlsl::ies::ProfileProperties
1922
{
23+
using base_t = nbl::hlsl::ies::ProfileProperties;
2024
NBL_CONSTEXPR_STATIC_INLINE auto IES_TEXTURE_STORAGE_FORMAT = asset::EF_R16_UNORM;
21-
hlsl::uint32_t2 optimalIESResolution; //! Optimal resolution for IES CDC texture
25+
hlsl::uint32_t2 optimalIESResolution; //! Optimal resolution for IES Octahedral Candela Map texture
2226
};
2327

2428
struct accessor_t
@@ -45,33 +49,17 @@ class CIESProfile
4549

4650
inline key_t vAnglesCount() const { return (key_t)vAngles.size(); }
4751
inline key_t hAnglesCount() const { return (key_t)hAngles.size(); }
48-
inline properties_t::LuminairePlanesSymmetry symmetry() const { return properties.symmetry; }
52+
inline const properties_t::base_t& getProperties() const { return static_cast<const properties_t::base_t&>(properties); }
4953

5054
core::vector<value_t> hAngles; //! The angular displacement indegreesfrom straight down, a value represents spherical coordinate "theta" with physics convention. Note that if symmetry is OTHER_HALF_SYMMETRIC then real horizontal angle provided by IES data is (hAngles[index] + 90) - the reason behind it is we patch 1995 IES OTHER_HALF_SYMETRIC case to be HALF_SYMETRIC
5155
core::vector<value_t> vAngles; //! Measurements in degrees of angular displacement measured counterclockwise in a horizontal plane for Type C photometry and clockwise for Type A and B photometry, a value represents spherical coordinate "phi" with physics convention
5256
core::vector<value_t> data; //! Candela scalar values
5357
properties_t properties; //! Profile properties
5458
};
55-
56-
using sampler_t = nbl::hlsl::ies::CandelaSampler<accessor_t>;
57-
58-
CIESProfile() = default;
59-
~CIESProfile() = default;
59+
using texture_t = nbl::hlsl::ies::Texture<accessor_t>;
6060

6161
inline const accessor_t& getAccessor() const { return accessor; }
6262

63-
inline hlsl::float32_t getAvgEmmision(const bool fullDomain=false) const
64-
{
65-
if (fullDomain)
66-
{
67-
const float cosLo = std::cos(core::radians(accessor.vAngles.front()));
68-
const float cosHi = std::cos(core::radians<float>(accessor.vAngles.back()));
69-
const float dsinTheta = cosLo - cosHi;
70-
return accessor.properties.totalEmissionIntegral*(0.5/core::PI<float>())/dsinTheta;
71-
}
72-
return accessor.properties.avgEmmision;
73-
}
74-
7563
template<class ExecutionPolicy>
7664
core::smart_refctd_ptr<asset::ICPUImageView> createIESTexture(ExecutionPolicy&& policy, const float flatten = 0.0, const bool fullDomainFlatten=false, uint32_t width = properties_t::CDC_DEFAULT_TEXTURE_WIDTH, uint32_t height = properties_t::CDC_DEFAULT_TEXTURE_HEIGHT) const;
7765
core::smart_refctd_ptr<asset::ICPUImageView> createIESTexture(const float flatten = 0.0, const bool fullDomainFlatten=false, uint32_t width = properties_t::CDC_DEFAULT_TEXTURE_WIDTH, uint32_t height = properties_t::CDC_DEFAULT_TEXTURE_HEIGHT) const;

0 commit comments

Comments
 (0)