2626 * // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
2727 * // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2828 */
29+ use crate :: gamma:: { pq_from_linear_with_reference_display, pq_to_linear_unscaled} ;
2930use crate :: mlaf:: { fmla, mlaf} ;
3031use crate :: spline:: FilmicSplineParameters ;
3132use crate :: GainHdrMetadata ;
@@ -49,8 +50,10 @@ pub enum AgxLook {
4950/// many of the supported tone mapping methods.
5051#[ derive( Debug , Copy , Clone , PartialOrd , PartialEq ) ]
5152pub enum ToneMappingMethod {
53+ /// Fast and accurate tuned Reinhard, preferred
54+ TunedReinhard ( GainHdrMetadata ) ,
5255 /// ITU-R broadcasting TV [recommendation 2408](https://www.itu.int/dms_pub/itu-r/opb/rep/R-REP-BT.2408-4-2021-PDF-E.pdf)
53- Rec2408 ( GainHdrMetadata ) ,
56+ Itu2408 ( GainHdrMetadata ) ,
5457 /// The ['Uncharted 2' filmic](https://www.gdcvault.com/play/1012351/Uncharted-2-HDR)
5558 /// tone mapping method.
5659 Filmic ,
@@ -84,12 +87,146 @@ pub(crate) trait ToneMap {
8487
8588#[ derive( Debug , Clone , Copy ) ]
8689pub ( crate ) struct Rec2408ToneMapper < const CN : usize > {
90+ content_max_brightness : f32 ,
91+ display_max_brightness : f32 ,
92+ primaries : [ f32 ; 3 ] ,
93+ content_min_luminance : f32 ,
94+ content_luminance_range : f32 ,
95+ inv_pq_mastering_range : f32 ,
96+ min_lum : f32 ,
97+ max_lum : f32 ,
98+ ks : f32 ,
99+ inv_one_minus_ks : f32 ,
100+ one_minus_ks : f32 ,
101+ normalizer : f32 ,
102+ inv_target_peak : f32 ,
103+ }
104+
105+ impl < const CN : usize > Rec2408ToneMapper < CN > {
106+ pub ( crate ) fn new (
107+ content_max_brightness : f32 ,
108+ display_max_brightness : f32 ,
109+ primaries : [ f32 ; 3 ] ,
110+ ) -> Self {
111+ let content_luminance_black = pq_from_linear_with_reference_display ( 0. , 10000.0 ) ;
112+ let content_luminance_white =
113+ pq_from_linear_with_reference_display ( content_max_brightness / 10000.0 , 10000.0 ) ;
114+ let content_luminance_range = content_luminance_white - content_luminance_black;
115+ let inv_pq_mastering_range = 1.0 / content_luminance_range;
116+
117+ let min_lum = ( pq_from_linear_with_reference_display ( 0. , 10000.0 )
118+ - content_luminance_black)
119+ * inv_pq_mastering_range;
120+ let max_lum =
121+ ( pq_from_linear_with_reference_display ( display_max_brightness / 10000.0 , 10000.0 )
122+ - content_luminance_black)
123+ * inv_pq_mastering_range;
124+ let ks = 1.5 * max_lum - 0.5 ;
125+
126+ Self {
127+ content_max_brightness,
128+ display_max_brightness,
129+ primaries,
130+ content_min_luminance : content_luminance_black,
131+ content_luminance_range,
132+ inv_pq_mastering_range,
133+ min_lum,
134+ max_lum,
135+ ks,
136+ inv_one_minus_ks : 1.0 / ( 1.0 - ks) . max ( 1e-6 ) ,
137+ one_minus_ks : 1.0 - ks,
138+ normalizer : content_max_brightness / display_max_brightness,
139+ inv_target_peak : 1.0 / display_max_brightness,
140+ }
141+ }
142+ }
143+
144+ impl < const CN : usize > Rec2408ToneMapper < CN > {
145+ #[ inline( always) ]
146+ fn t ( & self , a : f32 ) -> f32 {
147+ ( a - self . ks ) * self . inv_one_minus_ks
148+ }
149+
150+ #[ inline]
151+ fn hermite_spline ( & self , b : f32 ) -> f32 {
152+ let t_b = self . t ( b) ;
153+ let t_b_2 = t_b * t_b;
154+ let t_b_3 = t_b_2 * t_b;
155+ fmla ( 2.0 , t_b_3, fmla ( -3.0 , t_b_2, 1.0 ) ) * self . ks
156+ + fmla ( -2.0 , t_b_2, t_b_3 + t_b) * self . one_minus_ks
157+ + fmla ( -2.0 , t_b_3, 3.0 * t_b_2) * self . max_lum
158+ }
159+
160+ #[ inline( always) ]
161+ fn make_luma_scale ( & self , luma : f32 ) -> f32 {
162+ let s = pq_from_linear_with_reference_display ( luma / 10000.0 , 10000.0 ) ;
163+ let normalized_pq =
164+ ( ( s - self . content_min_luminance ) * self . inv_pq_mastering_range ) . min ( 1.0 ) ;
165+
166+ let e2 = if normalized_pq < self . ks {
167+ normalized_pq
168+ } else {
169+ self . hermite_spline ( normalized_pq)
170+ } ;
171+
172+ let one_minus_e2 = 1.0 - e2;
173+ let one_minus_e2_2 = one_minus_e2 * one_minus_e2;
174+ let one_minus_e2_4 = one_minus_e2_2 * one_minus_e2_2;
175+ let e3 = fmla ( self . min_lum , one_minus_e2_4, e2) ;
176+ let e4 = e3 * self . content_luminance_range + self . content_min_luminance ;
177+ let d4 = pq_to_linear_unscaled ( e4) * 10000.0 ;
178+ let new_luminance = d4. min ( self . display_max_brightness ) . max ( 0. ) ;
179+
180+ let min_luminance = 1e-6 ;
181+ let use_limit = luma <= min_luminance;
182+ let ratio = new_luminance / luma. max ( min_luminance) ;
183+ let limit = new_luminance * self . inv_target_peak ;
184+ let scale = ratio * self . normalizer ;
185+ if use_limit {
186+ limit
187+ } else {
188+ scale
189+ }
190+ }
191+ }
192+
193+ impl < const CN : usize > ToneMap for Rec2408ToneMapper < CN > {
194+ fn process_lane ( & self , in_place : & mut [ f32 ] ) {
195+ for chunk in in_place. chunks_exact_mut ( CN ) {
196+ let luma = fmla (
197+ chunk[ 0 ] ,
198+ self . primaries [ 0 ] ,
199+ fmla ( chunk[ 1 ] , self . primaries [ 1 ] , chunk[ 2 ] * self . primaries [ 2 ] ) ,
200+ ) * self . content_max_brightness ;
201+ if luma == 0. {
202+ chunk[ 0 ] = 0. ;
203+ chunk[ 1 ] = 0. ;
204+ chunk[ 2 ] = 0. ;
205+ continue ;
206+ }
207+ let scale = self . make_luma_scale ( luma) ;
208+ chunk[ 0 ] *= scale;
209+ chunk[ 1 ] *= scale;
210+ chunk[ 2 ] *= scale;
211+ }
212+ }
213+
214+ fn process_luma_lane ( & self , chunk : & mut [ f32 ] ) {
215+ for chunk in chunk. chunks_exact_mut ( CN ) {
216+ let scale = self . make_luma_scale ( chunk[ 0 ] * self . content_max_brightness ) ;
217+ chunk[ 0 ] *= scale;
218+ }
219+ }
220+ }
221+
222+ #[ derive( Debug , Clone , Copy ) ]
223+ pub ( crate ) struct TunedReinhardToneMapper < const CN : usize > {
87224 w_a : f32 ,
88225 w_b : f32 ,
89226 primaries : [ f32 ; 3 ] ,
90227}
91228
92- impl < const CN : usize > Rec2408ToneMapper < CN > {
229+ impl < const CN : usize > TunedReinhardToneMapper < CN > {
93230 pub ( crate ) fn new (
94231 content_max_brightness : f32 ,
95232 display_max_brightness : f32 ,
@@ -107,14 +244,14 @@ impl<const CN: usize> Rec2408ToneMapper<CN> {
107244 }
108245}
109246
110- impl < const CN : usize > Rec2408ToneMapper < CN > {
247+ impl < const CN : usize > TunedReinhardToneMapper < CN > {
111248 #[ inline( always) ]
112249 fn tonemap ( & self , luma : f32 ) -> f32 {
113250 mlaf ( 1f32 , self . w_a , luma) / mlaf ( 1f32 , self . w_b , luma)
114251 }
115252}
116253
117- impl < const CN : usize > ToneMap for Rec2408ToneMapper < CN > {
254+ impl < const CN : usize > ToneMap for TunedReinhardToneMapper < CN > {
118255 fn process_lane ( & self , in_place : & mut [ f32 ] ) {
119256 for chunk in in_place. chunks_exact_mut ( CN ) {
120257 let luma = fmla (
0 commit comments