From a1c4367b75aa63e98a14526c8cc32c3991495683 Mon Sep 17 00:00:00 2001 From: Alina Gotovtseva <35537930+alinaka@users.noreply.github.com> Date: Mon, 23 Sep 2024 03:17:35 +0100 Subject: [PATCH 1/5] Add TouchDesigner to supported apps (#2037) Signed-off-by: Alina Gotovtseva Co-authored-by: zachlewis --- docs/guides/using_ocio/compatible_software.rst | 10 ++++++++++ docs/site/homepage/data/en/supported_apps.yml | 7 +++++++ .../images/supported_apps/touchdesigner.png | Bin 0 -> 19186 bytes 3 files changed, 17 insertions(+) create mode 100644 docs/site/homepage/static/images/supported_apps/touchdesigner.png diff --git a/docs/guides/using_ocio/compatible_software.rst b/docs/guides/using_ocio/compatible_software.rst index 9bfe1ebf10..6b5e902e18 100644 --- a/docs/guides/using_ocio/compatible_software.rst +++ b/docs/guides/using_ocio/compatible_software.rst @@ -398,6 +398,16 @@ Documentation: - `Color Management `__ +TouchDesigner (Derivative) +************************** + +Website: ``__ + +Documentation: + +- `OpenColorIO TOP `__ + + Unreal Engine (Epic Games) ************************** diff --git a/docs/site/homepage/data/en/supported_apps.yml b/docs/site/homepage/data/en/supported_apps.yml index 7b044988cc..1854374772 100644 --- a/docs/site/homepage/data/en/supported_apps.yml +++ b/docs/site/homepage/data/en/supported_apps.yml @@ -192,6 +192,13 @@ supported_apps: content : Version 2019.3 and up. link : "https://www.substance3d.com/products/substance-designer/" + # portfolio item loop + - name : TouchDesigner + image : images/supported_apps/touchdesigner.png + categories : ["2D", "3D", "Animation", "Compositing", "Editing", "Lighting", "Playback", "Render", "Simulation", "Texture", "Tracking"] + content : Native support. + link : "http://www.derivative.ca/" + # portfolio item loop - name : Unreal Engine image : images/supported_apps/unreal.png diff --git a/docs/site/homepage/static/images/supported_apps/touchdesigner.png b/docs/site/homepage/static/images/supported_apps/touchdesigner.png new file mode 100644 index 0000000000000000000000000000000000000000..a5990f1eec49dfa93ff443c4687a57b4cc9497fa GIT binary patch literal 19186 zcmeHvc~p{V`>@lrZUZU|PUW-5p#jwmqJD4GHm3L=8vgS`UwzTY3;Ip6utdwySkbUgH2&wXFpeeKU3 z|DE%RRm*jkYiMY!I(5?idku}Rhcq-6@Ru$I{s)wN#0mKGRp|F8zSSt9ZW#uCEQB6) zI;x@ZCw9g3kBflc%dVdE4AszBp{4luRR=42T0`S>>?!-BZr6QztfN#P_w>J|R;j1m zKYsK3!kGPft6tpvHDyiJZO+$cZ{EC{Qm`+quk6lk*jCT8tB^Wh|Fmq4_CD>cyUrtf zJ}%uD6jjn}QKjJ+$Ek25+^rkgO+O~(K<&YQ-p2L7yA z17NT5hsKv5|K&VzMhfJZnVEWw+-zo{#DJOS6`Sb!Y<;TpDJyX6V80_OCS7 z47qJ>Kjo7DwZ_)=$9DOtIJ+epcKNO`cDq4}J4`GYj^0we2ZW zyoH>jG$vu8^>;pmB@;@5*DnjpuYqa4-!@0Lk=Br1I9>nEu{rYHcC^R5F97`>JI96J zPv(E!<$(H5bB-C`rPwWbk|@g8pW{isVa#f`4POxX!jUgJ^8d%F!CD!wb8bc%crWuq zIcL~GAC*!l@)q5TIcEGD>nNh49co6X3BDwLDExxWg4|=}wvh{R8!2EM+Asf@y~6Gq z(2fQjSz@_*Sy(`s01M@87+SMdVWXQuzuQbZntW_QZq}&uU5|K?wY|dpj(^zlrZM!p zcR$-fcS+^fMd+B-YZay%ePjs{cywq-dsg$8<-aiJi{^YeR$q+5|80vYcs-Kwq8rz_ zvps8=*ey5lHn?&ke??f_Y&iUG**Lm3Y9eiHf_nh4=7FfQgoA&j+bIpY_KsG3Gd%Mz}FPx&-TPNE=5pQq!E%2H13NHBqmj~P1ds@!<`OOTVv5c?ZEe%uJ`WV~T zli;8Qxgf%!na+nBUJ#%Cp#CbY^TK1j{6g^^=0fYjcwzOz8oSsyMQCq}v&$>i%XgR> zUOSYugUZz&TA*jBi0`MaFF@O-L?Z$CIH9HbPL#zd(U67K+xwPVD6bU@m`P6*bAFAb~MLG7MLGLm`J>p}Of_MU`8 zeyQp#f@=*EmxWbqrFQRBXHktk(q}=gZ_uiEVfZpNK2Q#(_@>$wH8ok4+!dIOtWl>2 zZPL*zT}Q_Jv!lKGn-JVB^=+aEx1z@N%N~hd)rusS1zt8!{ZQO4zXjD21Z_HWQXk8Z z|5+pxSArd-7HFi9X@)tD6}KGC;cipI3TbMH2Jv<;H3{pDnFcf?I?By=oPDYNEm@{V zeaE61GqzDf*Q%ieGDxv?k=EY0E=!I@_@Z})$AR%e1gn>+&68?#CF*R)rJgh>^%1cs z4z_g%Ku7~;Pm~k2E-*5F`Vi0HRzY_jz@<5Ls1*hMU;`5tW6+Yi*pUDSL$numv1AY0 zhLR?_PwXxs-=&YVbZT>>l&|D&+VA7cTs=nT#O&^r$2`${ut*|eg0BYJsZ-L7VUtTDHE;M4ns2QgEiz>@!DaRB6NMPZ+V9A4bvN-9$yr` zw{pb9yFh%K)1t)x#tSz9>K_Jh>P2~nnh9z_NTlIIZ8!9{xnRO4_Bw@hcfiR)pg0y|#OP3v$gtUD-}92=AIpU8(Q) zJ0MrwTA5JQ%qJ)Gasg5K)OoQ(=YBjQv#v^OC%InGUhgLK4>UwCUP@L#x{jS+C?uoU3UxKXT6pz%VP(% z2ST7K6Yu74Tp!RT@BS&<@i)_^^e$%C#^3N0V%w^+5xr)M21#r;XX(u9_U#b3k^vXf zc6MZg5Kp`0q%v>5BZwy+=KUgs1qJVM%@If>T!Q@wUdf79X65&EU2po(F(fI6Juv#T z0N-#ngPa&T=}s6_Awm67W>)77*&-7^Kf^nH4{yd3VXuv4!&u6Cfa~+e$B|Mrsp-+5 zHKK(vCc&9QP?IqfojEe6VMPgNQrJ>_LH4thv!ssg5Soi&XYw}jg{9Bfx(wEf!ILg|oEq6!HYw8VflPp#;nvDLaK;Bc!UiTBvFKo6 ziKaw`zfv3B-Q&m4oLkuX@#6|JxoIt?DpJtT!AyVXzqLeDpKr;lE0#sdc~Svp!$%%X zN0udV_v+;Z#9~V%Iy_JL(bG8=u7q8vxeChTy{HGIwSC~r97yG)jNgmEEPI=WkxTZb zbmxkoS1=3_G#C@XshuelZhmzsK^S4SG=zGFUZdhn4Zyz_4;CdjGBFd(JTvh0XzJ#f zP4BuM$#R*MJf2`ETZBPQzBV=k)s7=(wB$68P`PKaXv<%Ygo8>n4lhzDuVi{41BaRH zn?c>rAXJ_V`jcs@%@KYacONX0iZPO=rOzn`L#Rdt6FqYbr9OTYHaxl~FauSINb$TD^Nawb>9-r-Ip>B5%4K#SawZeh22i+kCE`{FTQ#e7_R!Nl=Qu z_F(wn^ycsk$_(z%?rfNzc&@%y51tyE+T72yb%^%&jl4aDczJk}1H5|f;ObrAJ=4bv zTsv?-nzoL(kh1#ra3*|mQr!tSB?J4bT$9H|TSBppHe`tu3&!<@zG*m|sAOhf}1Y-j5k%@XV-%CW0MpHQjnlhtA~BF((!ru{?!;EkZa%AddCy zwt7MQ#V1kd}0s5JF-h_qxVd&JQhHQsIW|o z623R?pYls~WVXF(4r`VYBYjtaTYh#YP*pe#qk(|sDzEgB&)qAn`klC?Yw*vzZrpfx zc{1y6Qh_CV?)0p7;STa5>%;4QiRcL-y{j-sIg^@>67@$f)nYz2yZ&mTKci ztn)??LGe|KL**R=Viod1CF+!YUid3RNViI{n{3HZCf*~(lhX`d`QH5jxq&HcdH*+% z^5QX6*#1ODnDW#eVZX1l*}x5$2!tPikc|KR@j#1N?8uEpZ~HE0rEO;k=*>nDnsTG7 zeXnUY;-t{jAdh?FB8zmG=1*z!&p_p4`jl_XAuCU4*GUA}KM)L!7cKa~osh0%`nHPq z+q=9O!DX~3DeP#f7H`oXPv#vnq~MluKNIJalWkOf`2lKNH_IMgrQ}>RqRX+jq5APb zW80f;5BV3h)Xy(@y;%@qoBtVNAng=JiZAZJpVth)ti^Mb`Z?K_5E+E{s`J~%%miS0 zQnAe@dw8X4`7Tp5|pgwKHx`|J!IqC}1BMr?ZGFcO&_aci%C zD?-@DBK1QrEynuzJIsS316ku$8C7ALsK312C>(k^jtb)xl;4Nts&G&6m=aAB!G0oA zsQKs5;Ze8=Qqj7G7e;qauxw%|U}hUka@80rL}=-EoTt_rI+Pse#tsKIjc zL2Qw1<&LS9ZSA6;VTc&A!$|p@h;v=bUsrNXgRBOdp$|`#N_2AF?bV-_b$og{9FXS7 zlKtCL;9VeQL8otN%q+@s=fCIQ<3HUB$yV_>!JPs^WDM63Sec1Vq+--~H8UF^=BT>M zU(efI3M2D*Bt#v^ag~3o!ypg>)zcVk+gkbYs_tjL1>q_`zX!$%BO+=GCTfZ*IW@@i zjg6U!&ZGqoSr^Txl6b=pH{`7lZw5jNQ{@pmyvTr4rCZWPt~Spx?`<`*N`j*t3EXG; z1(x%db%2Mv@D5WOGkF1bHaGfErmt1ahjI?LLey<8=_C`gy*Bw*WJfrh9{23^B7Jp< zAbtj;Bh_LdAlq2pJ&Zfz(Uxi6S#l=cHWYOki%1gbZ#02mmF#h=cv0A;fC(vo;or?% z^-ZB{$#L}}p@2BX1*H{BYoi>_LC}&nfsJn@ z@&iIz*WeqJC;Umx#DKJasxK7)=z)4n>ag10{^dnjtz^&RC#i>Sr@xP{y8;eRM~G9kc^EuZ!I z=aYIui zC^FIB^-1OygFrxdJFksgm2HEHXZR@@*hO$WQy5;IAzFnq4`5G+%|uW3^tC8_65r_$ zB21>rB2JMA2Oy~^2@u*sNoM@x+qZ?9 zY57R@X2%o&r--va%PZF5$tuQG-;fMkb>jcV%6z@CypfQ1-#)LCOhEl;|M`L+y1KjT zC#o`N2$rqm*Z!@`JEN~@p*_Uy`V$*@u75m)@l=>Lj5ZZl*OC#oH)d{)g%9NE%VkXl zG~b=V0~XS(A^plm7`;X<_kjy)bxWE+RkdWy!l{r2vI`c{{gGHNe}Z}r<$xlbCFqO) z{wR{K9co%Oo=9tL=~ILoeZ}XE=Pd^_;WrHMWZT=Z+Cv-ivLZvpHerBb`zTqyzOPmm zT%{xKFnwr={udaKw%jfq_MKjxn^IueJ?BrH7YWc;cX}tHU>WWAKkZA2Qh2kGnkBSj z&Eh#j8N&jSh?*Rh%gR^o1I=!a z?&PmsOD;jdtV)wad;j`Y4ZfMLknYG;P5sge)~A(Qqx&EP#B-g3302M2&rY@;pEb#2T)c+na0$cFq^mS~J%sC^QCyZ&d{yO}JO4>bXK=CXUya0x^_E6FqbxIQ% z9DOD$zpY|cq`gk4BNxVX1>0tkQ!9hKpH&frCNH>sZr^~xLPi04CDMN6snW7N4Y2*% zXI~I_f$Y#o5GhOYI}zw7>hmvMJxjM%F|9Z&hOOUyu|jWFT)|BECUPSRhVmd}Ja=$V zUu`zf#h~`}Vil@{u(HUzW7p&20kB&glUWbxL|U@m*)*D1K#bfbJ= zma7%oz=o5G1kZQzhV}SzrQpl%uOBkm(I)!D3}=87zwtcjp95(P5s%MGGZ%2tx)f?5 zQ0diCUj;dvjcj(x$8MS>F(LqO8AD|hgsV=Ihhpxdq>jFDD)STKC-Pk#)C*6i^SgCF zy}ZyGh*{Kmyl9GB8JwplY)k)y=RLck1X!-pOr&-_wVN5_v&#J`J=YZH1`TKmP?uCA zQ%QkdjPrVR(HO`0!vFIt*HWt>|9QJKyHZO);4u{5(y%9S3{Rz=diq4kzORxW*BZJ&w$39Ax2zjM5V^%PCS|>81a)ajV^iGYZ9}B71`a7Ej^LIGW=9Rllnv_b<)?M@0fu zr0Tn`Ytn5OgVc8mpilMITSW?Rdg3;Ze;rZj=y3qI0?_=U*IED&5JQ=xKv5p(zD`h7 zv->_XM^E*Tg_egPiKohWpUAa<=gvlhu}85myh@EcZN`touM!ktXhfft*C$C>(SgKP z5&enx4Xsr-Bq)W>>jI|w6IZgcBrC@i=>`AC`Q=f@YBF87uiiy0$kUUy>3pK;3`(yM zu&xHanC+iqJE{mNEURJ-_$(V6QMSn^P0Gr`{s70`NfLdAMYd#bSmZU%zi^NM8S>Z! ztA0bI?Z4qGeLpe+;Cl?^o%+eUptA$5B9nYMAbaVC@dPz<+XDjXG5ywCctA*JtA@f5 zkySq%MESHELl7#hwHi4KZvzq=>OJ|R$WMNY3ExPrK#j#&9*<{uDbGib574Z+s>gqx z{PvUU669rB#R)%$YO76^`U(0D+sL!ov$jGWC{Bv=?FCicvYGEz1K7=ER@mz+7K-D@ z2AkE5U6^F@pX>E)+Np-1+Zud5d3st%gDyLdoMop}QFpBG4%bw8bKD}YIEJ?p$@K)% zjuY?@&FEN4n?hZXoYrYs6e<1^0Sd$i>T!$(Vx3rm?}cufTQy)%Kx1vaH$U0|ptw;( zy-Bmdfz$YXe#5G_8}~p$mHcyS4}P%@-#{jCs_s)}=}mNzE;|iC4iuM`#-entY*UA( z2RE+6H-ToMCB0$}czTfUvp`e-$RSi(Yo{}6-G%Wi)-jURal*Oh9tGj5xNH46-@0`` zOnANTzB$}kvBUocPN9|FSkwoTB6E0KYg4hMA`s%c|B)FV6pBRZ7t+X(m?zOISW)No;{&xz>GJ#{)05Y2gK&l$v{I>b`^Wm!~tHabi=2a2-`x->Wqj}c=G_h zLe&4NY1G7uUNylmH}MW5D(K4rw!wHwKbu^BW?y4#rs#}ps2oTZT(e<_&jG=~ymFV| zoJ%*&0~!gN4;}}SjqA0#^OFrl?D#Gqq_H)thh~d7;+ssiSKBOsx&2-Nu{s06XM|+uTwDI4lH1LWvh2Hc{sGKHl_~Zp#y^3~m*QX*Fxn~?)dpHUN)l}aPC}yUB#41x z;+>H&m*~Xc*34NKLF>aYSM4bz&rhSf+M;(M-`(RkUI82TRg*uAh6co|byB+USIes! zD~B4d0C}spudP3G4alC|^{H+Vac?-*)QzY(!Ux7$vRo zBHj6Qi}kY!MK?KW^u9S-DDpa|sEIJs4%2OGKhRA~Jne_XUjwYhhwqV%T!H7Y?JDj*F(pFwh|o)H`UaTjf+ z{~Vw61PV4ZPX~gnQfCfZ7T-u@6S93a`OB{}Y2Oym+E-FQ(Dn2wrA~0y;Xj&8!7-H4 z?<4)?=b6xpX^y>U)Mrf?$gtPA=m^xm^+TYGZJe!*#iS@F%^prro>N!i>&l^IQq5*< znh{W^yn&)#E(lk3aULrch>!%6juFiVDqZ5`#j**hOHws4xoZLDY z!+Iueus0OwcXX-5=P5nD7gAq-BUH@y#BB^^cJkZSppOSg-cnBhT@echI8lXIH)T^I zQ-Q)iU16g5di%TxzA9FQ^RoYh>Crl(Lwlo9@YX%fUQ#;uLOM4OJvdv} z7q12?j^#>S8_S5D&30SL2^==kw&pO=b2y62oh^hxpDi!2R4blJ1~!+8oID9Q=xjNn z=fWA8C-2c!C)7HBf*(+lRV%dC&DLf>wDE46Nb1LiOlCRLsb3fC?yufjisd|wsIPj& z+Sii#x>LT*2qLwpHs3P89^4tcv6A_apM28F9ZIyRunJyzLWe%3EIGzP*K1z6`#IhetZ1M9$eyF}mF3n%m zuISM2jZgCQ_dru6)e+eEA>nFI`w>;o3)lJt>x*(*Vd(FX2~@5QdKVUtowPKHV?-!7 zK2n{A!AA3eDk)8?IrFd(2rBxS-pxf;rFZf|UhRPNs`M$0T|0HUihr(SL31XV%gw_? zPXcE8HvHO7g`Q1cId+gfr6Rhh^1YC5Faekv%(7knqRfcG7ZrmA#40VCP0=r~WEJN+ z;a16B6Otk9wb;n=rl-+GqEMaz>7Fo@ z_wgT~?38mU!yKEpH!=`>{DJFPb-X4BDk#$=TSDS3%l95G7vIr*pt$o%hj&6wy*)QKv{F#aV~0tJK3KA?)G? zKU8o9iBf98BOe;buA5jhJg;%(%mTDMUbu?bR33G2sy2{j4kk7|Oa>@Z?}O)*VG-V| zy7$SL0|E|Oibr%sW{PwxTmI^W>ytNlC!L(vTj#pP3uOdE7t1ZUH?YnGQTiCs(sVf` zde%`cD0hHY%t<}h6&R|6)wv_;5`!~-%uaMd@qno+`%<%a1kiS-xYOC!&f4akgaS=urP<{Esu&f!+P2@1 zD=jYXt{d-91dlfQ0a4dY`4Pb}s^px9QY>yrm&8UiBT>P%n`>o{@DnqVx{}*7z4Vwa7qUKE4@SU+4aNTj#3YHF^=QWf@2|{4fMbOKb3XvDeJh~4! z7T{?hjjVerIU|mPp;`*_&H=p#Rcq$)-q0K{P@(#GqrB>-=*XDPOWrRZbY5{ewUy^> zBk57&;6k)^v^gfMaFrgZ{vE+TllGYALXH#6*AybIjm`}sN9G-yEe9{r+srxaG= z2BTkGKvZRA0Tg#BS>$vn{iokbMBc(gwqHLsE^N|CEt_h7E%+V_r=>^q*r4F>+0hQm z>YXX8>k`z`)=j_+k7r~=@Y%o1B1Ip8Xf|RM9T?ydzFd4g2S!#EIe?RF&6~KKvE>BD zcBl)tW2_+;`+{f%d2|wiNhJ9z%r7@NJZg%^ z>u|A`>()}Q`GXjp@)T&=KFqHtx$7L@sv6!(h}&^^kb`lPr0}-2OCdfD6s*yG>;W+6 zHldtz1E$~_qDUy@c;pXR-*H2jR)fW{l0slIj%4)Bl+Cl$1cryw;1~mXn`OAGLp~k6 ztEoCI+Fwp6vqz?)Ip<4YPD*0SjY4csB$h=$`9p$JXp`XbyOKg?*IqgY;b54Xn80vV zt#_>oR|>Ko0cPGFRht`Z=w`nYZ4r869XB_CKc4j=q$`tB&*T=@?hc$KOODg0d$^F2 z$WyvH!)`kGLgk_Ic)QpI4v)kI3sMkW!pj05(*6QwJvL;x}fM{+zC{vv!tXy3$UNWI zIo>6U{L{C-{+(Q5@{heFReKmHovMv}zFNmMBTwcW;@F`oB2d3(IQC14#_O2yKp)?n zlSMaS#&@ten1iqgH3wQ}kd1kaPCi2xL;TJniXQ?%McNC6`5p z)F@rv1Q;udh)1Log$I~c?|6a;{mqQNEm@*n(VezdbZOM-g6@Y(D-R+d9+%xPHllVj z`0FtGe4ds>r`ov5T`J7=D~OikuS;$0F;;_Xht)=UYa=!ymc+YGfZ3Gkv7VVJzj|Vs zZV1kA#ovQ0u^e;*djbxB)z*7WYq**)Bh?~fi*_J=l-kk+|8ou5`DsJ$FYv61YB(qT z1V;bbn&Bw*kztB$h%eTTTL5`bDKp5=}dPIv0j8s=_ z?U+Ateg9ml=#t{Je?ToH-_j{>ciu--BcH>9dt*EZJs(6!XrRMv94B5 zmwpQ%N4KByp7#>{?X)625GGo;P+L{qit(p*2c-SgNHq#*H?>)QsOe#03LW{%eMT{)yTUeeJBKDJHZ0FwUuV z&~%;Q8D17jT(6!XZhyc-3tJ;~$tu4UxHj-LvDiKc(f`EfHy{-7ElnW%gmAb?j%? z8eqBKH+}Ga|LV62(*jxGftb~!JJmiw{&EY#mwO?;Tu|{p<;o6(J#cNy&xv#QEc_SU z(ei(Oiwtn9&<{TWch6}2v}h@Cv&@$t|K&W$ Date: Mon, 23 Sep 2024 03:48:08 +0100 Subject: [PATCH 2/5] Add --bitdepth flag to ocioconvert (#2038) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémi Achard Co-authored-by: Doug Walker --- src/apps/ocioconvert/main.cpp | 51 +++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/src/apps/ocioconvert/main.cpp b/src/apps/ocioconvert/main.cpp index b380d0916d..60305e06ec 100644 --- a/src/apps/ocioconvert/main.cpp +++ b/src/apps/ocioconvert/main.cpp @@ -49,6 +49,8 @@ int main(int argc, const char **argv) std::vector intAttrs; std::vector stringAttrs; + std::string outputDepth; + bool usegpu = false; bool usegpuLegacy = false; bool outputgpuInfo = false; @@ -84,6 +86,7 @@ int main(int argc, const char **argv) "--help", &help, "Display the help and exit", "-v" , &verbose, "Display general information", "", "\nOpenImageIO or OpenEXR options:", + "--bitdepth %s", &outputDepth, "Output image bitdepth", "--float-attribute %L", &floatAttrs, "\"name=float\" pair defining OIIO float attribute " "for outputimage", "--int-attribute %L", &intAttrs, "\"name=int\" pair defining an int attribute " @@ -115,6 +118,31 @@ int main(int argc, const char **argv) } #endif // OCIO_GPU_ENABLED + OCIO::BitDepth userOutputBitDepth = OCIO::BIT_DEPTH_UNKNOWN; + if (!outputDepth.empty()) + { + if (outputDepth == "uint8") + { + userOutputBitDepth = OCIO::BIT_DEPTH_UINT8; + } + else if (outputDepth == "uint16") + { + userOutputBitDepth = OCIO::BIT_DEPTH_UINT16; + } + else if (outputDepth == "half") + { + userOutputBitDepth = OCIO::BIT_DEPTH_F16; + } + else if (outputDepth == "float") + { + userOutputBitDepth = OCIO::BIT_DEPTH_F32; + } + else + { + throw OCIO::Exception("Unsupported output bitdepth, must be uint8, uint16, half or float."); + } + } + const char * inputimage = nullptr; const char * inputcolorspace = nullptr; const char * outputimage = nullptr; @@ -477,17 +505,24 @@ int main(int argc, const char **argv) const OCIO::BitDepth inputBitDepth = imgInput.getBitDepth(); OCIO::BitDepth outputBitDepth; - if (inputBitDepth == OCIO::BIT_DEPTH_UINT16 || inputBitDepth == OCIO::BIT_DEPTH_F32) + if (userOutputBitDepth != OCIO::BIT_DEPTH_UNKNOWN) { - outputBitDepth = OCIO::BIT_DEPTH_F32; - } - else if (inputBitDepth == OCIO::BIT_DEPTH_UINT8 || inputBitDepth == OCIO::BIT_DEPTH_F16) - { - outputBitDepth = OCIO::BIT_DEPTH_F16; + outputBitDepth = userOutputBitDepth; } else { - throw OCIO::Exception("Unsupported input bitdepth, must be uint8, uint16, half or float."); + if (inputBitDepth == OCIO::BIT_DEPTH_UINT16 || inputBitDepth == OCIO::BIT_DEPTH_F32) + { + outputBitDepth = OCIO::BIT_DEPTH_F32; + } + else if (inputBitDepth == OCIO::BIT_DEPTH_UINT8 || inputBitDepth == OCIO::BIT_DEPTH_F16) + { + outputBitDepth = OCIO::BIT_DEPTH_F16; + } + else + { + throw OCIO::Exception("Unsupported input bitdepth, must be uint8, uint16, half or float."); + } } OCIO::ConstCPUProcessorRcPtr cpuProcessor @@ -611,7 +646,7 @@ int main(int argc, const char **argv) imgOutput->attribute("oiio:ColorSpace", outputcolorspace); } - imgOutput->write(outputimage); + imgOutput->write(outputimage, userOutputBitDepth); } catch (...) { From 145bce2b1100c202b11a1a0ffdf124d665f9ddb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Achard?= Date: Tue, 24 Sep 2024 01:34:18 +0100 Subject: [PATCH 3/5] Add VFX 2024 to CI (#2053) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add VFX 2024 ASWF iamges to CI Signed-off-by: Rémi Achard * Bump CMake to 3.14 minimum to fix warning in FetchContent Signed-off-by: Rémi Achard * Fix error message formating Signed-off-by: Rémi Achard * Reduce warnings Signed-off-by: Rémi Achard --------- Signed-off-by: Rémi Achard Co-authored-by: Doug Walker --- .github/workflows/ci_workflow.yml | 90 +++++++++---------- CMakeLists.txt | 2 +- docs/quick_start/installation.rst | 2 +- pyproject.toml | 2 +- setup.py | 2 +- share/ci/scripts/macos/install_doxygen.sh | 4 +- .../modules/install/Installsse2neon.cmake | 2 +- src/OpenColorIO/Config.cpp | 2 +- tests/cmake-consumer/CMakeLists.txt.in | 2 +- 9 files changed, 54 insertions(+), 54 deletions(-) diff --git a/.github/workflows/ci_workflow.yml b/.github/workflows/ci_workflow.yml index f292ff7d6e..3722ec3382 100644 --- a/.github/workflows/ci_workflow.yml +++ b/.github/workflows/ci_workflow.yml @@ -40,7 +40,7 @@ jobs: # --------------------------------------------------------------------------- linux: - name: 'Linux CentOS 7 VFX CY${{ matrix.vfx-cy }} + name: 'Linux VFX CY${{ matrix.vfx-cy }} <${{ matrix.compiler-desc }} config=${{ matrix.build-type }}, shared=${{ matrix.build-shared }}, @@ -52,7 +52,7 @@ jobs: if: | github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository - # GH-hosted VM. The build runs in CentOS 7 'container' defined below. + # GH-hosted VM. The build runs in ASWF 'container' defined below. runs-on: ubuntu-latest container: # DockerHub: https://hub.docker.com/u/aswf @@ -64,7 +64,7 @@ jobs: build: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] include: # ------------------------------------------------------------------- - # VFX CY2023 (Python 3.10) + # VFX CY2024 (Python 3.11) # ------------------------------------------------------------------- - build: 12 build-type: Debug @@ -77,7 +77,7 @@ jobs: cxx-compiler: clang++ cc-compiler: clang compiler-desc: Clang - vfx-cy: 2023 + vfx-cy: 2024 install-ext-packages: MISSING - build: 11 build-type: Release @@ -90,7 +90,7 @@ jobs: cxx-compiler: g++ cc-compiler: gcc compiler-desc: GCC - vfx-cy: 2023 + vfx-cy: 2024 install-ext-packages: ALL - build: 10 build-type: Release @@ -103,10 +103,10 @@ jobs: cxx-compiler: g++ cc-compiler: gcc compiler-desc: GCC - vfx-cy: 2023 + vfx-cy: 2024 install-ext-packages: ALL # ------------------------------------------------------------------- - # VFX CY2022 (Python 3.9) + # VFX CY2023 (Python 3.10) # ------------------------------------------------------------------- - build: 9 build-type: Debug @@ -119,8 +119,8 @@ jobs: cxx-compiler: clang++ cc-compiler: clang compiler-desc: Clang - vfx-cy: 2022 - install-ext-packages: ALL + vfx-cy: 2023 + install-ext-packages: MISSING - build: 8 build-type: Release build-shared: 'ON' @@ -132,8 +132,8 @@ jobs: cxx-compiler: g++ cc-compiler: gcc compiler-desc: GCC - vfx-cy: 2022 - install-ext-packages: MISSING + vfx-cy: 2023 + install-ext-packages: ALL - build: 7 build-type: Release build-shared: 'OFF' @@ -145,40 +145,40 @@ jobs: cxx-compiler: g++ cc-compiler: gcc compiler-desc: GCC - vfx-cy: 2022 + vfx-cy: 2023 install-ext-packages: ALL # ------------------------------------------------------------------- - # VFX CY2021 (Python 3.7) + # VFX CY2022 (Python 3.9) # ------------------------------------------------------------------- - build: 6 - build-type: Release + build-type: Debug build-shared: 'ON' build-docs: 'OFF' - build-openfx: 'OFF' + build-openfx: 'ON' use-simd: 'ON' use-oiio: 'ON' cxx-standard: 17 cxx-compiler: clang++ cc-compiler: clang compiler-desc: Clang - vfx-cy: 2021 - install-ext-packages: MISSING + vfx-cy: 2022 + install-ext-packages: ALL - build: 5 build-type: Release - build-shared: 'OFF' - build-docs: 'OFF' + build-shared: 'ON' + build-docs: 'ON' build-openfx: 'ON' use-simd: 'OFF' use-oiio: 'OFF' - cxx-standard: 14 - cxx-compiler: clang++ - cc-compiler: clang - compiler-desc: Clang - vfx-cy: 2021 - install-ext-packages: ALL + cxx-standard: 17 + cxx-compiler: g++ + cc-compiler: gcc + compiler-desc: GCC + vfx-cy: 2022 + install-ext-packages: MISSING - build: 4 - build-type: Debug - build-shared: 'ON' + build-type: Release + build-shared: 'OFF' build-docs: 'OFF' build-openfx: 'OFF' use-simd: 'ON' @@ -187,39 +187,39 @@ jobs: cxx-compiler: g++ cc-compiler: gcc compiler-desc: GCC - vfx-cy: 2021 + vfx-cy: 2022 install-ext-packages: ALL # ------------------------------------------------------------------- - # VFX CY2020 (Python 3.7) + # VFX CY2021 (Python 3.7) # ------------------------------------------------------------------- - build: 3 build-type: Release build-shared: 'ON' build-docs: 'OFF' - build-openfx: 'ON' - use-simd: 'OFF' - use-oiio: 'OFF' - cxx-standard: 14 + build-openfx: 'OFF' + use-simd: 'ON' + use-oiio: 'ON' + cxx-standard: 17 cxx-compiler: clang++ cc-compiler: clang compiler-desc: Clang - vfx-cy: 2020 + vfx-cy: 2021 install-ext-packages: MISSING - build: 2 - build-type: Debug + build-type: Release build-shared: 'OFF' build-docs: 'OFF' build-openfx: 'ON' - use-simd: 'ON' - use-oiio: 'ON' + use-simd: 'OFF' + use-oiio: 'OFF' cxx-standard: 14 - cxx-compiler: g++ - cc-compiler: gcc - compiler-desc: GCC - vfx-cy: 2020 + cxx-compiler: clang++ + cc-compiler: clang + compiler-desc: Clang + vfx-cy: 2021 install-ext-packages: ALL - build: 1 - build-type: Release + build-type: Debug build-shared: 'ON' build-docs: 'OFF' build-openfx: 'OFF' @@ -229,7 +229,7 @@ jobs: cxx-compiler: g++ cc-compiler: gcc compiler-desc: GCC - vfx-cy: 2020 + vfx-cy: 2021 install-ext-packages: ALL env: CXX: ${{ matrix.cxx-compiler }} @@ -517,11 +517,11 @@ jobs: python-version: '3.11' steps: - name: Setup Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install docs env run: share/ci/scripts/macos/install_docs_env.sh if: matrix.build-docs == 'ON' diff --git a/CMakeLists.txt b/CMakeLists.txt index 3d2ad73654..0eaec4bef9 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -5,7 +5,7 @@ ############################################################################### # CMake definition. -cmake_minimum_required(VERSION 3.13) +cmake_minimum_required(VERSION 3.14) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} diff --git a/docs/quick_start/installation.rst b/docs/quick_start/installation.rst index a2e74f8175..5c34c9fe99 100644 --- a/docs/quick_start/installation.rst +++ b/docs/quick_start/installation.rst @@ -123,7 +123,7 @@ items manually: Required components: - C++ 11-17 compiler (gcc, clang, msvc) -- CMake >= 3.13 +- CMake >= 3.14 - \*Expat >= 2.4.1 (XML parser for CDL/CLF/CTF) - \*yaml-cpp >= 0.7.0 (YAML parser for Configs) - \*Imath >= 3.0 (for half domain LUTs) diff --git a/pyproject.toml b/pyproject.toml index df4058a1c5..c0a0affc60 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,7 @@ requires = [ "setuptools>=42", "wheel", - "cmake>=3.13", + "cmake>=3.14", "ninja; sys_platform != 'win32' and platform_machine != 'arm64'", # Documentation requirements (see docs/requirements.txt for details) "urllib3<2", diff --git a/setup.py b/setup.py index 0362d084ef..6481424bab 100644 --- a/setup.py +++ b/setup.py @@ -42,7 +42,7 @@ def cmake_find_package(package_name): cmakelist_path = os.path.join(tmpdir, "CMakeLists.txt") with open(cmakelist_path, "w") as f: f.write(""" -cmake_minimum_required(VERSION 3.13) +cmake_minimum_required(VERSION 3.14) project(test LANGUAGES CXX) find_package({} REQUIRED) diff --git a/share/ci/scripts/macos/install_doxygen.sh b/share/ci/scripts/macos/install_doxygen.sh index d9925c598d..2dc282fee8 100755 --- a/share/ci/scripts/macos/install_doxygen.sh +++ b/share/ci/scripts/macos/install_doxygen.sh @@ -7,7 +7,7 @@ set -ex DOXYGEN_VERSION="$1" if [ "$DOXYGEN_VERSION" == "latest" ]; then - brew install doxygen + brew install --quiet --formula doxygen else - brew install doxygen@${DOXYGEN_VERSION} + brew install --quiet --formula doxygen@${DOXYGEN_VERSION} fi diff --git a/share/cmake/modules/install/Installsse2neon.cmake b/share/cmake/modules/install/Installsse2neon.cmake index ab15a5c2f3..47877436a6 100644 --- a/share/cmake/modules/install/Installsse2neon.cmake +++ b/share/cmake/modules/install/Installsse2neon.cmake @@ -24,7 +24,7 @@ FetchContent_Declare(sse2neon FetchContent_GetProperties(sse2neon) if(NOT sse2neon_POPULATED) - FetchContent_Populate(sse2neon) + FetchContent_MakeAvailable(sse2neon) set(_EXT_DIST_INCLUDE "${CMAKE_BINARY_DIR}/ext/dist/${CMAKE_INSTALL_INCLUDEDIR}") file(COPY "${sse2neon_SOURCE_DIR}/sse2neon.h" DESTINATION "${_EXT_DIST_INCLUDE}/sse2neon") diff --git a/src/OpenColorIO/Config.cpp b/src/OpenColorIO/Config.cpp index 3d2a863986..bf2f2d29f8 100644 --- a/src/OpenColorIO/Config.cpp +++ b/src/OpenColorIO/Config.cpp @@ -755,7 +755,7 @@ class Config::Impl std::ostringstream os; os << "Config failed view validation. The display '" << display << "' "; os << "contains a shared view '" << (*sharedViewIt).m_name; - os << "that refers to a color space, '" << display << "', "; + os << "' that refers to a color space, '" << display << "', "; os << "that is not a display-referred color space."; m_validationtext = os.str(); throw Exception(m_validationtext.c_str()); diff --git a/tests/cmake-consumer/CMakeLists.txt.in b/tests/cmake-consumer/CMakeLists.txt.in index f965980bd3..5b1042e07e 100644 --- a/tests/cmake-consumer/CMakeLists.txt.in +++ b/tests/cmake-consumer/CMakeLists.txt.in @@ -3,7 +3,7 @@ # Check the OCIO CMake config find module -cmake_minimum_required(VERSION 3.13) +cmake_minimum_required(VERSION 3.14) project(consumer LANGUAGES CXX) if(NOT CMAKE_BUILD_TYPE) From 77e92a917729cfe28f16f59d708c039bad231db2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Achard?= Date: Tue, 24 Sep 2024 14:40:00 +0100 Subject: [PATCH 4/5] Python 3.13 wheels (#2052) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Python 3.13 wheels Signed-off-by: Rémi Achard * Build mac ARM wheels on Apple Silicon runners to enable testing Signed-off-by: Rémi Achard * Fix upload_pypi job dependencies Signed-off-by: Rémi Achard --------- Signed-off-by: Rémi Achard --- .github/workflows/wheel_workflow.yml | 87 ++++++++++++++++------- pyproject.toml | 7 +- setup.cfg | 1 + share/cmake/modules/FindExtPackages.cmake | 2 +- 4 files changed, 70 insertions(+), 27 deletions(-) diff --git a/.github/workflows/wheel_workflow.yml b/.github/workflows/wheel_workflow.yml index 76838cbb49..d940ac69a9 100644 --- a/.github/workflows/wheel_workflow.yml +++ b/.github/workflows/wheel_workflow.yml @@ -81,10 +81,6 @@ jobs: # ------------------------------------------------------------------- # CPython 64 bits manylinux_2_28 # ------------------------------------------------------------------- - - build: CPython 3.7 64 bits manylinux_2_28 - manylinux: manylinux_2_28 - python: cp37-manylinux_x86_64 - arch: x86_64 - build: CPython 3.8 64 bits manylinux_2_28 manylinux: manylinux_2_28 python: cp38-manylinux_x86_64 @@ -105,13 +101,13 @@ jobs: manylinux: manylinux_2_28 python: cp312-manylinux_x86_64 arch: x86_64 + - build: CPython 3.13 64 bits manylinux_2_28 + manylinux: manylinux_2_28 + python: cp313-manylinux_x86_64 + arch: x86_64 # ------------------------------------------------------------------- # CPython 64 bits manylinux2014 # ------------------------------------------------------------------- - - build: CPython 3.7 64 bits manylinux2014 - manylinux: manylinux2014 - python: cp37-manylinux_x86_64 - arch: x86_64 - build: CPython 3.8 64 bits manylinux2014 manylinux: manylinux2014 python: cp38-manylinux_x86_64 @@ -132,13 +128,13 @@ jobs: manylinux: manylinux2014 python: cp312-manylinux_x86_64 arch: x86_64 + - build: CPython 3.13 64 bits manylinux2014 + manylinux: manylinux2014 + python: cp313-manylinux_x86_64 + arch: x86_64 # ------------------------------------------------------------------- # CPython ARM 64 bits manylinux2014 # ------------------------------------------------------------------- - - build: CPython 3.7 ARM 64 bits manylinux2014 - manylinux: manylinux2014 - python: cp37-manylinux_aarch64 - arch: aarch64 - build: CPython 3.8 ARM 64 bits manylinux2014 manylinux: manylinux2014 python: cp38-manylinux_aarch64 @@ -159,6 +155,10 @@ jobs: manylinux: manylinux2014 python: cp312-manylinux_aarch64 arch: aarch64 + - build: CPython 3.13 ARM 64 bits manylinux2014 + manylinux: manylinux2014 + python: cp313-manylinux_aarch64 + arch: aarch64 steps: - uses: actions/checkout@v4 @@ -166,7 +166,7 @@ jobs: - uses: actions/setup-python@v5 name: Install Python with: - python-version: '3.8' + python-version: '3.9' - name: Set up QEMU uses: docker/setup-qemu-action@v3 @@ -174,7 +174,7 @@ jobs: platforms: all - name: Build wheels - uses: pypa/cibuildwheel@v2.16.5 + uses: pypa/cibuildwheel@v2.21.1 env: CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS: ${{ matrix.arch }} @@ -203,9 +203,6 @@ jobs: # ------------------------------------------------------------------- # CPython 64 bits # ------------------------------------------------------------------- - - build: CPython 3.7 64 bits - python: cp37-macosx_x86_64 - arch: x86_64 - build: CPython 3.8 64 bits python: cp38-macosx_x86_64 arch: x86_64 @@ -221,6 +218,43 @@ jobs: - build: CPython 3.12 64 bits python: cp312-macosx_x86_64 arch: x86_64 + - build: CPython 3.13 64 bits + python: cp313-macosx_x86_64 + arch: x86_64 + + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + name: Install Python + with: + python-version: '3.9' + + - name: Build wheels + uses: pypa/cibuildwheel@v2.21.1 + env: + CIBW_BUILD: ${{ matrix.python }} + CIBW_ARCHS: ${{ matrix.arch }} + + - uses: actions/upload-artifact@v4 + with: + name: cibw-wheels-${{ matrix.python }} + path: ./wheelhouse/*.whl + + # --------------------------------------------------------------------------- + # macOS ARM Wheels + # --------------------------------------------------------------------------- + + macos-arm: + name: Build wheels on macOS ARM + runs-on: macos-14 + # Don't run on OCIO forks + if: | + github.event_name != 'schedule' || + github.repository == 'AcademySoftwareFoundation/OpenColorIO' + strategy: + matrix: + include: # ------------------------------------------------------------------- # CPython ARM 64 bits # ------------------------------------------------------------------- @@ -239,6 +273,9 @@ jobs: - build: CPython 3.12 ARM 64 bits python: cp312-macosx_arm64 arch: arm64 + - build: CPython 3.13 ARM 64 bits + python: cp313-macosx_arm64 + arch: arm64 steps: - uses: actions/checkout@v4 @@ -246,10 +283,10 @@ jobs: - uses: actions/setup-python@v5 name: Install Python with: - python-version: '3.8' + python-version: '3.9' - name: Build wheels - uses: pypa/cibuildwheel@v2.16.5 + uses: pypa/cibuildwheel@v2.21.1 env: CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS: ${{ matrix.arch }} @@ -276,9 +313,6 @@ jobs: # ------------------------------------------------------------------- # CPython 64 bits # ------------------------------------------------------------------- - - build: CPython 3.7 64 bits - python: cp37-win_amd64 - arch: AMD64 - build: CPython 3.8 64 bits python: cp38-win_amd64 arch: AMD64 @@ -294,6 +328,9 @@ jobs: - build: CPython 3.12 64 bits python: cp312-win_amd64 arch: AMD64 + - build: CPython 3.13 64 bits + python: cp313-win_amd64 + arch: AMD64 steps: - uses: actions/checkout@v4 @@ -301,10 +338,10 @@ jobs: - uses: actions/setup-python@v5 name: Install Python with: - python-version: '3.8' + python-version: '3.9' - name: Build wheels - uses: pypa/cibuildwheel@v2.16.5 + uses: pypa/cibuildwheel@v2.21.1 env: CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS: ${{ matrix.arch }} @@ -316,7 +353,7 @@ jobs: upload_pypi: - needs: [sdist, linux, macos, windows] + needs: [sdist, linux, macos, macos-arm, windows] runs-on: ubuntu-latest if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v') steps: diff --git a/pyproject.toml b/pyproject.toml index c0a0affc60..356f50f215 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,8 +30,13 @@ test-command = [ before-build = "share/ci/scripts/linux/yum/install_docs_env.sh" [tool.cibuildwheel.macos] +# cibuildwheel in some cases set this to 10.9 by default, OCIO needs >= 10.13 +# macOS ARM wheels needs 11.0, cibuildwheel will automatically bump where appropriate +environment = { MACOSX_DEPLOYMENT_TARGET="10.13" } before-build = "share/ci/scripts/macos/install_docs_env.sh" [tool.cibuildwheel.windows] -environment = { PATH="$GITHUB_WORKSPACE/doxygen;$PATH" } +# OCIO_PYTHON_LOAD_DLLS_FROM_PATH can cause segfaults in the current wheel workflow +# Disable for now as wheels are built with static dependencies. +environment = { PATH="$GITHUB_WORKSPACE/doxygen;$PATH", OCIO_PYTHON_LOAD_DLLS_FROM_PATH="0" } before-build = 'bash -c "share/ci/scripts/windows/install_docs_env.sh $GITHUB_WORKSPACE/doxygen"' diff --git a/setup.cfg b/setup.cfg index f53c6050c7..d5af16ae6f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -13,6 +13,7 @@ classifiers = Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 + Programming Language :: Python :: 3.13 Programming Language :: Python :: Implementation :: CPython Programming Language :: C++ description = OpenColorIO (OCIO) is a complete color management solution geared towards motion picture production with an emphasis on visual effects and computer animation. diff --git a/share/cmake/modules/FindExtPackages.cmake b/share/cmake/modules/FindExtPackages.cmake index 9b06319221..accdecec2a 100644 --- a/share/cmake/modules/FindExtPackages.cmake +++ b/share/cmake/modules/FindExtPackages.cmake @@ -179,7 +179,7 @@ if(OCIO_BUILD_PYTHON OR OCIO_BUILD_DOCS) # pybind11 2.9 fixes issues with MS Visual Studio 2022 (Debug). ocio_handle_dependency( pybind11 REQUIRED ALLOW_INSTALL MIN_VERSION 2.9.2 - RECOMMENDED_VERSION 2.11.1) + RECOMMENDED_VERSION 2.12.1) endif() endif() From e0666e20220c0c5cf6367c8748ba3cdf0b99a5e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9mi=20Achard?= Date: Wed, 25 Sep 2024 20:08:43 +0100 Subject: [PATCH 5/5] ACES2 Output Transforms (#1983) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ACES2 BuiltinTransforms Signed-off-by: Rémi Achard * Fix NaNs and add bound checking on CPU gamut table sampling Signed-off-by: Rémi Achard * Use powf instead of pow for consistency Signed-off-by: Rémi Achard * Remove f suffix for floating-point literals in shaders Signed-off-by: Rémi Achard * Address PR feedback around unit tests Signed-off-by: Rémi Achard --------- Signed-off-by: Rémi Achard --- include/OpenColorIO/OpenColorTypes.h | 30 +- src/OpenColorIO/CMakeLists.txt | 1 + src/OpenColorIO/Config.cpp | 57 +- src/OpenColorIO/GpuShaderUtils.cpp | 83 + src/OpenColorIO/GpuShaderUtils.h | 9 +- src/OpenColorIO/OCIOYaml.cpp | 27 +- src/OpenColorIO/ParseUtils.cpp | 52 +- .../fileformats/ctf/CTFTransform.cpp | 12 +- .../fileformats/ctf/CTFTransform.h | 5 +- .../ops/fixedfunction/ACES2/ColorLib.h | 71 + .../ops/fixedfunction/ACES2/Common.h | 133 ++ .../ops/fixedfunction/ACES2/MatrixLib.h | 115 ++ .../ops/fixedfunction/ACES2/Transform.cpp | 930 ++++++++++++ .../ops/fixedfunction/ACES2/Transform.h | 34 + .../ops/fixedfunction/FixedFunctionOpCPU.cpp | 405 +++++ .../ops/fixedfunction/FixedFunctionOpData.cpp | 291 +++- .../ops/fixedfunction/FixedFunctionOpData.h | 52 +- .../ops/fixedfunction/FixedFunctionOpGPU.cpp | 1341 +++++++++++++++++ src/OpenColorIO/transforms/builtins/ACES.cpp | 387 +++++ .../builtins/ColorMatrixHelpers.cpp | 20 + .../transforms/builtins/ColorMatrixHelpers.h | 10 + src/bindings/python/PyTypes.cpp | 8 + tests/cpu/CMakeLists.txt | 1 + tests/cpu/Config_tests.cpp | 205 +++ .../FixedFunctionOpCPU_tests.cpp | 520 ++++++- .../cpu/transforms/BuiltinTransform_tests.cpp | 64 + tests/gpu/FixedFunctionOp_test.cpp | 488 ++++++ 27 files changed, 5248 insertions(+), 103 deletions(-) create mode 100644 src/OpenColorIO/ops/fixedfunction/ACES2/ColorLib.h create mode 100644 src/OpenColorIO/ops/fixedfunction/ACES2/Common.h create mode 100644 src/OpenColorIO/ops/fixedfunction/ACES2/MatrixLib.h create mode 100644 src/OpenColorIO/ops/fixedfunction/ACES2/Transform.cpp create mode 100644 src/OpenColorIO/ops/fixedfunction/ACES2/Transform.h diff --git a/include/OpenColorIO/OpenColorTypes.h b/include/OpenColorIO/OpenColorTypes.h index 33654baf83..2302c1e9d2 100644 --- a/include/OpenColorIO/OpenColorTypes.h +++ b/include/OpenColorIO/OpenColorTypes.h @@ -474,19 +474,23 @@ enum RangeStyle /// Enumeration of the :cpp:class:`FixedFunctionTransform` transform algorithms. enum FixedFunctionStyle { - FIXED_FUNCTION_ACES_RED_MOD_03 = 0, ///< Red modifier (ACES 0.3/0.7) - FIXED_FUNCTION_ACES_RED_MOD_10, ///< Red modifier (ACES 1.0) - FIXED_FUNCTION_ACES_GLOW_03, ///< Glow function (ACES 0.3/0.7) - FIXED_FUNCTION_ACES_GLOW_10, ///< Glow function (ACES 1.0) - FIXED_FUNCTION_ACES_DARK_TO_DIM_10, ///< Dark to dim surround correction (ACES 1.0) - FIXED_FUNCTION_REC2100_SURROUND, ///< Rec.2100 surround correction (takes one double for the gamma param) - FIXED_FUNCTION_RGB_TO_HSV, ///< Classic RGB to HSV function - FIXED_FUNCTION_XYZ_TO_xyY, ///< CIE XYZ to 1931 xy chromaticity coordinates - FIXED_FUNCTION_XYZ_TO_uvY, ///< CIE XYZ to 1976 u'v' chromaticity coordinates - FIXED_FUNCTION_XYZ_TO_LUV, ///< CIE XYZ to 1976 CIELUV colour space (D65 white) - FIXED_FUNCTION_ACES_GAMUTMAP_02, ///< ACES 0.2 Gamut clamping algorithm -- NOT IMPLEMENTED YET - FIXED_FUNCTION_ACES_GAMUTMAP_07, ///< ACES 0.7 Gamut clamping algorithm -- NOT IMPLEMENTED YET - FIXED_FUNCTION_ACES_GAMUT_COMP_13 ///< ACES 1.3 Parametric Gamut Compression (expects ACEScg values) + FIXED_FUNCTION_ACES_RED_MOD_03 = 0, ///< Red modifier (ACES 0.3/0.7) + FIXED_FUNCTION_ACES_RED_MOD_10, ///< Red modifier (ACES 1.0) + FIXED_FUNCTION_ACES_GLOW_03, ///< Glow function (ACES 0.3/0.7) + FIXED_FUNCTION_ACES_GLOW_10, ///< Glow function (ACES 1.0) + FIXED_FUNCTION_ACES_DARK_TO_DIM_10, ///< Dark to dim surround correction (ACES 1.0) + FIXED_FUNCTION_REC2100_SURROUND, ///< Rec.2100 surround correction (takes one double for the gamma param) + FIXED_FUNCTION_RGB_TO_HSV, ///< Classic RGB to HSV function + FIXED_FUNCTION_XYZ_TO_xyY, ///< CIE XYZ to 1931 xy chromaticity coordinates + FIXED_FUNCTION_XYZ_TO_uvY, ///< CIE XYZ to 1976 u'v' chromaticity coordinates + FIXED_FUNCTION_XYZ_TO_LUV, ///< CIE XYZ to 1976 CIELUV colour space (D65 white) + FIXED_FUNCTION_ACES_GAMUTMAP_02, ///< ACES 0.2 Gamut clamping algorithm -- NOT IMPLEMENTED YET + FIXED_FUNCTION_ACES_GAMUTMAP_07, ///< ACES 0.7 Gamut clamping algorithm -- NOT IMPLEMENTED YET + FIXED_FUNCTION_ACES_GAMUT_COMP_13, ///< ACES 1.3 Parametric Gamut Compression (expects ACEScg values) + FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20, ///< ACES 2.0 Display Rendering -- EXPERIMENTAL + FIXED_FUNCTION_ACES_RGB_TO_JMH_20, ///< ACES 2.0 RGB to JMh -- EXPERIMENTAL + FIXED_FUNCTION_ACES_TONESCALE_COMPRESS_20, ///< ACES 2.0 Tonescale and chroma compression -- EXPERIMENTAL + FIXED_FUNCTION_ACES_GAMUT_COMPRESS_20, ///< ACES 2.0 Gamut compression -- EXPERIMENTAL }; /// Enumeration of the :cpp:class:`ExposureContrastTransform` transform algorithms. diff --git a/src/OpenColorIO/CMakeLists.txt b/src/OpenColorIO/CMakeLists.txt index 4db41aa096..7d2894da6b 100755 --- a/src/OpenColorIO/CMakeLists.txt +++ b/src/OpenColorIO/CMakeLists.txt @@ -84,6 +84,7 @@ set(SOURCES ops/exposurecontrast/ExposureContrastOpData.cpp ops/exposurecontrast/ExposureContrastOpGPU.cpp ops/exposurecontrast/ExposureContrastOp.cpp + ops/fixedfunction/ACES2/Transform.cpp ops/fixedfunction/FixedFunctionOpCPU.cpp ops/fixedfunction/FixedFunctionOpData.cpp ops/fixedfunction/FixedFunctionOpGPU.cpp diff --git a/src/OpenColorIO/Config.cpp b/src/OpenColorIO/Config.cpp index bf2f2d29f8..c3d63698bc 100644 --- a/src/OpenColorIO/Config.cpp +++ b/src/OpenColorIO/Config.cpp @@ -5233,7 +5233,38 @@ void Config::Impl::checkVersionConsistency(ConstTransformRcPtr & transform) cons } if (m_majorVersion == 2 && m_minorVersion < 4 && ( 0 == Platform::Strcasecmp(blt->getStyle(), "APPLE_LOG_to_ACES2065-1") - || 0 == Platform::Strcasecmp(blt->getStyle(), "CURVE - APPLE_LOG_to_LINEAR") ) + || 0 == Platform::Strcasecmp(blt->getStyle(), "CURVE - APPLE_LOG_to_LINEAR") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-REC709_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-P3-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-108nit-P3-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-300nit-P3-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-P3-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-P3-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-P3-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-P3-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-REC2020_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-REC2020_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-REC2020_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-REC2020_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-REC709-D60-in-REC709-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-REC709-D60-in-P3-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-REC709-D60-in-REC2020-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-P3-D60-in-P3-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-P3-D60-in-XYZ-E_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-108nit-P3-D60-in-P3-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-300nit-P3-D60-in-XYZ-E_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-P3-D60-in-P3-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-P3-D60-in-P3-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-P3-D60-in-P3-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-P3-D60-in-P3-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-P3-D60-in-REC2020-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-P3-D60-in-REC2020-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-P3-D60-in-REC2020-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-P3-D60-in-REC2020-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-REC2020-D60-in-REC2020-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-REC2020-D60-in-REC2020-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-REC2020-D60-in-REC2020-D65_2.0") + || 0 == Platform::Strcasecmp(blt->getStyle(), "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-REC2020-D60-in-REC2020-D65_2.0") ) ) { std::ostringstream os; @@ -5311,6 +5342,30 @@ void Config::Impl::checkVersionConsistency(ConstTransformRcPtr & transform) cons throw Exception("Only config version 2.1 (or higher) can have " "FixedFunctionTransform style 'ACES_GAMUT_COMP_13'."); } + + if (m_majorVersion == 2 && m_minorVersion < 4 && ff->getStyle() == FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20) + { + throw Exception("Only config version 2.4 (or higher) can have " + "FixedFunctionTransform style 'ACES_OUTPUT_TRANSFORM_20'."); + } + + if (m_majorVersion == 2 && m_minorVersion < 4 && ff->getStyle() == FIXED_FUNCTION_ACES_RGB_TO_JMH_20) + { + throw Exception("Only config version 2.4 (or higher) can have " + "FixedFunctionTransform style 'ACES_RGB_TO_JMH_20'."); + } + + if (m_majorVersion == 2 && m_minorVersion < 4 && ff->getStyle() == FIXED_FUNCTION_ACES_TONESCALE_COMPRESS_20) + { + throw Exception("Only config version 2.4 (or higher) can have " + "FixedFunctionTransform style 'ACES_TONESCALE_COMPRESS_20'."); + } + + if (m_majorVersion == 2 && m_minorVersion < 4 && ff->getStyle() == FIXED_FUNCTION_ACES_GAMUT_COMPRESS_20) + { + throw Exception("Only config version 2.4 (or higher) can have " + "FixedFunctionTransform style 'ACES_GAMUT_COMPRESS_20'."); + } } else if (DynamicPtrCast(transform)) { diff --git a/src/OpenColorIO/GpuShaderUtils.cpp b/src/OpenColorIO/GpuShaderUtils.cpp index a3adefbdc0..faa6ba1714 100644 --- a/src/OpenColorIO/GpuShaderUtils.cpp +++ b/src/OpenColorIO/GpuShaderUtils.cpp @@ -399,6 +399,16 @@ std::string GpuShaderText::intKeywordConst() const return str; } +std::string GpuShaderText::intDecl(const std::string & name) const +{ + if (name.empty()) + { + throw Exception("GPU variable name is empty."); + } + + return intKeyword() + " " + name; +} + std::string GpuShaderText::colorDecl(const std::string & name) const { if (name.empty()) @@ -617,6 +627,13 @@ std::string GpuShaderText::float2Keyword() const return getVecKeyword<2>(m_lang); } +std::string GpuShaderText::float2Const(const std::string& x, const std::string& y) const +{ + std::ostringstream kw; + kw << float2Keyword() << "(" << x << ", " << y << ")"; + return kw.str(); +} + std::string GpuShaderText::float2Decl(const std::string & name) const { if (name.empty()) @@ -894,6 +911,72 @@ void GpuShaderText::declareUniformArrayInt(const std::string & uniformName, unsi newLine() << (m_lang == GPU_LANGUAGE_MSL_2_0 ? "" : "uniform ") << intKeyword() << " " << uniformName << "[" << size << "];"; } +// Keep the method private as only float & double types are expected +template +std::string matrix3Mul(const T * m3x3, const std::string & vecName, GpuLanguage lang) +{ + if (vecName.empty()) + { + throw Exception("GPU variable name is empty."); + } + + std::ostringstream kw; + switch (lang) + { + case GPU_LANGUAGE_GLSL_1_2: + case GPU_LANGUAGE_GLSL_1_3: + case GPU_LANGUAGE_GLSL_4_0: + case GPU_LANGUAGE_GLSL_ES_1_0: + case GPU_LANGUAGE_GLSL_ES_3_0: + { + // OpenGL shader program requests a transposed matrix + kw << "mat3(" + << getMatrixValues(m3x3, lang, true) << ") * " << vecName; + break; + } + case GPU_LANGUAGE_CG: + { + kw << "mul(half3x3(" + << getMatrixValues(m3x3, lang, false) << "), " << vecName << ")"; + break; + } + case GPU_LANGUAGE_HLSL_DX11: + { + kw << "mul(" << vecName + << ", float3x3(" << getMatrixValues(m3x3, lang, true) << "))"; + break; + } + case LANGUAGE_OSL_1: + { + kw << "matrix(" << getMatrixValues(m3x3, lang, false) << ") * " << vecName; + break; + } + case GPU_LANGUAGE_MSL_2_0: + { + kw << "float3x3(" << getMatrixValues(m3x3, lang, true) << ") * " << vecName; + break; + } + + default: + { + throw Exception("Unknown GPU shader language."); + } + } + return kw.str(); +} + +std::string GpuShaderText::mat3fMul(const float * m3x3, + const std::string & vecName) const +{ + return matrix3Mul(m3x3, vecName, m_lang); +} + +std::string GpuShaderText::mat3fMul(const double * m3x3, + const std::string & vecName) const +{ + return matrix3Mul(m3x3, vecName, m_lang); +} + // Keep the method private as only float & double types are expected template std::string matrix4Mul(const T * m4x4, const std::string & vecName, GpuLanguage lang) diff --git a/src/OpenColorIO/GpuShaderUtils.h b/src/OpenColorIO/GpuShaderUtils.h index b533110749..d9d666ffe9 100644 --- a/src/OpenColorIO/GpuShaderUtils.h +++ b/src/OpenColorIO/GpuShaderUtils.h @@ -75,6 +75,7 @@ class GpuShaderText std::string intKeyword() const; std::string intKeywordConst() const; + std::string intDecl(const std::string& name) const; std::string colorDecl(const std::string& name) const; @@ -105,6 +106,8 @@ class GpuShaderText // std::string float2Keyword() const; + // Get the string for creating constant vector with three elements + std::string float2Const(const std::string& x, const std::string& y) const; std::string float2Decl(const std::string& name) const; // @@ -195,7 +198,11 @@ class GpuShaderText // Matrix multiplication helpers // - // Get the string for multiplying a 4x4 matrix and a four-element vector + // Get the string for multiplying a 3x3 matrix and a three-elements vector + std::string mat3fMul(const float * m3x3, const std::string & vecName) const; + std::string mat3fMul(const double * m3x3, const std::string & vecName) const; + + // Get the string for multiplying a 4x4 matrix and a four-elements vector std::string mat4fMul(const float * m4x4, const std::string & vecName) const; std::string mat4fMul(const double * m4x4, const std::string & vecName) const; diff --git a/src/OpenColorIO/OCIOYaml.cpp b/src/OpenColorIO/OCIOYaml.cpp index fd5395dfc4..b1cee18982 100644 --- a/src/OpenColorIO/OCIOYaml.cpp +++ b/src/OpenColorIO/OCIOYaml.cpp @@ -1351,7 +1351,10 @@ inline void load(const YAML::Node& node, FixedFunctionTransformRcPtr& t) { std::vector params; load(iter->second, params); - t->setParams(¶ms[0], params.size()); + if (!params.empty()) + { + t->setParams(¶ms[0], params.size()); + } } else if(key == "style") { @@ -1359,6 +1362,17 @@ inline void load(const YAML::Node& node, FixedFunctionTransformRcPtr& t) load(iter->second, style); t->setStyle( FixedFunctionStyleFromString(style.c_str()) ); styleFound = true; + + const FixedFunctionStyle styleID = t->getStyle(); + if (styleID == FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20 + || styleID == FIXED_FUNCTION_ACES_RGB_TO_JMH_20 + || styleID == FIXED_FUNCTION_ACES_TONESCALE_COMPRESS_20 + || styleID == FIXED_FUNCTION_ACES_GAMUT_COMPRESS_20) + { + std::ostringstream os; + os << "FixedFunction style is experimental and may be removed in a future release: '" << style << "'."; + LogWarning(os.str()); + } } else if(key == "direction") { @@ -1393,6 +1407,17 @@ inline void save(YAML::Emitter& out, ConstFixedFunctionTransformRcPtr t) out << YAML::Key << "style"; out << YAML::Value << YAML::Flow << FixedFunctionStyleToString(t->getStyle()); + const FixedFunctionStyle styleID = t->getStyle(); + if (styleID == FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20 + || styleID == FIXED_FUNCTION_ACES_RGB_TO_JMH_20 + || styleID == FIXED_FUNCTION_ACES_TONESCALE_COMPRESS_20 + || styleID == FIXED_FUNCTION_ACES_GAMUT_COMPRESS_20) + { + std::ostringstream os; + os << "FixedFunction style is experimental and may be removed in a future release: '" << FixedFunctionStyleToString(t->getStyle()) << "'."; + LogWarning(os.str()); + } + const size_t numParams = t->getNumParams(); if(numParams>0) { diff --git a/src/OpenColorIO/ParseUtils.cpp b/src/OpenColorIO/ParseUtils.cpp index eaeefc00e4..5ecf07fe73 100644 --- a/src/OpenColorIO/ParseUtils.cpp +++ b/src/OpenColorIO/ParseUtils.cpp @@ -353,17 +353,21 @@ const char * FixedFunctionStyleToString(FixedFunctionStyle style) { switch(style) { - case FIXED_FUNCTION_ACES_RED_MOD_03: return "ACES_RedMod03"; - case FIXED_FUNCTION_ACES_RED_MOD_10: return "ACES_RedMod10"; - case FIXED_FUNCTION_ACES_GLOW_03: return "ACES_Glow03"; - case FIXED_FUNCTION_ACES_GLOW_10: return "ACES_Glow10"; - case FIXED_FUNCTION_ACES_DARK_TO_DIM_10: return "ACES_DarkToDim10"; - case FIXED_FUNCTION_ACES_GAMUT_COMP_13: return "ACES_GamutComp13"; - case FIXED_FUNCTION_REC2100_SURROUND: return "REC2100_Surround"; - case FIXED_FUNCTION_RGB_TO_HSV: return "RGB_TO_HSV"; - case FIXED_FUNCTION_XYZ_TO_xyY: return "XYZ_TO_xyY"; - case FIXED_FUNCTION_XYZ_TO_uvY: return "XYZ_TO_uvY"; - case FIXED_FUNCTION_XYZ_TO_LUV: return "XYZ_TO_LUV"; + case FIXED_FUNCTION_ACES_RED_MOD_03: return "ACES_RedMod03"; + case FIXED_FUNCTION_ACES_RED_MOD_10: return "ACES_RedMod10"; + case FIXED_FUNCTION_ACES_GLOW_03: return "ACES_Glow03"; + case FIXED_FUNCTION_ACES_GLOW_10: return "ACES_Glow10"; + case FIXED_FUNCTION_ACES_DARK_TO_DIM_10: return "ACES_DarkToDim10"; + case FIXED_FUNCTION_ACES_GAMUT_COMP_13: return "ACES_GamutComp13"; + case FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20: return "ACES2_OutputTransform"; + case FIXED_FUNCTION_ACES_RGB_TO_JMH_20: return "ACES2_RGB_TO_JMh"; + case FIXED_FUNCTION_ACES_TONESCALE_COMPRESS_20: return "ACES2_TonescaleCompress"; + case FIXED_FUNCTION_ACES_GAMUT_COMPRESS_20: return "ACES2_GamutCompress"; + case FIXED_FUNCTION_REC2100_SURROUND: return "REC2100_Surround"; + case FIXED_FUNCTION_RGB_TO_HSV: return "RGB_TO_HSV"; + case FIXED_FUNCTION_XYZ_TO_xyY: return "XYZ_TO_xyY"; + case FIXED_FUNCTION_XYZ_TO_uvY: return "XYZ_TO_uvY"; + case FIXED_FUNCTION_XYZ_TO_LUV: return "XYZ_TO_LUV"; case FIXED_FUNCTION_ACES_GAMUTMAP_02: case FIXED_FUNCTION_ACES_GAMUTMAP_07: throw Exception("Unimplemented fixed function types: " @@ -380,17 +384,21 @@ FixedFunctionStyle FixedFunctionStyleFromString(const char * style) const char * p = (style ? style : ""); const std::string str = StringUtils::Lower(p); - if(str == "aces_redmod03") return FIXED_FUNCTION_ACES_RED_MOD_03; - else if(str == "aces_redmod10") return FIXED_FUNCTION_ACES_RED_MOD_10; - else if(str == "aces_glow03") return FIXED_FUNCTION_ACES_GLOW_03; - else if(str == "aces_glow10") return FIXED_FUNCTION_ACES_GLOW_10; - else if(str == "aces_darktodim10") return FIXED_FUNCTION_ACES_DARK_TO_DIM_10; - else if(str == "aces_gamutcomp13") return FIXED_FUNCTION_ACES_GAMUT_COMP_13; - else if(str == "rec2100_surround") return FIXED_FUNCTION_REC2100_SURROUND; - else if(str == "rgb_to_hsv") return FIXED_FUNCTION_RGB_TO_HSV; - else if(str == "xyz_to_xyy") return FIXED_FUNCTION_XYZ_TO_xyY; - else if(str == "xyz_to_uvy") return FIXED_FUNCTION_XYZ_TO_uvY; - else if(str == "xyz_to_luv") return FIXED_FUNCTION_XYZ_TO_LUV; + if(str == "aces_redmod03") return FIXED_FUNCTION_ACES_RED_MOD_03; + else if(str == "aces_redmod10") return FIXED_FUNCTION_ACES_RED_MOD_10; + else if(str == "aces_glow03") return FIXED_FUNCTION_ACES_GLOW_03; + else if(str == "aces_glow10") return FIXED_FUNCTION_ACES_GLOW_10; + else if(str == "aces_darktodim10") return FIXED_FUNCTION_ACES_DARK_TO_DIM_10; + else if(str == "aces_gamutcomp13") return FIXED_FUNCTION_ACES_GAMUT_COMP_13; + else if(str == "aces2_outputtransform") return FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20; + else if(str == "aces2_rgb_to_jmh") return FIXED_FUNCTION_ACES_RGB_TO_JMH_20; + else if(str == "aces2_tonescalecompress") return FIXED_FUNCTION_ACES_TONESCALE_COMPRESS_20; + else if(str == "aces2_gamutcompress") return FIXED_FUNCTION_ACES_GAMUT_COMPRESS_20; + else if(str == "rec2100_surround") return FIXED_FUNCTION_REC2100_SURROUND; + else if(str == "rgb_to_hsv") return FIXED_FUNCTION_RGB_TO_HSV; + else if(str == "xyz_to_xyy") return FIXED_FUNCTION_XYZ_TO_xyY; + else if(str == "xyz_to_uvy") return FIXED_FUNCTION_XYZ_TO_uvY; + else if(str == "xyz_to_luv") return FIXED_FUNCTION_XYZ_TO_LUV; // Default style is meaningless. std::stringstream ss; diff --git a/src/OpenColorIO/fileformats/ctf/CTFTransform.cpp b/src/OpenColorIO/fileformats/ctf/CTFTransform.cpp index 80dd5812f7..700f8bf6f6 100644 --- a/src/OpenColorIO/fileformats/ctf/CTFTransform.cpp +++ b/src/OpenColorIO/fileformats/ctf/CTFTransform.cpp @@ -255,7 +255,17 @@ CTFVersion GetOpMinimumVersion(const ConstOpDataRcPtr & op) { minVersion = CTF_PROCESS_LIST_VERSION_2_1; } - + else if (ff->getStyle() == FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_FWD + || ff->getStyle() == FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_INV + || ff->getStyle() == FixedFunctionOpData::ACES_RGB_TO_JMh_20 + || ff->getStyle() == FixedFunctionOpData::ACES_JMh_TO_RGB_20 + || ff->getStyle() == FixedFunctionOpData::ACES_TONESCALE_COMPRESS_20_FWD + || ff->getStyle() == FixedFunctionOpData::ACES_TONESCALE_COMPRESS_20_INV + || ff->getStyle() == FixedFunctionOpData::ACES_GAMUT_COMPRESS_20_FWD + || ff->getStyle() == FixedFunctionOpData::ACES_GAMUT_COMPRESS_20_INV) + { + minVersion = CTF_PROCESS_LIST_VERSION_2_4; + } break; } case OpData::GradingPrimaryType: diff --git a/src/OpenColorIO/fileformats/ctf/CTFTransform.h b/src/OpenColorIO/fileformats/ctf/CTFTransform.h index bf655a7812..b15e98a63b 100644 --- a/src/OpenColorIO/fileformats/ctf/CTFTransform.h +++ b/src/OpenColorIO/fileformats/ctf/CTFTransform.h @@ -115,9 +115,12 @@ static const CTFVersion CTF_PROCESS_LIST_VERSION_2_0 = CTFVersion(2, 0); // Version 2.1 2021-08 adds the 'FIXED_FUNCTION_ACES_GAMUT_COMP_13' style to FixedFunctionOp. static const CTFVersion CTF_PROCESS_LIST_VERSION_2_1 = CTFVersion(2, 1); +// Version 2.4 2024-08 adds the ACES 2 related FixedFunctionOps. +static const CTFVersion CTF_PROCESS_LIST_VERSION_2_4 = CTFVersion(2, 4); + // Add new version before this line // and do not forget to update the following line. -static const CTFVersion CTF_PROCESS_LIST_VERSION = CTF_PROCESS_LIST_VERSION_2_1; +static const CTFVersion CTF_PROCESS_LIST_VERSION = CTF_PROCESS_LIST_VERSION_2_4; // Version 1.0 initial Autodesk version for InfoElt. diff --git a/src/OpenColorIO/ops/fixedfunction/ACES2/ColorLib.h b/src/OpenColorIO/ops/fixedfunction/ACES2/ColorLib.h new file mode 100644 index 0000000000..f10ddf3d56 --- /dev/null +++ b/src/OpenColorIO/ops/fixedfunction/ACES2/ColorLib.h @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright Contributors to the OpenColorIO Project. + +#ifndef INCLUDED_OCIO_ACES2COLORLIB_H +#define INCLUDED_OCIO_ACES2COLORLIB_H + +#include "transforms/builtins/ColorMatrixHelpers.h" +#include "MatrixLib.h" + + +namespace OCIO_NAMESPACE +{ + +namespace ACES2 +{ + +inline f3 HSV_to_RGB(const f3 &HSV) +{ + const float C = HSV[2] * HSV[1]; + const float X = C * (1.f - std::abs(std::fmod(HSV[0] * 6.f, 2.f) - 1.f)); + const float m = HSV[2] - C; + + f3 RGB{}; + if (HSV[0] < 1.f/6.f) { + RGB = {C, X, 0.f}; + } else if (HSV[0] < 2./6.) { + RGB = {X, C, 0.f}; + } else if (HSV[0] < 3./6.) { + RGB = {0.f, C, X}; + } else if (HSV[0] < 4./6.) { + RGB = {0.f, X, C}; + } else if (HSV[0] < 5./6.) { + RGB = {X, 0.f, C}; + } else { + RGB = {C, 0.f, X}; + } + RGB = add_f_f3(m, RGB); + return RGB; +} + +inline m33f RGBtoXYZ_f33(const Primaries &C) +{ + return m33_from_ocio_matrix_array( + *build_conversion_matrix(C, CIE_XYZ_ILLUM_E::primaries, ADAPTATION_NONE) + ); +} + +inline m33f XYZtoRGB_f33(const Primaries &C) +{ + return m33_from_ocio_matrix_array( + *build_conversion_matrix(C, CIE_XYZ_ILLUM_E::primaries, ADAPTATION_NONE)->inverse() + ); +} + +inline m33f RGBtoRGB_f33(const Primaries &Csrc, const Primaries &Cdst) +{ + return mult_f33_f33(XYZtoRGB_f33(Cdst), RGBtoXYZ_f33(Csrc)); +} + +constexpr m33f Identity_M33 = { + 1.f, 0.f, 0.f, + 0.f, 1.f, 0.f, + 0.f, 0.f, 1.f +}; + +} // ACES2 namespace + + +} // OCIO namespace + +#endif \ No newline at end of file diff --git a/src/OpenColorIO/ops/fixedfunction/ACES2/Common.h b/src/OpenColorIO/ops/fixedfunction/ACES2/Common.h new file mode 100644 index 0000000000..aeb1524071 --- /dev/null +++ b/src/OpenColorIO/ops/fixedfunction/ACES2/Common.h @@ -0,0 +1,133 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright Contributors to the OpenColorIO Project. + +#ifndef INCLUDED_OCIO_ACES2_COMMON_H +#define INCLUDED_OCIO_ACES2_COMMON_H + +#include "MatrixLib.h" +#include "ColorLib.h" + + +namespace OCIO_NAMESPACE +{ + +namespace ACES2 +{ + +constexpr int TABLE_SIZE = 360; +constexpr int TABLE_ADDITION_ENTRIES = 2; +constexpr int TABLE_TOTAL_SIZE = TABLE_SIZE + TABLE_ADDITION_ENTRIES; +constexpr int GAMUT_TABLE_BASE_INDEX = 1; + +struct Table3D +{ + static constexpr int base_index = GAMUT_TABLE_BASE_INDEX; + static constexpr int size = TABLE_SIZE; + static constexpr int total_size = TABLE_TOTAL_SIZE; + float table[TABLE_TOTAL_SIZE][3]; +}; + +struct Table1D +{ + static constexpr int base_index = GAMUT_TABLE_BASE_INDEX; + static constexpr int size = TABLE_SIZE; + static constexpr int total_size = TABLE_TOTAL_SIZE; + float table[TABLE_TOTAL_SIZE]; +}; + +struct JMhParams +{ + float F_L; + float z; + float A_w; + float A_w_J; + f3 XYZ_w; + f3 D_RGB; + m33f MATRIX_RGB_to_CAM16; + m33f MATRIX_CAM16_to_RGB; +}; + +struct ToneScaleParams +{ + float n; + float n_r; + float g; + float t_1; + float c_t; + float s_2; + float u_2; + float m_2; +}; + +struct ChromaCompressParams +{ + float limit_J_max; + float model_gamma; + float sat; + float sat_thr; + float compr; + Table1D reach_m_table; + float chroma_compress_scale; + static constexpr float cusp_mid_blend = 1.3f; +}; + +struct GamutCompressParams +{ + float limit_J_max; + float mid_J; + float model_gamma; + float focus_dist; + float lower_hull_gamma; + Table1D reach_m_table; + Table3D gamut_cusp_table; + Table1D upper_hull_gamma_table; +}; + +// CAM +constexpr float reference_luminance = 100.f; +constexpr float L_A = 100.f; +constexpr float Y_b = 20.f; +constexpr float ac_resp = 1.f; +constexpr float ra = 2.f * ac_resp; +constexpr float ba = 0.05f + (2.f - ra); +constexpr f3 surround = {0.9f, 0.59f, 0.9f}; // Dim surround + +// Chroma compression +constexpr float chroma_compress = 2.4f; +constexpr float chroma_compress_fact = 3.3f; +constexpr float chroma_expand = 1.3f; +constexpr float chroma_expand_fact = 0.69f; +constexpr float chroma_expand_thr = 0.5f; + +// Gamut compression +constexpr float smooth_cusps = 0.12f; +constexpr float smooth_m = 0.27f; +constexpr float cusp_mid_blend = 1.3f; +constexpr float focus_gain_blend = 0.3f; +constexpr float focus_adjust_gain = 0.55f; +constexpr float focus_distance = 1.35f; +constexpr float focus_distance_scaling = 1.75f; +constexpr float compression_threshold = 0.75f; + +namespace CAM16 +{ + static const Chromaticities red_xy(0.8336, 0.1735); + static const Chromaticities grn_xy(2.3854, -1.4659); + static const Chromaticities blu_xy(0.087 , -0.125 ); + static const Chromaticities wht_xy(0.333 , 0.333 ); + + const Primaries primaries(red_xy, grn_xy, blu_xy, wht_xy); +} + +// Table generation +constexpr float gammaMinimum = 0.0f; +constexpr float gammaMaximum = 5.0f; +constexpr float gammaSearchStep = 0.4f; +constexpr float gammaAccuracy = 1e-5f; + + +} // namespace ACES2 + +} // OCIO namespace + +#endif diff --git a/src/OpenColorIO/ops/fixedfunction/ACES2/MatrixLib.h b/src/OpenColorIO/ops/fixedfunction/ACES2/MatrixLib.h new file mode 100644 index 0000000000..27a24df0c9 --- /dev/null +++ b/src/OpenColorIO/ops/fixedfunction/ACES2/MatrixLib.h @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright Contributors to the OpenColorIO Project. + +#ifndef INCLUDED_OCIO_ACES2_MATRIXLIB_H +#define INCLUDED_OCIO_ACES2_MATRIXLIB_H + +#include "ops/matrix/MatrixOpData.h" + + +namespace OCIO_NAMESPACE +{ + +namespace ACES2 +{ + +using f2 = std::array; +using f3 = std::array; +using f4 = std::array; +using m33f = std::array; + + +inline f3 f3_from_f(float v) +{ + return f3 {v, v, v}; +} + +inline f3 add_f_f3(float v, const f3 &f3) +{ + return { + v + f3[0], + v + f3[1], + v + f3[2] + }; +} + +inline f3 mult_f_f3(float v, const f3 &f3) +{ + return { + v * f3[0], + v * f3[1], + v * f3[2] + }; +} + +inline f3 mult_f3_f33(const f3 &f3, const m33f &mat33) +{ + return { + f3[0] * mat33[0] + f3[1] * mat33[1] + f3[2] * mat33[2], + f3[0] * mat33[3] + f3[1] * mat33[4] + f3[2] * mat33[5], + f3[0] * mat33[6] + f3[1] * mat33[7] + f3[2] * mat33[8] + }; +} + +inline m33f mult_f33_f33(const m33f &a, const m33f &b) +{ + return { + a[0] * b[0] + a[1] * b[3] + a[2] * b[6], + a[0] * b[1] + a[1] * b[4] + a[2] * b[7], + a[0] * b[2] + a[1] * b[5] + a[2] * b[8], + + a[3] * b[0] + a[4] * b[3] + a[5] * b[6], + a[3] * b[1] + a[4] * b[4] + a[5] * b[7], + a[3] * b[2] + a[4] * b[5] + a[5] * b[8], + + a[6] * b[0] + a[7] * b[3] + a[8] * b[6], + a[6] * b[1] + a[7] * b[4] + a[8] * b[7], + a[6] * b[2] + a[7] * b[5] + a[8] * b[8] + }; +} + +inline m33f scale_f33(const m33f &mat33, const f3 &scale) +{ + return { + mat33[0] * scale[0], mat33[3], mat33[6], + mat33[1], mat33[4] * scale[1], mat33[7], + mat33[2], mat33[5], mat33[8] * scale[2] + }; +} + +inline m33f m33_from_ocio_matrix_array(const MatrixOpData::MatrixArray &array) +{ + const auto& v = array.getValues(); + return { + (float) v[0], (float) v[1], (float) v[2], + (float) v[4], (float) v[5], (float) v[6], + (float) v[8], (float) v[9], (float) v[10], + }; +} + +inline m33f invert_f33(const m33f &mat33) +{ + MatrixOpData::MatrixArray array; + MatrixOpData::MatrixArray::Values & v = array.getValues(); + + v[0] = mat33[0]; + v[1] = mat33[1]; + v[2] = mat33[2]; + + v[4] = mat33[3]; + v[5] = mat33[4]; + v[6] = mat33[5]; + + v[8] = mat33[6]; + v[9] = mat33[7]; + v[10] = mat33[8]; + + MatrixOpData::MatrixArray inverse = *(array.inverse()); + return m33_from_ocio_matrix_array(inverse); +} + +} // ACES2 namespace + +} // OCIO namespace + +#endif diff --git a/src/OpenColorIO/ops/fixedfunction/ACES2/Transform.cpp b/src/OpenColorIO/ops/fixedfunction/ACES2/Transform.cpp new file mode 100644 index 0000000000..80b8ba5c72 --- /dev/null +++ b/src/OpenColorIO/ops/fixedfunction/ACES2/Transform.cpp @@ -0,0 +1,930 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright Contributors to the OpenColorIO Project. + +#include "Transform.h" + + +namespace OCIO_NAMESPACE +{ + +namespace ACES2 +{ + +// +// Table lookups +// + +float wrap_to_360(float hue) +{ + float y = std::fmod(hue, 360.f); + if ( y < 0.f) + { + y = y + 360.f; + } + return y; +} + +float base_hue_for_position(int i_lo, int table_size) +{ + const float result = i_lo * 360.f / table_size; + return result; +} + +int hue_position_in_uniform_table(float hue, int table_size) +{ + const float wrapped_hue = wrap_to_360(hue); + return int(wrapped_hue / 360.f * (float) table_size); +} + +int next_position_in_table(int entry, int table_size) +{ + return (entry + 1) % table_size; +} + +int clamp_to_table_bounds(int entry, int table_size) +{ + return std::min(table_size - 1, std::max(0, entry)); +} + +f2 cusp_from_table(float h, const Table3D >) +{ + int i_lo = 0; + int i_hi = gt.base_index + gt.size; // allowed as we have an extra entry in the table + int i = clamp_to_table_bounds(hue_position_in_uniform_table(h, gt.size) + gt.base_index, gt.total_size); + + while (i_lo + 1 < i_hi) + { + if (h > gt.table[i][2]) + { + i_lo = i; + } + else + { + i_hi = i; + } + i = clamp_to_table_bounds((i_lo + i_hi) / 2, gt.total_size); + } + + i_hi = std::max(1, i_hi); + + const f3 lo { + gt.table[i_hi-1][0], + gt.table[i_hi-1][1], + gt.table[i_hi-1][2] + }; + + const f3 hi { + gt.table[i_hi][0], + gt.table[i_hi][1], + gt.table[i_hi][2] + }; + + const float t = (h - lo[2]) / (hi[2] - lo[2]); + const float cuspJ = lerpf(lo[0], hi[0], t); + const float cuspM = lerpf(lo[1], hi[1], t); + + return { cuspJ, cuspM }; +} + +float reach_m_from_table(float h, const ACES2::Table1D >) +{ + const int i_lo = clamp_to_table_bounds(hue_position_in_uniform_table(h, gt.size), gt.total_size); + const int i_hi = clamp_to_table_bounds(next_position_in_table(i_lo, gt.size), gt.total_size); + + const float t = (h - i_lo) / (i_hi - i_lo); + return lerpf(gt.table[i_lo], gt.table[i_hi], t); +} + +float hue_dependent_upper_hull_gamma(float h, const ACES2::Table1D >) +{ + const int i_lo = clamp_to_table_bounds(hue_position_in_uniform_table(h, gt.size) + gt.base_index, gt.total_size); + const int i_hi = clamp_to_table_bounds(next_position_in_table(i_lo, gt.size), gt.total_size); + + const float base_hue = (float) (i_lo - gt.base_index); + + const float t = wrap_to_360(h) - base_hue; + + return lerpf(gt.table[i_lo], gt.table[i_hi], t); +} + +// +// CAM +// + +// Post adaptation non linear response compression +float panlrc_forward(float v, float F_L) +{ + const float F_L_v = powf(F_L * std::abs(v) / reference_luminance, 0.42f); + // Note that std::copysign(1.f, 0.f) returns 1 but the CTL copysign(1.,0.) returns 0. + // TODO: Should we change the behaviour? + return (400.f * std::copysign(1.f, v) * F_L_v) / (27.13f + F_L_v); +} + +float panlrc_inverse(float v, float F_L) +{ + return std::copysign(1.f, v) * reference_luminance / F_L * powf((27.13f * std::abs(v) / (400.f - std::abs(v))), 1.f / 0.42f); +} + +// Optimization used during initialization +float Y_to_J(float Y, const JMhParams ¶ms) +{ + float F_L_Y = powf(params.F_L * std::abs(Y) / reference_luminance, 0.42f); + return std::copysign(1.f, Y) * reference_luminance * powf(((400.f * F_L_Y) / (27.13f + F_L_Y)) / params.A_w_J, surround[1] * params.z); +} + +f3 RGB_to_JMh(const f3 &RGB, const JMhParams &p) +{ + const float red = RGB[0]; + const float grn = RGB[1]; + const float blu = RGB[2]; + + const float red_m = red * p.MATRIX_RGB_to_CAM16[0] + grn * p.MATRIX_RGB_to_CAM16[1] + blu * p.MATRIX_RGB_to_CAM16[2]; + const float grn_m = red * p.MATRIX_RGB_to_CAM16[3] + grn * p.MATRIX_RGB_to_CAM16[4] + blu * p.MATRIX_RGB_to_CAM16[5]; + const float blu_m = red * p.MATRIX_RGB_to_CAM16[6] + grn * p.MATRIX_RGB_to_CAM16[7] + blu * p.MATRIX_RGB_to_CAM16[8]; + + const float red_a = panlrc_forward(red_m * p.D_RGB[0], p.F_L); + const float grn_a = panlrc_forward(grn_m * p.D_RGB[1], p.F_L); + const float blu_a = panlrc_forward(blu_m * p.D_RGB[2], p.F_L); + + const float A = 2.f * red_a + grn_a + 0.05f * blu_a; + const float a = red_a - 12.f * grn_a / 11.f + blu_a / 11.f; + const float b = (red_a + grn_a - 2.f * blu_a) / 9.f; + + const float J = 100.f * powf(A / p.A_w, surround[1] * p.z); + + const float M = J == 0.f ? 0.f : 43.f * surround[2] * sqrt(a * a + b * b); + + const float PI = 3.14159265358979f; + const float h_rad = std::atan2(b, a); + float h = std::fmod(h_rad * 180.f / PI, 360.f); + if (h < 0.f) + { + h += 360.f; + } + + return {J, M, h}; +} + +f3 JMh_to_RGB(const f3 &JMh, const JMhParams &p) +{ + const float J = JMh[0]; + const float M = JMh[1]; + const float h = JMh[2]; + + const float PI = 3.14159265358979f; + const float h_rad = h * PI / 180.f; + + const float scale = M / (43.f * surround[2]); + const float A = p.A_w * powf(J / 100.f, 1.f / (surround[1] * p.z)); + const float a = scale * cos(h_rad); + const float b = scale * sin(h_rad); + + const float red_a = (460.f * A + 451.f * a + 288.f * b) / 1403.f; + const float grn_a = (460.f * A - 891.f * a - 261.f * b) / 1403.f; + const float blu_a = (460.f * A - 220.f * a - 6300.f * b) / 1403.f; + + float red_m = panlrc_inverse(red_a, p.F_L) / p.D_RGB[0]; + float grn_m = panlrc_inverse(grn_a, p.F_L) / p.D_RGB[1]; + float blu_m = panlrc_inverse(blu_a, p.F_L) / p.D_RGB[2]; + + const float red = red_m * p.MATRIX_CAM16_to_RGB[0] + grn_m * p.MATRIX_CAM16_to_RGB[1] + blu_m * p.MATRIX_CAM16_to_RGB[2]; + const float grn = red_m * p.MATRIX_CAM16_to_RGB[3] + grn_m * p.MATRIX_CAM16_to_RGB[4] + blu_m * p.MATRIX_CAM16_to_RGB[5]; + const float blu = red_m * p.MATRIX_CAM16_to_RGB[6] + grn_m * p.MATRIX_CAM16_to_RGB[7] + blu_m * p.MATRIX_CAM16_to_RGB[8]; + + return {red, grn, blu}; +} + +// +// Tonescale / Chroma compress +// + +float chroma_compress_norm(float h, float chroma_compress_scale) +{ + const float PI = 3.14159265358979f; + const float h_rad = h / 180.f * PI; + const float a = cos(h_rad); + const float b = sin(h_rad); + const float cos_hr2 = a * a - b * b; + const float sin_hr2 = 2.0f * a * b; + const float cos_hr3 = 4.0f * a * a * a - 3.0f * a; + const float sin_hr3 = 3.0f * b - 4.0f * b * b * b; + + const float M = 11.34072f * a + + 16.46899f * cos_hr2 + + 7.88380f * cos_hr3 + + 14.66441f * b + + -6.37224f * sin_hr2 + + 9.19364f * sin_hr3 + + 77.12896f; + + return M * chroma_compress_scale; +} + +float toe_fwd( float x, float limit, float k1_in, float k2_in) +{ + if (x > limit) + { + return x; + } + + const float k2 = std::max(k2_in, 0.001f); + const float k1 = sqrt(k1_in * k1_in + k2 * k2); + const float k3 = (limit + k1) / (limit + k2); + return 0.5f * (k3 * x - k1 + sqrt((k3 * x - k1) * (k3 * x - k1) + 4.f * k2 * k3 * x)); +} + +float toe_inv( float x, float limit, float k1_in, float k2_in) +{ + if (x > limit) + { + return x; + } + + const float k2 = std::max(k2_in, 0.001f); + const float k1 = sqrt(k1_in * k1_in + k2 * k2); + const float k3 = (limit + k1) / (limit + k2); + return (x * x + k1 * x) / (k3 * (x + k2)); +} + +f3 tonescale_chroma_compress_fwd(const f3 &JMh, const JMhParams &p, const ToneScaleParams &pt, const ChromaCompressParams &pc) +{ + const float J = JMh[0]; + const float M = JMh[1]; + const float h = JMh[2]; + + // Tonescale applied in Y (convert to and from J) + const float A = p.A_w_J * powf(std::abs(J) / 100.f, 1.f / (surround[1] * p.z)); + const float Y = std::copysign(1.f, J) * 100.f / p.F_L * powf((27.13f * A) / (400.f - A), 1.f / 0.42f) / 100.f; + + const float f = pt.m_2 * powf(std::max(0.f, Y) / (Y + pt.s_2), pt.g); + const float Y_ts = std::max(0.f, f * f / (f + pt.t_1)) * pt.n_r; + + const float F_L_Y = powf(p.F_L * std::abs(Y_ts) / 100.f, 0.42f); + const float J_ts = std::copysign(1.f, Y_ts) * 100.f * powf(((400.f * F_L_Y) / (27.13f + F_L_Y)) / p.A_w_J, surround[1] * p.z); + + // ChromaCompress + float M_cp = M; + + if (M != 0.0) + { + const float nJ = J_ts / pc.limit_J_max; + const float snJ = std::max(0.f, 1.f - nJ); + const float Mnorm = chroma_compress_norm(h, pc.chroma_compress_scale); + const float limit = powf(nJ, pc.model_gamma) * reach_m_from_table(h, pc.reach_m_table) / Mnorm; + + M_cp = M * powf(J_ts / J, pc.model_gamma); + M_cp = M_cp / Mnorm; + M_cp = limit - toe_fwd(limit - M_cp, limit - 0.001f, snJ * pc.sat, sqrt(nJ * nJ + pc.sat_thr)); + M_cp = toe_fwd(M_cp, limit, nJ * pc.compr, snJ); + M_cp = M_cp * Mnorm; + } + + return {J_ts, M_cp, h}; +} + +f3 tonescale_chroma_compress_inv(const f3 &JMh, const JMhParams &p, const ToneScaleParams &pt, const ChromaCompressParams &pc) +{ + const float J_ts = JMh[0]; + const float M_cp = JMh[1]; + const float h = JMh[2]; + + // Inverse Tonescale applied in Y (convert to and from J) + const float A = p.A_w_J * powf(std::abs(J_ts) / 100.f, 1.f / (surround[1] * p.z)); + const float Y_ts = std::copysign(1.f, J_ts) * 100.f / p.F_L * powf((27.13f * A) / (400.f - A), 1.f / 0.42f) / 100.f; + + const float Z = std::max(0.f, std::min(pt.n / (pt.u_2 * pt.n_r), Y_ts)); + const float ht = (Z + sqrt(Z * (4.f * pt.t_1 + Z))) / 2.f; + const float Y = pt.s_2 / (powf((pt.m_2 / ht), (1.f / pt.g)) - 1.f) * pt.n_r; + + const float F_L_Y = powf(p.F_L * std::abs(Y) / 100.f, 0.42f); + const float J = std::copysign(1.f, Y) * 100.f * powf(((400.f * F_L_Y) / (27.13f + F_L_Y)) / p.A_w_J, surround[1] * p.z); + + // Inverse ChromaCompress + float M = M_cp; + + if (M_cp != 0.0) + { + const float nJ = J_ts / pc.limit_J_max; + const float snJ = std::max(0.f, 1.f - nJ); + const float Mnorm = chroma_compress_norm(h, pc.chroma_compress_scale); + const float limit = powf(nJ, pc.model_gamma) * reach_m_from_table(h, pc.reach_m_table) / Mnorm; + + M = M_cp / Mnorm; + M = toe_inv(M, limit, nJ * pc.compr, snJ); + M = limit - toe_inv(limit - M, limit - 0.001f, snJ * pc.sat, sqrt(nJ * nJ + pc.sat_thr)); + M = M * Mnorm; + M = M * powf(J_ts / J, -pc.model_gamma); + } + + return {J, M, h}; +} + +JMhParams init_JMhParams(const Primaries &P) +{ + JMhParams p; + + const m33f MATRIX_16 = XYZtoRGB_f33(CAM16::primaries); + const m33f RGB_to_XYZ = RGBtoXYZ_f33(P); + const f3 XYZ_w = mult_f3_f33(f3_from_f(reference_luminance), RGB_to_XYZ); + + const float Y_W = XYZ_w[1]; + + const f3 RGB_w = mult_f3_f33(XYZ_w, MATRIX_16); + + // Viewing condition dependent parameters + const float K = 1.f / (5.f * L_A + 1.f); + const float K4 = powf(K, 4.f); + const float N = Y_b / Y_W; + const float F_L = 0.2f * K4 * (5.f * L_A) + 0.1f * powf((1.f - K4), 2.f) * powf(5.f * L_A, 1.f/3.f); + const float z = 1.48f + sqrt(N); + + const f3 D_RGB = { + Y_W / RGB_w[0], + Y_W / RGB_w[1], + Y_W / RGB_w[2] + }; + + const f3 RGB_WC { + D_RGB[0] * RGB_w[0], + D_RGB[1] * RGB_w[1], + D_RGB[2] * RGB_w[2] + }; + + const f3 RGB_AW = { + panlrc_forward(RGB_WC[0], F_L), + panlrc_forward(RGB_WC[1], F_L), + panlrc_forward(RGB_WC[2], F_L) + }; + + const float A_w = ra * RGB_AW[0] + RGB_AW[1] + ba * RGB_AW[2]; + + const float F_L_W = powf(F_L, 0.42f); + const float A_w_J = (400.f * F_L_W) / (27.13f + F_L_W); + + p.XYZ_w = XYZ_w; + p.F_L = F_L; + p.z = z; + p.D_RGB = D_RGB; + p.A_w = A_w; + p.A_w_J = A_w_J; + + p.MATRIX_RGB_to_CAM16 = mult_f33_f33(RGBtoRGB_f33(P, CAM16::primaries), scale_f33(Identity_M33, f3_from_f(100.f))); + p.MATRIX_CAM16_to_RGB = invert_f33(p.MATRIX_RGB_to_CAM16); + + return p; +} + +Table3D make_gamut_table(const Primaries &P, float peakLuminance) +{ + const JMhParams params = init_JMhParams(P); + + Table3D gamutCuspTableUnsorted{}; + for (int i = 0; i < gamutCuspTableUnsorted.size; i++) + { + const float hNorm = (float) i / gamutCuspTableUnsorted.size; + const f3 HSV = {hNorm, 1., 1.}; + const f3 RGB = HSV_to_RGB(HSV); + const f3 scaledRGB = mult_f_f3(peakLuminance / reference_luminance, RGB); + const f3 JMh = RGB_to_JMh(scaledRGB, params); + + gamutCuspTableUnsorted.table[i][0] = JMh[0]; + gamutCuspTableUnsorted.table[i][1] = JMh[1]; + gamutCuspTableUnsorted.table[i][2] = JMh[2]; + } + + int minhIndex = 0; + for (int i = 0; i < gamutCuspTableUnsorted.size; i++) + { + if ( gamutCuspTableUnsorted.table[i][2] < gamutCuspTableUnsorted.table[minhIndex][2]) + minhIndex = i; + } + + Table3D gamutCuspTable{}; + for (int i = 0; i < gamutCuspTableUnsorted.size; i++) + { + gamutCuspTable.table[i + gamutCuspTable.base_index][0] = gamutCuspTableUnsorted.table[(minhIndex+i) % gamutCuspTableUnsorted.size][0]; + gamutCuspTable.table[i + gamutCuspTable.base_index][1] = gamutCuspTableUnsorted.table[(minhIndex+i) % gamutCuspTableUnsorted.size][1]; + gamutCuspTable.table[i + gamutCuspTable.base_index][2] = gamutCuspTableUnsorted.table[(minhIndex+i) % gamutCuspTableUnsorted.size][2]; + } + + // Copy last populated entry to first empty spot + gamutCuspTable.table[0][0] = gamutCuspTable.table[gamutCuspTable.base_index + gamutCuspTable.size-1][0]; + gamutCuspTable.table[0][1] = gamutCuspTable.table[gamutCuspTable.base_index + gamutCuspTable.size-1][1]; + gamutCuspTable.table[0][2] = gamutCuspTable.table[gamutCuspTable.base_index + gamutCuspTable.size-1][2]; + + // Copy first populated entry to last empty spot + gamutCuspTable.table[gamutCuspTable.base_index + gamutCuspTable.size][0] = gamutCuspTable.table[gamutCuspTable.base_index][0]; + gamutCuspTable.table[gamutCuspTable.base_index + gamutCuspTable.size][1] = gamutCuspTable.table[gamutCuspTable.base_index][1]; + gamutCuspTable.table[gamutCuspTable.base_index + gamutCuspTable.size][2] = gamutCuspTable.table[gamutCuspTable.base_index][2]; + + // Wrap the hues, to maintain monotonicity. These entries will fall outside [0.0, 360.0] + gamutCuspTable.table[0][2] = gamutCuspTable.table[0][2] - 360.f; + gamutCuspTable.table[gamutCuspTable.size+1][2] = gamutCuspTable.table[gamutCuspTable.size+1][2] + 360.f; + + return gamutCuspTable; +} + +bool any_below_zero(const f3 &rgb) +{ + return (rgb[0] < 0. || rgb[1] < 0. || rgb[2] < 0.); +} + +Table1D make_reach_m_table(const Primaries &P, float peakLuminance) +{ + const JMhParams params = init_JMhParams(P); + const float limit_J_max = Y_to_J(peakLuminance, params); + + Table1D gamutReachTable{}; + + for (int i = 0; i < gamutReachTable.size; i++) { + const float hue = (float) i; + + const float search_range = 50.f; + float low = 0.; + float high = low + search_range; + bool outside = false; + + while ((outside != true) & (high < 1300.f)) + { + const f3 searchJMh = {limit_J_max, high, hue}; + const f3 newLimitRGB = JMh_to_RGB(searchJMh, params); + outside = any_below_zero(newLimitRGB); + if (outside == false) + { + low = high; + high = high + search_range; + } + } + + while (high-low > 1e-2) + { + const float sampleM = (high + low) / 2.f; + const f3 searchJMh = {limit_J_max, sampleM, hue}; + const f3 newLimitRGB = JMh_to_RGB(searchJMh, params); + outside = any_below_zero(newLimitRGB); + if (outside) + { + high = sampleM; + } + else + { + low = sampleM; + } + } + + gamutReachTable.table[i] = high; + } + + return gamutReachTable; +} + +bool outside_hull(const f3 &rgb) +{ + // limit value, once we cross this value, we are outside of the top gamut shell + const float maxRGBtestVal = 1.0; + return rgb[0] > maxRGBtestVal || rgb[1] > maxRGBtestVal || rgb[2] > maxRGBtestVal; +} + +float get_focus_gain(float J, float cuspJ, float limit_J_max) +{ + const float thr = lerpf(cuspJ, limit_J_max, focus_gain_blend); + + if (J > thr) + { + // Approximate inverse required above threshold + float gain = (limit_J_max - thr) / std::max(0.0001f, (limit_J_max - std::min(limit_J_max, J))); + return powf(log10(gain), 1.f / focus_adjust_gain) + 1.f; + } + else + { + // Analytic inverse possible below cusp + return 1.f; + } +} + +float solve_J_intersect(float J, float M, float focusJ, float maxJ, float slope_gain) +{ + const float a = M / (focusJ * slope_gain); + float b = 0.f; + float c = 0.f; + float intersectJ = 0.f; + + if (J < focusJ) + { + b = 1.f - M / slope_gain; + } + else + { + b = - (1.f + M / slope_gain + maxJ * M / (focusJ * slope_gain)); + } + + if (J < focusJ) + { + c = -J; + } + else + { + c = maxJ * M / slope_gain + J; + } + + const float root = sqrt(b * b - 4.f * a * c); + + if (J < focusJ) + { + intersectJ = 2.f * c / (-b - root); + } + else + { + intersectJ = 2.f * c / (-b + root); + } + + return intersectJ; +} + +float smin(float a, float b, float s) +{ + const float h = std::max(s - std::abs(a - b), 0.f) / s; + return std::min(a, b) - h * h * h * s * (1.f / 6.f); +} + +f3 find_gamut_boundary_intersection(const f3 &JMh_s, const f2 &JM_cusp_in, float J_focus, float J_max, float slope_gain, float gamma_top, float gamma_bottom) +{ + const float s = std::max(0.000001f, smooth_cusps); + const f2 JM_cusp = { + JM_cusp_in[0], + JM_cusp_in[1] * (1.f + smooth_m * s) + }; + + const float J_intersect_source = solve_J_intersect(JMh_s[0], JMh_s[1], J_focus, J_max, slope_gain); + const float J_intersect_cusp = solve_J_intersect(JM_cusp[0], JM_cusp[1], J_focus, J_max, slope_gain); + + float slope = 0.f; + if (J_intersect_source < J_focus) + { + slope = J_intersect_source * (J_intersect_source - J_focus) / (J_focus * slope_gain); + } + else + { + slope = (J_max - J_intersect_source) * (J_intersect_source - J_focus) / (J_focus * slope_gain); + } + + const float M_boundary_lower = J_intersect_cusp * powf(J_intersect_source / J_intersect_cusp, 1.f / gamma_bottom) / (JM_cusp[0] / JM_cusp[1] - slope); + const float M_boundary_upper = JM_cusp[1] * (J_max - J_intersect_cusp) * powf((J_max - J_intersect_source) / (J_max - J_intersect_cusp), 1.f / gamma_top) / (slope * JM_cusp[1] + J_max - JM_cusp[0]); + const float M_boundary = JM_cusp[1] * smin(M_boundary_lower / JM_cusp[1], M_boundary_upper / JM_cusp[1], s); + const float J_boundary = J_intersect_source + slope * M_boundary; + + return {J_boundary, M_boundary, J_intersect_source}; +} + +f3 get_reach_boundary( + float J, + float M, + float h, + const f2 &JMcusp, + float focusJ, + float limit_J_max, + float model_gamma, + float focus_dist, + const ACES2::Table1D & reach_m_table +) +{ + const float reachMaxM = reach_m_from_table(h, reach_m_table); + + const float slope_gain = limit_J_max * focus_dist * get_focus_gain(J, JMcusp[0], limit_J_max); + + const float intersectJ = solve_J_intersect(J, M, focusJ, limit_J_max, slope_gain); + + float slope; + if (intersectJ < focusJ) + { + slope = intersectJ * (intersectJ - focusJ) / (focusJ * slope_gain); + } + else + { + slope = (limit_J_max - intersectJ) * (intersectJ - focusJ) / (focusJ * slope_gain); + } + + const float boundary = limit_J_max * powf(intersectJ / limit_J_max, model_gamma) * reachMaxM / (limit_J_max - slope * reachMaxM); + return {J, boundary, h}; +} + +float compression_function( + float v, + float thr, + float lim, + bool invert) +{ + float s = (lim - thr) * (1.f - thr) / (lim - 1.f); + float nd = (v - thr) / s; + + float vCompressed; + + if (invert) { + if (v < thr || lim <= 1.0001f || v > thr + s) { + vCompressed = v; + } else { + vCompressed = thr + s * (-nd / (nd - 1.f)); + } + } else { + if (v < thr || lim <= 1.0001f) { + vCompressed = v; + } else { + vCompressed = thr + s * nd / (1.f + nd); + } + } + + return vCompressed; +} + +f3 compressGamut(const f3 &JMh, float Jx, const ACES2::GamutCompressParams& p, bool invert) +{ + const float J = JMh[0]; + const float M = JMh[1]; + const float h = JMh[2]; + + if (M < 0.0001f || J > p.limit_J_max) + { + return {J, 0.f, h}; + } + else + { + const f2 project_from = {J, M}; + const f2 JMcusp = cusp_from_table(h, p.gamut_cusp_table); + const float focusJ = lerpf(JMcusp[0], p.mid_J, std::min(1.f, cusp_mid_blend - (JMcusp[0] / p.limit_J_max))); + const float slope_gain = p.limit_J_max * p.focus_dist * get_focus_gain(Jx, JMcusp[0], p.limit_J_max); + + const float gamma_top = hue_dependent_upper_hull_gamma(h, p.upper_hull_gamma_table); + const float gamma_bottom = p.lower_hull_gamma; + + const f3 boundaryReturn = find_gamut_boundary_intersection({J, M, h}, JMcusp, focusJ, p.limit_J_max, slope_gain, gamma_top, gamma_bottom); + const f2 JMboundary = {boundaryReturn[0], boundaryReturn[1]}; + const f2 project_to = {boundaryReturn[2], 0.f}; + + if (JMboundary[1] <= 0.0f) + { + return {J, 0.f, h}; + } + + const f3 reachBoundary = get_reach_boundary(JMboundary[0], JMboundary[1], h, JMcusp, focusJ, p.limit_J_max, p.model_gamma, p.focus_dist, p.reach_m_table); + + const float difference = std::max(1.0001f, reachBoundary[1] / JMboundary[1]); + const float threshold = std::max(compression_threshold, 1.f / difference); + + float v = project_from[1] / JMboundary[1]; + v = compression_function(v, threshold, difference, invert); + + const f2 JMcompressed { + project_to[0] + v * (JMboundary[0] - project_to[0]), + project_to[1] + v * (JMboundary[1] - project_to[1]) + }; + + return {JMcompressed[0], JMcompressed[1], h}; + } +} + +f3 gamut_compress_fwd(const f3 &JMh, const GamutCompressParams &p) +{ + return compressGamut(JMh, JMh[0], p, false); +} + +f3 gamut_compress_inv(const f3 &JMh, const GamutCompressParams &p) +{ + const f2 JMcusp = cusp_from_table(JMh[2], p.gamut_cusp_table); + float Jx = JMh[0]; + + f3 unCompressedJMh; + + // Analytic inverse below threshold + if (Jx <= lerpf(JMcusp[0], p.limit_J_max, focus_gain_blend)) + { + unCompressedJMh = compressGamut(JMh, Jx, p, true); + } + // Approximation above threshold + else + { + Jx = compressGamut(JMh, Jx, p, true)[0]; + unCompressedJMh = compressGamut(JMh, Jx, p, true); + } + + return unCompressedJMh; +} + +bool evaluate_gamma_fit( + const f2 &JMcusp, + const f3 testJMh[3], + float topGamma, + float peakLuminance, + float limit_J_max, + float mid_J, + float focus_dist, + float lower_hull_gamma, + const JMhParams &limitJMhParams) +{ + const float focusJ = lerpf(JMcusp[0], mid_J, std::min(1.f, cusp_mid_blend - (JMcusp[0] / limit_J_max))); + + for (size_t testIndex = 0; testIndex < 3; testIndex++) + { + const float slope_gain = limit_J_max * focus_dist * get_focus_gain(testJMh[testIndex][0], JMcusp[0], limit_J_max); + const f3 approxLimit = find_gamut_boundary_intersection(testJMh[testIndex], JMcusp, focusJ, limit_J_max, slope_gain, topGamma, lower_hull_gamma); + const f3 approximate_JMh = {approxLimit[0], approxLimit[1], testJMh[testIndex][2]}; + const f3 newLimitRGB = JMh_to_RGB(approximate_JMh, limitJMhParams); + const f3 newLimitRGBScaled = mult_f_f3(reference_luminance / peakLuminance, newLimitRGB); + + if (!outside_hull(newLimitRGBScaled)) + { + return false; + } + } + + return true; +} + +Table1D make_upper_hull_gamma( + const Table3D &gamutCuspTable, + float peakLuminance, + float limit_J_max, + float mid_J, + float focus_dist, + float lower_hull_gamma, + const JMhParams &limitJMhParams) +{ + const int test_count = 3; + const float testPositions[test_count] = {0.01f, 0.5f, 0.99f}; + + Table1D gammaTable{}; + Table1D gamutTopGamma{}; + + for (int i = 0; i < gammaTable.size; i++) + { + gammaTable.table[i] = -1.f; + + const float hue = (float) i; + const f2 JMcusp = cusp_from_table(hue, gamutCuspTable); + + f3 testJMh[test_count]{}; + for (int testIndex = 0; testIndex < test_count; testIndex++) + { + const float testJ = JMcusp[0] + ((limit_J_max - JMcusp[0]) * testPositions[testIndex]); + testJMh[testIndex] = { + testJ, + JMcusp[1], + hue + }; + } + + const float search_range = gammaSearchStep; + float low = gammaMinimum; + float high = low + search_range; + bool outside = false; + + while (!(outside) && (high < 5.f)) + { + const bool gammaFound = evaluate_gamma_fit(JMcusp, testJMh, high, peakLuminance, limit_J_max, mid_J, focus_dist, lower_hull_gamma, limitJMhParams); + if (!gammaFound) + { + low = high; + high = high + search_range; + } + else + { + outside = true; + } + } + + float testGamma = -1.f; + while ( (high-low) > gammaAccuracy) + { + testGamma = (high + low) / 2.f; + const bool gammaFound = evaluate_gamma_fit(JMcusp, testJMh, testGamma, peakLuminance, limit_J_max, mid_J, focus_dist, lower_hull_gamma, limitJMhParams); + if (gammaFound) + { + high = testGamma; + gammaTable.table[i] = high; + } + else + { + low = testGamma; + } + } + + // Duplicate gamma value to array, leaving empty entries at first and last position + gamutTopGamma.table[i+gamutTopGamma.base_index] = gammaTable.table[i]; + } + + // Copy last populated entry to first empty spot + gamutTopGamma.table[0] = gammaTable.table[gammaTable.size-1]; + + // Copy first populated entry to last empty spot + gamutTopGamma.table[gamutTopGamma.total_size-1] = gammaTable.table[0]; + + return gamutTopGamma; +} + +// Tonescale pre-calculations +ToneScaleParams init_ToneScaleParams(float peakLuminance) +{ + // Preset constants that set the desired behavior for the curve + const float n = peakLuminance; + + const float n_r = 100.0f; // normalized white in nits (what 1.0 should be) + const float g = 1.15f; // surround / contrast + const float c = 0.18f; // anchor for 18% grey + const float c_d = 10.013f; // output luminance of 18% grey (in nits) + const float w_g = 0.14f; // change in grey between different peak luminance + const float t_1 = 0.04f; // shadow toe or flare/glare compensation + const float r_hit_min = 128.f; // scene-referred value "hitting the roof" + const float r_hit_max = 896.f; // scene-referred value "hitting the roof" + + // Calculate output constants + const float r_hit = r_hit_min + (r_hit_max - r_hit_min) * (log(n/n_r)/log(10000.f/100.f)); + const float m_0 = (n / n_r); + const float m_1 = 0.5f * (m_0 + sqrt(m_0 * (m_0 + 4.f * t_1))); + const float u = powf((r_hit/m_1)/((r_hit/m_1)+1.f),g); + const float m = m_1 / u; + const float w_i = log(n/100.f)/log(2.f); + const float c_t = c_d/n_r * (1.f + w_i * w_g); + const float g_ip = 0.5f * (c_t + sqrt(c_t * (c_t + 4.f * t_1))); + const float g_ipp2 = -(m_1 * powf((g_ip/m),(1.f/g))) / (powf(g_ip/m , 1.f/g)-1.f); + const float w_2 = c / g_ipp2; + const float s_2 = w_2 * m_1; + const float u_2 = powf((r_hit/m_1)/((r_hit/m_1) + w_2), g); + const float m_2 = m_1 / u_2; + + ToneScaleParams TonescaleParams = { + n, + n_r, + g, + t_1, + c_t, + s_2, + u_2, + m_2 + }; + + return TonescaleParams; +} + +ChromaCompressParams init_ChromaCompressParams(float peakLuminance) +{ + const ToneScaleParams tsParams = init_ToneScaleParams(peakLuminance); + const JMhParams inputJMhParams = init_JMhParams(ACES_AP0::primaries); + + float limit_J_max = Y_to_J(peakLuminance, inputJMhParams); + + // Calculated chroma compress variables + const float log_peak = log10( tsParams.n / tsParams.n_r); + const float compr = chroma_compress + (chroma_compress * chroma_compress_fact) * log_peak; + const float sat = std::max(0.2f, chroma_expand - (chroma_expand * chroma_expand_fact) * log_peak); + const float sat_thr = chroma_expand_thr / tsParams.n; + const float model_gamma = 1.f / (surround[1] * (1.48f + sqrt(Y_b / L_A))); + + ChromaCompressParams params{}; + params.limit_J_max = limit_J_max; + params.model_gamma = model_gamma; + params.sat = sat; + params.sat_thr = sat_thr; + params.compr = compr; + params.chroma_compress_scale = powf(0.03379f * peakLuminance, 0.30596f) - 0.45135f; + params.reach_m_table = make_reach_m_table(ACES_AP1::primaries, peakLuminance); + return params; +} + +GamutCompressParams init_GamutCompressParams(float peakLuminance, const Primaries &limitingPrimaries) +{ + const ToneScaleParams tsParams = init_ToneScaleParams(peakLuminance); + const JMhParams inputJMhParams = init_JMhParams(ACES_AP0::primaries); + + float limit_J_max = Y_to_J(peakLuminance, inputJMhParams); + float mid_J = Y_to_J(tsParams.c_t * 100.f, inputJMhParams); + + // Calculated chroma compress variables + const float log_peak = log10( tsParams.n / tsParams.n_r); + const float model_gamma = 1.f / (surround[1] * (1.48f + sqrt(Y_b / L_A))); + const float focus_dist = focus_distance + focus_distance * focus_distance_scaling * log_peak; + const float lower_hull_gamma = 1.14f + 0.07f * log_peak; + + const JMhParams limitJMhParams = init_JMhParams(limitingPrimaries); + + GamutCompressParams params{}; + params.limit_J_max = limit_J_max; + params.mid_J = mid_J; + params.model_gamma = model_gamma; + params.focus_dist = focus_dist; + params.lower_hull_gamma = lower_hull_gamma; + params.reach_m_table = make_reach_m_table(ACES_AP1::primaries, peakLuminance); + params.gamut_cusp_table = make_gamut_table(limitingPrimaries, peakLuminance); + params.upper_hull_gamma_table = make_upper_hull_gamma( + params.gamut_cusp_table, + peakLuminance, + limit_J_max, + mid_J, + focus_dist, + lower_hull_gamma, + limitJMhParams); + + return params; +} + +} // namespace ACES2 + +} // OCIO namespace diff --git a/src/OpenColorIO/ops/fixedfunction/ACES2/Transform.h b/src/OpenColorIO/ops/fixedfunction/ACES2/Transform.h new file mode 100644 index 0000000000..ff3f969e5a --- /dev/null +++ b/src/OpenColorIO/ops/fixedfunction/ACES2/Transform.h @@ -0,0 +1,34 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright Contributors to the OpenColorIO Project. + +#ifndef INCLUDED_OCIO_ACES2_TRANSFORM_H +#define INCLUDED_OCIO_ACES2_TRANSFORM_H + +#include "Common.h" + +namespace OCIO_NAMESPACE +{ + +namespace ACES2 +{ + +JMhParams init_JMhParams(const Primaries &P); +ToneScaleParams init_ToneScaleParams(float peakLuminance); +ChromaCompressParams init_ChromaCompressParams(float peakLuminance); +GamutCompressParams init_GamutCompressParams(float peakLuminance, const Primaries &P); + +f3 RGB_to_JMh(const f3 &RGB, const JMhParams &p); +f3 JMh_to_RGB(const f3 &JMh, const JMhParams &p); + +f3 tonescale_chroma_compress_fwd(const f3 &JMh, const JMhParams &p, const ToneScaleParams &pt, const ChromaCompressParams &pc); +f3 tonescale_chroma_compress_inv(const f3 &JMh, const JMhParams &p, const ToneScaleParams &pt, const ChromaCompressParams &pc); + +f3 gamut_compress_fwd(const f3 &JMh, const GamutCompressParams &p); +f3 gamut_compress_inv(const f3 &JMh, const GamutCompressParams &p); + + +} // namespace ACES2 + +} // OCIO namespace + +#endif diff --git a/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpCPU.cpp b/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpCPU.cpp index a974cb7880..3cc15ecf6f 100644 --- a/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpCPU.cpp +++ b/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpCPU.cpp @@ -3,9 +3,11 @@ #include #include +#include #include +#include "ACES2/Transform.h" #include "BitDepthUtils.h" #include "MathUtils.h" #include "ops/fixedfunction/FixedFunctionOpCPU.h" @@ -126,6 +128,80 @@ class Renderer_ACES_GamutComp13_Inv : public Renderer_ACES_GamutComp13_Fwd void apply(const void * inImg, void * outImg, long numPixels) const override; }; +class Renderer_ACES_OutputTransform20 : public OpCPU +{ +public: + Renderer_ACES_OutputTransform20() = delete; + explicit Renderer_ACES_OutputTransform20(ConstFixedFunctionOpDataRcPtr & data); + + void apply(const void * inImg, void * outImg, long numPixels) const override; + +private: + void fwd(const void * inImg, void * outImg, long numPixels) const; + void inv(const void * inImg, void * outImg, long numPixels) const; + +protected: + bool m_fwd; + ACES2::JMhParams m_pIn; + ACES2::JMhParams m_pOut; + ACES2::ToneScaleParams m_t; + ACES2::ChromaCompressParams m_c; + ACES2::GamutCompressParams m_g; +}; + +class Renderer_ACES_RGB_TO_JMh_20 : public OpCPU +{ +public: + Renderer_ACES_RGB_TO_JMh_20() = delete; + explicit Renderer_ACES_RGB_TO_JMh_20(ConstFixedFunctionOpDataRcPtr & data); + + void apply(const void * inImg, void * outImg, long numPixels) const override; + +private: + void fwd(const void * inImg, void * outImg, long numPixels) const; + void inv(const void * inImg, void * outImg, long numPixels) const; + +protected: + bool m_fwd; + ACES2::JMhParams m_p; +}; + +class Renderer_ACES_TONESCALE_COMPRESS_20 : public OpCPU +{ +public: + Renderer_ACES_TONESCALE_COMPRESS_20() = delete; + explicit Renderer_ACES_TONESCALE_COMPRESS_20(ConstFixedFunctionOpDataRcPtr & data); + + void apply(const void * inImg, void * outImg, long numPixels) const override; + +private: + void fwd(const void * inImg, void * outImg, long numPixels) const; + void inv(const void * inImg, void * outImg, long numPixels) const; + +protected: + bool m_fwd; + ACES2::JMhParams m_p; + ACES2::ToneScaleParams m_t; + ACES2::ChromaCompressParams m_c; +}; + +class Renderer_ACES_GAMUT_COMPRESS_20 : public OpCPU +{ +public: + Renderer_ACES_GAMUT_COMPRESS_20() = delete; + explicit Renderer_ACES_GAMUT_COMPRESS_20(ConstFixedFunctionOpDataRcPtr & data); + + void apply(const void * inImg, void * outImg, long numPixels) const override; + +private: + void fwd(const void * inImg, void * outImg, long numPixels) const; + void inv(const void * inImg, void * outImg, long numPixels) const; + +protected: + bool m_fwd; + ACES2::GamutCompressParams m_g; +}; + class Renderer_REC2100_Surround : public OpCPU { public: @@ -791,6 +867,306 @@ void Renderer_ACES_GamutComp13_Inv::apply(const void * inImg, void * outImg, lon } } +Renderer_ACES_OutputTransform20::Renderer_ACES_OutputTransform20(ConstFixedFunctionOpDataRcPtr & data) + : OpCPU() +{ + m_fwd = FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_FWD == data->getStyle(); + + const float peak_luminance = (float) data->getParams()[0]; + + const float lim_red_x = (float) data->getParams()[1]; + const float lim_red_y = (float) data->getParams()[2]; + const float lim_green_x = (float) data->getParams()[3]; + const float lim_green_y = (float) data->getParams()[4]; + const float lim_blue_x = (float) data->getParams()[5]; + const float lim_blue_y = (float) data->getParams()[6]; + const float lim_white_x = (float) data->getParams()[7]; + const float lim_white_y = (float) data->getParams()[8]; + + const Primaries lim_primaries = { + {lim_red_x , lim_red_y }, + {lim_green_x, lim_green_y}, + {lim_blue_x , lim_blue_y }, + {lim_white_x, lim_white_y} + }; + + m_pIn = ACES2::init_JMhParams(ACES_AP0::primaries); + m_pOut = ACES2::init_JMhParams(lim_primaries); + m_t = ACES2::init_ToneScaleParams(peak_luminance); + m_c = ACES2::init_ChromaCompressParams(peak_luminance); + m_g = ACES2::init_GamutCompressParams(peak_luminance, lim_primaries); +} + +void Renderer_ACES_OutputTransform20::apply(const void * inImg, void * outImg, long numPixels) const +{ + if (m_fwd) + { + fwd(inImg, outImg, numPixels); + } + else + { + inv(inImg, outImg, numPixels); + } +} + +void Renderer_ACES_OutputTransform20::fwd(const void * inImg, void * outImg, long numPixels) const +{ + const float * in = (const float *)inImg; + float * out = (float *)outImg; + + for(long idx=0; idxgetStyle(); + + const float red_x = (float) data->getParams()[0]; + const float red_y = (float) data->getParams()[1]; + const float green_x = (float) data->getParams()[2]; + const float green_y = (float) data->getParams()[3]; + const float blue_x = (float) data->getParams()[4]; + const float blue_y = (float) data->getParams()[5]; + const float white_x = (float) data->getParams()[6]; + const float white_y = (float) data->getParams()[7]; + + const Primaries primaries = { + {red_x , red_y }, + {green_x, green_y}, + {blue_x , blue_y }, + {white_x, white_y} + }; + + m_p = ACES2::init_JMhParams(primaries); +} + +void Renderer_ACES_RGB_TO_JMh_20::apply(const void * inImg, void * outImg, long numPixels) const +{ + if (m_fwd) + { + fwd(inImg, outImg, numPixels); + } + else + { + inv(inImg, outImg, numPixels); + } +} + +void Renderer_ACES_RGB_TO_JMh_20::fwd(const void * inImg, void * outImg, long numPixels) const +{ + const float * in = (const float *)inImg; + float * out = (float *)outImg; + + for(long idx=0; idxgetStyle(); + + const float peak_luminance = (float) data->getParams()[0]; + + m_p = ACES2::init_JMhParams(ACES_AP0::primaries); + m_t = ACES2::init_ToneScaleParams(peak_luminance); + m_c = ACES2::init_ChromaCompressParams(peak_luminance); +} + +void Renderer_ACES_TONESCALE_COMPRESS_20::apply(const void * inImg, void * outImg, long numPixels) const +{ + if (m_fwd) + { + fwd(inImg, outImg, numPixels); + } + else + { + inv(inImg, outImg, numPixels); + } +} + +void Renderer_ACES_TONESCALE_COMPRESS_20::fwd(const void * inImg, void * outImg, long numPixels) const +{ + const float * in = (const float *)inImg; + float * out = (float *)outImg; + + for(long idx=0; idxgetStyle(); + + const float peakLuminance = (float) data->getParams()[0]; + + const float red_x = (float) data->getParams()[1]; + const float red_y = (float) data->getParams()[2]; + const float green_x = (float) data->getParams()[3]; + const float green_y = (float) data->getParams()[4]; + const float blue_x = (float) data->getParams()[5]; + const float blue_y = (float) data->getParams()[6]; + const float white_x = (float) data->getParams()[7]; + const float white_y = (float) data->getParams()[8]; + + const Primaries limitingPrimaries = { + {red_x , red_y }, + {green_x, green_y}, + {blue_x , blue_y }, + {white_x, white_y} + }; + + m_g = ACES2::init_GamutCompressParams(peakLuminance, limitingPrimaries); +} + +void Renderer_ACES_GAMUT_COMPRESS_20::apply(const void * inImg, void * outImg, long numPixels) const +{ + if (m_fwd) + { + fwd(inImg, outImg, numPixels); + } + else + { + inv(inImg, outImg, numPixels); + } +} + +void Renderer_ACES_GAMUT_COMPRESS_20::fwd(const void * inImg, void * outImg, long numPixels) const +{ + const float * in = (const float *)inImg; + float * out = (float *)outImg; + + for(long idx=0; idx(func); } + + case FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_FWD: + case FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_INV: + { + // Sharing same renderer (param will be inverted to handle direction). + return std::make_shared(func); + } + + case FixedFunctionOpData::ACES_RGB_TO_JMh_20: + case FixedFunctionOpData::ACES_JMh_TO_RGB_20: + { + // Sharing same renderer (param will be inverted to handle direction). + return std::make_shared(func); + } + + case FixedFunctionOpData::ACES_TONESCALE_COMPRESS_20_FWD: + case FixedFunctionOpData::ACES_TONESCALE_COMPRESS_20_INV: + { + // Sharing same renderer (param will be inverted to handle direction). + return std::make_shared(func); + } + + case FixedFunctionOpData::ACES_GAMUT_COMPRESS_20_FWD: + case FixedFunctionOpData::ACES_GAMUT_COMPRESS_20_INV: + { + // Sharing same renderer (param will be inverted to handle direction). + return std::make_shared(func); + } + case FixedFunctionOpData::REC2100_SURROUND_FWD: case FixedFunctionOpData::REC2100_SURROUND_INV: { diff --git a/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpData.cpp b/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpData.cpp index ce835fd6a8..1926cc2eb0 100644 --- a/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpData.cpp +++ b/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpData.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: BSD-3-Clause // Copyright Contributors to the OpenColorIO Project. +#include #include #include @@ -11,34 +12,65 @@ namespace OCIO_NAMESPACE { +namespace +{ + void check_param_bounds(const std::string & name, double val, double low, double high) + { + if (val < low || val > high) + { + std::stringstream ss; + ss << "Parameter " << val << " (" << name << ") is outside valid range [" << low << "," << high << "]"; + throw Exception(ss.str().c_str()); + } + }; + + void check_param_no_frac(const std::string & name, double val) + { + if (floor(val) != val) + { + std::stringstream ss; + ss << "Parameter " << val << " (" << name << ") cannot include any fractional component"; + throw Exception(ss.str().c_str()); + } + }; +} + namespace DefaultValues { const int FLOAT_DECIMALS = 7; } -constexpr char ACES_RED_MOD_03_FWD_STR[] = "RedMod03Fwd"; -constexpr char ACES_RED_MOD_03_REV_STR[] = "RedMod03Rev"; -constexpr char ACES_RED_MOD_10_FWD_STR[] = "RedMod10Fwd"; -constexpr char ACES_RED_MOD_10_REV_STR[] = "RedMod10Rev"; -constexpr char ACES_GLOW_03_FWD_STR[] = "Glow03Fwd"; -constexpr char ACES_GLOW_03_REV_STR[] = "Glow03Rev"; -constexpr char ACES_GLOW_10_FWD_STR[] = "Glow10Fwd"; -constexpr char ACES_GLOW_10_REV_STR[] = "Glow10Rev"; -constexpr char ACES_DARK_TO_DIM_10_STR[] = "DarkToDim10"; -constexpr char ACES_DIM_TO_DARK_10_STR[] = "DimToDark10"; -constexpr char ACES_GAMUT_COMP_13_FWD_STR[]= "GamutComp13Fwd"; -constexpr char ACES_GAMUT_COMP_13_REV_STR[]= "GamutComp13Rev"; -constexpr char SURROUND_STR[] = "Surround"; // Old name for Rec2100SurroundFwd -constexpr char REC_2100_SURROUND_FWD_STR[] = "Rec2100SurroundFwd"; -constexpr char REC_2100_SURROUND_REV_STR[] = "Rec2100SurroundRev"; -constexpr char RGB_TO_HSV_STR[] = "RGB_TO_HSV"; -constexpr char HSV_TO_RGB_STR[] = "HSV_TO_RGB"; -constexpr char XYZ_TO_xyY_STR[] = "XYZ_TO_xyY"; -constexpr char xyY_TO_XYZ_STR[] = "xyY_TO_XYZ"; -constexpr char XYZ_TO_uvY_STR[] = "XYZ_TO_uvY"; -constexpr char uvY_TO_XYZ_STR[] = "uvY_TO_XYZ"; -constexpr char XYZ_TO_LUV_STR[] = "XYZ_TO_LUV"; -constexpr char LUV_TO_XYZ_STR[] = "LUV_TO_XYZ"; +constexpr char ACES_RED_MOD_03_FWD_STR[] = "RedMod03Fwd"; +constexpr char ACES_RED_MOD_03_REV_STR[] = "RedMod03Rev"; +constexpr char ACES_RED_MOD_10_FWD_STR[] = "RedMod10Fwd"; +constexpr char ACES_RED_MOD_10_REV_STR[] = "RedMod10Rev"; +constexpr char ACES_GLOW_03_FWD_STR[] = "Glow03Fwd"; +constexpr char ACES_GLOW_03_REV_STR[] = "Glow03Rev"; +constexpr char ACES_GLOW_10_FWD_STR[] = "Glow10Fwd"; +constexpr char ACES_GLOW_10_REV_STR[] = "Glow10Rev"; +constexpr char ACES_DARK_TO_DIM_10_STR[] = "DarkToDim10"; +constexpr char ACES_DIM_TO_DARK_10_STR[] = "DimToDark10"; +constexpr char ACES_GAMUT_COMP_13_FWD_STR[] = "GamutComp13Fwd"; +constexpr char ACES_GAMUT_COMP_13_REV_STR[] = "GamutComp13Rev"; +constexpr char ACES_OUTPUT_TRANSFORM_20_FWD_STR[] = "ACESOutputTransform20Fwd"; +constexpr char ACES_OUTPUT_TRANSFORM_20_INV_STR[] = "ACESOutputTransform20Inv"; +constexpr char ACES_RGB_TO_JMh_20_STR[] = "RGB_TO_JMh_20"; +constexpr char ACES_JMh_TO_RGB_20_STR[] = "JMh_TO_RGB_20"; +constexpr char ACES_TONESCALE_COMPRESS_20_FWD_STR[] = "ToneScaleCompress20Fwd"; +constexpr char ACES_TONESCALE_COMPRESS_20_INV_STR[] = "ToneScaleCompress20Inv"; +constexpr char ACES_GAMUT_COMPRESS_20_FWD_STR[] = "GamutCompress20Fwd"; +constexpr char ACES_GAMUT_COMPRESS_20_INV_STR[] = "GamutCompress20Inv"; +constexpr char SURROUND_STR[] = "Surround"; // Old name for Rec2100SurroundFwd +constexpr char REC_2100_SURROUND_FWD_STR[] = "Rec2100SurroundFwd"; +constexpr char REC_2100_SURROUND_REV_STR[] = "Rec2100SurroundRev"; +constexpr char RGB_TO_HSV_STR[] = "RGB_TO_HSV"; +constexpr char HSV_TO_RGB_STR[] = "HSV_TO_RGB"; +constexpr char XYZ_TO_xyY_STR[] = "XYZ_TO_xyY"; +constexpr char xyY_TO_XYZ_STR[] = "xyY_TO_XYZ"; +constexpr char XYZ_TO_uvY_STR[] = "XYZ_TO_uvY"; +constexpr char uvY_TO_XYZ_STR[] = "uvY_TO_XYZ"; +constexpr char XYZ_TO_LUV_STR[] = "XYZ_TO_LUV"; +constexpr char LUV_TO_XYZ_STR[] = "LUV_TO_XYZ"; // NOTE: Converts the enumeration value to its string representation (i.e. CLF reader). @@ -74,6 +106,22 @@ const char * FixedFunctionOpData::ConvertStyleToString(Style style, bool detaile return detailed ? "ACES_GamutComp13 (Forward)" : ACES_GAMUT_COMP_13_FWD_STR; case ACES_GAMUT_COMP_13_INV: return detailed ? "ACES_GamutComp13 (Inverse)" : ACES_GAMUT_COMP_13_REV_STR; + case ACES_OUTPUT_TRANSFORM_20_FWD: + return detailed ? "ACES_OutputTransform20 (Forward)" : ACES_OUTPUT_TRANSFORM_20_FWD_STR; + case ACES_OUTPUT_TRANSFORM_20_INV: + return detailed ? "ACES_OutputTransform20 (Inverse)" : ACES_OUTPUT_TRANSFORM_20_INV_STR; + case ACES_RGB_TO_JMh_20: + return ACES_RGB_TO_JMh_20_STR; + case ACES_JMh_TO_RGB_20: + return ACES_JMh_TO_RGB_20_STR; + case ACES_TONESCALE_COMPRESS_20_FWD: + return detailed ? "ACES_ToneScaleCompress20 (Forward)" : ACES_TONESCALE_COMPRESS_20_FWD_STR; + case ACES_TONESCALE_COMPRESS_20_INV: + return detailed ? "ACES_ToneScaleCompress20 (Inverse)" : ACES_TONESCALE_COMPRESS_20_INV_STR; + case ACES_GAMUT_COMPRESS_20_FWD: + return detailed ? "ACES_GamutCompress20 (Forward)" : ACES_GAMUT_COMPRESS_20_FWD_STR; + case ACES_GAMUT_COMPRESS_20_INV: + return detailed ? "ACES_GamutCompress20 (Inverse)" : ACES_GAMUT_COMPRESS_20_INV_STR; case REC2100_SURROUND_FWD: return detailed ? "REC2100_Surround (Forward)" : REC_2100_SURROUND_FWD_STR; case REC2100_SURROUND_INV: @@ -155,6 +203,38 @@ FixedFunctionOpData::Style FixedFunctionOpData::GetStyle(const char * name) { return ACES_GAMUT_COMP_13_INV; } + else if (0 == Platform::Strcasecmp(name, ACES_OUTPUT_TRANSFORM_20_FWD_STR)) + { + return ACES_OUTPUT_TRANSFORM_20_FWD; + } + else if (0 == Platform::Strcasecmp(name, ACES_OUTPUT_TRANSFORM_20_INV_STR)) + { + return ACES_OUTPUT_TRANSFORM_20_INV; + } + else if (0 == Platform::Strcasecmp(name, ACES_RGB_TO_JMh_20_STR)) + { + return ACES_RGB_TO_JMh_20; + } + else if (0 == Platform::Strcasecmp(name, ACES_JMh_TO_RGB_20_STR)) + { + return ACES_JMh_TO_RGB_20; + } + else if (0 == Platform::Strcasecmp(name, ACES_TONESCALE_COMPRESS_20_FWD_STR)) + { + return ACES_TONESCALE_COMPRESS_20_FWD; + } + else if (0 == Platform::Strcasecmp(name, ACES_TONESCALE_COMPRESS_20_INV_STR)) + { + return ACES_TONESCALE_COMPRESS_20_INV; + } + else if (0 == Platform::Strcasecmp(name, ACES_GAMUT_COMPRESS_20_FWD_STR)) + { + return ACES_GAMUT_COMPRESS_20_FWD; + } + else if (0 == Platform::Strcasecmp(name, ACES_GAMUT_COMPRESS_20_INV_STR)) + { + return ACES_GAMUT_COMPRESS_20_INV; + } else if (0 == Platform::Strcasecmp(name, SURROUND_STR) || 0 == Platform::Strcasecmp(name, REC_2100_SURROUND_FWD_STR)) { @@ -242,6 +322,26 @@ FixedFunctionOpData::Style FixedFunctionOpData::ConvertStyle(FixedFunctionStyle return isForward ? FixedFunctionOpData::ACES_GAMUT_COMP_13_FWD : FixedFunctionOpData::ACES_GAMUT_COMP_13_INV; } + case FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20: + { + return isForward ? FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_FWD : + FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_INV; + } + case FIXED_FUNCTION_ACES_RGB_TO_JMH_20: + { + return isForward ? FixedFunctionOpData::ACES_RGB_TO_JMh_20 : + FixedFunctionOpData::ACES_JMh_TO_RGB_20; + } + case FIXED_FUNCTION_ACES_TONESCALE_COMPRESS_20: + { + return isForward ? FixedFunctionOpData::ACES_TONESCALE_COMPRESS_20_FWD : + FixedFunctionOpData::ACES_TONESCALE_COMPRESS_20_INV; + } + case FIXED_FUNCTION_ACES_GAMUT_COMPRESS_20: + { + return isForward ? FixedFunctionOpData::ACES_GAMUT_COMPRESS_20_FWD : + FixedFunctionOpData::ACES_GAMUT_COMPRESS_20_INV; + } case FIXED_FUNCTION_REC2100_SURROUND: { return isForward ? FixedFunctionOpData::REC2100_SURROUND_FWD : @@ -307,6 +407,22 @@ FixedFunctionStyle FixedFunctionOpData::ConvertStyle(FixedFunctionOpData::Style case FixedFunctionOpData::ACES_GAMUT_COMP_13_INV: return FIXED_FUNCTION_ACES_GAMUT_COMP_13; + case FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_FWD: + case FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_INV: + return FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20; + + case FixedFunctionOpData::ACES_RGB_TO_JMh_20: + case FixedFunctionOpData::ACES_JMh_TO_RGB_20: + return FIXED_FUNCTION_ACES_RGB_TO_JMH_20; + + case FixedFunctionOpData::ACES_TONESCALE_COMPRESS_20_FWD: + case FixedFunctionOpData::ACES_TONESCALE_COMPRESS_20_INV: + return FIXED_FUNCTION_ACES_TONESCALE_COMPRESS_20; + + case FixedFunctionOpData::ACES_GAMUT_COMPRESS_20_FWD: + case FixedFunctionOpData::ACES_GAMUT_COMPRESS_20_INV: + return FIXED_FUNCTION_ACES_GAMUT_COMPRESS_20; + case FixedFunctionOpData::REC2100_SURROUND_FWD: case FixedFunctionOpData::REC2100_SURROUND_INV: return FIXED_FUNCTION_REC2100_SURROUND; @@ -381,33 +497,79 @@ void FixedFunctionOpData::validate() const const double thr_yellow = m_params[5]; const double power = m_params[6]; - auto check_bounds = [](const std::string & name, double val, double low, double high) - { - if (val < low || val > high) - { - std::stringstream ss; - ss << "Parameter " << val << " (" << name << ") is outside valid range [" << low << "," << high << "]"; - throw Exception(ss.str().c_str()); - } - }; - // Clamped to the smallest increment above 1 in half float precision for numerical stability. static constexpr double lim_low_bound = 1.001; static constexpr double lim_hi_bound = 65504.0; - check_bounds("lim_cyan", lim_cyan, lim_low_bound, lim_hi_bound); - check_bounds("lim_magenta", lim_magenta, lim_low_bound, lim_hi_bound); - check_bounds("lim_yellow", lim_yellow, lim_low_bound, lim_hi_bound); + check_param_bounds("lim_cyan", lim_cyan, lim_low_bound, lim_hi_bound); + check_param_bounds("lim_magenta", lim_magenta, lim_low_bound, lim_hi_bound); + check_param_bounds("lim_yellow", lim_yellow, lim_low_bound, lim_hi_bound); static constexpr double thr_low_bound = 0.0; // Clamped to the smallest increment below 1 in half float precision for numerical stability. static constexpr double thr_hi_bound = 0.9995; - check_bounds("thr_cyan", thr_cyan, thr_low_bound, thr_hi_bound); - check_bounds("thr_magenta", thr_magenta, thr_low_bound, thr_hi_bound); - check_bounds("thr_yellow", thr_yellow, thr_low_bound, thr_hi_bound); + check_param_bounds("thr_cyan", thr_cyan, thr_low_bound, thr_hi_bound); + check_param_bounds("thr_magenta", thr_magenta, thr_low_bound, thr_hi_bound); + check_param_bounds("thr_yellow", thr_yellow, thr_low_bound, thr_hi_bound); static constexpr double pwr_low_bound = 1.0; static constexpr double pwr_hi_bound = 65504.0; - check_bounds("power", power, pwr_low_bound, pwr_hi_bound); + check_param_bounds("power", power, pwr_low_bound, pwr_hi_bound); + } + else if (m_style == ACES_OUTPUT_TRANSFORM_20_FWD || m_style == ACES_OUTPUT_TRANSFORM_20_INV) + { + if (m_params.size() != 9) + { + std::stringstream ss; + ss << "The style '" << ConvertStyleToString(m_style, true) + << "' must have 9 parameters but " + << m_params.size() << " found."; + throw Exception(ss.str().c_str()); + } + + const double peak_luminance = m_params[0]; + check_param_bounds("peak_luminance", peak_luminance, 1, 10000); + check_param_no_frac("peak_luminance", peak_luminance); + } + else if (m_style == ACES_RGB_TO_JMh_20 || m_style == ACES_JMh_TO_RGB_20) + { + if (m_params.size() != 8) + { + std::stringstream ss; + ss << "The style '" << ConvertStyleToString(m_style, true) + << "' must have 8 parameters but " + << m_params.size() << " found."; + throw Exception(ss.str().c_str()); + } + } + else if (m_style == ACES_TONESCALE_COMPRESS_20_FWD || m_style == ACES_TONESCALE_COMPRESS_20_INV) + { + if (m_params.size() != 1) + { + std::stringstream ss; + ss << "The style '" << ConvertStyleToString(m_style, true) + << "' must have 1 parameters but " + << m_params.size() << " found."; + throw Exception(ss.str().c_str()); + } + + const double peak_luminance = m_params[0]; + check_param_bounds("peak_luminance", peak_luminance, 1, 10000); + check_param_no_frac("peak_luminance", peak_luminance); + } + else if (m_style == ACES_GAMUT_COMPRESS_20_FWD || m_style == ACES_GAMUT_COMPRESS_20_INV) + { + if (m_params.size() != 9) + { + std::stringstream ss; + ss << "The style '" << ConvertStyleToString(m_style, true) + << "' must have 9 parameters but " + << m_params.size() << " found."; + throw Exception(ss.str().c_str()); + } + + const double peak_luminance = m_params[0]; + check_param_bounds("peak_luminance", peak_luminance, 1, 10000); + check_param_no_frac("peak_luminance", peak_luminance); } else if (m_style==REC2100_SURROUND_FWD || m_style == REC2100_SURROUND_INV) { @@ -530,6 +692,47 @@ void FixedFunctionOpData::invert() noexcept setStyle(ACES_GAMUT_COMP_13_FWD); break; } + case ACES_OUTPUT_TRANSFORM_20_FWD: + { + setStyle(ACES_OUTPUT_TRANSFORM_20_INV); + break; + } + case ACES_OUTPUT_TRANSFORM_20_INV: + { + setStyle(ACES_OUTPUT_TRANSFORM_20_FWD); + break; + } + case ACES_RGB_TO_JMh_20: + { + setStyle(ACES_JMh_TO_RGB_20); + break; + } + case ACES_JMh_TO_RGB_20: + { + setStyle(ACES_RGB_TO_JMh_20); + break; + } + case ACES_TONESCALE_COMPRESS_20_FWD: + { + setStyle(ACES_TONESCALE_COMPRESS_20_INV); + break; + } + case ACES_TONESCALE_COMPRESS_20_INV: + { + setStyle(ACES_TONESCALE_COMPRESS_20_FWD); + break; + } + case ACES_GAMUT_COMPRESS_20_FWD: + { + setStyle(ACES_GAMUT_COMPRESS_20_INV); + break; + } + case ACES_GAMUT_COMPRESS_20_INV: + { + setStyle(ACES_GAMUT_COMPRESS_20_FWD); + break; + } + case REC2100_SURROUND_FWD: { setStyle(REC2100_SURROUND_INV); @@ -609,6 +812,10 @@ TransformDirection FixedFunctionOpData::getDirection() const noexcept case FixedFunctionOpData::ACES_GLOW_10_FWD: case FixedFunctionOpData::ACES_DARK_TO_DIM_10_FWD: case FixedFunctionOpData::ACES_GAMUT_COMP_13_FWD: + case FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_FWD: + case FixedFunctionOpData::ACES_RGB_TO_JMh_20: + case FixedFunctionOpData::ACES_TONESCALE_COMPRESS_20_FWD: + case FixedFunctionOpData::ACES_GAMUT_COMPRESS_20_FWD: case FixedFunctionOpData::REC2100_SURROUND_FWD: case FixedFunctionOpData::RGB_TO_HSV: case FixedFunctionOpData::XYZ_TO_xyY: @@ -622,6 +829,10 @@ TransformDirection FixedFunctionOpData::getDirection() const noexcept case FixedFunctionOpData::ACES_GLOW_10_INV: case FixedFunctionOpData::ACES_DARK_TO_DIM_10_INV: case FixedFunctionOpData::ACES_GAMUT_COMP_13_INV: + case FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_INV: + case FixedFunctionOpData::ACES_JMh_TO_RGB_20: + case FixedFunctionOpData::ACES_TONESCALE_COMPRESS_20_INV: + case FixedFunctionOpData::ACES_GAMUT_COMPRESS_20_INV: case FixedFunctionOpData::REC2100_SURROUND_INV: case FixedFunctionOpData::HSV_TO_RGB: case FixedFunctionOpData::xyY_TO_XYZ: diff --git a/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpData.h b/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpData.h index a640c66161..4b3fa99657 100644 --- a/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpData.h +++ b/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpData.h @@ -26,28 +26,36 @@ class FixedFunctionOpData : public OpData enum Style { - ACES_RED_MOD_03_FWD = 0, // Red modifier (ACES 0.3/0.7) - ACES_RED_MOD_03_INV, // Red modifier inverse (ACES 0.3/0.7) - ACES_RED_MOD_10_FWD, // Red modifier (ACES 1.0) - ACES_RED_MOD_10_INV, // Red modifier inverse (ACES v1.0) - ACES_GLOW_03_FWD, // Glow function (ACES 0.3/0.7) - ACES_GLOW_03_INV, // Glow function inverse (ACES 0.3/0.7) - ACES_GLOW_10_FWD, // Glow function (ACES 1.0) - ACES_GLOW_10_INV, // Glow function inverse (ACES 1.0) - ACES_DARK_TO_DIM_10_FWD, // Dark to dim surround correction (ACES 1.0) - ACES_DARK_TO_DIM_10_INV, // Dim to dark surround correction (ACES 1.0) - ACES_GAMUT_COMP_13_FWD, // Parametric Gamut Compression (ACES 1.3) - ACES_GAMUT_COMP_13_INV, // Parametric Gamut Compression inverse (ACES 1.3) - REC2100_SURROUND_FWD, // Rec.2100 surround correction (takes one double for the gamma param) - REC2100_SURROUND_INV, // Rec.2100 surround correction inverse (takes one gamma param) - RGB_TO_HSV, // Classic RGB to HSV function - HSV_TO_RGB, // Classic HSV to RGB function - XYZ_TO_xyY, // CIE XYZ to 1931 xy chromaticity coordinates - xyY_TO_XYZ, // Inverse of above - XYZ_TO_uvY, // CIE XYZ to 1976 u'v' chromaticity coordinates - uvY_TO_XYZ, // Inverse of above - XYZ_TO_LUV, // CIE XYZ to 1976 CIELUV colour space (D65 white) - LUV_TO_XYZ // Inverse of above + ACES_RED_MOD_03_FWD = 0, // Red modifier (ACES 0.3/0.7) + ACES_RED_MOD_03_INV, // Red modifier inverse (ACES 0.3/0.7) + ACES_RED_MOD_10_FWD, // Red modifier (ACES 1.0) + ACES_RED_MOD_10_INV, // Red modifier inverse (ACES v1.0) + ACES_GLOW_03_FWD, // Glow function (ACES 0.3/0.7) + ACES_GLOW_03_INV, // Glow function inverse (ACES 0.3/0.7) + ACES_GLOW_10_FWD, // Glow function (ACES 1.0) + ACES_GLOW_10_INV, // Glow function inverse (ACES 1.0) + ACES_DARK_TO_DIM_10_FWD, // Dark to dim surround correction (ACES 1.0) + ACES_DARK_TO_DIM_10_INV, // Dim to dark surround correction (ACES 1.0) + ACES_GAMUT_COMP_13_FWD, // Parametric Gamut Compression (ACES 1.3) + ACES_GAMUT_COMP_13_INV, // Parametric Gamut Compression inverse (ACES 1.3) + REC2100_SURROUND_FWD, // Rec.2100 surround correction (takes one double for the gamma param) + REC2100_SURROUND_INV, // Rec.2100 surround correction inverse (takes one gamma param) + RGB_TO_HSV, // Classic RGB to HSV function + HSV_TO_RGB, // Classic HSV to RGB function + XYZ_TO_xyY, // CIE XYZ to 1931 xy chromaticity coordinates + xyY_TO_XYZ, // Inverse of above + XYZ_TO_uvY, // CIE XYZ to 1976 u'v' chromaticity coordinates + uvY_TO_XYZ, // Inverse of above + XYZ_TO_LUV, // CIE XYZ to 1976 CIELUV colour space (D65 white) + LUV_TO_XYZ, // Inverse of above + ACES_OUTPUT_TRANSFORM_20_FWD, // ACES2 Output transform + ACES_OUTPUT_TRANSFORM_20_INV, // ACES2 Output transform (inverse) + ACES_RGB_TO_JMh_20, // ACES2 RGB to JMh + ACES_JMh_TO_RGB_20, // ACES2 JMh to RGB + ACES_TONESCALE_COMPRESS_20_FWD, // ACES2 Tonescale and chroma compression + ACES_TONESCALE_COMPRESS_20_INV, // ACES2 Tonescale and chroma compression (inv) + ACES_GAMUT_COMPRESS_20_FWD, // ACES2 Gamut compression + ACES_GAMUT_COMPRESS_20_INV // ACES2 Gamut compression (inv) }; static const char * ConvertStyleToString(Style style, bool detailed); diff --git a/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpGPU.cpp b/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpGPU.cpp index 20c1b1b1b4..7161deafb3 100644 --- a/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpGPU.cpp +++ b/src/OpenColorIO/ops/fixedfunction/FixedFunctionOpGPU.cpp @@ -5,7 +5,9 @@ #include +#include "utils/StringUtils.h" #include "ops/fixedfunction/FixedFunctionOpGPU.h" +#include "ACES2/Transform.h" namespace OCIO_NAMESPACE @@ -347,6 +349,1305 @@ void Add_GamutComp_13_Inv_Shader(GpuShaderText & ss, ); } +void _Add_RGB_to_JMh_Shader( + GpuShaderCreatorRcPtr & shaderCreator, + GpuShaderText & ss, + const ACES2::JMhParams & p) +{ + const std::string pxl(shaderCreator->getPixelName()); + + ss.newLine() << ss.float3Decl("lms") << " = " << ss.mat3fMul(&p.MATRIX_RGB_to_CAM16[0], pxl + ".rgb") << ";"; + ss.newLine() << "lms = " << "lms * " << ss.float3Const(p.D_RGB[0], p.D_RGB[1], p.D_RGB[2]) << ";"; + + ss.newLine() << ss.float3Decl("F_L_v") << " = pow(" << p.F_L << " * abs(lms) / 100.0, " << ss.float3Const(0.42f) << ");"; + ss.newLine() << ss.float3Decl("rgb_a") << " = (400.0 * sign(lms) * F_L_v) / (27.13 + F_L_v);"; + + ss.newLine() << ss.floatDecl("A") << " = 2.0 * rgb_a.r + rgb_a.g + 0.05 * rgb_a.b;"; + ss.newLine() << ss.floatDecl("a") << " = rgb_a.r - 12.0 * rgb_a.g / 11.0 + rgb_a.b / 11.0;"; + ss.newLine() << ss.floatDecl("b") << " = (rgb_a.r + rgb_a.g - 2.0 * rgb_a.b) / 9.0;"; + + ss.newLine() << ss.floatDecl("J") << " = 100.0 * pow(A / " << p.A_w << ", " << ACES2::surround[1] << " * " << p.z << ");"; + + ss.newLine() << ss.floatDecl("M") << " = (J == 0.0) ? 0.0 : 43.0 * " << ACES2::surround[2] << " * sqrt(a * a + b * b);"; + + ss.newLine() << ss.floatDecl("h") << " = (a == 0.0) ? 0.0 : " << ss.atan2("b", "a") << " * 180.0 / 3.14159265358979;"; + ss.newLine() << "h = h - floor(h / 360.0) * 360.0;"; + ss.newLine() << "h = (h < 0.0) ? h + 360.0 : h;"; + + ss.newLine() << pxl << ".rgb = " << ss.float3Const("J", "M", "h") << ";"; +} + +void _Add_JMh_to_RGB_Shader( + GpuShaderCreatorRcPtr & shaderCreator, + GpuShaderText & ss, + const ACES2::JMhParams & p) +{ + const std::string pxl(shaderCreator->getPixelName()); + + ss.newLine() << ss.floatDecl("h") << " = " << pxl << ".b * 3.14159265358979 / 180.0;"; + + ss.newLine() << ss.floatDecl("scale") << " = " << pxl << ".g / (43.0 * " << ACES2::surround[2] << ");"; + ss.newLine() << ss.floatDecl("A") << " = " << p.A_w << " * pow(" << pxl << ".r / 100.0, 1.0 / (" << ACES2::surround[1] << " * " << p.z << "));"; + ss.newLine() << ss.floatDecl("a") << " = scale * cos(h);"; + ss.newLine() << ss.floatDecl("b") << " = scale * sin(h);"; + + ss.newLine() << ss.float3Decl("rgb_a") << ";"; + ss.newLine() << "rgb_a.r = (460.0 * A + 451.0 * a + 288.0 *b) / 1403.0;"; + ss.newLine() << "rgb_a.g = (460.0 * A - 891.0 * a - 261.0 *b) / 1403.0;"; + ss.newLine() << "rgb_a.b = (460.0 * A - 220.0 * a - 6300.0 *b) / 1403.0;"; + + ss.newLine() << ss.float3Decl("lms") << " = sign(rgb_a) * 100.0 / " << p.F_L << " * pow(27.13 * abs(rgb_a) / (400.0 - abs(rgb_a)), " << ss.float3Const(1.f / 0.42f) << ");"; + ss.newLine() << "lms = " << "lms / " << ss.float3Const(p.D_RGB[0], p.D_RGB[1], p.D_RGB[2]) << ";"; + + ss.newLine() << pxl << ".rgb = " << ss.mat3fMul(&p.MATRIX_CAM16_to_RGB[0], "lms") << ";"; +} + +std::string _Add_Reach_table( + GpuShaderCreatorRcPtr & shaderCreator, + unsigned resourceIndex, + const ACES2::Table1D & table) +{ + // Reserve name + std::ostringstream resName; + resName << shaderCreator->getResourcePrefix() + << std::string("_") + << std::string("reach_m_table") + << resourceIndex; + + // Note: Remove potentially problematic double underscores from GLSL resource names. + std::string name(resName.str()); + StringUtils::ReplaceInPlace(name, "__", "_"); + + // Register texture + GpuShaderDesc::TextureDimensions dimensions = GpuShaderDesc::TEXTURE_1D; + if (shaderCreator->getLanguage() == GPU_LANGUAGE_GLSL_ES_1_0 + || shaderCreator->getLanguage() == GPU_LANGUAGE_GLSL_ES_3_0 + || !shaderCreator->getAllowTexture1D()) + { + dimensions = GpuShaderDesc::TEXTURE_2D; + } + + shaderCreator->addTexture( + name.c_str(), + GpuShaderText::getSamplerName(name).c_str(), + table.size, + 1, + GpuShaderCreator::TEXTURE_RED_CHANNEL, + dimensions, + INTERP_NEAREST, + &(table.table[0])); + + + if (dimensions == GpuShaderDesc::TEXTURE_1D) + { + GpuShaderText ss(shaderCreator->getLanguage()); + ss.declareTex1D(name); + shaderCreator->addToDeclareShaderCode(ss.string().c_str()); + } + else + { + GpuShaderText ss(shaderCreator->getLanguage()); + ss.declareTex2D(name); + shaderCreator->addToDeclareShaderCode(ss.string().c_str()); + } + + // Sampler function + GpuShaderText ss(shaderCreator->getLanguage()); + + ss.newLine() << ss.floatKeyword() << " " << name << "_sample(float h)"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.floatDecl("hwrap") << " = h;"; + ss.newLine() << "hwrap = hwrap - floor(hwrap / 360.0) * 360.0;"; + ss.newLine() << "hwrap = (hwrap < 0.0) ? hwrap + 360.0 : hwrap;"; + + ss.newLine() << ss.floatDecl("i_lo") << " = floor(hwrap);"; + ss.newLine() << ss.floatDecl("i_hi") << " = (i_lo + 1);"; + ss.newLine() << "i_hi = i_hi - floor(i_hi / 360.0) * 360.0;"; + + if (dimensions == GpuShaderDesc::TEXTURE_1D) + { + ss.newLine() << ss.floatDecl("lo") << " = " << ss.sampleTex1D(name, "(i_lo + 0.5) / 360.0") << ".r;"; + ss.newLine() << ss.floatDecl("hi") << " = " << ss.sampleTex1D(name, "(i_hi + 0.5) / 360.0") << ".r;"; + } + else + { + ss.newLine() << ss.floatDecl("lo") << " = " << ss.sampleTex2D(name, ss.float2Const("(i_lo + 0.5) / 360.0", "0.0")) << ".r;"; + ss.newLine() << ss.floatDecl("hi") << " = " << ss.sampleTex2D(name, ss.float2Const("(i_hi + 0.5) / 360.0", "0.5")) << ".r;"; + } + + ss.newLine() << ss.floatDecl("t") << " = (h - i_lo) / (i_hi - i_lo);"; + ss.newLine() << "return " << ss.lerp("lo", "hi", "t") << ";"; + + ss.dedent(); + ss.newLine() << "}"; + + shaderCreator->addToHelperShaderCode(ss.string().c_str()); + + return name; +} + +std::string _Add_Toe_func( + GpuShaderCreatorRcPtr & shaderCreator, + unsigned resourceIndex, + bool invert) +{ + // Reserve name + std::ostringstream resName; + resName << shaderCreator->getResourcePrefix() + << std::string("_") + << std::string("toe") + << (invert ? std::string("_inv") : std::string("_fwd")) + << resourceIndex; + + // Note: Remove potentially problematic double underscores from GLSL resource names. + std::string name(resName.str()); + StringUtils::ReplaceInPlace(name, "__", "_"); + + GpuShaderText ss(shaderCreator->getLanguage()); + + ss.newLine() << ss.floatKeyword() << " " << name << "(float x, float limit, float k1_in, float k2_in)"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.floatDecl("k2") << " = max(k2_in, 0.001);"; + ss.newLine() << ss.floatDecl("k1") << " = sqrt(k1_in * k1_in + k2 * k2);"; + ss.newLine() << ss.floatDecl("k3") << " = (limit + k1) / (limit + k2);"; + + if (invert) + { + ss.newLine() << "return (x > limit) ? x : (x * x + k1 * x) / (k3 * (x + k2));"; + } + else + { + ss.newLine() << "return (x > limit) ? x : 0.5 * (k3 * x - k1 + sqrt((k3 * x - k1) * (k3 * x - k1) + 4.0 * k2 * k3 * x));"; + } + + ss.dedent(); + ss.newLine() << "}"; + + shaderCreator->addToHelperShaderCode(ss.string().c_str()); + + return name; +} + +void _Add_Tonescale_Compress_Fwd_Shader( + GpuShaderCreatorRcPtr & shaderCreator, + GpuShaderText & ss, + unsigned resourceIndex, + const ACES2::JMhParams & p, + const ACES2::ToneScaleParams & t, + const ACES2::ChromaCompressParams & c, + const std::string & reachName) +{ + std::string toeName = _Add_Toe_func(shaderCreator, resourceIndex, false); + + const std::string pxl(shaderCreator->getPixelName()); + + ss.newLine() << ss.floatDecl("J") << " = " << pxl << ".r;"; + ss.newLine() << ss.floatDecl("M") << " = " << pxl << ".g;"; + ss.newLine() << ss.floatDecl("h") << " = " << pxl << ".b;"; + + // Tonescale applied in Y (convert to and from J) + ss.newLine() << ss.floatDecl("A") << " = " << p.A_w_J << " * pow(abs(J) / 100.0, 1.0 / (" << ACES2::surround[1] << " * " << p.z << "));"; + ss.newLine() << ss.floatDecl("Y") << " = sign(J) * 100.0 / " << p.F_L << " * pow((27.13 * A) / (400.0 - A), 1.0 / 0.42) / 100.0;"; + + ss.newLine() << ss.floatDecl("f") << " = " << t.m_2 << " * pow(max(0.0, Y) / (Y + " << t.s_2 << "), " << t.g << ");"; + ss.newLine() << ss.floatDecl("Y_ts") << " = max(0.0, f * f / (f + " << t.t_1 << ")) * " << t.n_r << ";"; + + ss.newLine() << ss.floatDecl("F_L_Y") << " = pow(" << p.F_L << " * abs(Y_ts) / 100.0, 0.42);"; + ss.newLine() << ss.floatDecl("J_ts") << " = sign(Y_ts) * 100.0 * pow(((400.0 * F_L_Y) / (27.13 + F_L_Y)) / " << p.A_w_J << ", " << ACES2::surround[1] << " * " << p.z << ");"; + + // ChromaCompress + ss.newLine() << ss.floatDecl("M_cp") << " = M;"; + + ss.newLine() << "if (M != 0.0)"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.floatDecl("nJ") << " = J_ts / " << c.limit_J_max << ";"; + ss.newLine() << ss.floatDecl("snJ") << " = max(0.0, 1.0 - nJ);"; + + // Mnorm + ss.newLine() << ss.floatDecl("Mnorm") << ";"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.floatDecl("PI") << " = 3.14159265358979;"; + ss.newLine() << ss.floatDecl("h_rad") << " = h / 180.0 * PI;"; + ss.newLine() << ss.floatDecl("a") << " = cos(h_rad);"; + ss.newLine() << ss.floatDecl("b") << " = sin(h_rad);"; + ss.newLine() << ss.floatDecl("cos_hr2") << " = a * a - b * b;"; + ss.newLine() << ss.floatDecl("sin_hr2") << " = 2.0 * a * b;"; + ss.newLine() << ss.floatDecl("cos_hr3") << " = 4.0 * a * a * a - 3.0 * a;"; + ss.newLine() << ss.floatDecl("sin_hr3") << " = 3.0 * b - 4.0 * b * b * b;"; + ss.newLine() << ss.floatDecl("M") << " = 11.34072 * a + 16.46899 * cos_hr2 + 7.88380 * cos_hr3 + 14.66441 * b + -6.37224 * sin_hr2 + 9.19364 * sin_hr3 + 77.12896;"; + ss.newLine() << "Mnorm = M * " << c.chroma_compress_scale << ";"; + + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << ss.floatDecl("reachM") << " = " << reachName << "_sample(h);"; + ss.newLine() << ss.floatDecl("limit") << " = pow(nJ, " << c.model_gamma << ") * reachM / Mnorm;"; + ss.newLine() << "M_cp = M * pow(J_ts / J, " << c.model_gamma << ");"; + ss.newLine() << "M_cp = M_cp / Mnorm;"; + + ss.newLine() << "M_cp = limit - " << toeName << "(limit - M_cp, limit - 0.001, snJ * " << c.sat << ", sqrt(nJ * nJ + " << c.sat_thr << "));"; + ss.newLine() << "M_cp = " << toeName << "(M_cp, limit, nJ * " << c.compr << ", snJ);"; + ss.newLine() << "M_cp = M_cp * Mnorm;"; + + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << pxl << ".rgb = " << ss.float3Const("J_ts", "M_cp", "h") << ";"; +} + +void _Add_Tonescale_Compress_Inv_Shader( + GpuShaderCreatorRcPtr & shaderCreator, + GpuShaderText & ss, + unsigned resourceIndex, + const ACES2::JMhParams & p, + const ACES2::ToneScaleParams & t, + const ACES2::ChromaCompressParams & c, + const std::string & reachName) +{ + std::string toeName = _Add_Toe_func(shaderCreator, resourceIndex, true); + + const std::string pxl(shaderCreator->getPixelName()); + + ss.newLine() << ss.floatDecl("J_ts") << " = " << pxl << ".r;"; + ss.newLine() << ss.floatDecl("M_cp") << " = " << pxl << ".g;"; + ss.newLine() << ss.floatDecl("h") << " = " << pxl << ".b;"; + + // Inverse Tonescale applied in Y (convert to and from J) + ss.newLine() << ss.floatDecl("A") << " = " << p.A_w_J << " * pow(abs(J_ts) / 100.0, 1.0 / (" << ACES2::surround[1] << " * " << p.z << "));"; + ss.newLine() << ss.floatDecl("Y_ts") << " = sign(J_ts) * 100.0 / " << p.F_L << " * pow((27.13 * A) / (400.0 - A), 1.0 / 0.42) / 100.0;"; + + ss.newLine() << ss.floatDecl("Z") << " = max(0.0, min(" << t.n << " / (" << t.u_2 * t.n_r << "), Y_ts));"; + ss.newLine() << ss.floatDecl("ht") << " = (Z + sqrt(Z * (4.0 * " << t.t_1 << " + Z))) / 2.0;"; + ss.newLine() << ss.floatDecl("Y") << " = " << t.s_2 << " / (pow((" << t.m_2 << " / ht), (1.0 / " << t.g << ")) - 1.0);"; + + ss.newLine() << ss.floatDecl("F_L_Y") << " = pow(" << p.F_L << " * abs(Y * 100.0) / 100.0, 0.42);"; + ss.newLine() << ss.floatDecl("J") << " = sign(Y) * 100.0 * pow(((400.0 * F_L_Y) / (27.13 + F_L_Y)) / " << p.A_w_J << ", " << ACES2::surround[1] << " * " << p.z << ");"; + + // ChromaCompress + ss.newLine() << ss.floatDecl("M") << " = M_cp;"; + + ss.newLine() << "if (M_cp != 0.0)"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.floatDecl("nJ") << " = J_ts / " << c.limit_J_max << ";"; + ss.newLine() << ss.floatDecl("snJ") << " = max(0.0, 1.0 - nJ);"; + + // Mnorm + ss.newLine() << ss.floatDecl("Mnorm") << ";"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.floatDecl("PI") << " = 3.14159265358979;"; + ss.newLine() << ss.floatDecl("h_rad") << " = h / 180.0 * PI;"; + ss.newLine() << ss.floatDecl("a") << " = cos(h_rad);"; + ss.newLine() << ss.floatDecl("b") << " = sin(h_rad);"; + ss.newLine() << ss.floatDecl("cos_hr2") << " = a * a - b * b;"; + ss.newLine() << ss.floatDecl("sin_hr2") << " = 2.0 * a * b;"; + ss.newLine() << ss.floatDecl("cos_hr3") << " = 4.0 * a * a * a - 3.0 * a;"; + ss.newLine() << ss.floatDecl("sin_hr3") << " = 3.0 * b - 4.0 * b * b * b;"; + ss.newLine() << ss.floatDecl("M") << " = 11.34072 * a + 16.46899 * cos_hr2 + 7.88380 * cos_hr3 + 14.66441 * b + -6.37224 * sin_hr2 + 9.19364 * sin_hr3 + 77.12896;"; + ss.newLine() << "Mnorm = M * " << c.chroma_compress_scale << ";"; + + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << ss.floatDecl("reachM") << " = " << reachName << "_sample(h);"; + ss.newLine() << ss.floatDecl("limit") << " = pow(nJ, " << c.model_gamma << ") * reachM / Mnorm;"; + + ss.newLine() << "M = M_cp / Mnorm;"; + ss.newLine() << "M = " << toeName << "(M, limit, nJ * " << c.compr << ", snJ);"; + ss.newLine() << "M = limit - " << toeName << "(limit - M, limit - 0.001, snJ * " << c.sat << ", sqrt(nJ * nJ + " << c.sat_thr << "));"; + ss.newLine() << "M = M * Mnorm;"; + ss.newLine() << "M = M * pow(J_ts / J, " << -c.model_gamma << ");"; + + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << pxl << ".rgb = " << ss.float3Const("J", "M", "h") << ";"; +} + +std::string _Add_Cusp_table( + GpuShaderCreatorRcPtr & shaderCreator, + unsigned resourceIndex, + const ACES2::GamutCompressParams & g) +{ + // Reserve name + std::ostringstream resName; + resName << shaderCreator->getResourcePrefix() + << std::string("_") + << std::string("gamut_cusp_table") + << resourceIndex; + + // Note: Remove potentially problematic double underscores from GLSL resource names. + std::string name(resName.str()); + StringUtils::ReplaceInPlace(name, "__", "_"); + + // Register texture + GpuShaderDesc::TextureDimensions dimensions = GpuShaderDesc::TEXTURE_1D; + if (shaderCreator->getLanguage() == GPU_LANGUAGE_GLSL_ES_1_0 + || shaderCreator->getLanguage() == GPU_LANGUAGE_GLSL_ES_3_0 + || !shaderCreator->getAllowTexture1D()) + { + dimensions = GpuShaderDesc::TEXTURE_2D; + } + + shaderCreator->addTexture( + name.c_str(), + GpuShaderText::getSamplerName(name).c_str(), + g.gamut_cusp_table.total_size, + 1, + GpuShaderCreator::TEXTURE_RGB_CHANNEL, + dimensions, + INTERP_NEAREST, + &(g.gamut_cusp_table.table[0][0])); + + if (dimensions == GpuShaderDesc::TEXTURE_1D) + { + GpuShaderText ss(shaderCreator->getLanguage()); + ss.declareTex1D(name); + shaderCreator->addToDeclareShaderCode(ss.string().c_str()); + } + else + { + GpuShaderText ss(shaderCreator->getLanguage()); + ss.declareTex2D(name); + shaderCreator->addToDeclareShaderCode(ss.string().c_str()); + } + + // Sampler function + GpuShaderText ss(shaderCreator->getLanguage()); + + const std::string hues_array_name = name + "_hues_array"; + std::vector hues_array(g.gamut_cusp_table.total_size); + for (int i = 0; i < g.gamut_cusp_table.total_size; ++i) + { + hues_array[i] = g.gamut_cusp_table.table[i][2]; + } + ss.declareFloatArrayConst(hues_array_name, (int) hues_array.size(), hues_array.data()); + + ss.newLine() << ss.float2Keyword() << " " << name << "_sample(float h)"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.floatDecl("i_lo") << " = 0;"; + ss.newLine() << ss.floatDecl("i_hi") << " = " << g.gamut_cusp_table.base_index + g.gamut_cusp_table.size << ";"; + + ss.newLine() << ss.floatDecl("hwrap") << " = h;"; + ss.newLine() << "hwrap = hwrap - floor(hwrap / 360.0) * 360.0;"; + ss.newLine() << "hwrap = (hwrap < 0.0) ? hwrap + 360.0 : hwrap;"; + ss.newLine() << ss.intDecl("i") << " = " << ss.intKeyword() << "(hwrap + " << g.gamut_cusp_table.base_index << ");"; + + ss.newLine() << "while (i_lo + 1 < i_hi)"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.floatDecl("hcur") << " = " << hues_array_name << "[i];"; + + ss.newLine() << "if (h > hcur)"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "i_lo = i;"; + ss.dedent(); + ss.newLine() << "}"; + ss.newLine() << "else"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "i_hi = i;"; + ss.dedent(); + ss.newLine() << "}"; + ss.newLine() << "i = " << ss.intKeyword() << "((i_lo + i_hi) / 2.0);"; + + ss.dedent(); + ss.newLine() << "}"; + + if (dimensions == GpuShaderDesc::TEXTURE_1D) + { + ss.newLine() << ss.float3Decl("lo") << " = " << ss.sampleTex1D(name, std::string("(i_hi - 1 + 0.5) / ") + std::to_string(g.gamut_cusp_table.total_size)) << ".rgb;"; + ss.newLine() << ss.float3Decl("hi") << " = " << ss.sampleTex1D(name, std::string("(i_hi + 0.5) / ") + std::to_string(g.gamut_cusp_table.total_size)) << ".rgb;"; + } + else + { + ss.newLine() << ss.float3Decl("lo") << " = " << ss.sampleTex2D(name, ss.float2Const(std::string("(i_hi - 1 + 0.5) / ") + std::to_string(g.gamut_cusp_table.total_size), "0.5")) << ".rgb;"; + ss.newLine() << ss.float3Decl("hi") << " = " << ss.sampleTex2D(name, ss.float2Const(std::string("(i_hi + 0.5) / ") + std::to_string(g.gamut_cusp_table.total_size), "0.5")) << ".rgb;"; + } + + ss.newLine() << ss.floatDecl("t") << " = (h - lo.b) / (hi.b - lo.b);"; + ss.newLine() << ss.floatDecl("cuspJ") << " = " << ss.lerp("lo.r", "hi.r", "t") << ";"; + ss.newLine() << ss.floatDecl("cuspM") << " = " << ss.lerp("lo.g", "hi.g", "t") << ";"; + + ss.newLine() << "return " << ss.float2Const("cuspJ", "cuspM") << ";"; + + ss.dedent(); + ss.newLine() << "}"; + + shaderCreator->addToHelperShaderCode(ss.string().c_str()); + + return name; +} + +std::string _Add_Gamma_table( + GpuShaderCreatorRcPtr & shaderCreator, + unsigned resourceIndex, + const ACES2::GamutCompressParams & g) +{ + // Reserve name + std::ostringstream resName; + resName << shaderCreator->getResourcePrefix() + << std::string("_") + << std::string("upper_hull_gamma_table") + << resourceIndex; + + // Note: Remove potentially problematic double underscores from GLSL resource names. + std::string name(resName.str()); + StringUtils::ReplaceInPlace(name, "__", "_"); + + // Register texture + GpuShaderDesc::TextureDimensions dimensions = GpuShaderDesc::TEXTURE_1D; + if (shaderCreator->getLanguage() == GPU_LANGUAGE_GLSL_ES_1_0 + || shaderCreator->getLanguage() == GPU_LANGUAGE_GLSL_ES_3_0 + || !shaderCreator->getAllowTexture1D()) + { + dimensions = GpuShaderDesc::TEXTURE_2D; + } + + shaderCreator->addTexture( + name.c_str(), + GpuShaderText::getSamplerName(name).c_str(), + g.gamut_cusp_table.total_size, + 1, + GpuShaderCreator::TEXTURE_RED_CHANNEL, + dimensions, + INTERP_NEAREST, + &(g.upper_hull_gamma_table.table[0])); + + + if (dimensions == GpuShaderDesc::TEXTURE_1D) + { + GpuShaderText ss(shaderCreator->getLanguage()); + ss.declareTex1D(name); + shaderCreator->addToDeclareShaderCode(ss.string().c_str()); + } + else + { + GpuShaderText ss(shaderCreator->getLanguage()); + ss.declareTex2D(name); + shaderCreator->addToDeclareShaderCode(ss.string().c_str()); + } + + // Sampler function + GpuShaderText ss(shaderCreator->getLanguage()); + + ss.newLine() << ss.floatKeyword() << " " << name << "_sample(float h)"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.floatDecl("hwrap") << " = h;"; + ss.newLine() << "hwrap = hwrap - floor(hwrap / 360.0) * 360.0;"; + ss.newLine() << "hwrap = (hwrap < 0.0) ? hwrap + 360.0 : hwrap;"; + + ss.newLine() << ss.floatDecl("i_lo") << " = floor(hwrap) + " << g.upper_hull_gamma_table.base_index << ";"; + ss.newLine() << ss.floatDecl("i_hi") << " = (i_lo + 1);"; + ss.newLine() << "i_hi = i_hi - floor(i_hi / 360.0) * 360.0;"; + + ss.newLine() << ss.floatDecl("base_hue") << " = i_lo - " << g.upper_hull_gamma_table.base_index << ";"; + + if (dimensions == GpuShaderDesc::TEXTURE_1D) + { + ss.newLine() << ss.floatDecl("lo") << " = " << ss.sampleTex1D(name, std::string("(i_lo + 0.5) / ") + std::to_string(g.upper_hull_gamma_table.total_size)) << ".r;"; + ss.newLine() << ss.floatDecl("hi") << " = " << ss.sampleTex1D(name, std::string("(i_hi + 0.5) / ") + std::to_string(g.upper_hull_gamma_table.total_size)) << ".r;"; + } + else + { + ss.newLine() << ss.floatDecl("lo") << " = " << ss.sampleTex2D(name, ss.float2Const(std::string("(i_lo + 0.5) / ") + std::to_string(g.upper_hull_gamma_table.total_size), "0.5")) << ".r;"; + ss.newLine() << ss.floatDecl("hi") << " = " << ss.sampleTex2D(name, ss.float2Const(std::string("(i_hi + 0.5) / ") + std::to_string(g.upper_hull_gamma_table.total_size), "0.5")) << ".r;"; + } + + ss.newLine() << ss.floatDecl("t") << " = hwrap - base_hue;"; + ss.newLine() << "return " << ss.lerp("lo", "hi", "t") << ";"; + + ss.dedent(); + ss.newLine() << "}"; + + shaderCreator->addToHelperShaderCode(ss.string().c_str()); + + return name; +} + +std::string _Add_Focus_Gain_func( + GpuShaderCreatorRcPtr & shaderCreator, + unsigned resourceIndex, + const ACES2::GamutCompressParams & g) +{ + // Reserve name + std::ostringstream resName; + resName << shaderCreator->getResourcePrefix() + << std::string("_") + << std::string("get_focus_gain") + << resourceIndex; + + // Note: Remove potentially problematic double underscores from GLSL resource names. + std::string name(resName.str()); + StringUtils::ReplaceInPlace(name, "__", "_"); + + GpuShaderText ss(shaderCreator->getLanguage()); + + ss.newLine() << ss.floatKeyword() << " " << name << "(float J, float cuspJ)"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.floatDecl("thr") << " = " << ss.lerp("cuspJ", std::to_string(g.limit_J_max), std::to_string(ACES2::focus_gain_blend)) << ";"; + + ss.newLine() << "if (J > thr)"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << ss.floatDecl("gain") << " = ( " << g.limit_J_max << " - thr) / max(0.0001, (" << g.limit_J_max << " - min(" << g.limit_J_max << ", J)));"; + ss.newLine() << "return pow(log(gain)/log(10.0), 1.0 / " << ACES2::focus_adjust_gain << ") + 1.0;"; + ss.dedent(); + ss.newLine() << "}"; + ss.newLine() << "else"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "return 1.0;"; + ss.dedent(); + ss.newLine() << "}"; + + ss.dedent(); + ss.newLine() << "}"; + + shaderCreator->addToHelperShaderCode(ss.string().c_str()); + + return name; +} + +std::string _Add_Solve_J_Intersect_func( + GpuShaderCreatorRcPtr & shaderCreator, + unsigned resourceIndex, + const ACES2::GamutCompressParams & g) +{ + // Reserve name + std::ostringstream resName; + resName << shaderCreator->getResourcePrefix() + << std::string("_") + << std::string("solve_J_intersect") + << resourceIndex; + + // Note: Remove potentially problematic double underscores from GLSL resource names. + std::string name(resName.str()); + StringUtils::ReplaceInPlace(name, "__", "_"); + + GpuShaderText ss(shaderCreator->getLanguage()); + + ss.newLine() << ss.floatKeyword() << " " << name << "(float J, float M, float focusJ, float slope_gain)"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.floatDecl("a") << " = " << "M / (focusJ * slope_gain);"; + ss.newLine() << ss.floatDecl("b") << " = 0.0;"; + ss.newLine() << ss.floatDecl("c") << " = 0.0;"; + ss.newLine() << ss.floatDecl("intersectJ") << " = 0.0;"; + + ss.newLine() << "if (J < focusJ)"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "b = 1.0 - M / slope_gain;"; + ss.dedent(); + ss.newLine() << "}"; + ss.newLine() << "else"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "b = - (1.0 + M / slope_gain + " << g.limit_J_max << " * M / (focusJ * slope_gain));"; + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << "if (J < focusJ)"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "c = -J;"; + ss.dedent(); + ss.newLine() << "}"; + ss.newLine() << "else"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "c = " << g.limit_J_max << " * M / slope_gain + J;"; + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << ss.floatDecl("root") << " = sqrt(b * b - 4.0 * a * c);"; + + ss.newLine() << "if (J < focusJ)"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "intersectJ = 2.0 * c / (-b - root);"; + ss.dedent(); + ss.newLine() << "}"; + ss.newLine() << "else"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "intersectJ = 2.0 * c / (-b + root);"; + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << "return intersectJ;"; + + ss.dedent(); + ss.newLine() << "}"; + + shaderCreator->addToHelperShaderCode(ss.string().c_str()); + + return name; +} + +std::string _Add_Find_Gamut_Boundary_Intersection_func( + GpuShaderCreatorRcPtr & shaderCreator, + unsigned resourceIndex, + const ACES2::GamutCompressParams & g, + const std::string & solveJIntersectName) +{ + // Reserve name + std::ostringstream resName; + resName << shaderCreator->getResourcePrefix() + << std::string("_") + << std::string("find_gamut_boundary_intersection") + << resourceIndex; + + // Note: Remove potentially problematic double underscores from GLSL resource names. + std::string name(resName.str()); + StringUtils::ReplaceInPlace(name, "__", "_"); + + GpuShaderText ss(shaderCreator->getLanguage()); + + ss.newLine() << ss.float3Keyword() << " " << name << "(" << ss.float3Keyword() << " JMh_s, " << ss.float2Keyword() << " JM_cusp_in, float J_focus, float slope_gain, float gamma_top, float gamma_bottom)"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.float2Decl("JM_cusp") << " = " << ss.float2Const("JM_cusp_in.r", std::string("JM_cusp_in.g * (1.0 + ") + std::to_string(ACES2::smooth_m) + " * " + std::to_string(ACES2::smooth_cusps) + ")") << ";"; + + ss.newLine() << ss.floatDecl("J_intersect_source") << " = " << solveJIntersectName << "(JMh_s.r, JMh_s.g, J_focus, slope_gain);"; + ss.newLine() << ss.floatDecl("J_intersect_cusp") << " = " << solveJIntersectName << "(JM_cusp.r, JM_cusp.g, J_focus, slope_gain);"; + + ss.newLine() << ss.floatDecl("slope") << " = 0.0;"; + ss.newLine() << "if (J_intersect_source < J_focus)"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "slope = J_intersect_source * (J_intersect_source - J_focus) / (J_focus * slope_gain);"; + ss.dedent(); + ss.newLine() << "}"; + ss.newLine() << "else"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "slope = (" << g.limit_J_max << " - J_intersect_source) * (J_intersect_source - J_focus) / (J_focus * slope_gain);"; + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << ss.floatDecl("M_boundary_lower ") << " = J_intersect_cusp * pow(J_intersect_source / J_intersect_cusp, 1.0 / gamma_bottom) / (JM_cusp.r / JM_cusp.g - slope);"; + ss.newLine() << ss.floatDecl("M_boundary_upper") << " = JM_cusp.g * (" << g.limit_J_max << " - J_intersect_cusp) * pow((" << g.limit_J_max << " - J_intersect_source) / (" << g.limit_J_max << " - J_intersect_cusp), 1.0 / gamma_top) / (slope * JM_cusp.g + " << g.limit_J_max << " - JM_cusp.r);"; + + ss.newLine() << ss.floatDecl("smin") << " = 0.0;"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << ss.floatDecl("a") << " = M_boundary_lower / JM_cusp.g;"; + ss.newLine() << ss.floatDecl("b") << " = M_boundary_upper / JM_cusp.g;"; + ss.newLine() << ss.floatDecl("s") << " = " << ACES2::smooth_cusps << ";"; + + ss.newLine() << ss.floatDecl("h") << " = max(s - abs(a - b), 0.0) / s;"; + ss.newLine() << "smin = min(a, b) - h * h * h * s * (1.0 / 6.0);"; + + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << ss.floatDecl("M_boundary") << " = JM_cusp.g * smin;"; + ss.newLine() << ss.floatDecl("J_boundary") << "= J_intersect_source + slope * M_boundary;"; + + ss.newLine() << "return " << ss.float3Const("J_boundary", "M_boundary", "J_intersect_source") << ";"; + + ss.dedent(); + ss.newLine() << "}"; + + shaderCreator->addToHelperShaderCode(ss.string().c_str()); + + return name; +} + +std::string _Add_Reach_Boundary_func( + GpuShaderCreatorRcPtr & shaderCreator, + unsigned resourceIndex, + const ACES2::GamutCompressParams & g, + const std::string & reachName, + const std::string & getFocusGainName, + const std::string & solveJIntersectName) +{ + // Reserve name + std::ostringstream resName; + resName << shaderCreator->getResourcePrefix() + << std::string("_") + << std::string("get_reach_boundary") + << resourceIndex; + + // Note: Remove potentially problematic double underscores from GLSL resource names. + std::string name(resName.str()); + StringUtils::ReplaceInPlace(name, "__", "_"); + + GpuShaderText ss(shaderCreator->getLanguage()); + + ss.newLine() << ss.float3Keyword() << " " << name << "(float J, float M, float h, " << ss.float2Keyword() << " JMcusp, float focusJ)"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.floatDecl("reachMaxM") << " = " << reachName << "_sample(h);"; + ss.newLine() << ss.floatDecl("slope_gain") << " = " << g.limit_J_max << " * " << g.focus_dist << " * " << getFocusGainName << "(J, JMcusp.r);"; + ss.newLine() << ss.floatDecl("intersectJ") << " = " << solveJIntersectName << "(J, M, focusJ, slope_gain);"; + + ss.newLine() << ss.floatDecl("slope") << " = 0.0;"; + ss.newLine() << "if (intersectJ < focusJ)"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "slope = intersectJ * (intersectJ - focusJ) / (focusJ * slope_gain);"; + ss.dedent(); + ss.newLine() << "}"; + ss.newLine() << "else"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "slope = (" << g.limit_J_max << " - intersectJ) * (intersectJ - focusJ) / (focusJ * slope_gain);"; + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << ss.floatDecl("boundary") << " = " << g.limit_J_max << " * pow(intersectJ / " << g.limit_J_max << ", " << g.model_gamma << ") * reachMaxM / (" << g.limit_J_max << " - slope * reachMaxM);"; + + ss.newLine() << "return " << ss.float3Const("J", "boundary", "h") << ";"; + + ss.dedent(); + ss.newLine() << "}"; + + shaderCreator->addToHelperShaderCode(ss.string().c_str()); + + return name; +} + +std::string _Add_Compression_func( + GpuShaderCreatorRcPtr & shaderCreator, + unsigned resourceIndex, + bool invert) +{ + // Reserve name + std::ostringstream resName; + resName << shaderCreator->getResourcePrefix() + << std::string("_") + << std::string("compression") + << (invert ? std::string("_inv") : std::string("_fwd")) + << resourceIndex; + + // Note: Remove potentially problematic double underscores from GLSL resource names. + std::string name(resName.str()); + StringUtils::ReplaceInPlace(name, "__", "_"); + + GpuShaderText ss(shaderCreator->getLanguage()); + + ss.newLine() << ss.floatKeyword() << " " << name << "(float v, float thr, float lim)"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.floatDecl("s") << " = (lim - thr) * (1.0 - thr) / (lim - 1.0);"; + ss.newLine() << ss.floatDecl("nd") << " = (v - thr) / s;"; + + + ss.newLine() << ss.floatDecl("vCompressed") << " = 0.0;"; + + if (invert) + { + ss.newLine() << "if (v < thr || lim <= 1.0001 || v > thr + s)"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "vCompressed = v;"; + ss.dedent(); + ss.newLine() << "}"; + ss.newLine() << "else"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "vCompressed = thr + s * (-nd / (nd - 1));"; + ss.dedent(); + ss.newLine() << "}"; + } + else + { + ss.newLine() << "if (v < thr || lim <= 1.0001)"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "vCompressed = v;"; + ss.dedent(); + ss.newLine() << "}"; + ss.newLine() << "else"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "vCompressed = thr + s * nd / (1.0 + nd);"; + ss.dedent(); + ss.newLine() << "}"; + } + + ss.newLine() << "return vCompressed;"; + + ss.dedent(); + ss.newLine() << "}"; + + shaderCreator->addToHelperShaderCode(ss.string().c_str()); + + return name; +} + +std::string _Add_Compress_Gamut_func( + GpuShaderCreatorRcPtr & shaderCreator, + unsigned resourceIndex, + const ACES2::GamutCompressParams & g, + const std::string & cuspName, + const std::string & getFocusGainName, + const std::string & gammaName, + const std::string & findGamutBoundaryIntersectionName, + const std::string & getReachBoundaryName, + const std::string & compressionName) +{ + // Reserve name + std::ostringstream resName; + resName << shaderCreator->getResourcePrefix() + << std::string("_") + << std::string("gamut_compress") + << resourceIndex; + + // Note: Remove potentially problematic double underscores from GLSL resource names. + std::string name(resName.str()); + StringUtils::ReplaceInPlace(name, "__", "_"); + + GpuShaderText ss(shaderCreator->getLanguage()); + + ss.newLine() << ss.float3Keyword() << " " << name << "(" << ss.float3Keyword() << " JMh, float Jx)"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.floatDecl("J") << " = JMh.r;"; + ss.newLine() << ss.floatDecl("M") << " = JMh.g;"; + ss.newLine() << ss.floatDecl("h") << " = JMh.b;"; + + ss.newLine() << "if (M < 0.0001 || J > " << g.limit_J_max << ")"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "return " << ss.float3Const("J", "0.0", "h") << ";"; + ss.dedent(); + ss.newLine() << "}"; + ss.newLine() << "else"; + ss.newLine() << "{"; + ss.indent(); + + ss.newLine() << ss.float2Decl("project_from") << " = " << ss.float2Const("J", "M") << ";"; + ss.newLine() << ss.float2Decl("JMcusp") << " = " << cuspName << "_sample(h);"; + + ss.newLine() << ss.floatDecl("focusJ") << " = " << ss.lerp("JMcusp.r", std::to_string(g.mid_J), std::string("min(1.0, ") + std::to_string(ACES2::cusp_mid_blend) + " - (JMcusp.r / " + std::to_string(g.limit_J_max)) << "));"; + ss.newLine() << ss.floatDecl("slope_gain") << " = " << g.limit_J_max << " * " << g.focus_dist << " * " << getFocusGainName << "(Jx, JMcusp.r);"; + + ss.newLine() << ss.floatDecl("gamma_top") << " = " << gammaName << "_sample(h);"; + ss.newLine() << ss.floatDecl("gamma_bottom") << " = " << g.lower_hull_gamma << ";"; + + ss.newLine() << ss.float3Decl("boundaryReturn") << " = " << findGamutBoundaryIntersectionName << "(" << ss.float3Const("J", "M", "h") << ", JMcusp, focusJ, slope_gain, gamma_top, gamma_bottom);"; + ss.newLine() << ss.float2Decl("JMboundary") << " = " << ss.float2Const("boundaryReturn.r", "boundaryReturn.g") << ";"; + ss.newLine() << ss.float2Decl("project_to") << " = " << ss.float2Const("boundaryReturn.b", "0.0") << ";"; + + ss.newLine() << "if (JMboundary.g <= 0.0)"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "return " << ss.float3Const("J", "0.0", "h") << ";"; + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << ss.float3Decl("reachBoundary") << " = " << getReachBoundaryName << "(JMboundary.r, JMboundary.g, h, JMcusp, focusJ);"; + + ss.newLine() << ss.floatDecl("difference") << " = max(1.0001, reachBoundary.g / JMboundary.g);"; + ss.newLine() << ss.floatDecl("threshold") << " = max(" << ACES2::compression_threshold << ", 1.0 / difference);"; + + ss.newLine() << ss.floatDecl("v") << " = project_from.g / JMboundary.g;"; + ss.newLine() << "v = " << compressionName << "(v, threshold, difference);"; + + ss.newLine() << ss.float2Decl("JMcompressed") << " = " << ss.float2Const( + "project_to.r + v * (JMboundary.r - project_to.r)", + "project_to.g + v * (JMboundary.g - project_to.g)" + ) << ";"; + + ss.newLine() << "return " << ss.float3Const("JMcompressed.r", "JMcompressed.g", "h") << ";"; + + ss.dedent(); + ss.newLine() << "}"; + + ss.dedent(); + ss.newLine() << "}"; + + shaderCreator->addToHelperShaderCode(ss.string().c_str()); + + return name; +} + +void _Add_Gamut_Compress_Fwd_Shader( + GpuShaderCreatorRcPtr & shaderCreator, + GpuShaderText & ss, + unsigned int resourceIndex, + const ACES2::GamutCompressParams & g, + const std::string & reachName) +{ + std::string cuspName = _Add_Cusp_table(shaderCreator, resourceIndex, g); + std::string gammaName = _Add_Gamma_table(shaderCreator, resourceIndex, g); + std::string getFocusGainName = _Add_Focus_Gain_func(shaderCreator, resourceIndex, g); + std::string solveJIntersectName = _Add_Solve_J_Intersect_func(shaderCreator, resourceIndex, g); + std::string findGamutBoundaryIntersectionName = _Add_Find_Gamut_Boundary_Intersection_func(shaderCreator, resourceIndex, g, solveJIntersectName); + std::string getReachBoundaryName = _Add_Reach_Boundary_func(shaderCreator, resourceIndex, g, reachName, getFocusGainName, solveJIntersectName); + std::string compressionName = _Add_Compression_func(shaderCreator, resourceIndex, false); + std::string gamutCompressName = _Add_Compress_Gamut_func(shaderCreator, resourceIndex, g, cuspName, getFocusGainName, gammaName, findGamutBoundaryIntersectionName, getReachBoundaryName, compressionName); + + const std::string pxl(shaderCreator->getPixelName()); + + ss.newLine() << pxl << ".rgb = " << gamutCompressName << "(" << pxl << ".rgb, " << pxl << ".r);"; +} + +void _Add_Gamut_Compress_Inv_Shader( + GpuShaderCreatorRcPtr & shaderCreator, + GpuShaderText & ss, + unsigned int resourceIndex, + const ACES2::GamutCompressParams & g, + const std::string & reachName) +{ + std::string cuspName = _Add_Cusp_table(shaderCreator, resourceIndex, g); + std::string gammaName = _Add_Gamma_table(shaderCreator, resourceIndex, g); + std::string getFocusGainName = _Add_Focus_Gain_func(shaderCreator, resourceIndex, g); + std::string solveJIntersectName = _Add_Solve_J_Intersect_func(shaderCreator, resourceIndex, g); + std::string findGamutBoundaryIntersectionName = _Add_Find_Gamut_Boundary_Intersection_func(shaderCreator, resourceIndex, g, solveJIntersectName); + std::string getReachBoundaryName = _Add_Reach_Boundary_func(shaderCreator, resourceIndex, g, reachName, getFocusGainName, solveJIntersectName); + std::string compressionName = _Add_Compression_func(shaderCreator, resourceIndex, true); + std::string gamutCompressName = _Add_Compress_Gamut_func(shaderCreator, resourceIndex, g, cuspName, getFocusGainName, gammaName, findGamutBoundaryIntersectionName, getReachBoundaryName, compressionName); + + const std::string pxl(shaderCreator->getPixelName()); + + ss.newLine() << ss.float2Decl("JMcusp") << " = " << cuspName << "_sample(" << pxl << ".b);"; + ss.newLine() << ss.floatDecl("Jx") << " = " << pxl << ".r;"; + ss.newLine() << ss.float3Decl("unCompressedJMh") << ";"; + + // Analytic inverse below threshold + ss.newLine() << "if (Jx <= " << ss.lerp("JMcusp.r", std::to_string(g.limit_J_max), std::to_string(ACES2::focus_gain_blend)) << ")"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "unCompressedJMh = " << gamutCompressName << "(" << pxl << ".rgb, Jx);"; + ss.dedent(); + ss.newLine() << "}"; + // Approximation above threshold + ss.newLine() << "else"; + ss.newLine() << "{"; + ss.indent(); + ss.newLine() << "Jx = " << gamutCompressName << "(" << pxl << ".rgb, Jx).r;"; + ss.newLine() << "unCompressedJMh = " << gamutCompressName << "(" << pxl << ".rgb, Jx);"; + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << pxl << ".rgb = unCompressedJMh;"; +} + +void Add_ACES_OutputTransform_Fwd_Shader( + GpuShaderCreatorRcPtr & shaderCreator, + GpuShaderText & ss, + const FixedFunctionOpData::Params & params) +{ + const float peak_luminance = (float) params[0]; + + const float red_x = (float) params[1]; + const float red_y = (float) params[2]; + const float green_x = (float) params[3]; + const float green_y = (float) params[4]; + const float blue_x = (float) params[5]; + const float blue_y = (float) params[6]; + const float white_x = (float) params[7]; + const float white_y = (float) params[8]; + + const Primaries in_primaries = ACES_AP0::primaries; + + const Primaries lim_primaries = { + {red_x , red_y }, + {green_x, green_y}, + {blue_x , blue_y }, + {white_x, white_y} + }; + + ACES2::JMhParams pIn = ACES2::init_JMhParams(in_primaries); + ACES2::JMhParams pLim = ACES2::init_JMhParams(lim_primaries); + ACES2::ToneScaleParams t = ACES2::init_ToneScaleParams(peak_luminance); + ACES2::ChromaCompressParams c = ACES2::init_ChromaCompressParams(peak_luminance); + const ACES2::GamutCompressParams g = ACES2::init_GamutCompressParams(peak_luminance, lim_primaries); + + unsigned resourceIndex = shaderCreator->getNextResourceIndex(); + + std::string reachName = _Add_Reach_table(shaderCreator, resourceIndex, g.reach_m_table); + + ss.newLine() << ""; + ss.newLine() << "// Add RGB to JMh"; + ss.newLine() << ""; + ss.newLine() << "{"; + ss.indent(); + _Add_RGB_to_JMh_Shader(shaderCreator, ss, pIn); + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << ""; + ss.newLine() << "// Add ToneScale and ChromaCompress (fwd)"; + ss.newLine() << ""; + ss.newLine() << "{"; + ss.indent(); + _Add_Tonescale_Compress_Fwd_Shader(shaderCreator, ss, resourceIndex, pIn, t, c, reachName); + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << ""; + ss.newLine() << "// Add GamutCompress (fwd)"; + ss.newLine() << ""; + ss.newLine() << "{"; + ss.indent(); + _Add_Gamut_Compress_Fwd_Shader(shaderCreator, ss, resourceIndex, g, reachName); + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << ""; + ss.newLine() << "// Add JMh to RGB"; + ss.newLine() << ""; + ss.newLine() << "{"; + ss.indent(); + _Add_JMh_to_RGB_Shader(shaderCreator, ss, pLim); + ss.dedent(); + ss.newLine() << "}"; + +} + +void Add_ACES_OutputTransform_Inv_Shader( + GpuShaderCreatorRcPtr & shaderCreator, + GpuShaderText & ss, + const FixedFunctionOpData::Params & params) +{ + const float peak_luminance = (float) params[0]; + + const float red_x = (float) params[1]; + const float red_y = (float) params[2]; + const float green_x = (float) params[3]; + const float green_y = (float) params[4]; + const float blue_x = (float) params[5]; + const float blue_y = (float) params[6]; + const float white_x = (float) params[7]; + const float white_y = (float) params[8]; + + const Primaries in_primaries = ACES_AP0::primaries; + + const Primaries lim_primaries = { + {red_x , red_y }, + {green_x, green_y}, + {blue_x , blue_y }, + {white_x, white_y} + }; + + ACES2::JMhParams pIn = ACES2::init_JMhParams(in_primaries); + ACES2::JMhParams pLim = ACES2::init_JMhParams(lim_primaries); + ACES2::ToneScaleParams t = ACES2::init_ToneScaleParams(peak_luminance); + ACES2::ChromaCompressParams c = ACES2::init_ChromaCompressParams(peak_luminance); + const ACES2::GamutCompressParams g = ACES2::init_GamutCompressParams(peak_luminance, lim_primaries); + + unsigned resourceIndex = shaderCreator->getNextResourceIndex(); + + std::string reachName = _Add_Reach_table(shaderCreator, resourceIndex, c.reach_m_table); + + ss.newLine() << ""; + ss.newLine() << "// Add RGB to JMh"; + ss.newLine() << ""; + ss.newLine() << "{"; + ss.indent(); + _Add_RGB_to_JMh_Shader(shaderCreator, ss, pLim); + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << ""; + ss.newLine() << "// Add GamutCompress (inv)"; + ss.newLine() << ""; + ss.newLine() << "{"; + ss.indent(); + _Add_Gamut_Compress_Inv_Shader(shaderCreator, ss, resourceIndex, g, reachName); + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << ""; + ss.newLine() << "// Add ToneScale and ChromaCompress (inv)"; + ss.newLine() << ""; + ss.newLine() << "{"; + ss.indent(); + _Add_Tonescale_Compress_Inv_Shader(shaderCreator, ss, resourceIndex, pIn, t, c, reachName); + ss.dedent(); + ss.newLine() << "}"; + + ss.newLine() << ""; + ss.newLine() << "// Add JMh to RGB"; + ss.newLine() << ""; + ss.newLine() << "{"; + ss.indent(); + _Add_JMh_to_RGB_Shader(shaderCreator, ss, pIn); + ss.dedent(); + ss.newLine() << "}"; +} + +void Add_RGB_to_JMh_Shader( + GpuShaderCreatorRcPtr & shaderCreator, + GpuShaderText & ss, + const FixedFunctionOpData::Params & params) +{ + const float red_x = (float) params[0]; + const float red_y = (float) params[1]; + const float green_x = (float) params[2]; + const float green_y = (float) params[3]; + const float blue_x = (float) params[4]; + const float blue_y = (float) params[5]; + const float white_x = (float) params[6]; + const float white_y = (float) params[7]; + + const Primaries primaries = { + {red_x , red_y }, + {green_x, green_y}, + {blue_x , blue_y }, + {white_x, white_y} + }; + ACES2::JMhParams p = ACES2::init_JMhParams(primaries); + + _Add_RGB_to_JMh_Shader(shaderCreator, ss, p); +} + +void Add_JMh_to_RGB_Shader( + GpuShaderCreatorRcPtr & shaderCreator, + GpuShaderText & ss, + const FixedFunctionOpData::Params & params) +{ + const float red_x = (float) params[0]; + const float red_y = (float) params[1]; + const float green_x = (float) params[2]; + const float green_y = (float) params[3]; + const float blue_x = (float) params[4]; + const float blue_y = (float) params[5]; + const float white_x = (float) params[6]; + const float white_y = (float) params[7]; + + const Primaries primaries = { + {red_x , red_y }, + {green_x, green_y}, + {blue_x , blue_y }, + {white_x, white_y} + }; + + ACES2::JMhParams p = ACES2::init_JMhParams(primaries); + + _Add_JMh_to_RGB_Shader(shaderCreator, ss, p); +} + +void Add_Tonescale_Compress_Fwd_Shader( + GpuShaderCreatorRcPtr & shaderCreator, + GpuShaderText & ss, + const FixedFunctionOpData::Params & params) +{ + const float peak_luminance = (float) params[0]; + + ACES2::JMhParams p = ACES2::init_JMhParams(ACES_AP0::primaries); + ACES2::ToneScaleParams t = ACES2::init_ToneScaleParams(peak_luminance); + ACES2::ChromaCompressParams c = ACES2::init_ChromaCompressParams(peak_luminance); + + unsigned resourceIndex = shaderCreator->getNextResourceIndex(); + + std::string reachName = _Add_Reach_table(shaderCreator, resourceIndex, c.reach_m_table); + + _Add_Tonescale_Compress_Fwd_Shader(shaderCreator, ss, resourceIndex, p, t, c, reachName); +} + +void Add_Tonescale_Compress_Inv_Shader( + GpuShaderCreatorRcPtr & shaderCreator, + GpuShaderText & ss, + const FixedFunctionOpData::Params & params) +{ + const float peak_luminance = (float) params[0]; + + ACES2::JMhParams p = ACES2::init_JMhParams(ACES_AP0::primaries); + ACES2::ToneScaleParams t = ACES2::init_ToneScaleParams(peak_luminance); + ACES2::ChromaCompressParams c = ACES2::init_ChromaCompressParams(peak_luminance); + + unsigned resourceIndex = shaderCreator->getNextResourceIndex(); + + std::string reachName = _Add_Reach_table(shaderCreator, resourceIndex, c.reach_m_table); + + _Add_Tonescale_Compress_Inv_Shader(shaderCreator, ss, resourceIndex, p, t, c, reachName); +} + +void Add_Gamut_Compress_Fwd_Shader( + GpuShaderCreatorRcPtr & shaderCreator, + GpuShaderText & ss, + const FixedFunctionOpData::Params & params) +{ + const float peak_luminance = (float) params[0]; + + const float red_x = (float) params[1]; + const float red_y = (float) params[2]; + const float green_x = (float) params[3]; + const float green_y = (float) params[4]; + const float blue_x = (float) params[5]; + const float blue_y = (float) params[6]; + const float white_x = (float) params[7]; + const float white_y = (float) params[8]; + + const Primaries primaries = { + {red_x , red_y }, + {green_x, green_y}, + {blue_x , blue_y }, + {white_x, white_y} + }; + + const ACES2::GamutCompressParams g = ACES2::init_GamutCompressParams(peak_luminance, primaries); + + unsigned resourceIndex = shaderCreator->getNextResourceIndex(); + + std::string reachName = _Add_Reach_table(shaderCreator, resourceIndex, g.reach_m_table); + + _Add_Gamut_Compress_Fwd_Shader(shaderCreator, ss, resourceIndex, g, reachName); +} + +void Add_Gamut_Compress_Inv_Shader( + GpuShaderCreatorRcPtr & shaderCreator, + GpuShaderText & ss, + const FixedFunctionOpData::Params & params) +{ + const float peak_luminance = (float) params[0]; + + const float red_x = (float) params[1]; + const float red_y = (float) params[2]; + const float green_x = (float) params[3]; + const float green_y = (float) params[4]; + const float blue_x = (float) params[5]; + const float blue_y = (float) params[6]; + const float white_x = (float) params[7]; + const float white_y = (float) params[8]; + + const Primaries primaries = { + {red_x , red_y }, + {green_x, green_y}, + {blue_x , blue_y }, + {white_x, white_y} + }; + + const ACES2::GamutCompressParams g = ACES2::init_GamutCompressParams(peak_luminance, primaries); + + unsigned resourceIndex = shaderCreator->getNextResourceIndex(); + + std::string reachName = _Add_Reach_table(shaderCreator, resourceIndex, g.reach_m_table); + + _Add_Gamut_Compress_Inv_Shader(shaderCreator, ss, resourceIndex, g, reachName); +} + void Add_Surround_10_Fwd_Shader(GpuShaderCreatorRcPtr & shaderCreator, GpuShaderText & ss, float gamma) { const std::string pxl(shaderCreator->getPixelName()); @@ -622,6 +1923,46 @@ void GetFixedFunctionGPUShaderProgram(GpuShaderCreatorRcPtr & shaderCreator, ); break; } + case FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_FWD: + { + Add_ACES_OutputTransform_Fwd_Shader(shaderCreator, ss, func->getParams()); + break; + } + case FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_INV: + { + Add_ACES_OutputTransform_Inv_Shader(shaderCreator, ss, func->getParams()); + break; + } + case FixedFunctionOpData::ACES_RGB_TO_JMh_20: + { + Add_RGB_to_JMh_Shader(shaderCreator, ss, func->getParams()); + break; + } + case FixedFunctionOpData::ACES_JMh_TO_RGB_20: + { + Add_JMh_to_RGB_Shader(shaderCreator, ss, func->getParams()); + break; + } + case FixedFunctionOpData::ACES_TONESCALE_COMPRESS_20_FWD: + { + Add_Tonescale_Compress_Fwd_Shader(shaderCreator, ss, func->getParams()); + break; + } + case FixedFunctionOpData::ACES_TONESCALE_COMPRESS_20_INV: + { + Add_Tonescale_Compress_Inv_Shader(shaderCreator, ss, func->getParams()); + break; + } + case FixedFunctionOpData::ACES_GAMUT_COMPRESS_20_FWD: + { + Add_Gamut_Compress_Fwd_Shader(shaderCreator, ss, func->getParams()); + break; + } + case FixedFunctionOpData::ACES_GAMUT_COMPRESS_20_INV: + { + Add_Gamut_Compress_Inv_Shader(shaderCreator, ss, func->getParams()); + break; + } case FixedFunctionOpData::REC2100_SURROUND_FWD: { Add_Surround_Shader(shaderCreator, ss, (float) func->getParams()[0]); diff --git a/src/OpenColorIO/transforms/builtins/ACES.cpp b/src/OpenColorIO/transforms/builtins/ACES.cpp index d1e3321727..8f08c5829d 100644 --- a/src/OpenColorIO/transforms/builtins/ACES.cpp +++ b/src/OpenColorIO/transforms/builtins/ACES.cpp @@ -497,6 +497,73 @@ void Generate_roll_white_d65_ops(OpRcPtrVec & ops) } // namespace ACES_OUTPUT +namespace ACES2_OUTPUT +{ + void Generate_output_transform( + OpRcPtrVec & ops, + float peak_luminance, + const Primaries & limiting_pri, + const Primaries & encoding_pri, + float linear_scale, + bool scale_white) + { + // Clamp to AP1 + MatrixOpData::MatrixArrayPtr matrixToAP1 + = build_conversion_matrix(ACES_AP0::primaries, ACES_AP1::primaries, ADAPTATION_NONE); + CreateMatrixOp(ops, matrixToAP1, TRANSFORM_DIR_FORWARD); + + const float upperBound = 8.f * (128.f + 768.f * (std::log(peak_luminance / 100.f) / log(10000.f / 100.f))); + CreateRangeOp(ops, + 0., upperBound, + 0., upperBound, + TRANSFORM_DIR_FORWARD); + + CreateMatrixOp(ops, matrixToAP1, TRANSFORM_DIR_INVERSE); + + // Display rendering + CreateFixedFunctionOp(ops, FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_FWD, { + peak_luminance, + limiting_pri.m_red.m_xy[0], limiting_pri.m_red.m_xy[1], + limiting_pri.m_grn.m_xy[0], limiting_pri.m_grn.m_xy[1], + limiting_pri.m_blu.m_xy[0], limiting_pri.m_blu.m_xy[1], + limiting_pri.m_wht.m_xy[0], limiting_pri.m_wht.m_xy[1], + }); + + // Post transform clamp + const double normPeakLuminance = peak_luminance / 100.f; + CreateRangeOp(ops, + 0., normPeakLuminance, + 0., normPeakLuminance, + TRANSFORM_DIR_FORWARD); + + // White point simulation + if (scale_white) + { + MatrixOpData::MatrixArrayPtr matrixLimToOut + = build_conversion_matrix(limiting_pri, encoding_pri, ADAPTATION_NONE); + + MatrixOpData::Offsets white(1.f, 1.f, 1.f, 0.f); + white = matrixLimToOut->inner(white); + + const double scale = 1. / std::max(std::max(white[0], white[1]), white[2]); + const double scale4[4] = { scale, scale, scale, 1. }; + CreateScaleOp(ops, scale4, TRANSFORM_DIR_FORWARD); + } + + // Linear scale factor + if (linear_scale != 1.f) + { + const double scale = linear_scale; + const double scale4[4] = { scale, scale, scale, 1. }; + CreateScaleOp(ops, scale4, TRANSFORM_DIR_FORWARD); + } + + MatrixOpData::MatrixArrayPtr matrixToXYZ + = build_conversion_matrix_to_XYZ_D65(limiting_pri, ADAPTATION_NONE); + CreateMatrixOp(ops, matrixToXYZ, TRANSFORM_DIR_FORWARD); + } + +} // namespace ACES2_OUTPUT // // Create the built-in transforms. @@ -1047,6 +1114,326 @@ void RegisterAll(BuiltinTransformRegistryImpl & registry) noexcept "Component of ACES Output Transforms for 108 nit HDR D65 cinema", ACES2065_1_to_CIE_XYZ_hdr_cinema_108nits_p3lim_1_1_Functor); } + + // + // ACES 2 OUTPUT TRANSFORMS + // + + struct ACES2OutputTransform + { + std::string name; + std::string desc; + float peak_luminance; + Primaries limiting_primaries; + Primaries encoding_primaries; + float linear_scale; + bool scale_white; + }; + + const std::vector aces2_output_transforms { + // + // D65 + // + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-REC709_2.0", + "Component of ACES 2 Output Transforms for 100 nit SDR Rec709", + 100.f, + REC709::primaries, + REC709::primaries, + 1.f, + false + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-P3-D65_2.0", + "Component of ACES 2 Output Transforms for 100 nit SDR P3-D65", + 100.f, + P3_D65::primaries, + P3_D65::primaries, + 1.f, + false + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-108nit-P3-D65_2.0", + "Component of ACES 2 Output Transforms for 108 nit HDR P3-D65", + 225.f, // = 108 * (100/48); + P3_D65::primaries, + P3_D65::primaries, + 0.48f, + false + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-300nit-P3-D65_2.0", + "Component of ACES 2 Output Transforms for 300 nit HDR P3-D65", + 625.f, // = 300 * (100/48); + P3_D65::primaries, + P3_D65::primaries, + 0.48f, + false + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-P3-D65_2.0", + "Component of ACES 2 Output Transforms for 500 nit HDR P3-D65", + 500.f, + P3_D65::primaries, + P3_D65::primaries, + 1.f, + false + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-P3-D65_2.0", + "Component of ACES 2 Output Transforms for 1000 nit HDR P3-D65", + 1000.f, + P3_D65::primaries, + P3_D65::primaries, + 1.f, + false + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-P3-D65_2.0", + "Component of ACES 2 Output Transforms for 2000 nit HDR P3-D65", + 2000.f, + P3_D65::primaries, + P3_D65::primaries, + 1.f, + false + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-P3-D65_2.0", + "Component of ACES 2 Output Transforms for 4000 nit HDR P3-D65", + 4000.f, + P3_D65::primaries, + P3_D65::primaries, + 1.f, + false + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-REC2020_2.0", + "Component of ACES 2 Output Transforms for 500 nit HDR Rec2020", + 500.f, + REC2020::primaries, + REC2020::primaries, + 1.f, + false + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-REC2020_2.0", + "Component of ACES 2 Output Transforms for 1000 nit HDR Rec2020", + 1000.f, + REC2020::primaries, + REC2020::primaries, + 1.f, + false + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-REC2020_2.0", + "Component of ACES 2 Output Transforms for 2000 nit HDR Rec2020", + 2000.f, + REC2020::primaries, + REC2020::primaries, + 1.f, + false + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-REC2020_2.0", + "Component of ACES 2 Output Transforms for 4000 nit HDR Rec2020", + 4000.f, + REC2020::primaries, + REC2020::primaries, + 1.f, + false + }, + // + // D60 + // + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-REC709-D60-in-REC709-D65_2.0", + "Component of ACES 2 Output Transforms for 100 nit SDR Rec709 simulating D60 white in Rec709", + 100.f, + REC709_D60::primaries, + REC709::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-REC709-D60-in-P3-D65_2.0", + "Component of ACES 2 Output Transforms for 100 nit SDR Rec709 simulating D60 white in P3-D65", + 100.f, + REC709_D60::primaries, + P3_D65::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-REC709-D60-in-REC2020-D65_2.0", + "Component of ACES 2 Output Transforms for 100 nit SDR Rec709 simulating D60 white in Rec2020", + 100.f, + REC709_D60::primaries, + REC2020::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-P3-D60-in-P3-D65_2.0", + "Component of ACES 2 Output Transforms for 100 nit SDR P3-D60 simulating D60 white in P3-D65", + 100.f, + P3_D60::primaries, + P3_D65::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-P3-D60-in-XYZ-E_2.0", + "Component of ACES 2 Output Transforms for 100 nit SDR P3-D60 simulating D60 white in XYZ-E", + 100.f, + P3_D60::primaries, + CIE_XYZ_ILLUM_E::primaries, + 1.f, + false + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-108nit-P3-D60-in-P3-D65_2.0", + "Component of ACES 2 Output Transforms for 108 nit HDR P3-D60 simulating D60 white in P3-D65", + 225.f, // = 108 * (100/48); + P3_D60::primaries, + P3_D65::primaries, + 0.48f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-300nit-P3-D60-in-XYZ-E_2.0", + "Component of ACES 2 Output Transforms for 300 nit HDR P3-D60 simulating D60 white in XYZ-E", + 625.f, // = 300 * (100/48); + P3_D60::primaries, + CIE_XYZ_ILLUM_E::primaries, + 0.48f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-P3-D60-in-P3-D65_2.0", + "Component of ACES 2 Output Transforms for 500 nit HDR P3-D60 simulating D60 white in P3-D65", + 500.f, + P3_D60::primaries, + P3_D65::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-P3-D60-in-P3-D65_2.0", + "Component of ACES 2 Output Transforms for 1000 nit HDR P3-D60 simulating D60 white in P3-D65", + 1000.f, + P3_D60::primaries, + P3_D65::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-P3-D60-in-P3-D65_2.0", + "Component of ACES 2 Output Transforms for 2000 nit HDR P3-D60 simulating D60 white in P3-D65", + 2000.f, + P3_D60::primaries, + P3_D65::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-P3-D60-in-P3-D65_2.0", + "Component of ACES 2 Output Transforms for 4000 nit HDR P3-D60 simulating D60 white in P3-D65", + 4000.f, + P3_D60::primaries, + P3_D65::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-P3-D60-in-REC2020-D65_2.0", + "Component of ACES 2 Output Transforms for 500 nit HDR P3-D60 simulating D60 white in Rec2020", + 500.f, + P3_D60::primaries, + REC2020::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-P3-D60-in-REC2020-D65_2.0", + "Component of ACES 2 Output Transforms for 1000 nit HDR P3-D60 simulating D60 white in Rec2020", + 1000.f, + P3_D60::primaries, + REC2020::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-P3-D60-in-REC2020-D65_2.0", + "Component of ACES 2 Output Transforms for 2000 nit HDR P3-D60 simulating D60 white in Rec2020", + 2000.f, + P3_D60::primaries, + REC2020::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-P3-D60-in-REC2020-D65_2.0", + "Component of ACES 2 Output Transforms for 4000 nit HDR P3-D60 simulating D60 white in Rec2020", + 4000.f, + P3_D60::primaries, + REC2020::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-REC2020-D60-in-REC2020-D65_2.0", + "Component of ACES 2 Output Transforms for 500 nit HDR Rec2020 simulating D60 white in Rec2020", + 500.f, + REC2020_D60::primaries, + REC2020::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-REC2020-D60-in-REC2020-D65_2.0", + "Component of ACES 2 Output Transforms for 1000 nit HDR Rec2020 simulating D60 white in Rec2020", + 1000.f, + REC2020_D60::primaries, + REC2020::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-REC2020-D60-in-REC2020-D65_2.0", + "Component of ACES 2 Output Transforms for 2000 nit HDR Rec2020 simulating D60 white in Rec2020", + 2000.f, + REC2020_D60::primaries, + REC2020::primaries, + 1.f, + true + }, + { + "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-REC2020-D60-in-REC2020-D65_2.0", + "Component of ACES 2 Output Transforms for 4000 nit HDR Rec2020 simulating D60 white in Rec2020", + 4000.f, + REC2020_D60::primaries, + REC2020::primaries, + 1.f, + true + } + }; + + for (const auto& tr : aces2_output_transforms) + { + auto functor = [tr](OpRcPtrVec & ops) + { + ACES2_OUTPUT::Generate_output_transform( + ops, + tr.peak_luminance, + tr.limiting_primaries, + tr.encoding_primaries, + tr.linear_scale, + tr.scale_white + ); + }; + + registry.addBuiltin(tr.name.c_str(), tr.desc.c_str(), functor); + } } } // namespace ACES diff --git a/src/OpenColorIO/transforms/builtins/ColorMatrixHelpers.cpp b/src/OpenColorIO/transforms/builtins/ColorMatrixHelpers.cpp index a97f3d6fdf..3ab1dd13f2 100644 --- a/src/OpenColorIO/transforms/builtins/ColorMatrixHelpers.cpp +++ b/src/OpenColorIO/transforms/builtins/ColorMatrixHelpers.cpp @@ -51,6 +51,16 @@ static const Chromaticities wht_xy(0.3127, 0.3290); const Primaries primaries(red_xy, grn_xy, blu_xy, wht_xy); } +namespace REC709_D60 +{ +static const Chromaticities red_xy(0.64, 0.33 ); +static const Chromaticities grn_xy(0.30, 0.60 ); +static const Chromaticities blu_xy(0.15, 0.06 ); +static const Chromaticities wht_xy(0.32168, 0.33767); + +const Primaries primaries(red_xy, grn_xy, blu_xy, wht_xy); +} + namespace REC2020 { static const Chromaticities red_xy(0.708, 0.292 ); @@ -61,6 +71,16 @@ static const Chromaticities wht_xy(0.3127, 0.3290); const Primaries primaries(red_xy, grn_xy, blu_xy, wht_xy); } +namespace REC2020_D60 +{ +static const Chromaticities red_xy(0.708, 0.292 ); +static const Chromaticities grn_xy(0.170, 0.797 ); +static const Chromaticities blu_xy(0.131, 0.046 ); +static const Chromaticities wht_xy(0.32168, 0.33767); + +const Primaries primaries(red_xy, grn_xy, blu_xy, wht_xy); +} + namespace P3_DCI { static const Chromaticities red_xy(0.680, 0.320); diff --git a/src/OpenColorIO/transforms/builtins/ColorMatrixHelpers.h b/src/OpenColorIO/transforms/builtins/ColorMatrixHelpers.h index 30898312fc..323d5712ba 100644 --- a/src/OpenColorIO/transforms/builtins/ColorMatrixHelpers.h +++ b/src/OpenColorIO/transforms/builtins/ColorMatrixHelpers.h @@ -102,11 +102,21 @@ namespace REC709 extern const Primaries primaries; } +namespace REC709_D60 +{ +extern const Primaries primaries; +} + namespace REC2020 { extern const Primaries primaries; } +namespace REC2020_D60 +{ +extern const Primaries primaries; +} + namespace P3_DCI { extern const Primaries primaries; diff --git a/src/bindings/python/PyTypes.cpp b/src/bindings/python/PyTypes.cpp index 760d715587..2adff33c6c 100644 --- a/src/bindings/python/PyTypes.cpp +++ b/src/bindings/python/PyTypes.cpp @@ -583,6 +583,14 @@ void bindPyTypes(py::module & m) DOC(PyOpenColorIO, FixedFunctionStyle, FIXED_FUNCTION_ACES_GAMUTMAP_07)) .value("FIXED_FUNCTION_ACES_GAMUT_COMP_13", FIXED_FUNCTION_ACES_GAMUT_COMP_13, DOC(PyOpenColorIO, FixedFunctionStyle, FIXED_FUNCTION_ACES_GAMUT_COMP_13)) + .value("FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20", FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20, + DOC(PyOpenColorIO, FixedFunctionStyle, FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20)) + .value("FIXED_FUNCTION_ACES_RGB_TO_JMH_20", FIXED_FUNCTION_ACES_RGB_TO_JMH_20, + DOC(PyOpenColorIO, FixedFunctionStyle, FIXED_FUNCTION_ACES_RGB_TO_JMH_20)) + .value("FIXED_FUNCTION_ACES_TONESCALE_COMPRESS_20", FIXED_FUNCTION_ACES_TONESCALE_COMPRESS_20, + DOC(PyOpenColorIO, FixedFunctionStyle, FIXED_FUNCTION_ACES_TONESCALE_COMPRESS_20)) + .value("FIXED_FUNCTION_ACES_GAMUT_COMPRESS_20", FIXED_FUNCTION_ACES_GAMUT_COMPRESS_20, + DOC(PyOpenColorIO, FixedFunctionStyle, FIXED_FUNCTION_ACES_GAMUT_COMPRESS_20)) .export_values(); py::enum_( diff --git a/tests/cpu/CMakeLists.txt b/tests/cpu/CMakeLists.txt index 91246fcc9e..66c32cb8a7 100755 --- a/tests/cpu/CMakeLists.txt +++ b/tests/cpu/CMakeLists.txt @@ -148,6 +148,7 @@ set(SOURCES ops/cdl/CDLOpCPU.cpp ops/cdl/CDLOpGPU.cpp ops/exposurecontrast/ExposureContrastOpGPU.cpp + ops/fixedfunction/ACES2/Transform.cpp ops/fixedfunction/FixedFunctionOpGPU.cpp ops/gamma/GammaOpGPU.cpp ops/gradingprimary/GradingPrimaryOpGPU.cpp diff --git a/tests/cpu/Config_tests.cpp b/tests/cpu/Config_tests.cpp index f0835eca82..89730fc908 100644 --- a/tests/cpu/Config_tests.cpp +++ b/tests/cpu/Config_tests.cpp @@ -2189,6 +2189,12 @@ const std::string PROFILE_V21 = "environment:\n" " {}\n"; +const std::string PROFILE_V24 = + "ocio_profile_version: 2.4\n" + "\n" + "environment:\n" + " {}\n"; + const std::string SIMPLE_PROFILE_A = "search_path: luts\n" "strictparsing: true\n" @@ -2199,6 +2205,19 @@ const std::string SIMPLE_PROFILE_A = " scene_linear: lnh\n" "\n"; +const std::string SIMPLE_PROFILE_B = + "search_path: luts\n" + "strictparsing: true\n" + "luma: [0.2126, 0.7152, 0.0722]\n" + "\n" + "roles:\n" + " aces_interchange: lnh\n" + " color_timing: log\n" + " compositing_log: log\n" + " default: raw\n" + " scene_linear: lnh\n" + "\n"; + const std::string SIMPLE_PROFILE_DISPLAYS_LOOKS = "displays:\n" " sRGB:\n" @@ -2284,6 +2303,9 @@ const std::string PROFILE_V2_START = PROFILE_V2 + SIMPLE_PROFILE_A + const std::string PROFILE_V21_START = PROFILE_V21 + SIMPLE_PROFILE_A + DEFAULT_RULES + SIMPLE_PROFILE_B_V2; + +const std::string PROFILE_V24_START = PROFILE_V24 + SIMPLE_PROFILE_B + + DEFAULT_RULES + SIMPLE_PROFILE_B_V2; } OCIO_ADD_TEST(Config, serialize_colorspace_displayview_transforms) @@ -4922,6 +4944,189 @@ OCIO_ADD_TEST(Config, fixed_function_serialization) OCIO_CHECK_THROW_WHAT(config = OCIO::Config::CreateFromStream(is), OCIO::Exception, "'FixedFunctionTransform' parsing failed: style value is missing."); } + + { + const std::string strEnd = + " from_scene_reference: !\n" + " children:\n" + " - ! {style: ACES2_OutputTransform, params: [100, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329]}\n" + " - ! {style: ACES2_OutputTransform, params: [100, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329], direction: inverse}\n" + " - ! {style: ACES2_RGB_TO_JMh, params: [0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329]}\n" + " - ! {style: ACES2_RGB_TO_JMh, params: [0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329], direction: inverse}\n" + " - ! {style: ACES2_TonescaleCompress, params: [100]}\n" + " - ! {style: ACES2_TonescaleCompress, params: [100], direction: inverse}\n" + " - ! {style: ACES2_GamutCompress, params: [100, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329]}\n" + " - ! {style: ACES2_GamutCompress, params: [100, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329], direction: inverse}\n"; + + const std::string str = PROFILE_V24_START + strEnd; + + std::istringstream is; + is.str(str); + + OCIO::ConstConfigRcPtr config; + + { + OCIO::LogGuard log; // Mute the experimental warnings. + + OCIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is)); + OCIO_CHECK_NO_THROW(config->validate()); + + std::string expectedLog = +R"([OpenColorIO Warning]: FixedFunction style is experimental and may be removed in a future release: 'ACES2_OutputTransform'. +[OpenColorIO Warning]: FixedFunction style is experimental and may be removed in a future release: 'ACES2_OutputTransform'. +[OpenColorIO Warning]: FixedFunction style is experimental and may be removed in a future release: 'ACES2_RGB_TO_JMh'. +[OpenColorIO Warning]: FixedFunction style is experimental and may be removed in a future release: 'ACES2_RGB_TO_JMh'. +[OpenColorIO Warning]: FixedFunction style is experimental and may be removed in a future release: 'ACES2_TonescaleCompress'. +[OpenColorIO Warning]: FixedFunction style is experimental and may be removed in a future release: 'ACES2_TonescaleCompress'. +[OpenColorIO Warning]: FixedFunction style is experimental and may be removed in a future release: 'ACES2_GamutCompress'. +[OpenColorIO Warning]: FixedFunction style is experimental and may be removed in a future release: 'ACES2_GamutCompress'. +)"; + OCIO_CHECK_EQUAL(log.output(), expectedLog); + } + + { + OCIO::LogGuard log; // Mute the experimental warnings. + + // Write the config. + + std::stringstream ss; + OCIO_CHECK_NO_THROW(ss << *config.get()); + OCIO_CHECK_EQUAL(ss.str(), str); + } + } + + { + const std::string strEnd = + " from_scene_reference: !\n" + " children:\n" + " - ! {style: ACES2_OutputTransform, params: [100, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329]}\n" + " - ! {style: ACES2_OutputTransform, params: [100, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329], direction: inverse}\n"; + + const std::string str = PROFILE_V21_START + strEnd; + + OCIO::LogGuard log; // Mute the experimental warnings. + + std::istringstream is; + is.str(str); + + OCIO::ConstConfigRcPtr config; + OCIO_CHECK_THROW_WHAT(config = OCIO::Config::CreateFromStream(is), OCIO::Exception, + "Only config version 2.4 (or higher) can have FixedFunctionTransform style 'ACES_OUTPUT_TRANSFORM_20'."); + } + + { + const std::string strEnd = + " from_scene_reference: !\n" + " children:\n" + " - ! {style: ACES2_RGB_TO_JMh, params: [100, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329]}\n" + " - ! {style: ACES2_RGB_TO_JMh, params: [100, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329], direction: inverse}\n"; + + const std::string str = PROFILE_V21_START + strEnd; + + OCIO::LogGuard log; // Mute the experimental warnings. + + std::istringstream is; + is.str(str); + + OCIO::ConstConfigRcPtr config; + OCIO_CHECK_THROW_WHAT(config = OCIO::Config::CreateFromStream(is), OCIO::Exception, + "Only config version 2.4 (or higher) can have FixedFunctionTransform style 'ACES_RGB_TO_JMH_20'."); + } + + { + const std::string strEnd = + " from_scene_reference: !\n" + " children:\n" + " - ! {style: ACES2_TonescaleCompress, params: [100, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329]}\n" + " - ! {style: ACES2_TonescaleCompress, params: [100, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329], direction: inverse}\n"; + + const std::string str = PROFILE_V21_START + strEnd; + + OCIO::LogGuard log; // Mute the experimental warnings. + + std::istringstream is; + is.str(str); + + OCIO::ConstConfigRcPtr config; + OCIO_CHECK_THROW_WHAT(config = OCIO::Config::CreateFromStream(is), OCIO::Exception, + "Only config version 2.4 (or higher) can have FixedFunctionTransform style 'ACES_TONESCALE_COMPRESS_20'."); + } + + { + const std::string strEnd = + " from_scene_reference: !\n" + " children:\n" + " - ! {style: ACES2_GamutCompress, params: [100, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329]}\n" + " - ! {style: ACES2_GamutCompress, params: [100, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329], direction: inverse}\n"; + + const std::string str = PROFILE_V21_START + strEnd; + + OCIO::LogGuard log; // Mute the experimental warnings. + + std::istringstream is; + is.str(str); + + OCIO::ConstConfigRcPtr config; + OCIO_CHECK_THROW_WHAT(config = OCIO::Config::CreateFromStream(is), OCIO::Exception, + "Only config version 2.4 (or higher) can have FixedFunctionTransform style 'ACES_GAMUT_COMPRESS_20'."); + } + + { + const std::string strEnd = + " from_scene_reference: !\n" + " children:\n" + " - ! {style: ACES2_OutputTransform, params: []}\n"; + + const std::string str = PROFILE_V24_START + strEnd; + + OCIO::LogGuard log; // Mute the experimental warnings. + + std::istringstream is; + is.str(str); + + OCIO::ConstConfigRcPtr config; + OCIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is)); + OCIO_CHECK_THROW_WHAT(config->validate(), OCIO::Exception, + "The style 'ACES_OutputTransform20 (Forward)' must have 9 parameters but 0 found."); + } + + { + const std::string strEnd = + " from_scene_reference: !\n" + " children:\n" + " - ! {style: ACES2_OutputTransform, params: [-1, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329]}\n"; + + const std::string str = PROFILE_V24_START + strEnd; + + OCIO::LogGuard log; // Mute the experimental warnings. + + std::istringstream is; + is.str(str); + + OCIO::ConstConfigRcPtr config; + OCIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is)); + OCIO_CHECK_THROW_WHAT(config->validate(), OCIO::Exception, + "FixedFunctionTransform validation failed: Parameter -1 (peak_luminance) is outside valid range [1,10000]"); + } + + { + const std::string strEnd = + " from_scene_reference: !\n" + " children:\n" + " - ! {style: ACES2_OutputTransform, params: [100.5, 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329]}\n"; + + const std::string str = PROFILE_V24_START + strEnd; + + OCIO::LogGuard log; // Mute the experimental warnings. + + std::istringstream is; + is.str(str); + + OCIO::ConstConfigRcPtr config; + OCIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is)); + OCIO_CHECK_THROW_WHAT(config->validate(), OCIO::Exception, + "FixedFunctionTransform validation failed: Parameter 100.5 (peak_luminance) cannot include any fractional component"); + } } OCIO_ADD_TEST(Config, exposure_contrast_serialization) diff --git a/tests/cpu/ops/fixedfunction/FixedFunctionOpCPU_tests.cpp b/tests/cpu/ops/fixedfunction/FixedFunctionOpCPU_tests.cpp index 7e61872443..02d0d133f6 100644 --- a/tests/cpu/ops/fixedfunction/FixedFunctionOpCPU_tests.cpp +++ b/tests/cpu/ops/fixedfunction/FixedFunctionOpCPU_tests.cpp @@ -8,6 +8,7 @@ #include "testutils/UnitTest.h" #include "UnitTestUtils.h" +#include "ops/lut3d/Lut3DOp.h" namespace OCIO = OCIO_NAMESPACE; @@ -405,7 +406,524 @@ OCIO_ADD_TEST(FixedFunctionOpCPU, aces_gamut_map_13) 1e-6f, __LINE__); } -} +} + +OCIO_ADD_TEST(FixedFunctionOpCPU, aces_output_transform_20) +{ + const unsigned num_samples = 35; + + float input_32f[num_samples*4] = { + // ACEScg primaries and secondaries scaled by 4 + 2.781808965f, 0.179178253f, -0.022103530f, 1.0f, + 3.344523751f, 3.617862727f, -0.006002689f, 1.0f, + 0.562714786f, 3.438684474f, 0.016100841f, 1.0f, + 1.218191035f, 3.820821747f, 4.022103530f, 1.0f, + 0.655476249f, 0.382137273f, 4.006002689f, 1.0f, + 3.437285214f, 0.561315526f, 3.983899159f, 1.0f, + // OCIO test values + 0.110000000f, 0.020000000f, 0.040000000f, 0.5f, + 0.710000000f, 0.510000000f, 0.810000000f, 1.0f, + 0.430000000f, 0.820000000f, 0.710000000f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 0.118770000f, 0.087090000f, 0.058950000f, 1.0f, + 0.400020000f, 0.319160000f, 0.237360000f, 1.0f, + 0.184760000f, 0.203980000f, 0.313110000f, 1.0f, + 0.109010000f, 0.135110000f, 0.064930000f, 1.0f, + 0.266840000f, 0.246040000f, 0.409320000f, 1.0f, + 0.322830000f, 0.462080000f, 0.406060000f, 1.0f, + 0.386050000f, 0.227430000f, 0.057770000f, 1.0f, + 0.138220000f, 0.130370000f, 0.337030000f, 1.0f, + 0.302020000f, 0.137520000f, 0.127580000f, 1.0f, + 0.093100000f, 0.063470000f, 0.135250000f, 1.0f, + 0.348760000f, 0.436540000f, 0.106130000f, 1.0f, + 0.486550000f, 0.366850000f, 0.080610000f, 1.0f, + 0.087320000f, 0.074430000f, 0.272740000f, 1.0f, + 0.153660000f, 0.256920000f, 0.090710000f, 1.0f, + 0.217420000f, 0.070700000f, 0.051300000f, 1.0f, + 0.589190000f, 0.539430000f, 0.091570000f, 1.0f, + 0.309040000f, 0.148180000f, 0.274260000f, 1.0f, + 0.149010000f, 0.233780000f, 0.359390000f, 1.0f, + 0.866530000f, 0.867920000f, 0.858180000f, 1.0f, + 0.573560000f, 0.572560000f, 0.571690000f, 1.0f, + 0.353460000f, 0.353370000f, 0.353910000f, 1.0f, + 0.202530000f, 0.202430000f, 0.202870000f, 1.0f, + 0.094670000f, 0.095200000f, 0.096370000f, 1.0f, + 0.037450000f, 0.037660000f, 0.038950000f, 1.0f, + // Spectrally non-selective 18 % reflecting diffuser + 0.180000000f, 0.180000000f, 0.180000000f, 1.0f, + // Perfect reflecting diffuser + 0.977840000f, 0.977840000f, 0.977840000f, 1.0f, + }; + + float input2_32f[num_samples * 4]; + memcpy(&input2_32f[0], &input_32f[0], sizeof(float)*num_samples * 4); + + const float expected_32f[num_samples*4] = { + // ACEScg primaries and secondaries scaled by 4 + 4.965774059f, -0.032864563f, 0.041625995f, 1.0f, + 3.969441891f, 3.825784922f, -0.056133576f, 1.0f, + -0.075329021f, 3.688980103f, 0.270296901f, 1.0f, + -0.095423937f, 3.650517225f, 3.459972620f, 1.0f, + -0.028930068f, 0.196428135f, 2.796343565f, 1.0f, + 4.900805950f, -0.064376131f, 3.838256121f, 1.0f, + // OCIO test values + 0.096890204f, -0.001135312f, 0.018971510f, 0.5f, + 0.809614301f, 0.479856580f, 0.814239502f, 1.0f, + 0.107420206f, 0.920529068f, 0.726378500f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 0.115475260f, 0.050812904f, 0.030212952f, 1.0f, + 0.484879673f, 0.301042974f, 0.226768956f, 1.0f, + 0.098463766f, 0.160814658f, 0.277010560f, 1.0f, + 0.071130365f, 0.107334383f, 0.035097566f, 1.0f, + 0.207111493f, 0.198474824f, 0.375326216f, 1.0f, + 0.195447892f, 0.481111974f, 0.393299013f, 1.0f, + 0.571913838f, 0.196872935f, 0.041634772f, 1.0f, + 0.045791931f, 0.069875360f, 0.291233480f, 1.0f, + 0.424848706f, 0.083199009f, 0.102153838f, 1.0f, + 0.059589427f, 0.022219172f, 0.091246888f, 1.0f, + 0.360365510f, 0.478741467f, 0.086726837f, 1.0f, + 0.695662081f, 0.371994525f, 0.068298168f, 1.0f, + 0.011806309f, 0.021665439f, 0.199594811f, 1.0f, + 0.076526314f, 0.256237417f, 0.060564656f, 1.0f, + 0.300064564f, 0.023416257f, 0.030360471f, 1.0f, + 0.805484772f, 0.596903503f, 0.082996152f, 1.0f, + 0.388385952f, 0.079899102f, 0.245819211f, 1.0f, + 0.010952532f, 0.196105912f, 0.307181358f, 1.0f, + 0.921019495f, 0.921707213f, 0.912856042f, 1.0f, + 0.590192318f, 0.588423848f, 0.587825358f, 1.0f, + 0.337743521f, 0.337685764f, 0.338155121f, 1.0f, + 0.169265985f, 0.169178501f, 0.169557109f, 1.0f, + 0.058346048f, 0.059387825f, 0.060296260f, 1.0f, + 0.012581184f, 0.012947139f, 0.013654195f, 1.0f, + // Spectrally non-selective 18 % reflecting diffuser + 0.145115465f, 0.145115525f, 0.145115510f, 1.0f, + // Perfect reflecting diffuser + 1.041565657f, 1.041566014f, 1.041565657f, 1.0f, + }; + + OCIO::FixedFunctionOpData::Params params = { + // Peak luminance + 1000.f, + // P3D65 gamut + 0.680, 0.320, 0.265, 0.690, 0.150, 0.060, 0.3127, 0.3290 + }; + OCIO::ConstFixedFunctionOpDataRcPtr funcData + = std::make_shared(OCIO::FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_FWD, + params); + + ApplyFixedFunction(&input2_32f[0], &expected_32f[0], num_samples, + funcData, + 1e-5f, + __LINE__); + + OCIO::ConstFixedFunctionOpDataRcPtr funcData2 + = std::make_shared(OCIO::FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_INV, + params); + + ApplyFixedFunction(&input2_32f[0], &input_32f[0], num_samples, + funcData2, + 1e-4f, + __LINE__); +} + +OCIO_ADD_TEST(FixedFunctionOpCPU, aces_ot_20_rec709_100n_rt) +{ + const int lut_size = 8; + const int num_channels = 4; + int num_samples = lut_size * lut_size * lut_size; + std::vector input_32f(num_samples * num_channels, 0.f); + std::vector output_32f(num_samples * num_channels, 0.f); + + GenerateIdentityLut3D(input_32f.data(), lut_size, num_channels, OCIO::LUT3DORDER_FAST_RED); + + OCIO::FixedFunctionOpData::Params params = { + // Peak luminance + 100.f, + // Rec709 gamut + 0.6400, 0.3300, 0.3000, 0.6000, 0.1500, 0.0600, 0.3127, 0.3290 + }; + + OCIO::ConstFixedFunctionOpDataRcPtr funcData + = std::make_shared(OCIO::FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_INV, + params); + + OCIO::ConstOpCPURcPtr op; + OCIO_CHECK_NO_THROW(op = OCIO::GetFixedFunctionCPURenderer(funcData)); + OCIO_CHECK_NO_THROW(op->apply(&input_32f[0], &output_32f[0], num_samples)); + + OCIO::ConstFixedFunctionOpDataRcPtr funcData2 + = std::make_shared(OCIO::FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_FWD, + params); + + ApplyFixedFunction(&output_32f[0], &input_32f[0], num_samples, + funcData2, + 1e-3f, + __LINE__); +} + +OCIO_ADD_TEST(FixedFunctionOpCPU, aces_ot_20_p3d65_100n_rt) +{ + const int lut_size = 8; + const int num_channels = 4; + int num_samples = lut_size * lut_size * lut_size; + std::vector input_32f(num_samples * num_channels, 0.f); + std::vector output_32f(num_samples * num_channels, 0.f); + + GenerateIdentityLut3D(input_32f.data(), lut_size, num_channels, OCIO::LUT3DORDER_FAST_RED); + + OCIO::FixedFunctionOpData::Params params = { + // Peak luminance + 100.f, + // P3D65 gamut + 0.680, 0.320, 0.265, 0.690, 0.150, 0.060, 0.3127, 0.3290 + }; + + OCIO::ConstFixedFunctionOpDataRcPtr funcData + = std::make_shared(OCIO::FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_INV, + params); + + OCIO::ConstOpCPURcPtr op; + OCIO_CHECK_NO_THROW(op = OCIO::GetFixedFunctionCPURenderer(funcData)); + OCIO_CHECK_NO_THROW(op->apply(&input_32f[0], &output_32f[0], num_samples)); + + OCIO::ConstFixedFunctionOpDataRcPtr funcData2 + = std::make_shared(OCIO::FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_FWD, + params); + + ApplyFixedFunction(&output_32f[0], &input_32f[0], num_samples, + funcData2, + 1e-2f, + __LINE__); +} + +OCIO_ADD_TEST(FixedFunctionOpCPU, aces_ot_20_p3d65_1000n_rt) +{ + const int lut_size = 8; + const int num_channels = 4; + int num_samples = lut_size * lut_size * lut_size; + std::vector input_32f(num_samples * num_channels, 0.f); + std::vector output_32f(num_samples * num_channels, 0.f); + + GenerateIdentityLut3D(input_32f.data(), lut_size, num_channels, OCIO::LUT3DORDER_FAST_RED); + + const float normPeakLuminance = 10.f; + for (unsigned int i = 0; i < input_32f.size(); ++i) + { + input_32f[i] *= normPeakLuminance; + } + + OCIO::FixedFunctionOpData::Params params = { + // Peak luminance + 1000.f, + // P3D65 gamut + 0.680, 0.320, 0.265, 0.690, 0.150, 0.060, 0.3127, 0.3290 + }; + + OCIO::ConstFixedFunctionOpDataRcPtr funcData + = std::make_shared(OCIO::FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_INV, + params); + + OCIO::ConstOpCPURcPtr op; + OCIO_CHECK_NO_THROW(op = OCIO::GetFixedFunctionCPURenderer(funcData)); + OCIO_CHECK_NO_THROW(op->apply(&input_32f[0], &output_32f[0], num_samples)); + + OCIO::ConstFixedFunctionOpDataRcPtr funcData2 + = std::make_shared(OCIO::FixedFunctionOpData::ACES_OUTPUT_TRANSFORM_20_FWD, + params); + + ApplyFixedFunction(&output_32f[0], &input_32f[0], num_samples, + funcData2, + 1e-3f, + __LINE__); +} + +OCIO_ADD_TEST(FixedFunctionOpCPU, aces_rgb_to_jmh_20) +{ + const unsigned num_samples = 27; + + // The following input values are processed and carried over to the next + // FixedFunctionOp test along the ACES2 output transform steps. + + float input_32f[num_samples*4] = { + // ACEScg primaries and secondaries scaled by 4 + 2.781808965f, 0.179178253f, -0.022103530f, 1.0f, + 3.344523751f, 3.617862727f, -0.006002689f, 1.0f, + 0.562714786f, 3.438684474f, 0.016100841f, 1.0f, + 1.218191035f, 3.820821747f, 4.022103530f, 1.0f, + 0.655476249f, 0.382137273f, 4.006002689f, 1.0f, + 3.437285214f, 0.561315526f, 3.983899159f, 1.0f, + // OCIO test values + 0.110000000f, 0.020000000f, 0.040000000f, 0.5f, + 0.710000000f, 0.510000000f, 0.810000000f, 1.0f, + 0.430000000f, 0.820000000f, 0.710000000f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 0.118770000f, 0.087090000f, 0.058950000f, 1.0f, + 0.400020000f, 0.319160000f, 0.237360000f, 1.0f, + 0.184760000f, 0.203980000f, 0.313110000f, 1.0f, + 0.109010000f, 0.135110000f, 0.064930000f, 1.0f, + 0.266840000f, 0.246040000f, 0.409320000f, 1.0f, + 0.322830000f, 0.462080000f, 0.406060000f, 1.0f, + 0.386050000f, 0.227430000f, 0.057770000f, 1.0f, + 0.138220000f, 0.130370000f, 0.337030000f, 1.0f, + 0.302020000f, 0.137520000f, 0.127580000f, 1.0f, + 0.093100000f, 0.063470000f, 0.135250000f, 1.0f, + 0.348760000f, 0.436540000f, 0.106130000f, 1.0f, + 0.486550000f, 0.366850000f, 0.080610000f, 1.0f, + 0.087320000f, 0.074430000f, 0.272740000f, 1.0f, + 0.153660000f, 0.256920000f, 0.090710000f, 1.0f, + 0.217420000f, 0.070700000f, 0.051300000f, 1.0f, + 0.589190000f, 0.539430000f, 0.091570000f, 1.0f, + 0.309040000f, 0.148180000f, 0.274260000f, 1.0f, + 0.149010000f, 0.233780000f, 0.359390000f, 1.0f, + }; + + float input2_32f[num_samples * 4]; + memcpy(&input2_32f[0], &input_32f[0], sizeof(float)*num_samples * 4); + + const float expected_32f[num_samples*4] = { + // ACEScg primaries and secondaries scaled by 4 + 107.480636597f, 206.827301025f, 25.025110245f, 1.0f, + 173.194076538f, 133.330886841f, 106.183448792f, 1.0f, + 139.210220337f, 191.922363281f, 147.056488037f, 1.0f, + 157.905166626f, 111.975311279f, 192.204727173f, 1.0f, + 79.229278564f, 100.424659729f, 268.442108154f, 1.0f, + 132.888137817f, 173.358779907f, 341.715240479f, 1.0f, + // OCIO test values + 26.112514496f, 42.523605347f, 4.173158169f, 0.5f, + 79.190460205f, 25.002300262f, 332.159759521f, 1.0f, + 81.912559509f, 39.754810333f, 182.925750732f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 33.924663544f, 12.254567146f, 38.146659851f, 1.0f, + 61.332393646f, 15.169423103f, 39.841842651f, 1.0f, + 47.191543579f, 11.839941978f, 249.107116699f, 1.0f, + 37.328300476f, 13.224150658f, 128.878036499f, 1.0f, + 53.465549469f, 13.121579170f, 285.658966064f, 1.0f, + 65.414512634f, 19.172147751f, 179.324264526f, 1.0f, + 55.711513519f, 37.182041168f, 50.924011230f, 1.0f, + 40.020961761f, 20.762512207f, 271.008331299f, 1.0f, + 47.704769135f, 35.791145325f, 13.975610733f, 1.0f, + 30.385913849f, 14.544739723f, 317.544281006f, 1.0f, + 64.222846985f, 33.487697601f, 119.145133972f, 1.0f, + 65.570358276f, 35.864013672f, 70.842193604f, 1.0f, + 31.800464630f, 23.920211792f, 273.228973389f, 1.0f, + 47.950405121f, 28.027387619f, 144.154159546f, 1.0f, + 38.440967560f, 42.604164124f, 17.892261505f, 1.0f, + 75.117736816f, 40.952045441f, 90.752044678f, 1.0f, + 49.311210632f, 33.812240601f, 348.832092285f, 1.0f, + 47.441757202f, 22.915655136f, 218.454376221f, 1.0f, + }; + + // ACES AP0 + OCIO::FixedFunctionOpData::Params params = {0.7347, 0.2653, 0.0000, 1.0000, 0.0001, -0.0770, 0.32168, 0.33767}; + OCIO::ConstFixedFunctionOpDataRcPtr funcData + = std::make_shared(OCIO::FixedFunctionOpData::ACES_RGB_TO_JMh_20, + params); + + ApplyFixedFunction(&input2_32f[0], &expected_32f[0], num_samples, + funcData, + 1e-5f, + __LINE__); + + OCIO::ConstFixedFunctionOpDataRcPtr funcData2 + = std::make_shared(OCIO::FixedFunctionOpData::ACES_JMh_TO_RGB_20, + params); + + ApplyFixedFunction(&input2_32f[0], &input_32f[0], num_samples, + funcData2, + 1e-4f, + __LINE__); +} + +OCIO_ADD_TEST(FixedFunctionOpCPU, aces_tonescale_compress_20) +{ + const unsigned num_samples = 27; + + float input_32f[num_samples*4] = { + // ACEScg primaries and secondaries scaled by 4 + 107.480636597f, 206.827301025f, 25.025110245f, 1.0f, + 173.194076538f, 133.330886841f, 106.183448792f, 1.0f, + 139.210220337f, 191.922363281f, 147.056488037f, 1.0f, + 157.905166626f, 111.975311279f, 192.204727173f, 1.0f, + 79.229278564f, 100.424659729f, 268.442108154f, 1.0f, + 132.888137817f, 173.358779907f, 341.715240479f, 1.0f, + // OCIO test values + 26.112514496f, 42.523605347f, 4.173158169f, 0.5f, + 79.190460205f, 25.002300262f, 332.159759521f, 1.0f, + 81.912559509f, 39.754810333f, 182.925750732f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 33.924663544f, 12.254567146f, 38.146659851f, 1.0f, + 61.332393646f, 15.169423103f, 39.841842651f, 1.0f, + 47.191543579f, 11.839941978f, 249.107116699f, 1.0f, + 37.328300476f, 13.224150658f, 128.878036499f, 1.0f, + 53.465549469f, 13.121579170f, 285.658966064f, 1.0f, + 65.414512634f, 19.172147751f, 179.324264526f, 1.0f, + 55.711513519f, 37.182041168f, 50.924011230f, 1.0f, + 40.020961761f, 20.762512207f, 271.008331299f, 1.0f, + 47.704769135f, 35.791145325f, 13.975610733f, 1.0f, + 30.385913849f, 14.544739723f, 317.544281006f, 1.0f, + 64.222846985f, 33.487697601f, 119.145133972f, 1.0f, + 65.570358276f, 35.864013672f, 70.842193604f, 1.0f, + 31.800464630f, 23.920211792f, 273.228973389f, 1.0f, + 47.950405121f, 28.027387619f, 144.154159546f, 1.0f, + 38.440967560f, 42.604164124f, 17.892261505f, 1.0f, + 75.117736816f, 40.952045441f, 90.752044678f, 1.0f, + 49.311210632f, 33.812240601f, 348.832092285f, 1.0f, + 47.441757202f, 22.915655136f, 218.454376221f, 1.0f, + }; + + float input2_32f[num_samples * 4]; + memcpy(&input2_32f[0], &input_32f[0], sizeof(float)*num_samples * 4); + + const float expected_32f[num_samples*4] = { + // ACEScg primaries and secondaries scaled by 4 + 110.702453613f, 211.251770020f, 25.025110245f, 1.0f, + 168.016815186f, 129.796249390f, 106.183448792f, 1.0f, + 140.814849854f, 193.459213257f, 147.056488037f, 1.0f, + 156.429519653f, 110.938514709f, 192.204727173f, 1.0f, + 80.456542969f, 98.490524292f, 268.442108154f, 1.0f, + 135.172195435f, 175.559280396f, 341.715240479f, 1.0f, + // OCIO test values + 18.187314987f, 33.819175720f, 4.173158169f, 0.5f, + 80.413116455f, 21.309329987f, 332.159759521f, 1.0f, + 83.447891235f, 37.852291107f, 182.925750732f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 27.411964417f, 13.382769585f, 38.146659851f, 1.0f, + 59.987670898f, 14.391894341f, 39.841842651f, 1.0f, + 43.298923492f, 12.199877739f, 249.107116699f, 1.0f, + 31.489658356f, 14.075142860f, 128.878036499f, 1.0f, + 50.749198914f, 12.731814384f, 285.658966064f, 1.0f, + 64.728637695f, 18.593795776f, 179.324264526f, 1.0f, + 53.399448395f, 37.394428253f, 50.924011230f, 1.0f, + 34.719596863f, 21.616765976f, 271.008331299f, 1.0f, + 43.910713196f, 36.788166046f, 13.975610733f, 1.0f, + 23.196525574f, 15.118354797f, 317.544281006f, 1.0f, + 63.348674774f, 33.283493042f, 119.145133972f, 1.0f, + 64.908889771f, 35.371044159f, 70.842193604f, 1.0f, + 24.876911163f, 23.143159866f, 273.228973389f, 1.0f, + 44.203376770f, 28.918329239f, 144.154159546f, 1.0f, + 32.824356079f, 43.447875977f, 17.892261505f, 1.0f, + 75.830871582f, 39.872474670f, 90.752044678f, 1.0f, + 45.823116302f, 34.652069092f, 348.832092285f, 1.0f, + 43.597240448f, 23.079078674f, 218.454376221f, 1.0f, + }; + + OCIO::FixedFunctionOpData::Params params = {1000.f}; + OCIO::ConstFixedFunctionOpDataRcPtr funcData + = std::make_shared(OCIO::FixedFunctionOpData::ACES_TONESCALE_COMPRESS_20_FWD, + params); + + ApplyFixedFunction(&input2_32f[0], &expected_32f[0], num_samples, + funcData, + 1e-5f, + __LINE__); + + OCIO::ConstFixedFunctionOpDataRcPtr funcData2 + = std::make_shared(OCIO::FixedFunctionOpData::ACES_TONESCALE_COMPRESS_20_INV, + params); + + ApplyFixedFunction(&input2_32f[0], &input_32f[0], num_samples, + funcData2, + 1e-4f, + __LINE__); +} + +OCIO_ADD_TEST(FixedFunctionOpCPU, aces_gamut_map_20) +{ + const unsigned num_samples = 27; + + float input_32f[num_samples*4] = { + // ACEScg primaries and secondaries scaled by 4 + 110.702453613f, 211.251770020f, 25.025110245f, 1.0f, + 168.016815186f, 129.796249390f, 106.183448792f, 1.0f, + 140.814849854f, 193.459213257f, 147.056488037f, 1.0f, + 156.429519653f, 110.938514709f, 192.204727173f, 1.0f, + 80.456542969f, 98.490524292f, 268.442108154f, 1.0f, + 135.172195435f, 175.559280396f, 341.715240479f, 1.0f, + // OCIO test values + 18.187314987f, 33.819175720f, 4.173158169f, 0.5f, + 80.413116455f, 21.309329987f, 332.159759521f, 1.0f, + 83.447891235f, 37.852291107f, 182.925750732f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 27.411964417f, 13.382769585f, 38.146659851f, 1.0f, + 59.987670898f, 14.391894341f, 39.841842651f, 1.0f, + 43.298923492f, 12.199877739f, 249.107116699f, 1.0f, + 31.489658356f, 14.075142860f, 128.878036499f, 1.0f, + 50.749198914f, 12.731814384f, 285.658966064f, 1.0f, + 64.728637695f, 18.593795776f, 179.324264526f, 1.0f, + 53.399448395f, 37.394428253f, 50.924011230f, 1.0f, + 34.719596863f, 21.616765976f, 271.008331299f, 1.0f, + 43.910713196f, 36.788166046f, 13.975610733f, 1.0f, + 23.196525574f, 15.118354797f, 317.544281006f, 1.0f, + 63.348674774f, 33.283493042f, 119.145133972f, 1.0f, + 64.908889771f, 35.371044159f, 70.842193604f, 1.0f, + 24.876911163f, 23.143159866f, 273.228973389f, 1.0f, + 44.203376770f, 28.918329239f, 144.154159546f, 1.0f, + 32.824356079f, 43.447875977f, 17.892261505f, 1.0f, + 75.830871582f, 39.872474670f, 90.752044678f, 1.0f, + 45.823116302f, 34.652069092f, 348.832092285f, 1.0f, + 43.597240448f, 23.079078674f, 218.454376221f, 1.0f, + }; + + float input2_32f[num_samples * 4]; + memcpy(&input2_32f[0], &input_32f[0], sizeof(float)*num_samples * 4); + + const float expected_32f[num_samples*4] = { + // ACEScg primaries and secondaries scaled by 4 + 107.831291199f, 174.252944946f, 25.025119781f, 1.0f, + 168.028198242f, 118.224960327f, 106.183464050f, 1.0f, + 140.030105591f, 127.177192688f, 147.056488037f, 1.0f, + 156.512435913f, 73.218856812f, 192.204727173f, 1.0f, + 79.378631592f, 72.613555908f, 268.442108154f, 1.0f, + 133.827835083f, 149.929809570f, 341.715240479f, 1.0f, + // OCIO test values + 18.194000244f, 33.312938690f, 4.173166752f, 0.5f, + 80.413116455f, 21.309329987f, 332.159759521f, 1.0f, + 83.467437744f, 37.305160522f, 182.925750732f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 27.411962509f, 13.382793427f, 38.146591187f, 1.0f, + 59.987670898f, 14.391893387f, 39.841842651f, 1.0f, + 43.298923492f, 12.199877739f, 249.107116699f, 1.0f, + 31.489658356f, 14.075142860f, 128.878036499f, 1.0f, + 50.749198914f, 12.731814384f, 285.658966064f, 1.0f, + 64.728637695f, 18.593795776f, 179.324264526f, 1.0f, + 53.399448395f, 37.394428253f, 50.924011230f, 1.0f, + 34.719596863f, 21.616765976f, 271.008331299f, 1.0f, + 43.910709381f, 36.788166046f, 13.975610733f, 1.0f, + 23.196525574f, 15.118361473f, 317.544250488f, 1.0f, + 63.348674774f, 33.283493042f, 119.145133972f, 1.0f, + 64.908889771f, 35.371044159f, 70.842193604f, 1.0f, + 24.876916885f, 23.143167496f, 273.229034424f, 1.0f, + 44.203376770f, 28.918329239f, 144.154159546f, 1.0f, + 32.824352264f, 43.447864532f, 17.892255783f, 1.0f, + 75.830871582f, 39.872474670f, 90.752044678f, 1.0f, + 45.823104858f, 34.652038574f, 348.832092285f, 1.0f, + 43.635551453f, 21.629474640f, 218.454376221f, 1.0f, + }; + + OCIO::FixedFunctionOpData::Params params = { + // Peak luminance + 1000.f, + // P3D65 gamut + 0.680, 0.320, 0.265, 0.690, 0.150, 0.060, 0.3127, 0.3290 + }; + OCIO::ConstFixedFunctionOpDataRcPtr funcData + = std::make_shared(OCIO::FixedFunctionOpData::ACES_GAMUT_COMPRESS_20_FWD, + params); + + ApplyFixedFunction(&input2_32f[0], &expected_32f[0], num_samples, + funcData, + 1e-5f, + __LINE__); + + OCIO::ConstFixedFunctionOpDataRcPtr funcData2 + = std::make_shared(OCIO::FixedFunctionOpData::ACES_GAMUT_COMPRESS_20_INV, + params); + + ApplyFixedFunction(&input2_32f[0], &input_32f[0], num_samples, + funcData2, + 1e-5f, + __LINE__); +} OCIO_ADD_TEST(FixedFunctionOpCPU, rec2100_surround) { diff --git a/tests/cpu/transforms/BuiltinTransform_tests.cpp b/tests/cpu/transforms/BuiltinTransform_tests.cpp index 32b8200b0b..52304d2a3f 100644 --- a/tests/cpu/transforms/BuiltinTransform_tests.cpp +++ b/tests/cpu/transforms/BuiltinTransform_tests.cpp @@ -429,6 +429,70 @@ AllValues UnitTestValues { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-CINEMA-108nit-7.2nit-P3lim_1.1", { { 0.5f, 0.4f, 0.3f }, { 0.22214814f, 0.21179835f, 0.15639816f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-REC709_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.26260212f, 0.25207470f, 0.20617338f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-P3-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.26260212f, 0.25207472f, 0.20617332f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-108nit-P3-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.16253406f, 0.15513624f, 0.12449740f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-300nit-P3-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.20592399f, 0.19440515f, 0.15028581f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-P3-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.41039306f, 0.38813826f, 0.30191866f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-P3-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.46536570f, 0.43852836f, 0.33688113f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-P3-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.51225936f, 0.48264506f, 0.37060050f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-P3-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.55653524f, 0.51967940f, 0.38678724f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-REC2020_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.41039258f, 0.38813800f, 0.30191845f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-REC2020_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.46536540f, 0.43852820f, 0.33688095f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-REC2020_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.51225930f, 0.48264477f, 0.37060022f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-REC2020_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.55653550f, 0.51967950f, 0.38678730f } } }, + + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-REC709-D60-in-REC709-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.25147703f, 0.24029444f, 0.18221131f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-REC709-D60-in-P3-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.25373828f, 0.24245512f, 0.18384966f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-REC709-D60-in-REC2020-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.25712878f, 0.24569483f, 0.18630630f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-P3-D60-in-P3-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.25373834f, 0.24245517f, 0.18384990f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - SDR-100nit-P3-D60-in-XYZ-E_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.26332240f, 0.25161302f, 0.19079340f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-108nit-P3-D60-in-P3-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.15705064f, 0.14920068f, 0.11100890f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-300nit-P3-D60-in-XYZ-E_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.20469205f, 0.19229384f, 0.13782679f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-P3-D60-in-P3-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.39655724f, 0.37322620f, 0.26917280f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-P3-D60-in-P3-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.44968104f, 0.42165324f, 0.30032730f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-P3-D60-in-P3-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.49499428f, 0.46407104f, 0.33038715f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-P3-D60-in-P3-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.53778990f, 0.49960223f, 0.34477120f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-P3-D60-in-REC2020-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.40185606f, 0.37821326f, 0.27276948f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-P3-D60-in-REC2020-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.45568960f, 0.42728746f, 0.30434040f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-P3-D60-in-REC2020-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.50160843f, 0.47027197f, 0.33480182f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-P3-D60-in-REC2020-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.54497580f, 0.50627790f, 0.34937808f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-500nit-REC2020-D60-in-REC2020-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.40185624f, 0.37821335f, 0.27276933f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-1000nit-REC2020-D60-in-REC2020-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.45568994f, 0.42728750f, 0.30434027f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-2000nit-REC2020-D60-in-REC2020-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.50160870f, 0.47027220f, 0.33480210f } } }, + { "ACES-OUTPUT - ACES2065-1_to_CIE-XYZ-D65 - HDR-4000nit-REC2020-D60-in-REC2020-D65_2.0", + { { 0.5f, 0.4f, 0.3f }, { 0.54497580f, 0.50627780f, 0.34937814f } } }, + { "APPLE_LOG_to_ACES2065-1", { { 0.5f, 0.4f, 0.3f }, { 0.153334766f, 0.083515430f, 0.032948254f } } }, { "CURVE - APPLE_LOG_to_LINEAR", diff --git a/tests/gpu/FixedFunctionOp_test.cpp b/tests/gpu/FixedFunctionOp_test.cpp index 2ed4bdc1a3..171f241207 100644 --- a/tests/gpu/FixedFunctionOp_test.cpp +++ b/tests/gpu/FixedFunctionOp_test.cpp @@ -305,6 +305,494 @@ OCIO_ADD_GPU_TEST(FixedFunction, style_aces_gamutcomp13_inv) test.setErrorThreshold(3e-6f); } +OCIO_ADD_GPU_TEST(FixedFunction, style_aces2_output_transform_fwd) +{ + const double data[9] = { + // Peak luminance + 1000.f, + // P3D65 gamut + 0.680, 0.320, 0.265, 0.690, 0.150, 0.060, 0.3127, 0.3290 + }; + OCIO::FixedFunctionTransformRcPtr func = + OCIO::FixedFunctionTransform::Create(OCIO::FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20, &data[0], 9); + func->setDirection(OCIO::TRANSFORM_DIR_FORWARD); + + test.setProcessor(func); + + OCIOGPUTest::CustomValues values; + values.m_inputValues = + { + // ACEScg primaries and secondaries scaled by 4 + 2.781808965f, 0.179178253f, -0.022103530f, 1.0f, + 3.344523751f, 3.617862727f, -0.006002689f, 1.0f, + 0.562714786f, 3.438684474f, 0.016100841f, 1.0f, + 1.218191035f, 3.820821747f, 4.022103530f, 1.0f, + 0.655476249f, 0.382137273f, 4.006002689f, 1.0f, + 3.437285214f, 0.561315526f, 3.983899159f, 1.0f, + // OCIO test values + 0.110000000f, 0.020000000f, 0.040000000f, 0.5f, + 0.710000000f, 0.510000000f, 0.810000000f, 1.0f, + 0.430000000f, 0.820000000f, 0.710000000f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 0.118770000f, 0.087090000f, 0.058950000f, 1.0f, + 0.400020000f, 0.319160000f, 0.237360000f, 1.0f, + 0.184760000f, 0.203980000f, 0.313110000f, 1.0f, + 0.109010000f, 0.135110000f, 0.064930000f, 1.0f, + 0.266840000f, 0.246040000f, 0.409320000f, 1.0f, + 0.322830000f, 0.462080000f, 0.406060000f, 1.0f, + 0.386050000f, 0.227430000f, 0.057770000f, 1.0f, + 0.138220000f, 0.130370000f, 0.337030000f, 1.0f, + 0.302020000f, 0.137520000f, 0.127580000f, 1.0f, + 0.093100000f, 0.063470000f, 0.135250000f, 1.0f, + 0.348760000f, 0.436540000f, 0.106130000f, 1.0f, + 0.486550000f, 0.366850000f, 0.080610000f, 1.0f, + 0.087320000f, 0.074430000f, 0.272740000f, 1.0f, + 0.153660000f, 0.256920000f, 0.090710000f, 1.0f, + 0.217420000f, 0.070700000f, 0.051300000f, 1.0f, + 0.589190000f, 0.539430000f, 0.091570000f, 1.0f, + 0.309040000f, 0.148180000f, 0.274260000f, 1.0f, + 0.149010000f, 0.233780000f, 0.359390000f, 1.0f, + 0.866530000f, 0.867920000f, 0.858180000f, 1.0f, + 0.573560000f, 0.572560000f, 0.571690000f, 1.0f, + 0.353460000f, 0.353370000f, 0.353910000f, 1.0f, + 0.202530000f, 0.202430000f, 0.202870000f, 1.0f, + 0.094670000f, 0.095200000f, 0.096370000f, 1.0f, + 0.037450000f, 0.037660000f, 0.038950000f, 1.0f, + // Spectrally non-selective 18 % reflecting diffuser + 0.180000000f, 0.180000000f, 0.180000000f, 1.0f, + // Perfect reflecting diffuser + 0.977840000f, 0.977840000f, 0.977840000f, 1.0f, + }; + test.setCustomValues(values); + + test.setErrorThreshold(2e-5f); +} + +OCIO_ADD_GPU_TEST(FixedFunction, style_aces2_output_transform_inv) +{ + const double data[9] = { + // Peak luminance + 1000.f, + // P3D65 gamut + 0.680, 0.320, 0.265, 0.690, 0.150, 0.060, 0.3127, 0.3290 + }; + OCIO::FixedFunctionTransformRcPtr func = + OCIO::FixedFunctionTransform::Create(OCIO::FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20, &data[0], 9); + func->setDirection(OCIO::TRANSFORM_DIR_INVERSE); + + test.setProcessor(func); + + OCIOGPUTest::CustomValues values; + values.m_inputValues = + { + // ACEScg primaries and secondaries scaled by 4 + 4.965774059f, -0.032864563f, 0.041625995f, 1.0f, + 3.969441891f, 3.825784922f, -0.056133576f, 1.0f, + -0.075329021f, 3.688980103f, 0.270296901f, 1.0f, + -0.095423937f, 3.650517225f, 3.459972620f, 1.0f, + -0.028930068f, 0.196428135f, 2.796343565f, 1.0f, + 4.900805950f, -0.064376131f, 3.838256121f, 1.0f, + // OCIO test values + 0.096890204f, -0.001135312f, 0.018971510f, 0.5f, + 0.809614301f, 0.479856580f, 0.814239502f, 1.0f, + 0.107420206f, 0.920529068f, 0.726378500f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 0.115475260f, 0.050812904f, 0.030212952f, 1.0f, + 0.484879673f, 0.301042974f, 0.226768956f, 1.0f, + 0.098463766f, 0.160814658f, 0.277010560f, 1.0f, + 0.071130365f, 0.107334383f, 0.035097566f, 1.0f, + 0.207111493f, 0.198474824f, 0.375326216f, 1.0f, + 0.195447892f, 0.481111974f, 0.393299013f, 1.0f, + 0.571913838f, 0.196872935f, 0.041634772f, 1.0f, + 0.045791931f, 0.069875360f, 0.291233480f, 1.0f, + 0.424848706f, 0.083199009f, 0.102153838f, 1.0f, + 0.059589427f, 0.022219172f, 0.091246888f, 1.0f, + 0.360365510f, 0.478741467f, 0.086726837f, 1.0f, + 0.695662081f, 0.371994525f, 0.068298168f, 1.0f, + 0.011806309f, 0.021665439f, 0.199594811f, 1.0f, + 0.076526314f, 0.256237417f, 0.060564656f, 1.0f, + 0.300064564f, 0.023416257f, 0.030360471f, 1.0f, + 0.805484772f, 0.596903503f, 0.082996152f, 1.0f, + 0.388385952f, 0.079899102f, 0.245819211f, 1.0f, + 0.010952532f, 0.196105912f, 0.307181358f, 1.0f, + 0.921019495f, 0.921707213f, 0.912856042f, 1.0f, + 0.590192318f, 0.588423848f, 0.587825358f, 1.0f, + 0.337743521f, 0.337685764f, 0.338155121f, 1.0f, + 0.169265985f, 0.169178501f, 0.169557109f, 1.0f, + 0.058346048f, 0.059387825f, 0.060296260f, 1.0f, + 0.012581184f, 0.012947139f, 0.013654195f, 1.0f, + // Spectrally non-selective 18 % reflecting diffuser + 0.145115465f, 0.145115525f, 0.145115510f, 1.0f, + // Perfect reflecting diffuser + 1.041565657f, 1.041566014f, 1.041565657f, 1.0f, + }; + test.setCustomValues(values); + + test.setErrorThreshold(1e-4f); +} + +OCIO_ADD_GPU_TEST(FixedFunction, style_aces2_output_transform_invfwd) +{ + const double data_inv[9] = { + // Peak luminance + 100.f, + // REC709 gamut + 0.64, 0.33, 0.3, 0.6, 0.15, 0.06, 0.3127, 0.329 + }; + OCIO::FixedFunctionTransformRcPtr func_inv = + OCIO::FixedFunctionTransform::Create(OCIO::FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20, &data_inv[0], 9); + func_inv->setDirection(OCIO::TRANSFORM_DIR_INVERSE); + + const double data_fwd[9] = { + // Peak luminance + 1000.f, + // P3D65 gamut + 0.680, 0.320, 0.265, 0.690, 0.150, 0.060, 0.3127, 0.3290 + }; + + OCIO::FixedFunctionTransformRcPtr func_fwd = + OCIO::FixedFunctionTransform::Create(OCIO::FIXED_FUNCTION_ACES_OUTPUT_TRANSFORM_20, &data_fwd[0], 9); + + OCIO::GroupTransformRcPtr grp = OCIO::GroupTransform::Create(); + grp->appendTransform(func_inv); + grp->appendTransform(func_fwd); + + test.setProcessor(grp); + + test.setErrorThreshold(5e-4f); +} + +OCIO_ADD_GPU_TEST(FixedFunction, style_aces2_rgb_to_jmh_fwd) +{ + // ACES AP0 + const double data[8] = { 0.7347, 0.2653, 0.0000, 1.0000, 0.0001, -0.0770, 0.32168, 0.33767 }; + OCIO::FixedFunctionTransformRcPtr func = + OCIO::FixedFunctionTransform::Create(OCIO::FIXED_FUNCTION_ACES_RGB_TO_JMH_20, &data[0], 8); + func->setDirection(OCIO::TRANSFORM_DIR_FORWARD); + + test.setProcessor(func); + + OCIOGPUTest::CustomValues values; + values.m_inputValues = + { + // ACEScg primaries and secondaries scaled by 4 + 2.781808965f, 0.179178253f, -0.022103530f, 1.0f, + 3.344523751f, 3.617862727f, -0.006002689f, 1.0f, + 0.562714786f, 3.438684474f, 0.016100841f, 1.0f, + 1.218191035f, 3.820821747f, 4.022103530f, 1.0f, + 0.655476249f, 0.382137273f, 4.006002689f, 1.0f, + 3.437285214f, 0.561315526f, 3.983899159f, 1.0f, + // OCIO test values + 0.110000000f, 0.020000000f, 0.040000000f, 0.5f, + 0.710000000f, 0.510000000f, 0.810000000f, 1.0f, + 0.430000000f, 0.820000000f, 0.710000000f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 0.118770000f, 0.087090000f, 0.058950000f, 1.0f, + 0.400020000f, 0.319160000f, 0.237360000f, 1.0f, + 0.184760000f, 0.203980000f, 0.313110000f, 1.0f, + 0.109010000f, 0.135110000f, 0.064930000f, 1.0f, + 0.266840000f, 0.246040000f, 0.409320000f, 1.0f, + 0.322830000f, 0.462080000f, 0.406060000f, 1.0f, + 0.386050000f, 0.227430000f, 0.057770000f, 1.0f, + 0.138220000f, 0.130370000f, 0.337030000f, 1.0f, + 0.302020000f, 0.137520000f, 0.127580000f, 1.0f, + 0.093100000f, 0.063470000f, 0.135250000f, 1.0f, + 0.348760000f, 0.436540000f, 0.106130000f, 1.0f, + 0.486550000f, 0.366850000f, 0.080610000f, 1.0f, + 0.087320000f, 0.074430000f, 0.272740000f, 1.0f, + 0.153660000f, 0.256920000f, 0.090710000f, 1.0f, + 0.217420000f, 0.070700000f, 0.051300000f, 1.0f, + 0.589190000f, 0.539430000f, 0.091570000f, 1.0f, + 0.309040000f, 0.148180000f, 0.274260000f, 1.0f, + 0.149010000f, 0.233780000f, 0.359390000f, 1.0f, + }; + test.setCustomValues(values); + + test.setErrorThreshold(2e-4f); +} + +OCIO_ADD_GPU_TEST(FixedFunction, style_aces2_rgb_to_jmh_inv) +{ + // ACES AP0 + const double data[8] = { 0.7347, 0.2653, 0.0000, 1.0000, 0.0001, -0.0770, 0.32168, 0.33767 }; + OCIO::FixedFunctionTransformRcPtr func = + OCIO::FixedFunctionTransform::Create(OCIO::FIXED_FUNCTION_ACES_RGB_TO_JMH_20, &data[0], 8); + func->setDirection(OCIO::TRANSFORM_DIR_INVERSE); + + test.setProcessor(func); + + OCIOGPUTest::CustomValues values; + values.m_inputValues = + { + // ACEScg primaries and secondaries scaled by 4 + 107.480636597f, 206.827301025f, 25.025110245f, 1.0f, + 173.194076538f, 133.330886841f, 106.183448792f, 1.0f, + 139.210220337f, 191.922363281f, 147.056488037f, 1.0f, + 157.905166626f, 111.975311279f, 192.204727173f, 1.0f, + 79.229278564f, 100.424659729f, 268.442108154f, 1.0f, + 132.888137817f, 173.358779907f, 341.715240479f, 1.0f, + // OCIO test values + 26.112514496f, 42.523605347f, 4.173158169f, 0.5f, + 79.190460205f, 25.002300262f, 332.159759521f, 1.0f, + 81.912559509f, 39.754810333f, 182.925750732f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 33.924663544f, 12.254567146f, 38.146659851f, 1.0f, + 61.332393646f, 15.169423103f, 39.841842651f, 1.0f, + 47.191543579f, 11.839941978f, 249.107116699f, 1.0f, + 37.328300476f, 13.224150658f, 128.878036499f, 1.0f, + 53.465549469f, 13.121579170f, 285.658966064f, 1.0f, + 65.414512634f, 19.172147751f, 179.324264526f, 1.0f, + 55.711513519f, 37.182041168f, 50.924011230f, 1.0f, + 40.020961761f, 20.762512207f, 271.008331299f, 1.0f, + 47.704769135f, 35.791145325f, 13.975610733f, 1.0f, + 30.385913849f, 14.544739723f, 317.544281006f, 1.0f, + 64.222846985f, 33.487697601f, 119.145133972f, 1.0f, + 65.570358276f, 35.864013672f, 70.842193604f, 1.0f, + 31.800464630f, 23.920211792f, 273.228973389f, 1.0f, + 47.950405121f, 28.027387619f, 144.154159546f, 1.0f, + 38.440967560f, 42.604164124f, 17.892261505f, 1.0f, + 75.117736816f, 40.952045441f, 90.752044678f, 1.0f, + 49.311210632f, 33.812240601f, 348.832092285f, 1.0f, + 47.441757202f, 22.915655136f, 218.454376221f, 1.0f, + }; + test.setCustomValues(values); + + test.setErrorThreshold(1e-4f); +} + +OCIO_ADD_GPU_TEST(FixedFunction, style_aces2_tonescale_compress_fwd) +{ + const double data[1] = { 1000.f }; + OCIO::FixedFunctionTransformRcPtr func = + OCIO::FixedFunctionTransform::Create(OCIO::FIXED_FUNCTION_ACES_TONESCALE_COMPRESS_20, &data[0], 1); + func->setDirection(OCIO::TRANSFORM_DIR_FORWARD); + + test.setProcessor(func); + + OCIOGPUTest::CustomValues values; + values.m_inputValues = + { + // ACEScg primaries and secondaries scaled by 4 + 107.480636597f, 206.827301025f, 25.025110245f, 1.0f, + 173.194076538f, 133.330886841f, 106.183448792f, 1.0f, + 139.210220337f, 191.922363281f, 147.056488037f, 1.0f, + 157.905166626f, 111.975311279f, 192.204727173f, 1.0f, + 79.229278564f, 100.424659729f, 268.442108154f, 1.0f, + 132.888137817f, 173.358779907f, 341.715240479f, 1.0f, + // OCIO test values + 26.112514496f, 42.523605347f, 4.173158169f, 0.5f, + 79.190460205f, 25.002300262f, 332.159759521f, 1.0f, + 81.912559509f, 39.754810333f, 182.925750732f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 33.924663544f, 12.254567146f, 38.146659851f, 1.0f, + 61.332393646f, 15.169423103f, 39.841842651f, 1.0f, + 47.191543579f, 11.839941978f, 249.107116699f, 1.0f, + 37.328300476f, 13.224150658f, 128.878036499f, 1.0f, + 53.465549469f, 13.121579170f, 285.658966064f, 1.0f, + 65.414512634f, 19.172147751f, 179.324264526f, 1.0f, + 55.711513519f, 37.182041168f, 50.924011230f, 1.0f, + 40.020961761f, 20.762512207f, 271.008331299f, 1.0f, + 47.704769135f, 35.791145325f, 13.975610733f, 1.0f, + 30.385913849f, 14.544739723f, 317.544281006f, 1.0f, + 64.222846985f, 33.487697601f, 119.145133972f, 1.0f, + 65.570358276f, 35.864013672f, 70.842193604f, 1.0f, + 31.800464630f, 23.920211792f, 273.228973389f, 1.0f, + 47.950405121f, 28.027387619f, 144.154159546f, 1.0f, + 38.440967560f, 42.604164124f, 17.892261505f, 1.0f, + 75.117736816f, 40.952045441f, 90.752044678f, 1.0f, + 49.311210632f, 33.812240601f, 348.832092285f, 1.0f, + 47.441757202f, 22.915655136f, 218.454376221f, 1.0f, + 93.610260010f, 0.439610571f, 108.271926880f, 1.0f, + 77.237663269f, 0.131636351f, 33.296318054f, 1.0f, + 61.655914307f, 0.041985143f, 291.004058838f, 1.0f, + 47.493667603f, 0.048908804f, 297.386047363f, 1.0f, + 33.264842987f, 0.283808023f, 234.276382446f, 1.0f, + 21.467216492f, 0.409062684f, 255.025634766f, 1.0f, + // Spectrally non-selective 18 % reflecting diffuser + 44.938602448f, 0.000004705f, 299.357757568f, 1.0f, + // Perfect reflecting diffuser + 98.969635010f, 0.000083445f, 5.640549183f, 1.0f, + }; + test.setCustomValues(values); + + test.setErrorThreshold(1e-4f); +} + +OCIO_ADD_GPU_TEST(FixedFunction, style_aces2_tonescale_compress_inv) +{ + const double data[1] = { 1000.f }; + OCIO::FixedFunctionTransformRcPtr func = + OCIO::FixedFunctionTransform::Create(OCIO::FIXED_FUNCTION_ACES_TONESCALE_COMPRESS_20, &data[0], 1); + func->setDirection(OCIO::TRANSFORM_DIR_INVERSE); + + test.setProcessor(func); + + OCIOGPUTest::CustomValues values; + values.m_inputValues = + { + // ACEScg primaries and secondaries scaled by 4 + 110.702453613f, 211.251770020f, 25.025110245f, 1.0f, + 168.016815186f, 129.796249390f, 106.183448792f, 1.0f, + 140.814849854f, 193.459213257f, 147.056488037f, 1.0f, + 156.429519653f, 110.938514709f, 192.204727173f, 1.0f, + 80.456542969f, 98.490524292f, 268.442108154f, 1.0f, + 135.172195435f, 175.559280396f, 341.715240479f, 1.0f, + // OCIO test values + 18.187314987f, 33.819175720f, 4.173158169f, 0.5f, + 80.413116455f, 21.309329987f, 332.159759521f, 1.0f, + 83.447891235f, 37.852291107f, 182.925750732f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 27.411964417f, 13.382769585f, 38.146659851f, 1.0f, + 59.987670898f, 14.391894341f, 39.841842651f, 1.0f, + 43.298923492f, 12.199877739f, 249.107116699f, 1.0f, + 31.489658356f, 14.075142860f, 128.878036499f, 1.0f, + 50.749198914f, 12.731814384f, 285.658966064f, 1.0f, + 64.728637695f, 18.593795776f, 179.324264526f, 1.0f, + 53.399448395f, 37.394428253f, 50.924011230f, 1.0f, + 34.719596863f, 21.616765976f, 271.008331299f, 1.0f, + 43.910713196f, 36.788166046f, 13.975610733f, 1.0f, + 23.196525574f, 15.118354797f, 317.544281006f, 1.0f, + 63.348674774f, 33.283493042f, 119.145133972f, 1.0f, + 64.908889771f, 35.371044159f, 70.842193604f, 1.0f, + 24.876911163f, 23.143159866f, 273.228973389f, 1.0f, + 44.203376770f, 28.918329239f, 144.154159546f, 1.0f, + 32.824356079f, 43.447875977f, 17.892261505f, 1.0f, + 75.830871582f, 39.872474670f, 90.752044678f, 1.0f, + 45.823116302f, 34.652069092f, 348.832092285f, 1.0f, + 43.597240448f, 23.079078674f, 218.454376221f, 1.0f, + 96.212783813f, 0.322624743f, 108.271926880f, 1.0f, + 78.222122192f, 0.094044082f, 33.296318054f, 1.0f, + 60.364795685f, 0.031291425f, 291.004058838f, 1.0f, + 43.659111023f, 0.038717352f, 297.386047363f, 1.0f, + 26.623359680f, 0.269155562f, 234.276382446f, 1.0f, + 12.961384773f, 0.366550505f, 255.025634766f, 1.0f, + // Spectrally non-selective 18 % reflecting diffuser + 40.609165192f, 0.000000000f, 299.357757568f, 1.0f, + // Perfect reflecting diffuser + 101.899215698f, 0.000068110f, 5.640549183f, 1.0f, + }; + test.setCustomValues(values); + + test.setErrorThreshold(1e-4f); +} + +OCIO_ADD_GPU_TEST(FixedFunction, style_aces2_gamut_compress_fwd) +{ + const double data[9] = { + // Peak luminance + 1000.f, + // P3D65 gamut + 0.680, 0.320, 0.265, 0.690, 0.150, 0.060, 0.3127, 0.3290 + }; + OCIO::FixedFunctionTransformRcPtr func = + OCIO::FixedFunctionTransform::Create(OCIO::FIXED_FUNCTION_ACES_GAMUT_COMPRESS_20, &data[0], 9); + func->setDirection(OCIO::TRANSFORM_DIR_FORWARD); + + test.setProcessor(func); + + OCIOGPUTest::CustomValues values; + values.m_inputValues = + { + // ACEScg primaries and secondaries scaled by 4 + 110.702453613f, 211.251770020f, 25.025110245f, 1.0f, + 168.016815186f, 129.796249390f, 106.183448792f, 1.0f, + 140.814849854f, 193.459213257f, 147.056488037f, 1.0f, + 156.429519653f, 110.938514709f, 192.204727173f, 1.0f, + 80.456542969f, 98.490524292f, 268.442108154f, 1.0f, + 135.172195435f, 175.559280396f, 341.715240479f, 1.0f, + // OCIO test values + 18.187314987f, 33.819175720f, 4.173158169f, 0.5f, + 80.413116455f, 21.309329987f, 332.159759521f, 1.0f, + 83.447891235f, 37.852291107f, 182.925750732f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 27.411964417f, 13.382769585f, 38.146659851f, 1.0f, + 59.987670898f, 14.391894341f, 39.841842651f, 1.0f, + 43.298923492f, 12.199877739f, 249.107116699f, 1.0f, + 31.489658356f, 14.075142860f, 128.878036499f, 1.0f, + 50.749198914f, 12.731814384f, 285.658966064f, 1.0f, + 64.728637695f, 18.593795776f, 179.324264526f, 1.0f, + 53.399448395f, 37.394428253f, 50.924011230f, 1.0f, + 34.719596863f, 21.616765976f, 271.008331299f, 1.0f, + 43.910713196f, 36.788166046f, 13.975610733f, 1.0f, + 23.196525574f, 15.118354797f, 317.544281006f, 1.0f, + 63.348674774f, 33.283493042f, 119.145133972f, 1.0f, + 64.908889771f, 35.371044159f, 70.842193604f, 1.0f, + 24.876911163f, 23.143159866f, 273.228973389f, 1.0f, + 44.203376770f, 28.918329239f, 144.154159546f, 1.0f, + 32.824356079f, 43.447875977f, 17.892261505f, 1.0f, + 75.830871582f, 39.872474670f, 90.752044678f, 1.0f, + 45.823116302f, 34.652069092f, 348.832092285f, 1.0f, + 43.597240448f, 23.079078674f, 218.454376221f, 1.0f, + 96.212783813f, 0.322624743f, 108.271926880f, 1.0f, + 78.222122192f, 0.094044082f, 33.296318054f, 1.0f, + 60.364795685f, 0.031291425f, 291.004058838f, 1.0f, + 43.659111023f, 0.038717352f, 297.386047363f, 1.0f, + 26.623359680f, 0.269155562f, 234.276382446f, 1.0f, + 12.961384773f, 0.366550505f, 255.025634766f, 1.0f, + // Spectrally non-selective 18 % reflecting diffuser + 40.609165192f, 0.000000000f, 299.357757568f, 1.0f, + // Perfect reflecting diffuser + 101.899215698f, 0.000068110f, 5.640549183f, 1.0f, + }; + test.setCustomValues(values); + + test.setErrorThreshold(1e-4f); +} + +OCIO_ADD_GPU_TEST(FixedFunction, style_aces2_gamut_compress_inv) +{ + const double data[9] = { + // Peak luminance + 1000.f, + // P3D65 gamut + 0.680, 0.320, 0.265, 0.690, 0.150, 0.060, 0.3127, 0.3290 + }; + OCIO::FixedFunctionTransformRcPtr func = + OCIO::FixedFunctionTransform::Create(OCIO::FIXED_FUNCTION_ACES_GAMUT_COMPRESS_20, &data[0], 9); + func->setDirection(OCIO::TRANSFORM_DIR_INVERSE); + + test.setProcessor(func); + + OCIOGPUTest::CustomValues values; + values.m_inputValues = + { + // ACEScg primaries and secondaries scaled by 4 + 107.831291199f, 174.252944946f, 25.025119781f, 1.0f, + 168.028198242f, 118.224960327f, 106.183464050f, 1.0f, + 140.030105591f, 127.177192688f, 147.056488037f, 1.0f, + 156.512435913f, 73.218856812f, 192.204727173f, 1.0f, + 79.378631592f, 72.613555908f, 268.442108154f, 1.0f, + 133.827835083f, 149.929809570f, 341.715240479f, 1.0f, + // OCIO test values + 18.194000244f, 33.312938690f, 4.173166752f, 0.5f, + 80.413116455f, 21.309329987f, 332.159759521f, 1.0f, + 83.467437744f, 37.305160522f, 182.925750732f, 0.0f, + // ColorChecker24 (SMPTE 2065-1 2021) + 27.411962509f, 13.382793427f, 38.146591187f, 1.0f, + 59.987670898f, 14.391893387f, 39.841842651f, 1.0f, + 43.298923492f, 12.199877739f, 249.107116699f, 1.0f, + 31.489658356f, 14.075142860f, 128.878036499f, 1.0f, + 50.749198914f, 12.731814384f, 285.658966064f, 1.0f, + 64.728637695f, 18.593795776f, 179.324264526f, 1.0f, + 53.399448395f, 37.394428253f, 50.924011230f, 1.0f, + 34.719596863f, 21.616765976f, 271.008331299f, 1.0f, + 43.910709381f, 36.788166046f, 13.975610733f, 1.0f, + 23.196525574f, 15.118361473f, 317.544250488f, 1.0f, + 63.348674774f, 33.283493042f, 119.145133972f, 1.0f, + 64.908889771f, 35.371044159f, 70.842193604f, 1.0f, + 24.876916885f, 23.143167496f, 273.229034424f, 1.0f, + 44.203376770f, 28.918329239f, 144.154159546f, 1.0f, + 32.824352264f, 43.447864532f, 17.892255783f, 1.0f, + 75.830871582f, 39.872474670f, 90.752044678f, 1.0f, + 45.823104858f, 34.652038574f, 348.832092285f, 1.0f, + 43.635551453f, 21.629474640f, 218.454376221f, 1.0f, + }; + test.setCustomValues(values); + + // TODO: Improve inversion match? + test.setErrorThreshold(4e-4f); +} + // The next four tests run into a problem on some graphics cards where 0.0 * Inf = 0.0, // rather than the correct value of NaN. Therefore turning off TestInfinity for these tests.