// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.

#ifndef __ACES__
#define __ACES__

/*
=============================================================================
	ACES: Academy Color Encoding System
	https://github.com/ampas/aces-dev/tree/v1.0
	
	License Terms for Academy Color Encoding System Components

	Academy Color Encoding System (ACES) software and tools are provided by the Academy under 
	the following terms and conditions: A worldwide, royalty-free, non-exclusive right to copy, modify, create
	derivatives, and use, in source and binary forms, is hereby granted, subject to acceptance of this license.

	Copyright (c) 2013 Academy of Motion Picture Arts and Sciences (A.M.P.A.S.). Portions contributed by
	others as indicated. All rights reserved.

	Performance of any of the aforementioned acts indicates acceptance to be bound by the following 
	terms and conditions:

	 *	Copies of source code, in whole or in part, must retain the above copyright 
		notice, this list of conditions and the Disclaimer of Warranty.
	 *	Use in binary form must retain the above copyright notice, this list of 
		conditions and the Disclaimer of Warranty in the documentation and/or other 
		materials provided with the distribution.
	 *	Nothing in this license shall be deemed to grant any rights to trademarks,
		copyrights, patents, trade secrets or any other intellectual property of 
		A.M.P.A.S. or any contributors, except as expressly stated herein.
	 *	Neither the name "A.M.P.A.S." nor the name of any other contributors to this 
		software may be used to endorse or promote products derivative of or based on
		this software without express prior written permission of A.M.P.A.S. or the
		contributors, as appropriate.

	This license shall be construed pursuant to the laws of the State of California,
	and any disputes related thereto shall be subject to the jurisdiction of the courts therein.

	Disclaimer of Warranty: THIS SOFTWARE IS PROVIDED BY A.M.P.A.S. AND CONTRIBUTORS "AS
	IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
	IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND 
	NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL A.M.P.A.S., OR ANY 
	CONTRIBUTORS OR DISTRIBUTORS, BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
	SPECIAL, EXEMPLARY, RESITUTIONARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 
	NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
	DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
	OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 
	NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
	EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

	WITHOUT LIMITING THE GENERALITY OF THE FOREGOING, THE ACADEMY SPECIFICALLY 
	DISCLAIMS ANY REPRESENTATIONS OR WARRANTIES WHATSOEVER RELATED TO PATENT OR 
	OTHER INTELLECTUAL PROPERTY RIGHTS IN THE ACADEMY COLOR ENCODING SYSTEM, OR 
	APPLICATIONS THEREOF, HELD BY PARTIES OTHER THAN A.M.P.A.S.,WHETHER DISCLOSED
	OR UNDISCLOSED.
=============================================================================
*/

static const float3x3 AP0_2_XYZ_MAT =
{
	0.9525523959, 0.0000000000, 0.0000936786,
	0.3439664498, 0.7281660966,-0.0721325464,
	0.0000000000, 0.0000000000, 1.0088251844,
};

static const float3x3 XYZ_2_AP0_MAT =
{
	 1.0498110175, 0.0000000000,-0.0000974845,
	-0.4959030231, 1.3733130458, 0.0982400361,
	 0.0000000000, 0.0000000000, 0.9912520182,
};

static const float3x3 AP1_2_XYZ_MAT = 
{
	 0.6624541811, 0.1340042065, 0.1561876870,
	 0.2722287168, 0.6740817658, 0.0536895174,
	-0.0055746495, 0.0040607335, 1.0103391003,
};

static const float3x3 XYZ_2_AP1_MAT =
{
	 1.6410233797, -0.3248032942, -0.2364246952,
	-0.6636628587,  1.6153315917,  0.0167563477,
	 0.0117218943, -0.0082844420,  0.9883948585,
};

static const float3x3 AP0_2_AP1_MAT = //mul( AP0_2_XYZ_MAT, XYZ_2_AP1_MAT );
{
	 1.4514393161, -0.2365107469, -0.2149285693,
	-0.0765537734,  1.1762296998, -0.0996759264,
	 0.0083161484, -0.0060324498,  0.9977163014,
};

static const float3x3 AP1_2_AP0_MAT = //mul( AP1_2_XYZ_MAT, XYZ_2_AP0_MAT );
{
	 0.6954522414,  0.1406786965,  0.1638690622,
	 0.0447945634,  0.8596711185,  0.0955343182,
	-0.0055258826,  0.0040252103,  1.0015006723,
};

static const float3 AP1_RGB2Y =
{
	0.2722287168, //AP1_2_XYZ_MAT[0][1],
	0.6740817658, //AP1_2_XYZ_MAT[1][1],
	0.0536895174, //AP1_2_XYZ_MAT[2][1]
};

// REC 709 primaries
static const float3x3 XYZ_2_sRGB_MAT =
{
	 3.2409699419, -1.5373831776, -0.4986107603,
	-0.9692436363,  1.8759675015,  0.0415550574,
	 0.0556300797, -0.2039769589,  1.0569715142,
};

static const float3x3 sRGB_2_XYZ_MAT =
{
	0.4124564, 0.3575761, 0.1804375,
	0.2126729, 0.7151522, 0.0721750,
	0.0193339, 0.1191920, 0.9503041,
};

// REC 2020 primaries
static const float3x3 XYZ_2_Rec2020_MAT =
{
	 1.7166084, -0.3556621, -0.2533601,
	-0.6666829,  1.6164776,  0.0157685,
	 0.0176422, -0.0427763,  0.94222867	
};

static const float3x3 Rec2020_2_XYZ_MAT =
{
	0.6369736, 0.1446172, 0.1688585,
	0.2627066, 0.6779996, 0.0592938,
	0.0000000, 0.0280728, 1.0608437
};

// P3, D65 primaries
static const float3x3 XYZ_2_P3D65_MAT =
{
	 2.4933963, -0.9313459, -0.4026945,
	-0.8294868,  1.7626597,  0.0236246,
	 0.0358507, -0.0761827,  0.9570140
};

static const float3x3 P3D65_2_XYZ_MAT =
{
	0.4865906, 0.2656683, 0.1981905,
	0.2289838, 0.6917402, 0.0792762,
	0.0000000, 0.0451135, 1.0438031
};

// Bradford chromatic adaptation transforms between ACES white point (D60) and sRGB white point (D65)
static const float3x3 D65_2_D60_CAT =
{
	 1.01303,    0.00610531, -0.014971,
	 0.00769823, 0.998165,   -0.00503203,
	-0.00284131, 0.00468516,  0.924507,
};

static const float3x3 D60_2_D65_CAT =
{
	 0.987224,   -0.00611327, 0.0159533,
	-0.00759836,  1.00186,    0.00533002,
	 0.00307257, -0.00509595, 1.08168,
};

static const float HALF_MAX = 65504.0;

float rgb_2_saturation( float3 rgb )
{
	float minrgb = min( min(rgb.r, rgb.g ), rgb.b );
	float maxrgb = max( max(rgb.r, rgb.g ), rgb.b );
	return ( max( maxrgb, 1e-10 ) - max( minrgb, 1e-10 ) ) / max( maxrgb, 1e-2 );
}

float glow_fwd( float ycIn, float glowGainIn, float glowMid)
{
   float glowGainOut;

   if (ycIn <= 2./3. * glowMid) {
     glowGainOut = glowGainIn;
   } else if ( ycIn >= 2 * glowMid) {
     glowGainOut = 0;
   } else {
     glowGainOut = glowGainIn * (glowMid / ycIn - 0.5);
   }

   return glowGainOut;
}

float glow_inv( float ycOut, float glowGainIn, float glowMid)
{
    float glowGainOut;

    if (ycOut <= ((1 + glowGainIn) * 2./3. * glowMid)) {
	    glowGainOut = -glowGainIn / (1 + glowGainIn);
    } else if ( ycOut >= (2. * glowMid)) {
		glowGainOut = 0.;
	} else {
		glowGainOut = glowGainIn * (glowMid / ycOut - 1./2.) / (glowGainIn / 2. - 1.);
    }

    return glowGainOut;
}


float sigmoid_shaper( float x)
{
	// Sigmoid function in the range 0 to 1 spanning -2 to +2.

	float t = max( 1 - abs( 0.5 * x ), 0 );
	float y = 1 + sign(x) * (1 - t*t);
	return 0.5 * y;
}


// ------- Red modifier functions
float cubic_basis_shaper
( 
  float x, 
  float w   // full base width of the shaper function (in degrees)
)
{
	//return Square( smoothstep( 0, 1, 1 - abs( 2 * x/w ) ) );

	float M[16] =
	{
		-1./6,  3./6, -3./6,  1./6 ,
		 3./6, -6./6,  3./6,  0./6 ,
		-3./6,  0./6,  3./6,  0./6 ,
		 1./6,  4./6,  1./6,  0./6 
	};
  
	float knots[5] = { -0.5 * w, -0.25 * w, 0, 0.25 * w, 0.5 * w };
  
	float y = 0;
	if ((x > knots[0]) && (x < knots[4]))
	{  
		float knot_coord = (x - knots[0]) * 4.0 / w;
		int j = knot_coord;
		float t = knot_coord - j;
      
		float monomials[4] = { t*t*t, t*t, t, 1.0 };

		// (if/else structure required for compatibility with CTL < v1.5.)
		if ( j == 3) {
			y = monomials[0] * M[0*4+0] + monomials[1] * M[1*4+0] + 
				monomials[2] * M[2*4+0] + monomials[3] * M[3*4+0];
		} else if ( j == 2) {
			y = monomials[0] * M[0*4+1] + monomials[1] * M[1*4+1] + 
				monomials[2] * M[2*4+1] + monomials[3] * M[3*4+1];
		} else if ( j == 1) {
			y = monomials[0] * M[0*4+2] + monomials[1] * M[1*4+2] + 
				monomials[2] * M[2*4+2] + monomials[3] * M[3*4+2];
		} else if ( j == 0) {
			y = monomials[0] * M[0*4+3] + monomials[1] * M[1*4+3] + 
				monomials[2] * M[2*4+3] + monomials[3] * M[3*4+3];
		} else {
			y = 0.0;
		}
	}
  
	return y * 1.5;
}

float center_hue( float hue, float centerH)
{
	float hueCentered = hue - centerH;
	if (hueCentered < -180.)
		hueCentered += 360;
	else if (hueCentered > 180.)
		hueCentered -= 360;
	return hueCentered;
}


// Textbook monomial to basis-function conversion matrix.
static const float3x3 M =
{
	{  0.5, -1.0, 0.5 },
	{ -1.0,  1.0, 0.5 },
	{  0.5,  0.0, 0.0 }
};

struct SegmentedSplineParams_c5
{
	float coefsLow[6];    // coefs for B-spline between minPoint and midPoint (units of log luminance)
	float coefsHigh[6];   // coefs for B-spline between midPoint and maxPoint (units of log luminance)
	float2 minPoint; // {luminance, luminance} linear extension below this
	float2 midPoint; // {luminance, luminance} 
	float2 maxPoint; // {luminance, luminance} linear extension above this
	float slopeLow;       // log-log slope of low linear extension
	float slopeHigh;      // log-log slope of high linear extension
};

struct SegmentedSplineParams_c9
{
	float coefsLow[10];    // coefs for B-spline between minPoint and midPoint (units of log luminance)
	float coefsHigh[10];   // coefs for B-spline between midPoint and maxPoint (units of log luminance)
	float2 minPoint; // {luminance, luminance} linear extension below this
	float2 midPoint; // {luminance, luminance} 
	float2 maxPoint; // {luminance, luminance} linear extension above this
	float slopeLow;       // log-log slope of low linear extension
	float slopeHigh;      // log-log slope of high linear extension
};

float segmented_spline_c5_fwd( float x )
{
	// RRT_PARAMS
	const SegmentedSplineParams_c5 C =
	{
		// coefsLow[6]
		{ -4.0000000000, -4.0000000000, -3.1573765773, -0.4852499958, 1.8477324706, 1.8477324706 },
		// coefsHigh[6]
		{ -0.7185482425, 2.0810307172, 3.6681241237, 4.0000000000, 4.0000000000, 4.0000000000 },
		{ 0.18*exp2(-15.0), 0.0001},    // minPoint
		{ 0.18,              4.8},    // midPoint
		{ 0.18*exp2(18.0),  10000.},    // maxPoint
		0.0,  // slopeLow
		0.0   // slopeHigh
	};

	const int N_KNOTS_LOW = 4;
	const int N_KNOTS_HIGH = 4;

	// Check for negatives or zero before taking the log. If negative or zero,
	// set to ACESMIN.1
	float xCheck = x <= 0 ? exp2(-14.0) : x;

	float logx = log10( xCheck);
	float logy;

	if ( logx <= log10(C.minPoint.x) )
	{ 
		logy = logx * C.slopeLow + ( log10(C.minPoint.y) - C.slopeLow * log10(C.minPoint.x) );
	}
	else if (( logx > log10(C.minPoint.x) ) && ( logx < log10(C.midPoint.x) ))
	{
		float knot_coord = (N_KNOTS_LOW-1) * (logx-log10(C.minPoint.x))/(log10(C.midPoint.x)-log10(C.minPoint.x));
		int j = knot_coord;
		float t = knot_coord - j;

		float3 cf = { C.coefsLow[ j], C.coefsLow[ j + 1], C.coefsLow[ j + 2]};
    
		float3 monomials = { t * t, t, 1.0 };
		logy = dot( monomials, mul( cf, M));
	}
	else if (( logx >= log10(C.midPoint.x) ) && ( logx < log10(C.maxPoint.x) ))
	{
		float knot_coord = (N_KNOTS_HIGH-1) * (logx-log10(C.midPoint.x))/(log10(C.maxPoint.x)-log10(C.midPoint.x));
		int j = knot_coord;
		float t = knot_coord - j;

		float3 cf = { C.coefsHigh[ j], C.coefsHigh[ j + 1], C.coefsHigh[ j + 2]};

		float3 monomials = { t * t, t, 1.0 };
		logy = dot( monomials, mul( cf, M));
	}
	else
	{ //if ( logIn >= log10(C.maxPoint.x) ) { 
		logy = logx * C.slopeHigh + ( log10(C.maxPoint.y) - C.slopeHigh * log10(C.maxPoint.x) );
	}

	return pow( 10, logy );
}

float segmented_spline_c5_rev( float y )
{  
	// RRT_PARAMS
	const SegmentedSplineParams_c5 C =
	{
		// coefsLow[6]
		{ -4.0000000000, -4.0000000000, -3.1573765773, -0.4852499958, 1.8477324706, 1.8477324706 },
		// coefsHigh[6]
		{ -0.7185482425, 2.0810307172, 3.6681241237, 4.0000000000, 4.0000000000, 4.0000000000 },
		{ 0.18*exp2(-15.0), 0.0001},    // minPoint
		{ 0.18,              4.8},    // midPoint
		{ 0.18*exp2(18.0),  10000.},    // maxPoint
		0.0,  // slopeLow
		0.0   // slopeHigh
	};

	const int N_KNOTS_LOW = 4;
	const int N_KNOTS_HIGH = 4;

	const float KNOT_INC_LOW = (log10(C.midPoint.x) - log10(C.minPoint.x)) / (N_KNOTS_LOW - 1.);
	const float KNOT_INC_HIGH = (log10(C.maxPoint.x) - log10(C.midPoint.x)) / (N_KNOTS_HIGH - 1.);

	int i;
  
	// KNOT_Y is luminance of the spline at each knot
	float KNOT_Y_LOW[N_KNOTS_LOW];
	for (i = 0; i < N_KNOTS_LOW; i = i+1)
	{
		KNOT_Y_LOW[i] = (C.coefsLow[i] + C.coefsLow[i+1]) / 2.;
	};

	float KNOT_Y_HIGH[N_KNOTS_HIGH];
	for (i = 0; i < N_KNOTS_HIGH; i = i+1) 
	{
		KNOT_Y_HIGH[i] = ( C.coefsHigh[i] + C.coefsHigh[i+1]) / 2.;
	};

	float logy = log10(max(y,1e-10));

	float logx;
	if (logy <= log10(C.minPoint.y)) 
	{
		logx = log10(C.minPoint.x);
	} 
	else if ( (logy > log10(C.minPoint.y)) && (logy <= log10(C.midPoint.y)) ) 
	{
		uint j;
		float3 cf;
		if (logy > KNOT_Y_LOW[ 0] && logy <= KNOT_Y_LOW[ 1]) {
			cf[ 0] = C.coefsLow[0];  cf[ 1] = C.coefsLow[1];  cf[ 2] = C.coefsLow[2];  j = 0;
		} else if ( logy > KNOT_Y_LOW[ 1] && logy <= KNOT_Y_LOW[ 2]) {
			cf[ 0] = C.coefsLow[1];  cf[ 1] = C.coefsLow[2];  cf[ 2] = C.coefsLow[3];  j = 1;
		} else if ( logy > KNOT_Y_LOW[ 2] && logy <= KNOT_Y_LOW[ 3]) {
			cf[ 0] = C.coefsLow[2];  cf[ 1] = C.coefsLow[3];  cf[ 2] = C.coefsLow[4];  j = 2;
		} 
    
		const float3 tmp = mul( cf, M);

		float a = tmp[ 0];
		float b = tmp[ 1];
		float c = tmp[ 2];
		c = c - logy;

		const float d = sqrt( b * b - 4. * a * c);

		const float t = ( 2. * c) / ( -d - b);

		logx = log10(C.minPoint.x) + ( t + j) * KNOT_INC_LOW;
	} 
	else if ( (logy > log10(C.midPoint.y)) && (logy < log10(C.maxPoint.y)) ) 
	{
		uint j;
		float3 cf;
		if ( logy > KNOT_Y_HIGH[ 0] && logy <= KNOT_Y_HIGH[ 1]) {
			cf[ 0] = C.coefsHigh[0];  cf[ 1] = C.coefsHigh[1];  cf[ 2] = C.coefsHigh[2];  j = 0;
		} else if ( logy > KNOT_Y_HIGH[ 1] && logy <= KNOT_Y_HIGH[ 2]) {
			cf[ 0] = C.coefsHigh[1];  cf[ 1] = C.coefsHigh[2];  cf[ 2] = C.coefsHigh[3];  j = 1;
		} else if ( logy > KNOT_Y_HIGH[ 2] && logy <= KNOT_Y_HIGH[ 3]) {
			cf[ 0] = C.coefsHigh[2];  cf[ 1] = C.coefsHigh[3];  cf[ 2] = C.coefsHigh[4];  j = 2;
		} 
    
		const float3 tmp = mul( cf, M);

		float a = tmp[ 0];
		float b = tmp[ 1];
		float c = tmp[ 2];
		c = c - logy;

		const float d = sqrt( b * b - 4. * a * c);

		const float t = ( 2. * c) / ( -d - b);

		logx = log10(C.midPoint.x) + ( t + j) * KNOT_INC_HIGH;
	} 
	else 
	{ //if ( logy >= log10(C.maxPoint.y) ) {
		logx = log10(C.maxPoint.x);
	}
  
	return pow(10, logx);
}

float segmented_spline_c9_fwd( float x, const SegmentedSplineParams_c9 C )
{
	const int N_KNOTS_LOW = 8;
	const int N_KNOTS_HIGH = 8;

	// Check for negatives or zero before taking the log. If negative or zero,
	// set to OCESMIN.
	float xCheck = x <= 0 ? 1e-4 : x;

	float logx = log10(xCheck);
	float logy;

	if (logx <= log10(C.minPoint.x))
	{
		logy = logx * C.slopeLow + (log10(C.minPoint.y) - C.slopeLow * log10(C.minPoint.x));
	}
	else if ((logx > log10(C.minPoint.x)) && (logx < log10(C.midPoint.x)))
	{
		float knot_coord = (N_KNOTS_LOW - 1) * (logx - log10(C.minPoint.x)) / (log10(C.midPoint.x) - log10(C.minPoint.x));
		int j = knot_coord;
		float t = knot_coord - j;

		float3 cf = { C.coefsLow[j], C.coefsLow[j + 1], C.coefsLow[j + 2] };

		float3 monomials = { t * t, t, 1.0 };
		logy = dot(monomials, mul(cf, M));
	}
	else if ((logx >= log10(C.midPoint.x)) && (logx < log10(C.maxPoint.x)))
	{
		float knot_coord = (N_KNOTS_HIGH - 1) * (logx - log10(C.midPoint.x)) / (log10(C.maxPoint.x) - log10(C.midPoint.x));
		int j = knot_coord;
		float t = knot_coord - j;

		float3 cf = { C.coefsHigh[j], C.coefsHigh[j + 1], C.coefsHigh[j + 2] };

		float3 monomials = { t * t, t, 1.0 };
		logy = dot(monomials, mul(cf, M));
	}
	else//if ( logIn >= log10(C.maxPoint.x) )
	{
		logy = logx * C.slopeHigh + (log10(C.maxPoint.y) - C.slopeHigh * log10(C.maxPoint.x));
	}

	return pow( 10, logy );
}

float segmented_spline_c9_rev( float y, const SegmentedSplineParams_c9 C )
{
	const int N_KNOTS_LOW = 8;
	const int N_KNOTS_HIGH = 8;

	const float KNOT_INC_LOW = (log10(C.midPoint.x) - log10(C.minPoint.x)) / (N_KNOTS_LOW - 1.);
	const float KNOT_INC_HIGH = (log10(C.maxPoint.x) - log10(C.midPoint.x)) / (N_KNOTS_HIGH - 1.);
  
	int i;

	// KNOT_Y is luminance of the spline at each knot
	float KNOT_Y_LOW[ N_KNOTS_LOW];
	for (i = 0; i < N_KNOTS_LOW; i = i+1) {
		KNOT_Y_LOW[ i] = ( C.coefsLow[i] + C.coefsLow[i+1]) / 2.;
	};

	float KNOT_Y_HIGH[ N_KNOTS_HIGH];
	for (i = 0; i < N_KNOTS_HIGH; i = i+1) {
		KNOT_Y_HIGH[ i] = ( C.coefsHigh[i] + C.coefsHigh[i+1]) / 2.;
	};

	float logy = log10( max( y, 1e-10));

	float logx;
	if (logy <= log10(C.minPoint.y)) {
		logx = log10(C.minPoint.x);
	} else if ( (logy > log10(C.minPoint.y)) && (logy <= log10(C.midPoint.y)) ) {
		uint j;
		float3 cf;
		if ( logy > KNOT_Y_LOW[ 0] && logy <= KNOT_Y_LOW[ 1]) {
			cf[ 0] = C.coefsLow[0];  cf[ 1] = C.coefsLow[1];  cf[ 2] = C.coefsLow[2];  j = 0;
		} else if ( logy > KNOT_Y_LOW[ 1] && logy <= KNOT_Y_LOW[ 2]) {
			cf[ 0] = C.coefsLow[1];  cf[ 1] = C.coefsLow[2];  cf[ 2] = C.coefsLow[3];  j = 1;
		} else if ( logy > KNOT_Y_LOW[ 2] && logy <= KNOT_Y_LOW[ 3]) {
			cf[ 0] = C.coefsLow[2];  cf[ 1] = C.coefsLow[3];  cf[ 2] = C.coefsLow[4];  j = 2;
		} else if ( logy > KNOT_Y_LOW[ 3] && logy <= KNOT_Y_LOW[ 4]) {
			cf[ 0] = C.coefsLow[3];  cf[ 1] = C.coefsLow[4];  cf[ 2] = C.coefsLow[5];  j = 3;
		} else if ( logy > KNOT_Y_LOW[ 4] && logy <= KNOT_Y_LOW[ 5]) {
			cf[ 0] = C.coefsLow[4];  cf[ 1] = C.coefsLow[5];  cf[ 2] = C.coefsLow[6];  j = 4;
		} else if ( logy > KNOT_Y_LOW[ 5] && logy <= KNOT_Y_LOW[ 6]) {
			cf[ 0] = C.coefsLow[5];  cf[ 1] = C.coefsLow[6];  cf[ 2] = C.coefsLow[7];  j = 5;
		} else if ( logy > KNOT_Y_LOW[ 6] && logy <= KNOT_Y_LOW[ 7]) {
			cf[ 0] = C.coefsLow[6];  cf[ 1] = C.coefsLow[7];  cf[ 2] = C.coefsLow[8];  j = 6;
		}
    
		const float3 tmp = mul( cf, M);

		float a = tmp[ 0];
		float b = tmp[ 1];
		float c = tmp[ 2];
		c = c - logy;

		const float d = sqrt( b * b - 4. * a * c);

		const float t = ( 2. * c) / ( -d - b);

		logx = log10(C.minPoint.x) + ( t + j) * KNOT_INC_LOW;
	} else if ( (logy > log10(C.midPoint.y)) && (logy < log10(C.maxPoint.y)) ) {
		uint j;
		float3 cf;
		if ( logy > KNOT_Y_HIGH[ 0] && logy <= KNOT_Y_HIGH[ 1]) {
			cf[ 0] = C.coefsHigh[0];  cf[ 1] = C.coefsHigh[1];  cf[ 2] = C.coefsHigh[2];  j = 0;
		} else if ( logy > KNOT_Y_HIGH[ 1] && logy <= KNOT_Y_HIGH[ 2]) {
			cf[ 0] = C.coefsHigh[1];  cf[ 1] = C.coefsHigh[2];  cf[ 2] = C.coefsHigh[3];  j = 1;
		} else if ( logy > KNOT_Y_HIGH[ 2] && logy <= KNOT_Y_HIGH[ 3]) {
			cf[ 0] = C.coefsHigh[2];  cf[ 1] = C.coefsHigh[3];  cf[ 2] = C.coefsHigh[4];  j = 2;
		} else if ( logy > KNOT_Y_HIGH[ 3] && logy <= KNOT_Y_HIGH[ 4]) {
			cf[ 0] = C.coefsHigh[3];  cf[ 1] = C.coefsHigh[4];  cf[ 2] = C.coefsHigh[5];  j = 3;
		} else if ( logy > KNOT_Y_HIGH[ 4] && logy <= KNOT_Y_HIGH[ 5]) {
			cf[ 0] = C.coefsHigh[4];  cf[ 1] = C.coefsHigh[5];  cf[ 2] = C.coefsHigh[6];  j = 4;
		} else if ( logy > KNOT_Y_HIGH[ 5] && logy <= KNOT_Y_HIGH[ 6]) {
			cf[ 0] = C.coefsHigh[5];  cf[ 1] = C.coefsHigh[6];  cf[ 2] = C.coefsHigh[7];  j = 5;
		} else if ( logy > KNOT_Y_HIGH[ 6] && logy <= KNOT_Y_HIGH[ 7]) {
			cf[ 0] = C.coefsHigh[6];  cf[ 1] = C.coefsHigh[7];  cf[ 2] = C.coefsHigh[8];  j = 6;
		}
    
		const float3 tmp = mul( cf, M);

		float a = tmp[ 0];
		float b = tmp[ 1];
		float c = tmp[ 2];
		c = c - logy;

		const float d = sqrt( b * b - 4. * a * c);

		const float t = ( 2. * c) / ( -d - b);

		logx = log10(C.midPoint.x) + ( t + j) * KNOT_INC_HIGH;
	} 
	else 
	{ //if ( logy >= log10(C.maxPoint.y) ) {
		logx = log10(C.maxPoint.x);
	}
  
	return pow(10, logx);
}

// Transformations from RGB to other color representations
float rgb_2_hue( float3 rgb ) 
{
	// Returns a geometric hue angle in degrees (0-360) based on RGB values.
	// For neutral colors, hue is undefined and the function will return a quiet NaN value.
	float hue;
	if (rgb[0] == rgb[1] && rgb[1] == rgb[2])
	{
		//hue = FLT_NAN; // RGB triplets where RGB are equal have an undefined hue
		hue = 0;
	}
	else
	{
		hue = (180. / PI) * atan2(sqrt(3.0)*(rgb[1] - rgb[2]), 2 * rgb[0] - rgb[1] - rgb[2]);
	}

	if (hue < 0.)
		hue = hue + 360;

	return clamp( hue, 0, 360 );
}

float rgb_2_yc( float3 rgb, float ycRadiusWeight = 1.75)
{
	// Converts RGB to a luminance proxy, here called YC
	// YC is ~ Y + K * Chroma
	// Constant YC is a cone-shaped surface in RGB space, with the tip on the 
	// neutral axis, towards white.
	// YC is normalized: RGB 1 1 1 maps to YC = 1
	//
	// ycRadiusWeight defaults to 1.75, although can be overridden in function 
	// call to rgb_2_yc
	// ycRadiusWeight = 1 -> YC for pure cyan, magenta, yellow == YC for neutral 
	// of same value
	// ycRadiusWeight = 2 -> YC for pure red, green, blue  == YC for  neutral of 
	// same value.

	float r = rgb[0]; 
	float g = rgb[1]; 
	float b = rgb[2];
  
	float chroma = sqrt(b*(b-g)+g*(g-r)+r*(r-b));

	return ( b + g + r + ycRadiusWeight * chroma) / 3.;
}

// 
// Reference Rendering Transform (RRT)
//
//   Input is ACES
//   Output is OCES
//
float3 RRT( float3 aces )
{
	// "Glow" module constants
	const float RRT_GLOW_GAIN = 0.05;
	const float RRT_GLOW_MID = 0.08;

	float saturation = rgb_2_saturation( aces );
	float ycIn = rgb_2_yc( aces );
	float s = sigmoid_shaper( (saturation - 0.4) / 0.2);
	float addedGlow = 1 + glow_fwd( ycIn, RRT_GLOW_GAIN * s, RRT_GLOW_MID);
	aces *= addedGlow;
  
	// --- Red modifier --- //
	const float RRT_RED_SCALE = 0.82;
	const float RRT_RED_PIVOT = 0.03;
	const float RRT_RED_HUE = 0;
	const float RRT_RED_WIDTH = 135;
	float hue = rgb_2_hue( aces );
	float centeredHue = center_hue( hue, RRT_RED_HUE );
	float hueWeight = cubic_basis_shaper( centeredHue, RRT_RED_WIDTH );
		
	aces.r += hueWeight * saturation * (RRT_RED_PIVOT - aces.r) * (1. - RRT_RED_SCALE);

	// --- ACES to RGB rendering space --- //
	aces = clamp( aces, 0, 65535 );  // avoids saturated negative colors from becoming positive in the matrix

	float3 rgbPre = mul( AP0_2_AP1_MAT, aces );

	rgbPre = clamp( rgbPre, 0, 65535 );

	// --- Global desaturation --- //
	const float RRT_SAT_FACTOR = 0.96;
	rgbPre = lerp( dot( rgbPre, AP1_RGB2Y ), rgbPre, RRT_SAT_FACTOR );

	// --- Apply the tonescale independently in rendering-space RGB --- //
	float3 rgbPost;
	rgbPost[0] = segmented_spline_c5_fwd( rgbPre[0] );
	rgbPost[1] = segmented_spline_c5_fwd( rgbPre[1] );
	rgbPost[2] = segmented_spline_c5_fwd( rgbPre[2] );

	// --- RGB rendering space to OCES --- //
	float3 rgbOces = mul( AP1_2_AP0_MAT, rgbPost );
	return rgbOces;
}

// 
// Inverse Reference Rendering Transform (RRT)
//
//   Input is OCES
//   Output is ACES
//

float3 Inverse_RRT( float3 oces )
{
	// "Glow" module constants
	const float RRT_GLOW_GAIN = 0.05;
	const float RRT_GLOW_MID = 0.08;

	// --- OCES to RGB rendering space --- //
    float3 rgbPre = mul(AP0_2_AP1_MAT, oces);

	// --- Apply the tonescale independently in rendering-space RGB --- //
    float3 rgbPost;
    rgbPost[0] = segmented_spline_c5_rev( rgbPre[0]);
    rgbPost[1] = segmented_spline_c5_rev( rgbPre[1]);
    rgbPost[2] = segmented_spline_c5_rev( rgbPre[2]);

	// --- Global desaturation --- //
	// rgbPost = mul( rgbPost, invert_f33(RRT_SAT_MAT));
	const float RRT_SAT_FACTOR = 0.96;
	rgbPost = lerp( dot( rgbPost, AP1_RGB2Y ), rgbPost, rcp(RRT_SAT_FACTOR) );

    rgbPost = clamp( rgbPost, 0., HALF_MAX);

	// --- RGB rendering space to ACES --- //
    float3 aces = mul( AP1_2_AP0_MAT, rgbPost);

    aces = clamp( aces, 0., HALF_MAX);

	// --- Red modifier --- //
	const float RRT_RED_SCALE = 0.82;
	const float RRT_RED_PIVOT = 0.03;
	const float RRT_RED_HUE = 0;
	const float RRT_RED_WIDTH = 135;
    float hue = rgb_2_hue( aces);
    float centeredHue = center_hue( hue, RRT_RED_HUE);
    float hueWeight = cubic_basis_shaper( centeredHue, RRT_RED_WIDTH);

    float minChan;
    if (centeredHue < 0) { 
		// min_f3(aces) = aces[1] (i.e. magenta-red)
		minChan = aces[1];
    } else { // min_f3(aces) = aces[2] (i.e. yellow-red)
		minChan = aces[2];
    }

    float a = hueWeight * (1. - RRT_RED_SCALE) - 1.;
    float b = aces[0] - hueWeight * (RRT_RED_PIVOT + minChan) * (1. - RRT_RED_SCALE);
    float c = hueWeight * RRT_RED_PIVOT * minChan * (1. - RRT_RED_SCALE);

    aces[0] = ( -b - sqrt( b * b - 4. * a * c)) / ( 2. * a);

	// --- Glow module --- //
    float saturation = rgb_2_saturation( aces);
    float ycOut = rgb_2_yc( aces);
    float s = sigmoid_shaper( (saturation - 0.4) / 0.2);
    float reducedGlow = 1. + glow_inv( ycOut, RRT_GLOW_GAIN * s, RRT_GLOW_MID);

    aces = reducedGlow * aces;

	// Assign ACES RGB to output variables (ACES)
	return aces;
}



// Transformations between CIE XYZ tristimulus values and CIE x,y 
// chromaticity coordinates
float3 XYZ_2_xyY( float3 XYZ )
{  
	float3 xyY;
	float divisor = (XYZ[0] + XYZ[1] + XYZ[2]);
	if (divisor == 0.) divisor = 1e-10;
	xyY[0] = XYZ[0] / divisor;
	xyY[1] = XYZ[1] / divisor;  
	xyY[2] = XYZ[1];
  
	return xyY;
}

float3 xyY_2_XYZ( float3 xyY )
{
	float3 XYZ;
	XYZ[0] = xyY[0] * xyY[2] / max( xyY[1], 1e-10);
	XYZ[1] = xyY[2];  
	XYZ[2] = (1.0 - xyY[0] - xyY[1]) * xyY[2] / max( xyY[1], 1e-10);

	return XYZ;
}


float3x3 ChromaticAdaptation( float2 src_xy, float2 dst_xy )
{
	// Von Kries chromatic adaptation 

	// Bradford
	const float3x3 ConeResponse =
	{
		 0.8951,  0.2664, -0.1614,
		-0.7502,  1.7135,  0.0367,
		 0.0389, -0.0685,  1.0296,
	};
	const float3x3 InvConeResponse =
	{
		 0.9869929, -0.1470543,  0.1599627,
		 0.4323053,  0.5183603,  0.0492912,
		-0.0085287,  0.0400428,  0.9684867,
	};

	float3 src_XYZ = xyY_2_XYZ( float3( src_xy, 1 ) );
	float3 dst_XYZ = xyY_2_XYZ( float3( dst_xy, 1 ) );

	float3 src_coneResp = mul( ConeResponse, src_XYZ );
	float3 dst_coneResp = mul( ConeResponse, dst_XYZ );

	float3x3 VonKriesMat =
	{
		{ dst_coneResp[0] / src_coneResp[0], 0.0, 0.0 },
		{ 0.0, dst_coneResp[1] / src_coneResp[1], 0.0 },
		{ 0.0, 0.0, dst_coneResp[2] / src_coneResp[2] }
	};

	return mul( InvConeResponse, mul( VonKriesMat, ConeResponse ) );
}

float Y_2_linCV( float Y, float Ymax, float Ymin) 
{
  return (Y - Ymin) / (Ymax - Ymin);
}

float linCV_2_Y( float linCV, float Ymax, float Ymin) 
{
  return linCV * (Ymax - Ymin) + Ymin;
}

// Gamma compensation factor
static const float DIM_SURROUND_GAMMA = 0.9811;

float3 darkSurround_to_dimSurround( float3 linearCV )
{
	float3 XYZ = mul( AP1_2_XYZ_MAT, linearCV );

	float3 xyY = XYZ_2_xyY(XYZ);
	xyY[2] = clamp( xyY[2], 0, 65535 );
	xyY[2] = pow( xyY[2], DIM_SURROUND_GAMMA );
	XYZ = xyY_2_XYZ(xyY);

	return mul( XYZ_2_AP1_MAT, XYZ );
}

float3 dimSurround_to_darkSurround( float3 linearCV)
{
  float3 XYZ = mul( linearCV, AP1_2_XYZ_MAT); 

  float3 xyY = XYZ_2_xyY(XYZ);
  xyY[2] = clamp( xyY[2], 0., 65535);
  xyY[2] = pow( xyY[2], 1./DIM_SURROUND_GAMMA);
  XYZ = xyY_2_XYZ(xyY);

  return mul( XYZ, XYZ_2_AP1_MAT);
}

// 
// Output Device Transform - RGB computer monitor (D60 simulation)
//

//
// Summary :
//  This transform is intended for mapping OCES onto a desktop computer monitor 
//  typical of those used in motion picture visual effects production used to 
//  simulate the image appearance produced by odt_p3d60. These monitors may
//  occasionally be referred to as "sRGB" displays, however, the monitor for
//  which this transform is designed does not exactly match the specifications
//  in IEC 61966-2-1:1999.
// 
//  The assumed observer adapted white is D60, and the viewing environment is 
//  that of a dim surround. 
//
//  The monitor specified is intended to be more typical of those found in 
//  visual effects production.
//
// Device Primaries : 
//  Primaries are those specified in Rec. ITU-R BT.709
//  CIE 1931 chromaticities:  x         y         Y
//              Red:          0.64      0.33
//              Green:        0.3       0.6
//              Blue:         0.15      0.06
//              White:        0.3217    0.329     100 cd/m^2
//
// Display EOTF :
//  The reference electro-optical transfer function specified in 
//  IEC 61966-2-1:1999.
//
// Signal Range:
//    This tranform outputs full range code values.
//
// Assumed observer adapted white point:
//         CIE 1931 chromaticities:    x            y
//                                     0.32168      0.33767
//
// Viewing Environment:
//   This ODT has a compensation for viewing environment variables more typical 
//   of those associated with video mastering.
//

//
//  Epic edits:
//  - This ODT has been modified to target an observer adapted white of D65.
//  - The output of the function is linear output referred values. The
//      linear to sRGB transform should be applied after this function.
//    

float3 ODT_sRGB_D65( float3 oces )
{
	// OCES to RGB rendering space
	float3 rgbPre = mul( AP0_2_AP1_MAT, oces );

	const SegmentedSplineParams_c9 ODT_48nits =
	{
		// coefsLow[10]
		{ -1.6989700043, -1.6989700043, -1.4779000000, -1.2291000000, -0.8648000000, -0.4480000000, 0.0051800000, 0.4511080334, 0.9113744414, 0.9113744414},
		// coefsHigh[10]
		{ 0.5154386965, 0.8470437783, 1.1358000000, 1.3802000000, 1.5197000000, 1.5985000000, 1.6467000000, 1.6746091357, 1.6878733390, 1.6878733390 },
		{segmented_spline_c5_fwd( 0.18*exp2(-6.5) ),  0.02},    // minPoint
		{segmented_spline_c5_fwd( 0.18 ),              4.8},    // midPoint  
		{segmented_spline_c5_fwd( 0.18*exp2(6.5) ),   48.0},    // maxPoint
		0.0,  // slopeLow
		0.04  // slopeHigh
	};

	// Apply the tonescale independently in rendering-space RGB
	float3 rgbPost;
	rgbPost[0] = segmented_spline_c9_fwd( rgbPre[0], ODT_48nits );
	rgbPost[1] = segmented_spline_c9_fwd( rgbPre[1], ODT_48nits );
	rgbPost[2] = segmented_spline_c9_fwd( rgbPre[2], ODT_48nits );

	// Target white and black points for cinema system tonescale
	const float CINEMA_WHITE = 48.0;
	const float CINEMA_BLACK = 0.02; // CINEMA_WHITE / 2400.

	// Scale luminance to linear code value
	float3 linearCV;
	linearCV[0] = Y_2_linCV( rgbPost[0], CINEMA_WHITE, CINEMA_BLACK );
	linearCV[1] = Y_2_linCV( rgbPost[1], CINEMA_WHITE, CINEMA_BLACK );
	linearCV[2] = Y_2_linCV( rgbPost[2], CINEMA_WHITE, CINEMA_BLACK );    

	// Apply gamma adjustment to compensate for dim surround
	linearCV = darkSurround_to_dimSurround( linearCV );

	// Apply desaturation to compensate for luminance difference
	const float ODT_SAT_FACTOR = 0.93;
	linearCV = lerp( dot( linearCV, AP1_RGB2Y ), linearCV, ODT_SAT_FACTOR );

	// Convert to display primary encoding
	// Rendering space RGB to XYZ
	float3 XYZ = mul( AP1_2_XYZ_MAT, linearCV );
	
	// Apply CAT from ACES white point to assumed observer adapted white point
	/*
	const float3x3 D60_2_D65_CAT =
	{
		 0.987224,   -0.00611327, 0.0159533,
		-0.00759836,  1.00186,    0.00533002,
		 0.00307257, -0.00509595, 1.08168,
	};
	*/
	XYZ = mul( D60_2_D65_CAT, XYZ );

	// CIE XYZ to display primaries
	linearCV = mul( XYZ_2_sRGB_MAT, XYZ );

	// Handle out-of-gamut values
	// Clip values < 0 or > 1 (i.e. projecting outside the display primaries)
	linearCV = saturate( linearCV );

	return linearCV;
}

// 
// Inverse Output Device Transform - RGB computer monitor (D60 simulation)
//

//
//  Epic edits:
//  - This Inverse ODT has been modified to accept an observer adapted white of D65.
//  - The input to the function is linear output referred values. The
//      sRGB to linear transform should be applied before this function.
//    

float3 Inverse_ODT_sRGB_D65(float3 linearCV)
{  
	// Convert from display primary encoding
    // Display primaries to CIE XYZ
    float3 XYZ = mul( sRGB_2_XYZ_MAT, linearCV);

    // CIE XYZ to rendering space RGB
    linearCV = mul( XYZ_2_AP1_MAT, XYZ);

	// Apply CAT from ACES white point to assumed observer adapted white point
	XYZ = mul( D65_2_D60_CAT, XYZ );

	// Undo desaturation to compensate for luminance difference
    //linearCV = mul( linearCV, invert_f33( ODT_SAT_MAT));
	const float ODT_SAT_FACTOR = 0.93;
	linearCV = lerp( dot( linearCV, AP1_RGB2Y ), linearCV, rcp(ODT_SAT_FACTOR) );

	// Undo gamma adjustment to compensate for dim surround
    linearCV = dimSurround_to_darkSurround( linearCV);

	// Undo scaling done for D60 simulation
	const float SCALE = 0.955;
    linearCV = linearCV * rcp(SCALE);

	// Target white and black points for cinema system tonescale
	const float CINEMA_WHITE = 48.0;
	const float CINEMA_BLACK = 0.02; // CINEMA_WHITE / 2400.

	// Scale linear code value to luminance
    float3 rgbPre;
    rgbPre[0] = linCV_2_Y( linearCV[0], CINEMA_WHITE, CINEMA_BLACK);
    rgbPre[1] = linCV_2_Y( linearCV[1], CINEMA_WHITE, CINEMA_BLACK);
    rgbPre[2] = linCV_2_Y( linearCV[2], CINEMA_WHITE, CINEMA_BLACK);

	const SegmentedSplineParams_c9 ODT_48nits =
	{
		// coefsLow[10]
		{ -1.6989700043, -1.6989700043, -1.4779000000, -1.2291000000, -0.8648000000, -0.4480000000, 0.0051800000, 0.4511080334, 0.9113744414, 0.9113744414},
		// coefsHigh[10]
		{ 0.5154386965, 0.8470437783, 1.1358000000, 1.3802000000, 1.5197000000, 1.5985000000, 1.6467000000, 1.6746091357, 1.6878733390, 1.6878733390 },
		{segmented_spline_c5_fwd( 0.18*pow(2.,-6.5) ),  0.02},    // minPoint
		{segmented_spline_c5_fwd( 0.18 ),                4.8},    // midPoint  
		{segmented_spline_c5_fwd( 0.18*pow(2.,6.5) ),   48.0},    // maxPoint
		0.0,  // slopeLow
		0.04  // slopeHigh
	};

	// Apply the tonescale independently in rendering-space RGB
    float3 rgbPost;
    rgbPost[0] = segmented_spline_c9_rev( rgbPre[0], ODT_48nits );
    rgbPost[1] = segmented_spline_c9_rev( rgbPre[1], ODT_48nits );
    rgbPost[2] = segmented_spline_c9_rev( rgbPre[2], ODT_48nits );

	// Rendering space RGB to OCES
    float3 oces = mul(AP1_2_AP0_MAT, rgbPost);

	return oces;
}


// 
// Output Device Transform - Rec.2020 (1000 cd/m^2)
//

//
// Summary :
//  This transform is intended for mapping OCES onto a Rec.2020 HDR display calibrated 
//  to a D65 white point at 1000 cd/m^2. The assumed observer adapted white is 
//  D65, and the viewing environment is that of a dim surround. 
//
// Device Primaries : 
//  Primaries are those specified in Rec. ITU-R BT.2020
//  CIE 1931 chromaticities:  x         y         Y
//              Red:          0.708     0.292
//              Green:        0.17      0.797
//              Blue:         0.131     0.046
//              White:        0.3127    0.329     1000 cd/m^2
//
// Display EOTF :
//  The reference electro-optical transfer function specified in SMPTE ST 
//  2084-2014. This transform makes no attempt to address the Annex functions 
//  which address integer quantization.
//
// Assumed observer adapted white point:
//         CIE 1931 chromaticities:    x            y
//                                     0.3127       0.329
//
// Viewing Environment:
//  This ODT is designed for a viewing environment more typically associated 
//  with video mastering.
//

float3 ODT_1000nits( float3 oces )
{
	// OCES to RGB rendering space
	float3 rgbPre = mul( AP0_2_AP1_MAT, oces );

	const SegmentedSplineParams_c9 ODT_1000nits =
	{
		// coefsLow[10]
		{ -4.9706219331, -3.0293780669, -2.1262, -1.5105, -1.0578, -0.4668, 0.11938, 0.7088134201, 1.2911865799, 1.2911865799 },
		// coefsHigh[10]
		{ 0.8089132070, 1.1910867930, 1.5683, 1.9483, 2.3083, 2.6384, 2.8595, 2.9872608805, 3.0127391195, 3.0127391195 },
		{segmented_spline_c5_fwd( 0.18*pow(2.,-12.) ), 0.0001},    // minPoint
		{segmented_spline_c5_fwd( 0.18 ),                10.0},    // midPoint  
		{segmented_spline_c5_fwd( 0.18*pow(2.,10.) ),  1000.0},    // maxPoint
		3.0,  // slopeLow
		0.06  // slopeHigh
	};

	// Apply the tonescale independently in rendering-space RGB
	float3 rgbPost;
	rgbPost[0] = segmented_spline_c9_fwd( rgbPre[0], ODT_1000nits );
	rgbPost[1] = segmented_spline_c9_fwd( rgbPre[1], ODT_1000nits );
	rgbPost[2] = segmented_spline_c9_fwd( rgbPre[2], ODT_1000nits );

	// Subtract small offset to allow for a code value of 0
	rgbPost -= 0.00003507384284432574;

	return rgbPost;
}


// 
// Output Device Transform - P3D60 (2000 cd/m^2)
//

//
// Summary :
//  This transform is intended for mapping OCES onto an HDR display calibrated 
//  to a D60 white point at 2000 cd/m^2. The assumed observer adapted white is 
//  D60, and the viewing environment is that of a dim surround. 
//
// Device Primaries : 
//  CIE 1931 chromaticities:  x         y         Y
//              Red:          0.68      0.32
//              Green:        0.265     0.69
//              Blue:         0.15      0.06
//              White:        0.32168   0.33767   2000 cd/m^2
//
//  Also assumes a black level of 0.005 cd/m^2
//
// Display EOTF :
//  The reference electro-optical transfer function specified in SMPTE ST 
//  2084-2014. This transform makes no attempt to address the Annex functions 
//  which address integer quantization.
//
// Assumed observer adapted white point:
//         CIE 1931 chromaticities:    x            y
//                                     0.32168      0.33767
//
// Viewing Environment:
//  This ODT is designed for a viewing environment more typically associated 
//  with video mastering.
//

float3 ODT_2000nits( float3 oces )
{
	// OCES to RGB rendering space
	float3 rgbPre = mul( AP0_2_AP1_MAT, oces );

	const SegmentedSplineParams_c9 ODT_2000nits =
	{
	  // coefsLow[10]
	  { -2.3010299957, -2.3010299957, -1.9312000000, -1.5205000000, -1.0578000000, -0.4668000000, 0.1193800000, 0.7088134201, 1.2911865799, 1.2911865799 },
	  // coefsHigh[10]
	  { 0.8019952042, 1.1980047958, 1.5943000000, 1.9973000000, 2.3783000000, 2.7684000000, 3.0515000000, 3.2746293562, 3.3274306351, 3.3274306351 },
	  {segmented_spline_c5_fwd( 0.18*pow(2.,-12.) ),  0.005},    // minPoint
	  {segmented_spline_c5_fwd( 0.18 ),                10.0},    // midPoint  
	  {segmented_spline_c5_fwd( 0.18*pow(2.,11.) ),  2000.0},    // maxPoint
	  0.0,  // slopeLow
	  0.12  // slopeHigh
	};

	// Apply the tonescale independently in rendering-space RGB
	float3 rgbPost;
	rgbPost[0] = segmented_spline_c9_fwd( rgbPre[0], ODT_2000nits );
	rgbPost[1] = segmented_spline_c9_fwd( rgbPre[1], ODT_2000nits );
	rgbPost[2] = segmented_spline_c9_fwd( rgbPre[2], ODT_2000nits );

	return rgbPost;
}

#endif
