From 969e64725e4dfc065b5299532c3bdbd073cada53 Mon Sep 17 00:00:00 2001 From: TSnake41 Date: Sat, 2 Oct 2021 19:14:10 +0200 Subject: [PATCH] Add FSR and custom font examples. --- examples/resources/NotoSans-Medium.ttf | Bin 0 -> 452608 bytes examples/resources/fsr/fsrEasu.frag | 3905 ++++++++++++++++++++++++ examples/resources/fsr/fsrRcas.frag | 3897 +++++++++++++++++++++++ examples/shaders_postprocess_fsr.lua | 232 ++ examples/text_custom_font.lua | 18 + 5 files changed, 8052 insertions(+) create mode 100644 examples/resources/NotoSans-Medium.ttf create mode 100644 examples/resources/fsr/fsrEasu.frag create mode 100644 examples/resources/fsr/fsrRcas.frag create mode 100644 examples/shaders_postprocess_fsr.lua create mode 100644 examples/text_custom_font.lua diff --git a/examples/resources/NotoSans-Medium.ttf b/examples/resources/NotoSans-Medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..25050f76b267a99ce10b820ed128cecd351f4f63 GIT binary patch literal 452608 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 z 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⪳qJs1T(mxY5h?q*N8`n2* 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 z zth}62QK8xCcq2x7}Rq{x$dseH722k4{jvRQN>yGiA^t?WDAW!0G^UP4rR9p&&2CWtUM)Op=#w zDK-EA5E_miqXDbj4AetHh|^`jHvy0?vO1;T$&ne^sWG=zA&03c8K|HF920)O%f$qL zsUKi^u0mI#gzwCm4?=^^DoA5KqKSkXV%MrRX`_(>i|~E2rgR!z_27e}xeA@wFHA+Q zW^J}nW!OTj>HMWR7E6)rwv5o8kik~ph-@y(xyB!yft)%=7Sh^_OYqp@oSrdYB--Go zDiWS}HmDH+`6~P63ZbDRpID<)AB1Vn7G%jtm8!JSqX1nt6+$~VB-FjMvTXz;)7o(% zOF2te0^t5_ZK2O2(?Ae>MZO|38__{q7;Z?y?1<|C{)FmQ#nPr-F3yFol(ww{;u94^ z>%P!?Ei<0!FL>uoM<4F%d-&M7cNWa8ow)kYp%YiHo$1jzb($=by62Tw#8+O~fA!20 z$B#cTdvfKm^AA6K{``Xve*P7EXP%{^IP+za$3!O)^rO9E<&$~bohNPl9@P9XoZ+V| z4qC>dj@XGql=6=#&4>NB9X_Nn7lx^|qTXJ@zeSScc^#I|i0?SMK(IihVIzHFRn>t5(JYa-)Y} zfO|0}D==4pweO;UCiY~JLmoi&6#>Ia>Kqvtz*?}`Y`yoJB;Vkj+@tV=P zIs?Eb#1l1EtTi{1wW((i_NWk0A0%rEfG|+1NZx}{3bIVNLpmBJ0}5azD2M+TseLw` zOgEqbQ>TG&Ydm-OAC4SPCT~9UmE#`|-s_jR;g34IL#r`c^||-KMEN_`2z;sf=KE`Qyd_Xnx#m5Yv7vIrV3YeZgri@+L`=y z4{`d3Io(a!+V>qxh9q)lsd~LD$Fmm=`s(_!FTWzboNU}AesW=x?2r*8W1Gcqh+)+s z>}^`%x&ak1_ZWLdMH?G-aWKsp%!^aNSyRAL&EU8km zZ38$5{$!Xu$h_f2uqfolW&mgo%nUiw%){$@_Q>cQEcZrWb%^CaO{)O)`X2G)U!_0YUi5S_rYQ%;mXFSditF zvekQTTwXrge_;zW$lZgD!(ZXjDQ*1ul=Fs=KH+tnm3PDQ0p%UrByhfIU!nC40f}DF zA7E4DDLsMK1>=hf+9f({?a^==IkxpGoh+LjlW1}n^8!e?M2MhzXt(B$X0RKGMrxRZ zm4YNFi;d3x0%{DmzjT(Djv#Gq>4^B^A0x>Fv+Ugw9It?}0xpT;WSK~BhX8;i06QOm ztj04@EHY1!Fo2N7^S7MvA>%30@bH3#zP|Og^&hqFALf&i@aKz;y5ZJsL+_v0mSLYT1HEx-ze zm_*7F-pmiH2uB#KhQe@I1^o!_{}$cgeTNU+JEU)F)C}E!=*ay8u-sSIGT6s64y~>o z)+Psahgrwi(tX22_b-hdcw~J1kprye^pPW{FMa22Cr-Q#UB?;$ritof98i@U>;yq86g>9vddVL2*`2%3B)jZsDIe@tb;OA)#Bp%^AiMDb!eZcKXIVxP}|_X zLx)6N^1o6sISu-BKl+p>goNFQ(}8uN+@UTA4tnhN3~o-?>~mCWfyw4JJe_Y+Sl|ui zhdjA9tBK+n{EQPaEM$x-1PSpU*GQ=bmDHN*YQR*<9b^|JB&~H@B&1XA#R(LBjTWh= z;)}eWh#Q)tc@~c@Bi~ZoS;UrR()vvCw0d3xS0cPB!D|?h`$nlxFejmgP!LMKf&^M< zf%F01Ec!qmN*lh`x9d)?Su2O{FG=~E3l!`hAK;x-{tm+@b;!$ zm!X#2F9+)J2Sb_h1_Mk$%&H_<0t8<#1;4X0;RQ#*9Y4PS8FaClG&7gmMVr$N(x#@G zHOwx?EqR&7Xl`F;S!KRF@$$4Y^P&0KKAmb@n=v%B&=_M&DdMsPg`kAZGa|kQex>tn z;Q4df5t{VmxPT*xVuijuCU%2S#fw;b@>+zltSJ4#kSDxbgGHx_NBk*kT-L^Pia1lH zX=Lp9a<#^&Iq`5$&%;+Ad#G>!{>Uw!Bf}2|uluf|kF7_V*zA+Xk3BI1MeO{u!J?Y> z;)&v2qElAK(4D4@YSr`Ta}>z=uv3sVsX-QjO${AD?86Zr=&}l32HQeZp;$%B-G-Rm zP2`|pDBS6?xOLc$7<_~-V91cp7~_rwoes#!z_-{oC{5Zx#Bc83U-VyPfk3FGySCwt zeGmIi=C8M>5AD*ol@YOn7)I%Pz4e8@;)cFh(_rxN#c=Pg&f&CiN~$KhT#7Z>!{r3x zc;H!z79*t*UcCck7?R)wH*q;0D6}|zM1Gs(58($O08x_$5-5H7?pudiU%LL8Tlc?r z@5HqD;-S^vc^44i{L?qI%f2;o+}*+L)5b)d9MQ+bX84F!^nq^9~HlzoR0+e1Oj_P?72&|55N0O_Akljsh>vZeecG6Rgf-J zly5b`nupE@9e^B^8hj!{nZKV5MRW?f9?lZ=2K`#d@(_8kQd%B7on)^-QROg4T&pIO zBmRr{qrmyqx>9FNZgr#Gd`6R*DdGeX&GpxQ_Q;ySsa0iK^vBn()0#3>Vx~!Rd_C*J zSyK0^8g{YISmQB^1$uU}9@^||=}I`KtuvqfMY7A;(BQP!H`>`3u&&^r6)Q0oJM91d zL=Wcym4Fgb%2R-;3}(=1FdCeiiP}Yu*~E!0V{mz$-qdMEx3>0+i396POQ**h`dXSW|3$!jya1Xxg(j$MLN#6_vXaZ7~KW(gfm;y(7%(d2K(##r_!RB3aG`Em=Gr`wt(q#sZiDUK3><3#D zK&in{5wRc1J_(YQZ0A&Lo4W>O{3Cfqv!9H1j7;2is`WrPx?DUowKCJ!yFAiSw`(*U ztvJ@Tc#Rn7-fL2srn>iEWw$T5auRLLjZ@8am9@Tt{!o1_OG#;Jym-<_w0G@#6hiQR;{V+ z{_Nh8sJEy(R!RIOog-BSIaEkJMw*TQU@vGA_y$W{1PD6x5lSZJ;*sw&&{3H{=O@TJ zI~;f18=vl@1GVM;^Avgjc0p*flJ1_CmcnwIy|HZmSmW`*{!{HAd*Qaz-Qx9Q-EBQt z#xaxmn!~-vn%nO<{IzdQJ^s|u>!dZI2VGyqen@phqf*2=hx8eKk{^U186tf5<`D%a zL=(Qnrh?vYet~p6@)}DuMdoZA`vxy9?>IlrLfPH{<<3&nwL96}$L{axedze{hkE1n zA7JNCtganBaTo_av)KYN)gNPQ6u1YSYj8Zokl0|runB!BCdTtHC^nwdN0*a)`{ zk-D$fkXI!Y8Xhl0*XR=%Pr`3ee3fZt~Eh6tV!Ck#sr%XvUT zY6DpTj!#~2C(INu1NH%YV5>6=H(%Se-Ws2(t}b%f-o`%tuIcQkt+lJZF49vz$i7u| zV(a{(Gsn8k4`^3#HuGJ7 zQ@kZZC`jZ%3#F|Uu0U@3bEgJvrCgONl=2o3MG@~>PbMgI!n)W+o^FEYY{bOh@jVjg zV`O4Nr&S9mv{}NY1wR5tL@pAG#bt4tX}!kk}SotxaA;K=uVkqSwdHU%EH>Jzyp!LBDCvubqkAIEREmVL>L8M(su( zgBlB!oK@WJ1{oF8Mn0qjr}=@PKEW6ATdbh~cUq}rzk*9AXAJ^!G>y6uF`*lOaJ6=| zBpMhgUpi&@+~*9(SIZ|$i>DeMdykRT>|1(h%;nf;&b{I2zQboMS###>ryc}FMfCGI z$nFSv4fD}IoWWDzv_sQ%K$K9vS{73Y*wCTUfa^%BQLGtx=b>O}6lNi03--m-tmCXW za07{Lit=-xh|>3v<714Sx-Q!6{xGxEp6>bJQw_V#vyZGc94QY)_EqdV>%z-!(_1sn zKV;B9J)8V1yYuCYK&@zQ3$%Dgva1H@JIJn@5^(mJO`JKFm6 zZ-n<^Es!t8E%?bV*9`h1hdImEv8e= z-u%M*R;H&fy!g)L*)N=gjn*$j zSCii%7R*N|jQ3Hz7Jam%j|HTs<)c0j(4FMmH)_;$MuX&ECx055c5)P#+}T(Qc^*$e zUO{OH>x+Esg>YVjP&{3O^06IM`WT@oe!x~0*Ez)FXP>yZK0EvT=?j+r4%XH3!#6S~ zItKPUocSM=waGqc>dgAy(2$`kqkYlwlD{mJ{Nph{m>hG+_pxsvtrqze@RSJUWkgIn zQf;vj`=BRKngy2y1i*`0QHs;tB@TqjLSc!ZQV@4j(4o8?bE|A}idKe<$w)Y&Q(adO zIDe>Nf0@5%vaGSCrimHfUEg%)nZ|>`=z3jGkFUGll=&(udj_(Dd=zH!KFA+GGO2G!WapWXe-8}-_B`2z($?ccT07RX23jvAyaI-X z)JP&WIgMc_BA;-x2{uV7F;>zx)0!c#lDtZ##evFTB@S`Ov6>~15{ zRoM%$i_#H*utK15C`d=7fUv2=b#yiozmTf)q2posw@23`Vg2@IB6hmY6ZhtCY^AUEO{|dNYIy z9q?B>3LKyTR-PSr{(*hb!XC4>ZT)O=j`ae|p{F4o4h_zA)g`nYrHxmAFzLaR`q_ z@OyHy_0Y>Tlod)@Pjr=SyDDo{IoQTFQNKx#A?CkL!hxXJ5&xd6@RFJ%e=Xr(=eu+5 z@IB+4un-H8Hg6+;zl+L*()Z+WB%*?4EbZ6G8d{H57Mf>1cJj?zo&LNGca|mSER9E< zv){d%I4M%&kzUUzBxZcRGm*TpXjZmlq&z;6|?%>xcNr~DcWfIqM{sqN~@EoeG(ixv*$g!5LbDXy@ zWTAAO@Clhf;Ff23E-Uc;Qwk1_!yt=*XM;&6rAa|VC8kUJAp&RoEL_rY8h-HVt3P0E zLmwErX9y=H;eDuu5IzqCeXYKj-oNqp8)o@4#)0pvA*;*8KR{NWM@x}^{xo_Wg7@{G zq-q8OGD@@ve84#Yi&XxA9UJ_c3Aq)8*g;{r7H5&6l1Hy1lqyMZ(U}M^{1zdedEj4q z9P%B|9%T{d{_G8j>bGhO!eHIdv-uKHUPDyuNa3Q8Y;C5A z0hBt)buda|of0DxKa!PbD=0OGdaJI#@8Z?#r;`t{iGixIa+S}W7tm>vU#{;(@(*kJ zt2}>ApT%r3-+Rj~kKS^kVSFF9j(ndlE7!z={e5i{ptB!-s#odE9riUo-}o$k8}&u? z!c1Z+jx1cE)@nqWn9d1TrZxaamLKjQ=(^B!$+?0pT%*?Fd>T}REl?B+km#FEAtuyN zp+<2elp7Z;;(=DZ$>)*L!dxWx>}_82lLUmUl%8kXJX*8t(p z*Q0MEU-(C?*D$SDk~6=@59~AYx&;P6Ng}E!Pw>6*R8cH~MJQ9!39UT83P^5*>AqP$ zD&@+!-6h#(pWRoqHu#24GpW%8D#gW1@9dwUcfbx6@Mb!T30cd4))0ZB#0@QQIZmx1 zu0yZk*Ovd_Bo>^a3}svj-p9Bz8>)HW^J~MyYfr8X4Xr&X_KYmz&&Z`ui^vYa6Y$Sz z@e=YDI}#RT)8Wica+Kq=CGt8*nh?>5yNhtV3OAWhcuM!dkXe;3On=f93X%`m5{D*7 zYK&xxI_TJ8G7iS~j|Jm)kGaShDLNdud##eat@ItMVI&jfJiWy zVX`30gFLipy`)3{{AcV*>*YQ7to4ZPOzvYvY&4n0{*C&l;xf5Lj3B81Ik}|Q1JECq z-R5eL`vs9ess!%AS_lRsMUwvrj%``bMHie%=8?VG4!cY0mM*3GRn>|$cc1Dzx%#(*Z|CY+e5q0?Ys4Xwi{npv8tJU2M_F({i z2=0v_cCgQU2m8!CFq%y^w&c5zs|2Rx4`&F0$0sv_(yDVm95F8D_x47{>=p$P6AsadxV%u=^5mS@GEQ(QHDV%z_1qi1& zbIWaU!-pXSuOLn`qz(|rkOvAZ%w=XQ2C<6mt~AlNyEnYn+p8Yxb$a}{g|;`^t7|(% z&!yLUX9oIxVW%tCO6!eyL%2^kD=xEcJ#-LOpl}EKG%MiTL2b6{DYYB#5zY$#%DNyI zQ~ZSdVuhc^;YT0D`1m<;h)t;E*n|Lgxu8N72)QX&NFR~P^0=Q6sU!%aShQ6Ci4mo{ z2r(|p=gV{5nI3&+#8%tRYl9y0=V?{LT5a3FZW7n%Jfa5$!c*dY$Pi@9<7G@ei4qL> z<6zn%H3+E!(0A4RIM|fZh6*Pe9o@mzwra;ym7P^po$O!AAAGR<;zf-8OQ6q3IDKl; z=_97o=cSF^tW()1kkB!@@%}&kSjxHn(~pUY*kgw_x^NbOvX|(L?`MC5K8rk$-Y-r- zU-t{`i3B7JapCIa`Sd$y(fe)w9kS?oq#Y4=(y+8DYzd^dbFG4|-0JttSC%v;28Rne z+g+Au*;KT&d!U_-mJSpa^!GIQ8-vNG%L+mb{kt2hyFk|x;W4%ZnP(Mx`=2R-9JXyw zqQ8aEA%XIY1P9WO$nK-lKm?di_yF`CIG`=rJjY&kK!?(D?72f2N%m!PS681p(~}q; zSspS!7TXt}IXd0i*x6o<2cu3OcI)qjLh?raFtyV2h88%) zVMIJ{TAHWxz`mH@l35mAIG7yo0>A;oS!-rhRD8QoCafk_Y$muWir`2=NA>71%A9n} zi{ntW*e}$;&eA~b*65MXgm#i~LGf$02>Am=ny|Fsfw@hKA(Ry3GzT9}l;Q{NaJ{08 zl~HOAf{LklgmeZQVz}^fIZ4M%>6Gxu#o~n)JG17LI2;ZBo{9d^K%3bA)7!6MiR-6N zjF&H$?;D#t(e(Xf-Jni8sy7bIv!Y$$Q`dF>D&vOzT}PXP3oiS<-5n!?KTdvU4_|Mk zm=inf6CcGOkz62T8lj}X(@GfNxk%SQ83DK%&Lyh+;+r9#9=>1TSixW@;INTHAhf+| zU^7G@IY*fDYyUiSRh6%DyzyePeQ-9qXJ6&ouDx%&_0H>FeWd@SBX8Jc?|F7Om?&x+ zbh_q>u3KHcnq&fsI`-wk30Ju};43Uj{x$1={U?+TZwiTdmmcYwl~|^upZ!y^ z{voOvC_qjg)&=?|9z)+;;ObK0e@O`=cB;X!Qq2GkTh~YRP-%G0!1l{>%>dL=I%(A5a*0rr zlQOB2#|@#q5u?4Ki8Q=XERTzzS%wfu_&`H!D3UTru8fp2uf%_#5F_M7k<<@QvFh(4 zl{>HZDX#lQH~y2|3L7UZAWDi#bP=&bizP%FoHB02LnR!C zO|2JR=6sRedU$#^5gaM5tleEx*cx3NFBuHy2b+A+s;1ha*5Cs@W8QJIvnUXWx=i7w zimoB6r8CSZlM4)|CBZDs#1XN`2}D#vI=Zgt3hg-OQ)9{Dh0qO3MsUzjN1~;% zK2}{(Rvaa)R+j@p9AzEk-^zmM!TD~Lk|LHm&rY%C<1hvwSjm3XIFL>)xipc)4x3ln z++(55Uzz`4b$Ea6fde@1GBDUtJ7x79 zixVSkPtR<3RkUSX4DYggrWbGkW@%ALX=%y$=m)ea?+K@Ez?t)0gF~gtZ>)4?7u=~5 zt76e;Z+nHPt_22B7eYngAENV3T!xjRV(N!dWqEc!b`t@WWWUf0)Gn? ziu*BBdLX6Xd|vK2G;peu?^il65b+3T!c^B&rnHnij8a4HKwvxFl!*Vi_ShKYcTnO} z>UC0`IZatfQ78b67KhcGiPUAm$9z1Iv&u!9v*LRR&?=7Aq|Fvf%O*^lEatzyvi)T5 z)opiP+j*>zwcfF3adURmo&Eitodf+HN9D;8!<`*%H|)Fh!N&S)(&r=jvl*OYIJFphDsM#`KdcC_iQc+n^xn(B5zd3`-99Ciu z1BloGp@%4+ja>|ddQ3#NE}b%qRYdz*+7)?*SV1O-#= zxEq!lgV@{KCZbnpMqo|Y-|4|v`gknpi}d%>;&2e%4tPTTK#&S^3S~lWSr(*BlQ&#W zj8mmi^!-kCXe7};n_3SwD~FIxpm&+xb1M19yJUh6l^3VeZ+_`7J#hqV8{BO8((Bmr zW$C2IB%&qN>{JW?TcTzbQjee)P@Y8w7fEzMvw1-+K&-tO+a-S1Fqs_SSska*Vw3^0lgGzcG*sO?JQUA?Xt#YRx$ zwH&1;jfc~}Nzos5(0T^?r|~lBVnhy#lX5HNHo^?UfhTg)ZS1^LbDS_83^_FP< z8%(OGXi!(|NAq$>-YBVNVz+2wlYSGvM)nEe4k+^3ss<M3*(h=+%tT@u)HOlK-iYIj2L=v{@9q1pz0R3kY5w?6 zlP}YO#(Vqr@*Hl&VdTERe@g%qsKqw&Iw{HkVvJjp)DQTjP$89T;dNRlcDwBGWOz6! zCI8>LThmWa?pP{aK67Jl=YjYn-unB^W0YqG>IUDlAIkqGJ^Vs_qSjxC(5A<2<9$GN zD{`4A_)KXv{D^X`mJVQ5dP5#xUauwhNOt*dp^j4Iz^9Qg$-7V6 zUZOPB2x_C!20NJY>z3xziGrdi6+%^%MXQUd1BKp5euR>JrBW!;7~uAxd^;Mmq_=Z3 zF2dD8t&LY`a1Y{^RNAuM6NwAg$0vPRwZ$!+-nwEZ@Nb$b#h#4o7y`7zX-Q8V= z_H%o0ek9;^hX)${EfKBWtRFhv&Q{v;`~i{lnlH;uxUUkzr>JLJux26!jXj0rn{=QFKY0ehE8|0Zp-!Lqkws1*Z}+ zmHB}~&~&r}9DuE#NCh%r3le#-UwS;O5_r8)Tle3)DN(t-J*a&Pn3i-Z!Wy{zMbNRl zMNuZ~D(5^2sOj7sIT4MEWLPFO8_v$eUyzJV6&V5A-P^L=+roa=I$eC5t3m{0V3&AFL| zd|T_7$R6$ul}}AgJ!Nn&&p!{=ljyJ74f>ZN5`0l2ki|_&gXJzDf(j)Fz0(BnYhwB} z%m7W6mt-_sfVItVuro-*eR@!bG#b9)7lQVZAV(t@2$#3P*}uVdxC%EMePP z=sstWxpO>xVW7qC?%vOCCWLSf6DmM!lw}J+C+C&PB9F4lAnHLa1ej%y1Q_j4pHSf_(czZG)7LGv4CSazdqzjs z?ifh0meJj9Z4dV*Y~@z0+dJ>Nw<6GU>G9s#*&j?!Sr_*onw{A%)59qK5%h=(G2xy> zt{pIQImoMs=+r8da$?UD)Tp`9$ca-SsL)*Qr^#{h7|1FjSt*=b5W$e!$>u1h6j`I& z+X7U!7?JzRilSI?481^_fIV1jfGMMiAscfiGQ<&JN2D>rWtvWBp&H~=)){;H+2S7p zB47(m4um}AQ1M2Ji|+k*g*I<|{MpD4vhp%h)JZNL2-f(*#r6G-HJ#CWlRsun>7;@_ zP=FQkGx+ap!WHWXABx$hH#XedZ~4(T`^!ss zeRyQ!TIBYH5LE%n@YWha$U6ML1fG)ZNAjF9bawOw7d|&I;Rj>4Y@Q>`=PjT+{d_jLs?qC7% zB3{RH@%~K2DQuXB7)2n<{dRZ~jyVsn|`%xSnbdL0d}ug^vZCLuoi_bymS=&T4+C%1nOWSD(WwtJyB_ZZ={%Kmn2J45hWr z5N{G#5QZeBYw=Vq1rI`T63GP$h&zQ!QE3R&Du9_tQBgSwLoO*KHSe1D3V`E)Xl})TDwPYy{Ue_sAQpJY*cgqcyn7_gF9z1uP{2Xd-tqao3%J_Ivu7kfQW?h)@gyUvv5x$pbHSgfF~s`WBrX#O;S{b)LJufl z1zT$jrNlRBKPGJ&6^0-_3$=H&HWidun618e;pp40x%QTie17lo8ur!2v4N2+NVP9!sX&Qj6IXg=tGMxbk+fLDQpg7Y4 z2Z%P*9qbj{HM~XV0lLhoGt13GIa*P>R+|`TJ@a^CFtKXYxQ0SQt*m*hZTD^isXXO{ zS+({TFU`K|T@&wqH_Vs4d#20_D^PuwB_0{y*w6k3^I+mR7g%!|f#E!4QFReKaS=gD zF!?-;42nN-iAsedo{~_iYm~pjY6hhaoL{SZgI*;5{t}|;#DjRgdN20z65&JAoEiPF zh*)0~k@Q|qf<77LPx7P|QwbopGT1a~%|!rgz&6P=nlOj=z@-C!`#g?ILz3(T%2hAl z(pWGWDZ!uLAqCL3-N2(_kq{6v@)25bk|87K<)C;A211Dx6+=Z$8u3#Z2B}N~$(*Er z#iwUxOF8*lhZ5Hu=xI58aG-u|0!GZ_t{b9y_pBJ2Ka~6(r#>6Kae5rM_@g&2&nDjx zJw3(Iy`?1*jZ}WFk4spepLBB)dFW>bM~G zOVN<6t$B!ofQGiw!ppphaknEaq>{sx`IWfBeCLjjwbIDlGwEXYRN@J#9CZ%|7nBr>1rX|hKhZv^`(FPDDh4JpuLuvw6VQpgtvt496?Wl@rz zhj1x;YecR%`-{FRXNA*{+nm=l(3BWh8_&|TTq}O&VF->DsIY(ijA61P`C~C}eu>L8m`Q zJ?8QeG6wn((=XuYRK1?lUR(rEkifzDbAP@)TGE7R7BNqE0ct-{7!Fa&Ht$Aug%aKCU1 zF*!9=?oTAHJ@v^CcOlyT_U_x_U5n$9XhnG_uUP!%zMlAauxz?*&!Oqc+Tw=12l_9D z##%dPhl@htn*3n3KLBaWeWAtTYpARD3$Xf-YR!7EFfeVZRoWVqSW%8EKhNdQ@jF5q zm={~h(a|%?07d$|RE93Oh}g?zmA9OMd-D7>yVpv4^O`H7KuBvRcVq|qX7RzU8}Dwu zdia`~RpkpcQFlYQuq!#fIMO#aJvOk&<))cE0{zA*oRjw!aKB(fvNh75vA00&RX)jk zOCDFsc#%KWF$~B#ta{EqP%AwU34E!O;zqe=mp9>cLaC~M?G>v2Ne`yYfz6l-O&OU5 zrnbFb`3fy|&j_&ZR4Xd=)bNXAZ>TcoE9%LYitKbTs4}u_%>fZvp3f^7#~wMq7rArD zd8bP0w48USl~T?-Az(q-#^Xp@QJ&pjaGh@{k(CKwJ@UAKZHNO*WDhLmT1x&KzSg+y zp;#PJF+Hxs11^VVv9Rdu3#A2$>5B(_PN@IVL4JAn?2BM z@nmP0I0ri){B%cRe&N!FDzm2~vUu<-eHzWUUMuCKq5phNt-`^yIn7R+QxwumpX52k z=?a)HUPR9)j7p(UNzZ*x^bP#T(oEtDp2#Q%;oqW?eskWo&Dy0mrAfn*M11U@Fefg; z>a#=20cI1i0TRC=C8BZSrs3^U76;Yn5>)Ue-mVPKu$eBWWo4%?G}PY0KCwD?bhJ0z zQCQGzxcT((x_5GN_2B4?-8yTrU;;OOzcI${hu%h*C+&#@T)2$zHQ~3?s1O6gUV)>K zaGC+3B_n$TK>NUT*6v5oa1ywJdxCsji#1qiAUAocj1lfh^0HadGk8^8q*OyJhCfi2 zXlpe?Bse=^0lH;jT&`s`$Su=&6Q)0 z1~`8}11@hjIDo42XnWbP+n3kkJ!kps=QA#rc6q%W zl}B#X{#*a(>MpBkkHKkhsoHSHLG>W5gP#mwC-1jx1aG=~BRWD>428ok6cLd|g6RIq zm6{2!CUeigz#kfxi;7np+IkWA$QzAKA2-FAuVJ@_nT(y2$=_t3?CWGk{rLx@Mctn? zb(@mk`MKFL=Wz6QSWU&o;>d}E87yORtUOWwMX}=H(OJwnObf9bYspRZK%_39o<0k) z03(IC!4=$=PsO$Ej@(qmKY2Saq&O@!)Xq~V86oDFPBn5^#f+K0%qPx%^@o4^+>IYK zS05f>VmA9}@}jlUA(|tmkAJtgJGk*nRmtaH%Es$?AFjr{I6Am?`w#QBQqtc9K z0?k_%;5KqEN`)G!KH)GV^z#;8BPg4{auj|#Vajp2yg6Q*GsvBf1X3W`FHSxM0SjD8 zF^S@_8r2Bb;ITpwblwowMp*w)0)cb}3@8H(mbq0T=}(xxleQceL8Kcgj0a7j55`9iYIpI2ZTTh@W3Yg|lwr}XOhvm$J;A8(g*+(f z`t-A|K#@$WrG{#C;y4J1^2vs`}1%*7h>d%rX{BS~dH1 zq3)`p?&uF*MtB?Zk2U`z^u0vEDkv%TQBr|~RZxHxL?SgU0h$Li2&lt&Q3;Zlc5VWg0b24R?dec}fZCkH&rNVJ z$Cb`aKxK`zlWDcbYZF)Pf8pe{hAg#8o2|e0#6O2;*Id~JyL`LH-)M^W&StaMlb`g| zxbtGUtnbq2R#vXFxe^nGO3<k;T| zZ;HQ1`(e6nd{VfZU#B?>^Y+Mu=C#at9-c4ND*g=Zk+!bXD&qPOo*$sP6MEy6 zTwvc^LjH3vT^D+hlR?kHb>KxS*CWz(d_REm6O`|7-QoM;&F?u+D9;_(d@g*6%5!zX zFkJ_ajDv?h#IL_1t$TYe(%S&?4j_^Ml#(D)WqAJk_RA_(qqBiCN z?r2*vs-QF_^1%aA&T<}aQ0u~ukceT7X>Dku-}<#->(`3oG^AuuTyqG=^ZZ?klQlJsiZZrkx*@=M z6)XfD)dN7deP0pLb{N||;S+QDd8R{S{mVIzKVA}Wl$tFKUd{?xWxk4C<{U2FUG-H} z-Qkmll;AF~+lw1YyZY7n-hqi)nHh9iaD&V&vo_bRpuN~ZlHZn>XS1RC$Qbkk{DB;3 z!%8+!{40V6b32x&TLLJ$V}aU2q3gKPllhXRs!%;Xe@%SV^tDyc*y4^CuVJOWB! zm(+dp?AK3Jf2;Q2D)m;a)~c%#+b%u#=%c@S*wL7q+u(W#&x0)C?}V2=tiK1pYBSrQ zC~aQ1NY_#OIM|Eh={_#9XM!+TQ}-eAK=pz}hy396E_z}VKaZ&vA z#xoeu$JOkaN4O=vIrfayGe^tQ`hvYHiFf{$*oxXv8~Jx2hBUz5q~qnVEeojbfFe!y z;Q%SkQqUSxmR<**;Vr>ICjllGzJN~wF{7(yM?c$k5_aY@-}~xUznA=M$Cw zF@WVMRap`rHN4OiuTM;itv$UpyY(tVQ`_jI*!r8I=-^*$RF+06K7oD`{^;+q z$96*x$C(U}%&HueP;p>P6;di?O=N@uYh0L&(#by#8!roVGq(oag54mgCXmgNG9uX< zSj#xfY2W`P?L7eFI;*td``+nAGn(Fel}5ddx<V%dpupZM1-f`B?Z`zD-(7@~P#n z{~Bn0T#f6X?cp7?|L9?mqx}lt7np>^S`}oK2d}wvY z;&A56Eb*56#Ev)Jxp#N=5dUs{?z{ES+Ogtb;xKx!&{PaAPoz{pjEHtr2EBy9N~_se zB=*n<-zmlhy$%0<4Nuhf#&(sVY`# zP`Iw(7!9)VpS<+aPqI((bMxWp+!)w9_&z_5FTt7l&yBIMM`Xm~aGiKu$@%+5IPUxS z*rm^h?dN~qfzRJx@cEGA{Lkr3KFE2X^xpP=-hA(aGJWp7DmEtEBNRNz*R7SFl@Xtm zp3Qu<;91Tmr7?|E7d%Pp4ILKimw%2;T#4s$>v})dRm#b`)Vhw!`H~hZG_c|GD;gPO zqp!a7(lS=_!!tj__f)R`L3HE%dZ}&^H8(5*pFu7uL>2am8iXT6L{5?Qj9j!h$)MpK z7{Vm5OeATd!~%AVm#!u$4WvM_>T*ZA9#IdXyX;`=&2Q~Jw`c!Yq<3WJp6yHSTFWgz z^O(%{-!pg1aC9noYH9zfT6rge#A$!|nD{=sYjs^6)uSLoh#Xcot+1>E1mCO zXHR73-*|&PF*J^UP%$4v$emX=r|FOLwH%r)SS!pljH_lo_jo2kXeY8_sIMFcQE2*H zJ&~qwlWA;V9#pcr78Mg6ws23+uX=h=G5lSY)>cb)hqZ;jlfJW;pUtK(-<`o58GpZ9&JW#p$d1AWnR zs8QpQ=J$O(H;1@sejW3h{CL50z*|1Oz9f*IVGz)h1*kL56l$jcrXa9J*h?A)iASF8 zX`B=HpFxb-pr>(PvDREHAxVGbvM4H;5%`FE7ty?eN{Kfv?@O7zOmVcc1re z&bRga-N6h(R8eXVXu%LH05D;KokmfZ(`+jykz~nL<>VzfZIQtPdY|FLr@ryx75~t7 zKI`%APQ`rCr|ff*6Md_Ufan3Rl{UIKoPODG9K zstSHp2nv%)u0iV=I!UId-pIZaFR(++;QPRz=XCTH+zc%*w{LVE@&NC{bvRfP!UIzcmC#HfF6&rhh_L0ie{J~@8@0gqWUZAg$$-g$(xBFAl zx`?zc4k4Ba!v{f*L~0H|#|>74^tF`w?Qocsq$5a%tkkR&h7BYoc$_Z(r%lKv2>p2 zH1U0d9!2NBW@GKeCQhTc4jLU^Euc|p&qRz5dnP@18v(gA?)62w4jF~M3g`hU%8E9_ zChuG=tPTqoXpbyBkbk|6qx=i(cm4;{F~=bZOS-`noQ*Ka&W zdX02FBh|RcjU)ekDmEp&4PGx;oZ^%8#?5EZaYh=G3M}EQbPvnj13Rc{{S~a^5;Bm& zz}yFC&;#q-8JZ^@STF$t2dkq~8}y$CL5wpwaZhSpDoI8#WogA53o@;~P`7L*2Spou zFu9{(4>(P|ftq>!(EO2(lF_1`r5J{ni<}IU7$3oMQFXX|;O-ojr%0m-#8#23i&}Gy zB1dQVNs6PXxZqZbGMX6X&+!F*NQ!FW zMeQ3X-oy~1>o#(=^$>Ln=zd2pb`!6NZ##4%e2Z3lbd%t zkqUW6B7Zo%dib);fddUKy(fCaj``WCl^4N1e_GbKGykq{;9Ytk>7U zAcs8^2owoJ#4GpED+C?WsrZ81tbANpj*Zf(zkKD`drsZ9^}vCy#`cc3R|&GA400e~%v|ldvf&8SydFA|bQN3eF^b4&4W1-gxdd(gm@6 zN#G*y!&xm6Q0v5llj?=YLV^x2o&-FJ(rd7?1i|8d2m|<~I3U1looqN*^Tkd$uj1$? zfhD}$$@`8jrzazc`LgPkuC}u7SlwcC)l57T>kF0Dd7DbgyW{T~+F>+qx7bReiG<5k z+E!KDAF~xt6r1B=chGGzm%=EeIr6dcxkCe|x&M3Nb+WTaGsoO7u$Pm-O)y94b5vWn z`s&T+Bs*Gq?m{~{xgIHePJi3a@f_Byd;JYD4Vf85}`OcdT4~Xz_rVb*d)%2X~pa>9l1fWtH8h zyrF^q>^s=<%H`vrD$KVCe|#SFt`d-^3tK>tEn!Ab12+_82@U4T7@2=C0NLlCSbjq6 zn9DlZYjXwPVH8j|7m2<&G|&doZxRYKKp>ThqfQ(Nf-Yj@0fmGDq1PF*5~R#5GAsEv zA3nT%_%MC@N5@`!ZS13Spbb7ouCu)^(SW8v&!Nvj1DFG70G|^LT!$b0dZh5W{MOg03#9%3`@ey6L~m4!KnDh^wGfypS^0z4g< z4VbIYs%2^I3!ouBE>4^4m>OvFTKw3!PPB*Xpp_xv!_rzx$n@qtoc^R|+xHYcOY|pQ z&yYWfXaj4j!5Zku>d+uUo7R9(9I{*a3Lv2kD~leus)z*%ZD~|WNB+*6FMIa?oH%>- zvQK>JikDwzN#=O*#q5u>&{OFf@dx=L)ItO?NnlY0>x$1LyHPEu&1SOYnGC@k*fk6L z#WM$=IeyD6Sei#){w~{_{WF6WgL(7sqxH}z@GI7X#LmWbtOs4kSsn5e&`thxT8rFs z?avoJhqcIE&wL%%F>Z)r_AeN>hIk{1UMM#RSf|e@P?s|viRDWC5uI?&CNpuK+Ikj3 z;~e;NJS>s@Pjtuo4y)*7@0;z)ZUvZ#vGDKabD)EdIgr}0ah>LX>m)N>=g&cU4p=e& zobZSHF52^I77(r%zt8#!cigNGx>^4cbL$lTjBo!k_Ss1hH_#(cW&?@P_=t^1!y@(b z>RSMYY! zb` z@gjSq>OeHUw=BDeL9z9pt(PE3?>XQ>;P+-^F`H2LhHyb(?+n;G3NI)Xdq_culP;b> zTn~ak$xW=HAkbPs(TJABpM0xx3d;gDURuq5aI)7C(`kLhoxfy6o3iv)^4jkPR=V%SY-pe|U$&S61(1ZCKf_v6}1?Y(D!$_RVZ1dq3-WHT%P7 zo?*d>fBE6@?M=76>0AS>CFOdH_;Jj|DFhL3Ihq+lw-uE-0*0-^imC)Hk`tD7NV^f_ zQ&gZ~iFjjtSOYXnP;7_Zn4m7@D4CD)Fz$R32GalaDkK1!DDRhmFo;pa>Z7)&G#d+k zuSYo(S*TtK@v2<4#Z7%f`$oM{1#6o=m<_*BGg*5^;k%sKkMA;uzr;e}sWQXXA?DBi ze%7||`K@dBW?%l&m(UQ=V*X?ITeDyON~ye*9B#k{F`utuJ^|seT!&*SKZSZ;F#C~T zhrcMLzd#SBcE&!O1t`3zrAKbH_^^sHjHJ>B3AD(Q>Y(_627qY^l#iXe54yzWU77GD zZeT1y8V3(8t3a_r#ARcI5T*{KZktSMEKrR66ehIVR`G7zIZA9m$0;n8_|*eDzoD_& zbVbRo26MDX?=gCUR*lNxDPj*TWPkW2@o?We@2%@vtW;59iPFB(*qcAUx^@pv$sAuvm?2jYE4d|?b}`FyoB--3&LzS{hJ5sxc}4_G6Y?W37%1??`(mxC;rIqEx5Km(sS zoaB7wn-Adiz%O=R@_vdCjs|=tDuPcdp~D$>T(A;|kgrYCYVcIZzq}%kp852Hv?l*> z{b%mGL<1eXAGgOl_OI5diDfp^OGLzu;Rb=ZWQp7RJEOV~z?Q3IB3 zk@^L|{sOH)S>nO(^b=gM2EiKB?ynGVU{_evc8!W5l%OgtWS5qrcQHz`nhoAD9`l3GBo zl;zq0Q92R^0qfw751;(h@uTnghktm_@e`lCy!^oN}+Tv=25&|pVnkNT#YHGO=2a(!lmMqxTLk<>x?PEZ`Q$B8mI4Pa+*DMM%=x$F!S ze~|%~WUL%9(BxHCQ{9{j3K6ZVp)`8bUqoOx@8{9T8nr0Ej(ppkmwBV(frRnFA(<;v zL+7p_shpG_L{G8#o!51AoSIII`IEL<-|+0N-QGIKs-rHjYxnGkuhy3EjmM`?b#$EB zncfnOZqe@gz>(EQx4P|PM*CgYoqfP=`TEzbwg=8$cbDBbW_NFWboIywc7=k$5cXpI zNBH9;XwnXDSevOrKn7GZcvq13mkZXxiKcBR=M0qcmmoBwA?`f~+q3{%*cddb9OX00 z^GM#q;WB%|?6J3ox6E(9eITy#}5Xpo1ao!o!LkzLEj=9|0wE3pkA z0j#8~AM~L|42p|zb6kNeK-wY30+@m56F995#f#O@k1(DhnEEb<#Z**Wgiv}sNZ}${ z9273&a@mLIGhS8xa+i$w*R<>_-?ZqwQGxJlUXds#W9t{R<7#s2f;^}5G z;@`AJ=mG#O0{lin+KoccY82XiIf6w!1!O`(yGST;Z~{J;1CQ8E=#WJJ=v6!s(uSTt z9J|Ob6||ppeo#AB-?aMLYZ%tq+rILqKh2KMY{Q_Q!!g=+oDa*f_S5{Zq;n+u408J@ zPwoy$ItIW_(D9rUFBFKOabe$7D%36om13bFL@Bp7vT>Uz^0nkh-UYX25`XC~d=-5w z$Eqrj0!d|vxLRk4s4+_@?}Ygw?Skuekf|W#c#Knu6kE~SbH~B$NS%ISb@e@p{#*AR zyrWkXb{$yWx#Qr0ooxT%$CpNKUt&q9dwqK!m|c4O$m#pu1D|h}0i>XGUv>V200IL0&6T%n%+Yh&K_WpI1&O zMTuZrY%`^ILVBF{F+SL+eF5o8Y22iMT8l|3i$w_0;ZP}s%oeJlsBa>EZwraXj=XSbyKCwh z-18wH#+Y4{G2%b~JLyvJHRWtFvfTMFakEQsp(v@zp#p)1WRcAvh!92EQkXxYBiQ{f zk#4d3;pgSwU?0x@{2;zd{sVD`t$RF{Jq&sDtk|}&b`N_fGSp>XdyL~Xq(^-d|Hgra z2#gw?%ULB55E0#2Dmelo=eic|0R!}4)oO*oOdk^IbV@CP85MGBR+*FT)KfxAXn+<| zlJt2g7&l)T(wp!l9JXSQ$%B6C2+7r-ug{RxKux%E`MFkmI$v7^AJi87&Tcz?-@w3q zCyu;jXz0EpCk`Aqeqv?Cqig-_W04Gk1sCXcm4JE-h2J^ z_r2uv`T2kOpIBMYHr7D?2K61*RFF=Mpyvww$#DVkW3mUKW4ef|7{CEZ*;Oje;}>F|%g zmb31OW@_MXcmui?D#vH)+`#iZC~AuhDDxEl0&MFkn zsf3}0I%KdIP&Gt>jtDA6E@3c&%8sZ?r$Wh1WLB^xQlv=&5(h^aUr7(iq&Dxu$Mm^Y ztJ{H!D&@oENa6|xFHcpW#A!Cy)s&|av1lyH;Temb;2F4aiQ#VmG?AS3y17If#R@ic zRM8y^zgP&_{A2rD?&&;QUU9N!$BxSnEN(frZzPjhD6OeDGj-}(F;d=bXXWQ%Hv{F9 z+b=iUcGz9r?d{D7EvT*!c?N?iz=+5Yfjs*U@P!EJ03kb!1r??bzysO)?>e;F*jYJSr(A7p ztDde~Wsyps&l6ahXMyY+P0bdI%S*pCH(9N4nN;BP!Hoo5eIPd}G0L&_qM%{m@ujQ&~}7mP8No zC`#db-7W{Y+l6XYZNMptN#!hZ$prc6Dwwjc{iRA+Dq9}rgM33_1&;N}OtpO>UobH+ z)RVH;`(ovD@z{(%m@HrWq$g7@rL z4{@w8Djfef8fk!8OSO2S_>6mnDg7HSgWkFwNFLY&Db$pI2R7-+gz~ouGF+1hyDqq$ zbS-3fAn}e+UTblYR;^MX6e&{B8V%x@SZHlX5@e*=(El70Q>wmHS6x-f(%BzajdA9# z9;!-b=g~%cHIa;N8GgAkk*I7cF7m|6Bb5-=_lif>uCVl!mi4DFLgEXC^)|kaF0vrO z3sBoxA;*ZQdIAn0_IMBJkz5R6nwX)S2Z3o@Yxkyn4GLM0oJfiL^wii>2gxBFj#Dw><~J<< zWmpVgR466MQ5ZfFOaU>S2ur~R6Ig2Dw3Q1{f&ysbu}k&!2E4wlyLLzW>T36ihP{W0 zqCU{nObiINT*TpAntxVW59s~kX~>g6E)xW)rXut{x#$BkqCQXnf<)*i7zu8L6`M0) zEfGr$&SDyaRF~QYGX&cPZ!j6uq)*Y`(MWQ~p#^WkV2oJH8#9M$_Z?Yn@2=j~yvlxk z>M#oJ$`#FnBQ&1ue=rq|F#BP?PoP6NY(2#CUG?!$0UgHk1V)kYAVezIR!E5yg~ln7 zOlwZC0@~OSC0_IZ(IWbEP_3W~=rM^3$C74+J+A!=^tefr5q7$+21NPZ>=BD8!5*m@ zs7kK|SDE%O)2+sn(Yc{Z(IwI5<<_p!vH^;nB>gE19a5>HAO}T(zG#K!)Gdr zcY)rAvQ9sLzRr`BLK0qH8k+JvZ zS5{s=`5(Wz`sXXJjPJ?bxjWU_CI9+KrN1ipn&|o7_p*Ov%IsS9H2|T%{GWGbfBBiu zFw^3%u9@n*^>h{1Q6`wyyTv)IqZ9icCI6F${7<33?te;N=zofYg8?5zF206}fWfC$ z;D17S5-6$X`=7}DgwAO2KAEOW9{u(6fvCkHWATxl*{dHN>OZF5_0r%D%V(I`QR{U- z@Z@{TYPD^*4`rLOuVV(!$ZGCh$^QP~8buw^0&1dni(9b=HeorVEdoD5(U*-V)AJon z$qOA!kRpW+D9{XvS+K(b&ryaPM>eDZLdIU;IO1{IV8-NI6SwZ%`Cw7`NNWtmJ%ZH{ z9os&c{q<+qm!J5TCGDhQ@wtD-!f1neu97L2;S&^8b5N{_M$XTdHdxEE_8O(kD-TbpJ?0rq@_BG@Y(A*=E^yo2&Fi-TrX4!dl$g zRTfF6NDrkxcEi}CA}MDTa!+J8!D4`qOQGDSf;k3Lbe4W{vrG_2sk0t%U8E~;>_MZX zG$xWeWI&T+(*X}Aoqz(m*R)iD?6bSF_c14kes$(M$EUV^vnXn`*3^s#uG}Vvz`fnV4bU zHH{5dAJ}(Iy5Z{OmAScK_zU-5yK*G^ zJi+ARDNGD?8Nfigv8OtUV*uNwZ@CG}h>gU1M41^^tAHbL;Ljh%r4DQ3alX|M`5YJe-i zWGu27Y!rwE@LjERP>xQ{F#+DmNGm1^f9>35M-N{4>kqPrZ#tLmYDXuMp|R1?dGXz| zYrlVIygU`ctKe%Er^PpbBe;d`Oa=!}P@aW*4q^tD2yzQi2}@0`33wvZv+&9jT&9+d z0z*I}UbEZc#-kuwI3G+pCA5|t_D_cpQ-?PV7N9hopM2m0xBqD6%Ky3Y%AcRTIo;PO zzVU*ifKzASc*E=QN{Id@eB zM|W`7q2->=&YtB%I}hr5X+K55i?Mwjye5Eb9mMd*UB!sTqR1}b;%Dg~;ouVjO#nYY z(ohged@7W(S@nomWC7)-c?MvvmO|az#rj&PW|9dh0i9V2n(NrAO zq-rw3B)lkWqYhIw`K==w@a?b`o(HE5erv#Vm-Jg#UgWnXe2uUYD$UD_aRV*M`K`$s z$;%oH9w#adJuc_8j)ZeA>%3l}WQk+fUb!#U?20>*mSck%f0b*+RTIec9gmhLU9GWw zSDrcAIO+9HYVW-D&No|(W4fYe=l9rs_uuDp?wNnqHEy)L8J{HM@g(0DlFMg+a#MY# z3w_imS_2t?RUjXAC29G2e?=$~iR3LsZW88tz=PXz=F4#sV!clVO6)O{$KDj0n!Dxu zt0#ZKcDFZQe>UCDt|+NeDJIbgepkcW#^&E!7J35u3yQ|zk0fYT2Mxk`N&hsC<9YsR zy&lC3?5N3TME`;!Jg6=7Pv=HNhTVpH`lIh-mgC<)d>N}-S^Ldux}&{$h5hsNw=oR# z2!3U}v?3MZYeRCkNW1~IPeAC)bVH7Bc%+p}k~12W^Qg!M!3&&C{Qq@QPp{mw^7PYJ z9YZeA%lG{0B*xZqT)bi5zI{{YM#skH*>l;3-1uhUdj2hO9TxB zvgQ~h;NN;6Q+?sR^tE^c$I>dXScol>Uc(Dm0(c}-rj zKIaWV|3^x6C$o{FV)Ca#YP3m*zaH^*I6k5b5{aW;XJ0vxw-M%aP5V{ji4R%*a4fBWwY4dAAgOa})I1cQ}+l|LH$0UGu%Q=eK`rg{|>rH=VY?>%Xg+m9>iAAOCpml~-RCz5OQC@v5xSC(D2E zwzpv(Fcx4(Rbd`p@=aTz{91CpX~Nu*w4QTIbL!5yr72&flwt;hg&%Y0DT-@RBZ-RK zE!;O9hvSm_rbCegXawLJ*;%v?h)k;I0)Sl8RYx-pwNAbw*C>iKE?_!Zf05Rtc6zjT zAA4cAeC;b@ld~Z_G1vzTcUCSd^CD0hyn*nB2G|V!>}Bz`0^R@|1+fg&gT25nO;E4o zv4h=yzF(U9G2-T2goosp#yQMkq+}k}BB-Byz~U>fZCHq3d-c^5z(z#xboP!7Fob0r z*(m@be0>$j_xTm4@kNd$2!&XJlOS*f$`~mWSm5&_P`&4U#G(2^1OX`(I5rZ1Kt5px zrh$KR-Ekn0XuRUDUORRkfba$lG+h87;Ps#Z;{R{Q7@cGZ*z{_Gz9?!?zJxeJxjZ?{ zb4YOham|TfBsylBFNFcXV_=}5kRffc)Fxza|LIQTt-W{lak%Ps-Sf`J+-S&4Z_c3q zY<8A?z?JrApM_z)hrNS_fb&E=z5+bH2#8^fdVdHCAQpx0Wr?jyRj$EMJ#Hgh{Got5 z=82)cNF;Gc3pB5L8o&;3cPFKPy7Qt%9m?u+wBvAh@= zLHmGWqbcN%s+=iBVG;n0HiSZP)pHZ&;Mt>}Ja!x9$#wccxpXAL?o9o4z77l#G<^5n}^=4Z-geqS3!CE)SItrp%_r zVt2G+dNN$nYcfrkt=Oj?FqjkIZxOP{C_2@o7Xbmmoel^Fi4U>^aO8o=u};w2L}GIh zAp$u?&MGnv*@nRac|dL|7+{!$1Voc1gI`5sMR{r@v9glf)^M=2d|%7d!V0Qek0y}% z{JH81x9h*wUU|(E?DZwL%zlwBI4!m>;!QYnruAl-596y68px;af#l`xb6AQO`_wlt z{{24nP~jeB++K*Qb8LbCOTJp}wXXF7?#;K6SK+sA5$cKJ|;bU|qte z4xR!-r=dZ{5nJ)Gs_xc6th+y$wpwewk+SJnblm5Q#Ik{1C9N6c6?Rz4e|k<+Tlwo< z@mOt;!OYYYP79->?f8I&Y^Pgn?}gBHcMN~A>oDq`z7Q@5T> zVRYQ&<|Rq-diLtaB3m05b`Or*QwG1^7p<+W9u2R23?lSHj?lu=&-xaX%2`dk;u|eB zUd!_^>G)WGhgwe5b!(Gqb38YXp!HG_`-q+Re@JoRgjQyp6Bp z-{WJ)nExMq?4Paa=&%16`})UPs#0}&kB0l$8~au4L9BDS(8q2EbND3I*+FqYh)sri z0|=bc9jI}cN^giz9yx>O5mDq@w?w$I4S+fivgCLccoYIEKwOoTbqkd%D;3L`>FpQ? z`{&g>dvb@i%pQj}NPCR$j-y6$gwPU1(gG?M8G-{vxESy`>_n>nAyOF!SYs4yB^!Zi zUy?^lh{W?E7nDvN)QRY!A#zH`FVkCw!)Y@@+il?>8h?6co44&>JY4 z(6bu#BxkVh5Uv|~vYAIa2ZXLnhYePV;BoO7F?5;~6$pDq7}kE`Ok7Dq{)@wbjAS9; zr1M^{|ENFk8THY= zbfe;=7~@(p*O#}chdQq}-#c=b`JM07j#EC2)Q6`WbcE_E0y1^s|3e!dNoj*loQPG5 z6u6G4b&#tat#&{w#DutCfopCJGX5DcN%#}3x`X$Q1~N6+N7$l`HVajZJp&|Q*!LYo?F>=~cv@0-BdmaX3` z>p=WSrKSQw&UbrP=r50>|W^_Q{}G-cWqp|as4*$3M$K12zSwMLhDn4?8&Gj*_6Lantu9H6@9%65kB~a;YI}2g;X#dqLz}_qF zX8r6xaqI#m56AaYe)486H127@Ji0q@{bAYU#!UfC|GW!(9;Su&{98PnQ9vn*tQy&Kc zvmJ6_P=nF{9TXypA&Fv>WJeG`ZKKgSa81gK=L;is#K9-qGA0INhXOtj!t7xlrCPEz z^Du@CAwyUn92q2rfcdbF+O3_Y_;SmZBbJ|zuCAKGrdLmAf5VDx@BCGECm#LHZyqK3COOl`eK{{+h@vnB{x$)AVFkaCqTCczGn~W^ zA{Qupfl>`OENn{bLpBAXdc%INEd_;1FIbtCqHrXpwy93mQOy zHTzum&_LHv@0Q-CCPZH{p$%Bi)rcb>7Wy+ifIMXV6>y=5Ltz)_P(h3j?jJawWoId= z18!CT35bJ5V-#`*qdkGp-PO`e(b~c=8&+~BJ?DXxKwkNem@jT9?Eo#3#j zKoiL3AW;Pj6MvyBrbIjT#qLNqfRt#zXs1MJ2P9!8?UkclUR#6S)PCesDOCm;`*%x; zwa)0aL>fgFXsb;31q~)}D)CJUU({#_c@;)wI8|!2Cv7T~IbIWwHMUl2EDd!=OUSJ$ zZktwE+M0|ypVjNomef>*6{^zab`Od(Fy=5e_lEVS4aL0{+YNVa-=Ab{X@84L6I6Xj z>2q0}Dw8^v^agBdb|0p#| zI;FJI67|zDA@>Mxet;zwQ6$t7D6RFmTU#3}<`T3q9vCxdN*fw%(bkgtP30w;>f!_F z$1Bs>WB!^tvsM?atnSnc-bKw#!tAoTYXnJJl%V*pIw0_^&l*1dUSEC{GJYq=KqXLkmCwFi<@(Fx6== za#1A^ynG`sCk_^ZUL?dwM0lHUs>W})?y4&fE-%i{&Ww!^Jy*mdIz+HI`09aNL7bpq z)FnXfhqR*7n!%xf4<#Z7|H$1(Kd_=XdxEq>T3#eNC5mytvoX;9L$sZ4$H~?K<`iN_K4x?L6PK1SbOeRms(E=>XB@lDTF@ zeUjYvTMgpBI_ex6yTJ_ybXnSGMz2(v*HP77;!tnt3)krMmG*Q^Fy5@MswzUr)UZXN ziC4y}Le*7)iYi3GxxJdI_Nbw8`{l<~+UDvajYaFV8SF;C*>8+zhKB>a!^O&?PUbFa z4tYZgd67l4s#loQx*}C+O`@|lW~EH0KxM7v=)E%(B5i@LXThSD33p`NQr|n7NcDtM zOeGH@yfJ`K8f7jCAkL%uA&mAx)FLEOg;+i0-$U_5t2CvO=OLd{K!eI>3qZ$!d^zud z&D*%JC{0GgJ`Yj>q{idOuc9tIU`~ax4m_?7<-+q8)&}xP(M0K2msj^sL>oPRN7$Z9 zw-3Mi?d$Hk=gsXaWz~nPcg^iPm3fi9dHd4V?M2!_jkd0)v8n4@%H_TLueDltI-Fz8 zjs4@KHeoG1z{`IpJ_&6R+_-Qp5dSF;VJ(7uEl6-egkCf^O6JWk0TWUsi2d|#Ost%h z|E-A;@ti-iS9UMν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 zI(Y+mx;OT`ew+<#components>", +// - H = 16-bit float (half) +// - F = 32-bit float (float) +// - D = 64-bit float (double) +// - P = 1-bit integer (predicate, not using bool because 'B' is used for byte) +// - B = 8-bit integer (byte) +// - W = 16-bit integer (word) +// - U = 32-bit integer (unsigned) +// - L = 64-bit integer (long) +// - Using "AS<#components>" for signed when required. +//------------------------------------------------------------------------------------------------------------------------------ +// TODO +// ==== +// - Make sure 'ALerp*(a,b,m)' does 'b*m+(-a*m+a)' (2 ops). +//------------------------------------------------------------------------------------------------------------------------------ +// CHANGE LOG +// ========== +// 20200914 - Expanded wave ops and prx code. +// 20200713 - Added [ZOL] section, fixed serious bugs in sRGB and Rec.709 color conversion code, etc. +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// COMMON +//============================================================================================================================== +#define A_2PI 6.28318530718 +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// CPU +// +// +//============================================================================================================================== +#ifdef A_CPU + // Supporting user defined overrides. + #ifndef A_RESTRICT + #define A_RESTRICT __restrict + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifndef A_STATIC + #define A_STATIC static + #endif +//------------------------------------------------------------------------------------------------------------------------------ + // Same types across CPU and GPU. + // Predicate uses 32-bit integer (C friendly bool). + typedef uint32_t AP1; + typedef float AF1; + typedef double AD1; + typedef uint8_t AB1; + typedef uint16_t AW1; + typedef uint32_t AU1; + typedef uint64_t AL1; + typedef int8_t ASB1; + typedef int16_t ASW1; + typedef int32_t ASU1; + typedef int64_t ASL1; +//------------------------------------------------------------------------------------------------------------------------------ + #define AD1_(a) ((AD1)(a)) + #define AF1_(a) ((AF1)(a)) + #define AL1_(a) ((AL1)(a)) + #define AU1_(a) ((AU1)(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ASL1_(a) ((ASL1)(a)) + #define ASU1_(a) ((ASU1)(a)) +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AU1 AU1_AF1(AF1 a){union{AF1 f;AU1 u;}bits;bits.f=a;return bits.u;} +//------------------------------------------------------------------------------------------------------------------------------ + #define A_TRUE 1 + #define A_FALSE 0 +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// CPU/GPU PORTING +// +//------------------------------------------------------------------------------------------------------------------------------ +// Get CPU and GPU to share all setup code, without duplicate code paths. +// This uses a lower-case prefix for special vector constructs. +// - In C restrict pointers are used. +// - In the shading language, in/inout/out arguments are used. +// This depends on the ability to access a vector value in both languages via array syntax (aka color[2]). +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR ARGUMENT/RETURN/INITIALIZATION PORTABILITY +//============================================================================================================================== + #define retAD2 AD1 *A_RESTRICT + #define retAD3 AD1 *A_RESTRICT + #define retAD4 AD1 *A_RESTRICT + #define retAF2 AF1 *A_RESTRICT + #define retAF3 AF1 *A_RESTRICT + #define retAF4 AF1 *A_RESTRICT + #define retAL2 AL1 *A_RESTRICT + #define retAL3 AL1 *A_RESTRICT + #define retAL4 AL1 *A_RESTRICT + #define retAU2 AU1 *A_RESTRICT + #define retAU3 AU1 *A_RESTRICT + #define retAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define inAD2 AD1 *A_RESTRICT + #define inAD3 AD1 *A_RESTRICT + #define inAD4 AD1 *A_RESTRICT + #define inAF2 AF1 *A_RESTRICT + #define inAF3 AF1 *A_RESTRICT + #define inAF4 AF1 *A_RESTRICT + #define inAL2 AL1 *A_RESTRICT + #define inAL3 AL1 *A_RESTRICT + #define inAL4 AL1 *A_RESTRICT + #define inAU2 AU1 *A_RESTRICT + #define inAU3 AU1 *A_RESTRICT + #define inAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define inoutAD2 AD1 *A_RESTRICT + #define inoutAD3 AD1 *A_RESTRICT + #define inoutAD4 AD1 *A_RESTRICT + #define inoutAF2 AF1 *A_RESTRICT + #define inoutAF3 AF1 *A_RESTRICT + #define inoutAF4 AF1 *A_RESTRICT + #define inoutAL2 AL1 *A_RESTRICT + #define inoutAL3 AL1 *A_RESTRICT + #define inoutAL4 AL1 *A_RESTRICT + #define inoutAU2 AU1 *A_RESTRICT + #define inoutAU3 AU1 *A_RESTRICT + #define inoutAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define outAD2 AD1 *A_RESTRICT + #define outAD3 AD1 *A_RESTRICT + #define outAD4 AD1 *A_RESTRICT + #define outAF2 AF1 *A_RESTRICT + #define outAF3 AF1 *A_RESTRICT + #define outAF4 AF1 *A_RESTRICT + #define outAL2 AL1 *A_RESTRICT + #define outAL3 AL1 *A_RESTRICT + #define outAL4 AL1 *A_RESTRICT + #define outAU2 AU1 *A_RESTRICT + #define outAU3 AU1 *A_RESTRICT + #define outAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define varAD2(x) AD1 x[2] + #define varAD3(x) AD1 x[3] + #define varAD4(x) AD1 x[4] + #define varAF2(x) AF1 x[2] + #define varAF3(x) AF1 x[3] + #define varAF4(x) AF1 x[4] + #define varAL2(x) AL1 x[2] + #define varAL3(x) AL1 x[3] + #define varAL4(x) AL1 x[4] + #define varAU2(x) AU1 x[2] + #define varAU3(x) AU1 x[3] + #define varAU4(x) AU1 x[4] +//------------------------------------------------------------------------------------------------------------------------------ + #define initAD2(x,y) {x,y} + #define initAD3(x,y,z) {x,y,z} + #define initAD4(x,y,z,w) {x,y,z,w} + #define initAF2(x,y) {x,y} + #define initAF3(x,y,z) {x,y,z} + #define initAF4(x,y,z,w) {x,y,z,w} + #define initAL2(x,y) {x,y} + #define initAL3(x,y,z) {x,y,z} + #define initAL4(x,y,z,w) {x,y,z,w} + #define initAU2(x,y) {x,y} + #define initAU3(x,y,z) {x,y,z} + #define initAU4(x,y,z,w) {x,y,z,w} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS +//------------------------------------------------------------------------------------------------------------------------------ +// TODO +// ==== +// - Replace transcendentals with manual versions. +//============================================================================================================================== + #ifdef A_GCC + A_STATIC AD1 AAbsD1(AD1 a){return __builtin_fabs(a);} + A_STATIC AF1 AAbsF1(AF1 a){return __builtin_fabsf(a);} + A_STATIC AU1 AAbsSU1(AU1 a){return AU1_(__builtin_abs(ASU1_(a)));} + A_STATIC AL1 AAbsSL1(AL1 a){return AL1_(__builtin_llabs(ASL1_(a)));} + #else + A_STATIC AD1 AAbsD1(AD1 a){return fabs(a);} + A_STATIC AF1 AAbsF1(AF1 a){return fabsf(a);} + A_STATIC AU1 AAbsSU1(AU1 a){return AU1_(abs(ASU1_(a)));} + A_STATIC AL1 AAbsSL1(AL1 a){return AL1_(labs((long)ASL1_(a)));} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ACosD1(AD1 a){return __builtin_cos(a);} + A_STATIC AF1 ACosF1(AF1 a){return __builtin_cosf(a);} + #else + A_STATIC AD1 ACosD1(AD1 a){return cos(a);} + A_STATIC AF1 ACosF1(AF1 a){return cosf(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ADotD2(inAD2 a,inAD2 b){return a[0]*b[0]+a[1]*b[1];} + A_STATIC AD1 ADotD3(inAD3 a,inAD3 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];} + A_STATIC AD1 ADotD4(inAD4 a,inAD4 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3];} + A_STATIC AF1 ADotF2(inAF2 a,inAF2 b){return a[0]*b[0]+a[1]*b[1];} + A_STATIC AF1 ADotF3(inAF3 a,inAF3 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];} + A_STATIC AF1 ADotF4(inAF4 a,inAF4 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3];} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 AExp2D1(AD1 a){return __builtin_exp2(a);} + A_STATIC AF1 AExp2F1(AF1 a){return __builtin_exp2f(a);} + #else + A_STATIC AD1 AExp2D1(AD1 a){return exp2(a);} + A_STATIC AF1 AExp2F1(AF1 a){return exp2f(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 AFloorD1(AD1 a){return __builtin_floor(a);} + A_STATIC AF1 AFloorF1(AF1 a){return __builtin_floorf(a);} + #else + A_STATIC AD1 AFloorD1(AD1 a){return floor(a);} + A_STATIC AF1 AFloorF1(AF1 a){return floorf(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ALerpD1(AD1 a,AD1 b,AD1 c){return b*c+(-a*c+a);} + A_STATIC AF1 ALerpF1(AF1 a,AF1 b,AF1 c){return b*c+(-a*c+a);} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ALog2D1(AD1 a){return __builtin_log2(a);} + A_STATIC AF1 ALog2F1(AF1 a){return __builtin_log2f(a);} + #else + A_STATIC AD1 ALog2D1(AD1 a){return log2(a);} + A_STATIC AF1 ALog2F1(AF1 a){return log2f(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 AMaxD1(AD1 a,AD1 b){return a>b?a:b;} + A_STATIC AF1 AMaxF1(AF1 a,AF1 b){return a>b?a:b;} + A_STATIC AL1 AMaxL1(AL1 a,AL1 b){return a>b?a:b;} + A_STATIC AU1 AMaxU1(AU1 a,AU1 b){return a>b?a:b;} +//------------------------------------------------------------------------------------------------------------------------------ + // These follow the convention that A integer types don't have signage, until they are operated on. + A_STATIC AL1 AMaxSL1(AL1 a,AL1 b){return (ASL1_(a)>ASL1_(b))?a:b;} + A_STATIC AU1 AMaxSU1(AU1 a,AU1 b){return (ASU1_(a)>ASU1_(b))?a:b;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 AMinD1(AD1 a,AD1 b){return a>ASL1_(b));} + A_STATIC AU1 AShrSU1(AU1 a,AU1 b){return AU1_(ASU1_(a)>>ASU1_(b));} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ASinD1(AD1 a){return __builtin_sin(a);} + A_STATIC AF1 ASinF1(AF1 a){return __builtin_sinf(a);} + #else + A_STATIC AD1 ASinD1(AD1 a){return sin(a);} + A_STATIC AF1 ASinF1(AF1 a){return sinf(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ASqrtD1(AD1 a){return __builtin_sqrt(a);} + A_STATIC AF1 ASqrtF1(AF1 a){return __builtin_sqrtf(a);} + #else + A_STATIC AD1 ASqrtD1(AD1 a){return sqrt(a);} + A_STATIC AF1 ASqrtF1(AF1 a){return sqrtf(a);} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS - DEPENDENT +//============================================================================================================================== + A_STATIC AD1 AClampD1(AD1 x,AD1 n,AD1 m){return AMaxD1(n,AMinD1(x,m));} + A_STATIC AF1 AClampF1(AF1 x,AF1 n,AF1 m){return AMaxF1(n,AMinF1(x,m));} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 AFractD1(AD1 a){return a-AFloorD1(a);} + A_STATIC AF1 AFractF1(AF1 a){return a-AFloorF1(a);} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 APowD1(AD1 a,AD1 b){return AExp2D1(b*ALog2D1(a));} + A_STATIC AF1 APowF1(AF1 a,AF1 b){return AExp2F1(b*ALog2F1(a));} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ARsqD1(AD1 a){return ARcpD1(ASqrtD1(a));} + A_STATIC AF1 ARsqF1(AF1 a){return ARcpF1(ASqrtF1(a));} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ASatD1(AD1 a){return AMinD1(1.0,AMaxD1(0.0,a));} + A_STATIC AF1 ASatF1(AF1 a){return AMinF1(1.0f,AMaxF1(0.0f,a));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR OPS +//------------------------------------------------------------------------------------------------------------------------------ +// These are added as needed for production or prototyping, so not necessarily a complete set. +// They follow a convention of taking in a destination and also returning the destination value to increase utility. +//============================================================================================================================== + A_STATIC retAD2 opAAbsD2(outAD2 d,inAD2 a){d[0]=AAbsD1(a[0]);d[1]=AAbsD1(a[1]);return d;} + A_STATIC retAD3 opAAbsD3(outAD3 d,inAD3 a){d[0]=AAbsD1(a[0]);d[1]=AAbsD1(a[1]);d[2]=AAbsD1(a[2]);return d;} + A_STATIC retAD4 opAAbsD4(outAD4 d,inAD4 a){d[0]=AAbsD1(a[0]);d[1]=AAbsD1(a[1]);d[2]=AAbsD1(a[2]);d[3]=AAbsD1(a[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAAbsF2(outAF2 d,inAF2 a){d[0]=AAbsF1(a[0]);d[1]=AAbsF1(a[1]);return d;} + A_STATIC retAF3 opAAbsF3(outAF3 d,inAF3 a){d[0]=AAbsF1(a[0]);d[1]=AAbsF1(a[1]);d[2]=AAbsF1(a[2]);return d;} + A_STATIC retAF4 opAAbsF4(outAF4 d,inAF4 a){d[0]=AAbsF1(a[0]);d[1]=AAbsF1(a[1]);d[2]=AAbsF1(a[2]);d[3]=AAbsF1(a[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAAddD2(outAD2 d,inAD2 a,inAD2 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];return d;} + A_STATIC retAD3 opAAddD3(outAD3 d,inAD3 a,inAD3 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];return d;} + A_STATIC retAD4 opAAddD4(outAD4 d,inAD4 a,inAD4 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];d[3]=a[3]+b[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAAddF2(outAF2 d,inAF2 a,inAF2 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];return d;} + A_STATIC retAF3 opAAddF3(outAF3 d,inAF3 a,inAF3 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];return d;} + A_STATIC retAF4 opAAddF4(outAF4 d,inAF4 a,inAF4 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];d[3]=a[3]+b[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opAAddOneD2(outAD2 d,inAD2 a,AD1 b){d[0]=a[0]+b;d[1]=a[1]+b;return d;} + A_STATIC retAD3 opAAddOneD3(outAD3 d,inAD3 a,AD1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;return d;} + A_STATIC retAD4 opAAddOneD4(outAD4 d,inAD4 a,AD1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;d[3]=a[3]+b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAAddOneF2(outAF2 d,inAF2 a,AF1 b){d[0]=a[0]+b;d[1]=a[1]+b;return d;} + A_STATIC retAF3 opAAddOneF3(outAF3 d,inAF3 a,AF1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;return d;} + A_STATIC retAF4 opAAddOneF4(outAF4 d,inAF4 a,AF1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;d[3]=a[3]+b;return d;} +//============================================================================================================================== + A_STATIC retAD2 opACpyD2(outAD2 d,inAD2 a){d[0]=a[0];d[1]=a[1];return d;} + A_STATIC retAD3 opACpyD3(outAD3 d,inAD3 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];return d;} + A_STATIC retAD4 opACpyD4(outAD4 d,inAD4 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];d[3]=a[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opACpyF2(outAF2 d,inAF2 a){d[0]=a[0];d[1]=a[1];return d;} + A_STATIC retAF3 opACpyF3(outAF3 d,inAF3 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];return d;} + A_STATIC retAF4 opACpyF4(outAF4 d,inAF4 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];d[3]=a[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opALerpD2(outAD2 d,inAD2 a,inAD2 b,inAD2 c){d[0]=ALerpD1(a[0],b[0],c[0]);d[1]=ALerpD1(a[1],b[1],c[1]);return d;} + A_STATIC retAD3 opALerpD3(outAD3 d,inAD3 a,inAD3 b,inAD3 c){d[0]=ALerpD1(a[0],b[0],c[0]);d[1]=ALerpD1(a[1],b[1],c[1]);d[2]=ALerpD1(a[2],b[2],c[2]);return d;} + A_STATIC retAD4 opALerpD4(outAD4 d,inAD4 a,inAD4 b,inAD4 c){d[0]=ALerpD1(a[0],b[0],c[0]);d[1]=ALerpD1(a[1],b[1],c[1]);d[2]=ALerpD1(a[2],b[2],c[2]);d[3]=ALerpD1(a[3],b[3],c[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opALerpF2(outAF2 d,inAF2 a,inAF2 b,inAF2 c){d[0]=ALerpF1(a[0],b[0],c[0]);d[1]=ALerpF1(a[1],b[1],c[1]);return d;} + A_STATIC retAF3 opALerpF3(outAF3 d,inAF3 a,inAF3 b,inAF3 c){d[0]=ALerpF1(a[0],b[0],c[0]);d[1]=ALerpF1(a[1],b[1],c[1]);d[2]=ALerpF1(a[2],b[2],c[2]);return d;} + A_STATIC retAF4 opALerpF4(outAF4 d,inAF4 a,inAF4 b,inAF4 c){d[0]=ALerpF1(a[0],b[0],c[0]);d[1]=ALerpF1(a[1],b[1],c[1]);d[2]=ALerpF1(a[2],b[2],c[2]);d[3]=ALerpF1(a[3],b[3],c[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opALerpOneD2(outAD2 d,inAD2 a,inAD2 b,AD1 c){d[0]=ALerpD1(a[0],b[0],c);d[1]=ALerpD1(a[1],b[1],c);return d;} + A_STATIC retAD3 opALerpOneD3(outAD3 d,inAD3 a,inAD3 b,AD1 c){d[0]=ALerpD1(a[0],b[0],c);d[1]=ALerpD1(a[1],b[1],c);d[2]=ALerpD1(a[2],b[2],c);return d;} + A_STATIC retAD4 opALerpOneD4(outAD4 d,inAD4 a,inAD4 b,AD1 c){d[0]=ALerpD1(a[0],b[0],c);d[1]=ALerpD1(a[1],b[1],c);d[2]=ALerpD1(a[2],b[2],c);d[3]=ALerpD1(a[3],b[3],c);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opALerpOneF2(outAF2 d,inAF2 a,inAF2 b,AF1 c){d[0]=ALerpF1(a[0],b[0],c);d[1]=ALerpF1(a[1],b[1],c);return d;} + A_STATIC retAF3 opALerpOneF3(outAF3 d,inAF3 a,inAF3 b,AF1 c){d[0]=ALerpF1(a[0],b[0],c);d[1]=ALerpF1(a[1],b[1],c);d[2]=ALerpF1(a[2],b[2],c);return d;} + A_STATIC retAF4 opALerpOneF4(outAF4 d,inAF4 a,inAF4 b,AF1 c){d[0]=ALerpF1(a[0],b[0],c);d[1]=ALerpF1(a[1],b[1],c);d[2]=ALerpF1(a[2],b[2],c);d[3]=ALerpF1(a[3],b[3],c);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMaxD2(outAD2 d,inAD2 a,inAD2 b){d[0]=AMaxD1(a[0],b[0]);d[1]=AMaxD1(a[1],b[1]);return d;} + A_STATIC retAD3 opAMaxD3(outAD3 d,inAD3 a,inAD3 b){d[0]=AMaxD1(a[0],b[0]);d[1]=AMaxD1(a[1],b[1]);d[2]=AMaxD1(a[2],b[2]);return d;} + A_STATIC retAD4 opAMaxD4(outAD4 d,inAD4 a,inAD4 b){d[0]=AMaxD1(a[0],b[0]);d[1]=AMaxD1(a[1],b[1]);d[2]=AMaxD1(a[2],b[2]);d[3]=AMaxD1(a[3],b[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMaxF2(outAF2 d,inAF2 a,inAF2 b){d[0]=AMaxF1(a[0],b[0]);d[1]=AMaxF1(a[1],b[1]);return d;} + A_STATIC retAF3 opAMaxF3(outAF3 d,inAF3 a,inAF3 b){d[0]=AMaxF1(a[0],b[0]);d[1]=AMaxF1(a[1],b[1]);d[2]=AMaxF1(a[2],b[2]);return d;} + A_STATIC retAF4 opAMaxF4(outAF4 d,inAF4 a,inAF4 b){d[0]=AMaxF1(a[0],b[0]);d[1]=AMaxF1(a[1],b[1]);d[2]=AMaxF1(a[2],b[2]);d[3]=AMaxF1(a[3],b[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMinD2(outAD2 d,inAD2 a,inAD2 b){d[0]=AMinD1(a[0],b[0]);d[1]=AMinD1(a[1],b[1]);return d;} + A_STATIC retAD3 opAMinD3(outAD3 d,inAD3 a,inAD3 b){d[0]=AMinD1(a[0],b[0]);d[1]=AMinD1(a[1],b[1]);d[2]=AMinD1(a[2],b[2]);return d;} + A_STATIC retAD4 opAMinD4(outAD4 d,inAD4 a,inAD4 b){d[0]=AMinD1(a[0],b[0]);d[1]=AMinD1(a[1],b[1]);d[2]=AMinD1(a[2],b[2]);d[3]=AMinD1(a[3],b[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMinF2(outAF2 d,inAF2 a,inAF2 b){d[0]=AMinF1(a[0],b[0]);d[1]=AMinF1(a[1],b[1]);return d;} + A_STATIC retAF3 opAMinF3(outAF3 d,inAF3 a,inAF3 b){d[0]=AMinF1(a[0],b[0]);d[1]=AMinF1(a[1],b[1]);d[2]=AMinF1(a[2],b[2]);return d;} + A_STATIC retAF4 opAMinF4(outAF4 d,inAF4 a,inAF4 b){d[0]=AMinF1(a[0],b[0]);d[1]=AMinF1(a[1],b[1]);d[2]=AMinF1(a[2],b[2]);d[3]=AMinF1(a[3],b[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMulD2(outAD2 d,inAD2 a,inAD2 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];return d;} + A_STATIC retAD3 opAMulD3(outAD3 d,inAD3 a,inAD3 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];return d;} + A_STATIC retAD4 opAMulD4(outAD4 d,inAD4 a,inAD4 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];d[3]=a[3]*b[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMulF2(outAF2 d,inAF2 a,inAF2 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];return d;} + A_STATIC retAF3 opAMulF3(outAF3 d,inAF3 a,inAF3 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];return d;} + A_STATIC retAF4 opAMulF4(outAF4 d,inAF4 a,inAF4 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];d[3]=a[3]*b[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMulOneD2(outAD2 d,inAD2 a,AD1 b){d[0]=a[0]*b;d[1]=a[1]*b;return d;} + A_STATIC retAD3 opAMulOneD3(outAD3 d,inAD3 a,AD1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;return d;} + A_STATIC retAD4 opAMulOneD4(outAD4 d,inAD4 a,AD1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;d[3]=a[3]*b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMulOneF2(outAF2 d,inAF2 a,AF1 b){d[0]=a[0]*b;d[1]=a[1]*b;return d;} + A_STATIC retAF3 opAMulOneF3(outAF3 d,inAF3 a,AF1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;return d;} + A_STATIC retAF4 opAMulOneF4(outAF4 d,inAF4 a,AF1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;d[3]=a[3]*b;return d;} +//============================================================================================================================== + A_STATIC retAD2 opANegD2(outAD2 d,inAD2 a){d[0]=-a[0];d[1]=-a[1];return d;} + A_STATIC retAD3 opANegD3(outAD3 d,inAD3 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];return d;} + A_STATIC retAD4 opANegD4(outAD4 d,inAD4 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];d[3]=-a[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opANegF2(outAF2 d,inAF2 a){d[0]=-a[0];d[1]=-a[1];return d;} + A_STATIC retAF3 opANegF3(outAF3 d,inAF3 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];return d;} + A_STATIC retAF4 opANegF4(outAF4 d,inAF4 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];d[3]=-a[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opARcpD2(outAD2 d,inAD2 a){d[0]=ARcpD1(a[0]);d[1]=ARcpD1(a[1]);return d;} + A_STATIC retAD3 opARcpD3(outAD3 d,inAD3 a){d[0]=ARcpD1(a[0]);d[1]=ARcpD1(a[1]);d[2]=ARcpD1(a[2]);return d;} + A_STATIC retAD4 opARcpD4(outAD4 d,inAD4 a){d[0]=ARcpD1(a[0]);d[1]=ARcpD1(a[1]);d[2]=ARcpD1(a[2]);d[3]=ARcpD1(a[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opARcpF2(outAF2 d,inAF2 a){d[0]=ARcpF1(a[0]);d[1]=ARcpF1(a[1]);return d;} + A_STATIC retAF3 opARcpF3(outAF3 d,inAF3 a){d[0]=ARcpF1(a[0]);d[1]=ARcpF1(a[1]);d[2]=ARcpF1(a[2]);return d;} + A_STATIC retAF4 opARcpF4(outAF4 d,inAF4 a){d[0]=ARcpF1(a[0]);d[1]=ARcpF1(a[1]);d[2]=ARcpF1(a[2]);d[3]=ARcpF1(a[3]);return d;} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HALF FLOAT PACKING +//============================================================================================================================== + // Convert float to half (in lower 16-bits of output). + // Same fast technique as documented here: ftp://ftp.fox-toolkit.org/pub/fasthalffloatconversion.pdf + // Supports denormals. + // Conversion rules are to make computations possibly "safer" on the GPU, + // -INF & -NaN -> -65504 + // +INF & +NaN -> +65504 + A_STATIC AU1 AU1_AH1_AF1(AF1 f){ + static AW1 base[512]={ + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0001,0x0002,0x0004,0x0008,0x0010,0x0020,0x0040,0x0080,0x0100, + 0x0200,0x0400,0x0800,0x0c00,0x1000,0x1400,0x1800,0x1c00,0x2000,0x2400,0x2800,0x2c00,0x3000,0x3400,0x3800,0x3c00, + 0x4000,0x4400,0x4800,0x4c00,0x5000,0x5400,0x5800,0x5c00,0x6000,0x6400,0x6800,0x6c00,0x7000,0x7400,0x7800,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8001,0x8002,0x8004,0x8008,0x8010,0x8020,0x8040,0x8080,0x8100, + 0x8200,0x8400,0x8800,0x8c00,0x9000,0x9400,0x9800,0x9c00,0xa000,0xa400,0xa800,0xac00,0xb000,0xb400,0xb800,0xbc00, + 0xc000,0xc400,0xc800,0xcc00,0xd000,0xd400,0xd800,0xdc00,0xe000,0xe400,0xe800,0xec00,0xf000,0xf400,0xf800,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff}; + static AB1 shift[512]={ + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x17,0x16,0x15,0x14,0x13,0x12,0x11,0x10,0x0f, + 0x0e,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d, + 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x17,0x16,0x15,0x14,0x13,0x12,0x11,0x10,0x0f, + 0x0e,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d, + 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18}; + union{AF1 f;AU1 u;}bits;bits.f=f;AU1 u=bits.u;AU1 i=u>>23;return (AU1)(base[i])+((u&0x7fffff)>>shift[i]);} +//------------------------------------------------------------------------------------------------------------------------------ + // Used to output packed constant. + A_STATIC AU1 AU1_AH2_AF2(inAF2 a){return AU1_AH1_AF1(a[0])+(AU1_AH1_AF1(a[1])<<16);} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// GLSL +// +// +//============================================================================================================================== +#if defined(A_GLSL) && defined(A_GPU) + #ifndef A_SKIP_EXT + #ifdef A_HALF + #extension GL_EXT_shader_16bit_storage:require + #extension GL_EXT_shader_explicit_arithmetic_types:require + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_LONG + #extension GL_ARB_gpu_shader_int64:require + #extension GL_NV_shader_atomic_int64:require + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_WAVE + #extension GL_KHR_shader_subgroup_arithmetic:require + #extension GL_KHR_shader_subgroup_ballot:require + #extension GL_KHR_shader_subgroup_quad:require + #extension GL_KHR_shader_subgroup_shuffle:require + #endif + #endif +//============================================================================================================================== + #define AP1 bool + #define AP2 bvec2 + #define AP3 bvec3 + #define AP4 bvec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF1 float + #define AF2 vec2 + #define AF3 vec3 + #define AF4 vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1 uint + #define AU2 uvec2 + #define AU3 uvec3 + #define AU4 uvec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASU1 int + #define ASU2 ivec2 + #define ASU3 ivec3 + #define ASU4 ivec4 +//============================================================================================================================== + #define AF1_AU1(x) uintBitsToFloat(AU1(x)) + #define AF2_AU2(x) uintBitsToFloat(AU2(x)) + #define AF3_AU3(x) uintBitsToFloat(AU3(x)) + #define AF4_AU4(x) uintBitsToFloat(AU4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AF1(x) floatBitsToUint(AF1(x)) + #define AU2_AF2(x) floatBitsToUint(AF2(x)) + #define AU3_AF3(x) floatBitsToUint(AF3(x)) + #define AU4_AF4(x) floatBitsToUint(AF4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH1_AF1_x(AF1 a){return packHalf2x16(AF2(a,0.0));} + #define AU1_AH1_AF1(a) AU1_AH1_AF1_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AH2_AF2 packHalf2x16 + #define AU1_AW2Unorm_AF2 packUnorm2x16 + #define AU1_AB4Unorm_AF4 packUnorm4x8 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF2_AH2_AU1 unpackHalf2x16 + #define AF2_AW2Unorm_AU1 unpackUnorm2x16 + #define AF4_AB4Unorm_AU1 unpackUnorm4x8 +//============================================================================================================================== + AF1 AF1_x(AF1 a){return AF1(a);} + AF2 AF2_x(AF1 a){return AF2(a,a);} + AF3 AF3_x(AF1 a){return AF3(a,a,a);} + AF4 AF4_x(AF1 a){return AF4(a,a,a,a);} + #define AF1_(a) AF1_x(AF1(a)) + #define AF2_(a) AF2_x(AF1(a)) + #define AF3_(a) AF3_x(AF1(a)) + #define AF4_(a) AF4_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_x(AU1 a){return AU1(a);} + AU2 AU2_x(AU1 a){return AU2(a,a);} + AU3 AU3_x(AU1 a){return AU3(a,a,a);} + AU4 AU4_x(AU1 a){return AU4(a,a,a,a);} + #define AU1_(a) AU1_x(AU1(a)) + #define AU2_(a) AU2_x(AU1(a)) + #define AU3_(a) AU3_x(AU1(a)) + #define AU4_(a) AU4_x(AU1(a)) +//============================================================================================================================== + AU1 AAbsSU1(AU1 a){return AU1(abs(ASU1(a)));} + AU2 AAbsSU2(AU2 a){return AU2(abs(ASU2(a)));} + AU3 AAbsSU3(AU3 a){return AU3(abs(ASU3(a)));} + AU4 AAbsSU4(AU4 a){return AU4(abs(ASU4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 ABfe(AU1 src,AU1 off,AU1 bits){return bitfieldExtract(src,ASU1(off),ASU1(bits));} + AU1 ABfi(AU1 src,AU1 ins,AU1 mask){return (ins&mask)|(src&(~mask));} + // Proxy for V_BFI_B32 where the 'mask' is set as 'bits', 'mask=(1<>ASU1(b));} + AU2 AShrSU2(AU2 a,AU2 b){return AU2(ASU2(a)>>ASU2(b));} + AU3 AShrSU3(AU3 a,AU3 b){return AU3(ASU3(a)>>ASU3(b));} + AU4 AShrSU4(AU4 a,AU4 b){return AU4(ASU4(a)>>ASU4(b));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL BYTE +//============================================================================================================================== + #ifdef A_BYTE + #define AB1 uint8_t + #define AB2 u8vec2 + #define AB3 u8vec3 + #define AB4 u8vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASB1 int8_t + #define ASB2 i8vec2 + #define ASB3 i8vec3 + #define ASB4 i8vec4 +//------------------------------------------------------------------------------------------------------------------------------ + AB1 AB1_x(AB1 a){return AB1(a);} + AB2 AB2_x(AB1 a){return AB2(a,a);} + AB3 AB3_x(AB1 a){return AB3(a,a,a);} + AB4 AB4_x(AB1 a){return AB4(a,a,a,a);} + #define AB1_(a) AB1_x(AB1(a)) + #define AB2_(a) AB2_x(AB1(a)) + #define AB3_(a) AB3_x(AB1(a)) + #define AB4_(a) AB4_x(AB1(a)) + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL HALF +//============================================================================================================================== + #ifdef A_HALF + #define AH1 float16_t + #define AH2 f16vec2 + #define AH3 f16vec3 + #define AH4 f16vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AW1 uint16_t + #define AW2 u16vec2 + #define AW3 u16vec3 + #define AW4 u16vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASW1 int16_t + #define ASW2 i16vec2 + #define ASW3 i16vec3 + #define ASW4 i16vec4 +//============================================================================================================================== + #define AH2_AU1(x) unpackFloat2x16(AU1(x)) + AH4 AH4_AU2_x(AU2 x){return AH4(unpackFloat2x16(x.x),unpackFloat2x16(x.y));} + #define AH4_AU2(x) AH4_AU2_x(AU2(x)) + #define AW2_AU1(x) unpackUint2x16(AU1(x)) + #define AW4_AU2(x) unpackUint4x16(pack64(AU2(x))) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AH2(x) packFloat2x16(AH2(x)) + AU2 AU2_AH4_x(AH4 x){return AU2(packFloat2x16(x.xy),packFloat2x16(x.zw));} + #define AU2_AH4(x) AU2_AH4_x(AH4(x)) + #define AU1_AW2(x) packUint2x16(AW2(x)) + #define AU2_AW4(x) unpack32(packUint4x16(AW4(x))) +//============================================================================================================================== + #define AW1_AH1(x) halfBitsToUint16(AH1(x)) + #define AW2_AH2(x) halfBitsToUint16(AH2(x)) + #define AW3_AH3(x) halfBitsToUint16(AH3(x)) + #define AW4_AH4(x) halfBitsToUint16(AH4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AH1_AW1(x) uint16BitsToHalf(AW1(x)) + #define AH2_AW2(x) uint16BitsToHalf(AW2(x)) + #define AH3_AW3(x) uint16BitsToHalf(AW3(x)) + #define AH4_AW4(x) uint16BitsToHalf(AW4(x)) +//============================================================================================================================== + AH1 AH1_x(AH1 a){return AH1(a);} + AH2 AH2_x(AH1 a){return AH2(a,a);} + AH3 AH3_x(AH1 a){return AH3(a,a,a);} + AH4 AH4_x(AH1 a){return AH4(a,a,a,a);} + #define AH1_(a) AH1_x(AH1(a)) + #define AH2_(a) AH2_x(AH1(a)) + #define AH3_(a) AH3_x(AH1(a)) + #define AH4_(a) AH4_x(AH1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AW1_x(AW1 a){return AW1(a);} + AW2 AW2_x(AW1 a){return AW2(a,a);} + AW3 AW3_x(AW1 a){return AW3(a,a,a);} + AW4 AW4_x(AW1 a){return AW4(a,a,a,a);} + #define AW1_(a) AW1_x(AW1(a)) + #define AW2_(a) AW2_x(AW1(a)) + #define AW3_(a) AW3_x(AW1(a)) + #define AW4_(a) AW4_x(AW1(a)) +//============================================================================================================================== + AW1 AAbsSW1(AW1 a){return AW1(abs(ASW1(a)));} + AW2 AAbsSW2(AW2 a){return AW2(abs(ASW2(a)));} + AW3 AAbsSW3(AW3 a){return AW3(abs(ASW3(a)));} + AW4 AAbsSW4(AW4 a){return AW4(abs(ASW4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AClampH1(AH1 x,AH1 n,AH1 m){return clamp(x,n,m);} + AH2 AClampH2(AH2 x,AH2 n,AH2 m){return clamp(x,n,m);} + AH3 AClampH3(AH3 x,AH3 n,AH3 m){return clamp(x,n,m);} + AH4 AClampH4(AH4 x,AH4 n,AH4 m){return clamp(x,n,m);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFractH1(AH1 x){return fract(x);} + AH2 AFractH2(AH2 x){return fract(x);} + AH3 AFractH3(AH3 x){return fract(x);} + AH4 AFractH4(AH4 x){return fract(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ALerpH1(AH1 x,AH1 y,AH1 a){return mix(x,y,a);} + AH2 ALerpH2(AH2 x,AH2 y,AH2 a){return mix(x,y,a);} + AH3 ALerpH3(AH3 x,AH3 y,AH3 a){return mix(x,y,a);} + AH4 ALerpH4(AH4 x,AH4 y,AH4 a){return mix(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + // No packed version of max3. + AH1 AMax3H1(AH1 x,AH1 y,AH1 z){return max(x,max(y,z));} + AH2 AMax3H2(AH2 x,AH2 y,AH2 z){return max(x,max(y,z));} + AH3 AMax3H3(AH3 x,AH3 y,AH3 z){return max(x,max(y,z));} + AH4 AMax3H4(AH4 x,AH4 y,AH4 z){return max(x,max(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMaxSW1(AW1 a,AW1 b){return AW1(max(ASU1(a),ASU1(b)));} + AW2 AMaxSW2(AW2 a,AW2 b){return AW2(max(ASU2(a),ASU2(b)));} + AW3 AMaxSW3(AW3 a,AW3 b){return AW3(max(ASU3(a),ASU3(b)));} + AW4 AMaxSW4(AW4 a,AW4 b){return AW4(max(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + // No packed version of min3. + AH1 AMin3H1(AH1 x,AH1 y,AH1 z){return min(x,min(y,z));} + AH2 AMin3H2(AH2 x,AH2 y,AH2 z){return min(x,min(y,z));} + AH3 AMin3H3(AH3 x,AH3 y,AH3 z){return min(x,min(y,z));} + AH4 AMin3H4(AH4 x,AH4 y,AH4 z){return min(x,min(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMinSW1(AW1 a,AW1 b){return AW1(min(ASU1(a),ASU1(b)));} + AW2 AMinSW2(AW2 a,AW2 b){return AW2(min(ASU2(a),ASU2(b)));} + AW3 AMinSW3(AW3 a,AW3 b){return AW3(min(ASU3(a),ASU3(b)));} + AW4 AMinSW4(AW4 a,AW4 b){return AW4(min(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARcpH1(AH1 x){return AH1_(1.0)/x;} + AH2 ARcpH2(AH2 x){return AH2_(1.0)/x;} + AH3 ARcpH3(AH3 x){return AH3_(1.0)/x;} + AH4 ARcpH4(AH4 x){return AH4_(1.0)/x;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARsqH1(AH1 x){return AH1_(1.0)/sqrt(x);} + AH2 ARsqH2(AH2 x){return AH2_(1.0)/sqrt(x);} + AH3 ARsqH3(AH3 x){return AH3_(1.0)/sqrt(x);} + AH4 ARsqH4(AH4 x){return AH4_(1.0)/sqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASatH1(AH1 x){return clamp(x,AH1_(0.0),AH1_(1.0));} + AH2 ASatH2(AH2 x){return clamp(x,AH2_(0.0),AH2_(1.0));} + AH3 ASatH3(AH3 x){return clamp(x,AH3_(0.0),AH3_(1.0));} + AH4 ASatH4(AH4 x){return clamp(x,AH4_(0.0),AH4_(1.0));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AShrSW1(AW1 a,AW1 b){return AW1(ASW1(a)>>ASW1(b));} + AW2 AShrSW2(AW2 a,AW2 b){return AW2(ASW2(a)>>ASW2(b));} + AW3 AShrSW3(AW3 a,AW3 b){return AW3(ASW3(a)>>ASW3(b));} + AW4 AShrSW4(AW4 a,AW4 b){return AW4(ASW4(a)>>ASW4(b));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL DOUBLE +//============================================================================================================================== + #ifdef A_DUBL + #define AD1 double + #define AD2 dvec2 + #define AD3 dvec3 + #define AD4 dvec4 +//------------------------------------------------------------------------------------------------------------------------------ + AD1 AD1_x(AD1 a){return AD1(a);} + AD2 AD2_x(AD1 a){return AD2(a,a);} + AD3 AD3_x(AD1 a){return AD3(a,a,a);} + AD4 AD4_x(AD1 a){return AD4(a,a,a,a);} + #define AD1_(a) AD1_x(AD1(a)) + #define AD2_(a) AD2_x(AD1(a)) + #define AD3_(a) AD3_x(AD1(a)) + #define AD4_(a) AD4_x(AD1(a)) +//============================================================================================================================== + AD1 AFractD1(AD1 x){return fract(x);} + AD2 AFractD2(AD2 x){return fract(x);} + AD3 AFractD3(AD3 x){return fract(x);} + AD4 AFractD4(AD4 x){return fract(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ALerpD1(AD1 x,AD1 y,AD1 a){return mix(x,y,a);} + AD2 ALerpD2(AD2 x,AD2 y,AD2 a){return mix(x,y,a);} + AD3 ALerpD3(AD3 x,AD3 y,AD3 a){return mix(x,y,a);} + AD4 ALerpD4(AD4 x,AD4 y,AD4 a){return mix(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARcpD1(AD1 x){return AD1_(1.0)/x;} + AD2 ARcpD2(AD2 x){return AD2_(1.0)/x;} + AD3 ARcpD3(AD3 x){return AD3_(1.0)/x;} + AD4 ARcpD4(AD4 x){return AD4_(1.0)/x;} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARsqD1(AD1 x){return AD1_(1.0)/sqrt(x);} + AD2 ARsqD2(AD2 x){return AD2_(1.0)/sqrt(x);} + AD3 ARsqD3(AD3 x){return AD3_(1.0)/sqrt(x);} + AD4 ARsqD4(AD4 x){return AD4_(1.0)/sqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ASatD1(AD1 x){return clamp(x,AD1_(0.0),AD1_(1.0));} + AD2 ASatD2(AD2 x){return clamp(x,AD2_(0.0),AD2_(1.0));} + AD3 ASatD3(AD3 x){return clamp(x,AD3_(0.0),AD3_(1.0));} + AD4 ASatD4(AD4 x){return clamp(x,AD4_(0.0),AD4_(1.0));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL LONG +//============================================================================================================================== + #ifdef A_LONG + #define AL1 uint64_t + #define AL2 u64vec2 + #define AL3 u64vec3 + #define AL4 u64vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASL1 int64_t + #define ASL2 i64vec2 + #define ASL3 i64vec3 + #define ASL4 i64vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AL1_AU2(x) packUint2x32(AU2(x)) + #define AU2_AL1(x) unpackUint2x32(AL1(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AL1 AL1_x(AL1 a){return AL1(a);} + AL2 AL2_x(AL1 a){return AL2(a,a);} + AL3 AL3_x(AL1 a){return AL3(a,a,a);} + AL4 AL4_x(AL1 a){return AL4(a,a,a,a);} + #define AL1_(a) AL1_x(AL1(a)) + #define AL2_(a) AL2_x(AL1(a)) + #define AL3_(a) AL3_x(AL1(a)) + #define AL4_(a) AL4_x(AL1(a)) +//============================================================================================================================== + AL1 AAbsSL1(AL1 a){return AL1(abs(ASL1(a)));} + AL2 AAbsSL2(AL2 a){return AL2(abs(ASL2(a)));} + AL3 AAbsSL3(AL3 a){return AL3(abs(ASL3(a)));} + AL4 AAbsSL4(AL4 a){return AL4(abs(ASL4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AL1 AMaxSL1(AL1 a,AL1 b){return AL1(max(ASU1(a),ASU1(b)));} + AL2 AMaxSL2(AL2 a,AL2 b){return AL2(max(ASU2(a),ASU2(b)));} + AL3 AMaxSL3(AL3 a,AL3 b){return AL3(max(ASU3(a),ASU3(b)));} + AL4 AMaxSL4(AL4 a,AL4 b){return AL4(max(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AL1 AMinSL1(AL1 a,AL1 b){return AL1(min(ASU1(a),ASU1(b)));} + AL2 AMinSL2(AL2 a,AL2 b){return AL2(min(ASU2(a),ASU2(b)));} + AL3 AMinSL3(AL3 a,AL3 b){return AL3(min(ASU3(a),ASU3(b)));} + AL4 AMinSL4(AL4 a,AL4 b){return AL4(min(ASU4(a),ASU4(b)));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// WAVE OPERATIONS +//============================================================================================================================== + #ifdef A_WAVE + // Where 'x' must be a compile time literal. + AF1 AWaveXorF1(AF1 v,AU1 x){return subgroupShuffleXor(v,x);} + AF2 AWaveXorF2(AF2 v,AU1 x){return subgroupShuffleXor(v,x);} + AF3 AWaveXorF3(AF3 v,AU1 x){return subgroupShuffleXor(v,x);} + AF4 AWaveXorF4(AF4 v,AU1 x){return subgroupShuffleXor(v,x);} + AU1 AWaveXorU1(AU1 v,AU1 x){return subgroupShuffleXor(v,x);} + AU2 AWaveXorU2(AU2 v,AU1 x){return subgroupShuffleXor(v,x);} + AU3 AWaveXorU3(AU3 v,AU1 x){return subgroupShuffleXor(v,x);} + AU4 AWaveXorU4(AU4 v,AU1 x){return subgroupShuffleXor(v,x);} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + AH2 AWaveXorH2(AH2 v,AU1 x){return AH2_AU1(subgroupShuffleXor(AU1_AH2(v),x));} + AH4 AWaveXorH4(AH4 v,AU1 x){return AH4_AU2(subgroupShuffleXor(AU2_AH4(v),x));} + AW2 AWaveXorW2(AW2 v,AU1 x){return AW2_AU1(subgroupShuffleXor(AU1_AW2(v),x));} + AW4 AWaveXorW4(AW4 v,AU1 x){return AW4_AU2(subgroupShuffleXor(AU2_AW4(v),x));} + #endif + #endif +//============================================================================================================================== +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// HLSL +// +// +//============================================================================================================================== +#if defined(A_HLSL) && defined(A_GPU) + #ifdef A_HLSL_6_2 + #define AP1 bool + #define AP2 bool2 + #define AP3 bool3 + #define AP4 bool4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF1 float32_t + #define AF2 float32_t2 + #define AF3 float32_t3 + #define AF4 float32_t4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1 uint32_t + #define AU2 uint32_t2 + #define AU3 uint32_t3 + #define AU4 uint32_t4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASU1 int32_t + #define ASU2 int32_t2 + #define ASU3 int32_t3 + #define ASU4 int32_t4 + #else + #define AP1 bool + #define AP2 bool2 + #define AP3 bool3 + #define AP4 bool4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF1 float + #define AF2 float2 + #define AF3 float3 + #define AF4 float4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1 uint + #define AU2 uint2 + #define AU3 uint3 + #define AU4 uint4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASU1 int + #define ASU2 int2 + #define ASU3 int3 + #define ASU4 int4 + #endif +//============================================================================================================================== + #define AF1_AU1(x) asfloat(AU1(x)) + #define AF2_AU2(x) asfloat(AU2(x)) + #define AF3_AU3(x) asfloat(AU3(x)) + #define AF4_AU4(x) asfloat(AU4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AF1(x) asuint(AF1(x)) + #define AU2_AF2(x) asuint(AF2(x)) + #define AU3_AF3(x) asuint(AF3(x)) + #define AU4_AF4(x) asuint(AF4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH1_AF1_x(AF1 a){return f32tof16(a);} + #define AU1_AH1_AF1(a) AU1_AH1_AF1_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH2_AF2_x(AF2 a){return f32tof16(a.x)|(f32tof16(a.y)<<16);} + #define AU1_AH2_AF2(a) AU1_AH2_AF2_x(AF2(a)) + #define AU1_AB4Unorm_AF4(x) D3DCOLORtoUBYTE4(AF4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AF2 AF2_AH2_AU1_x(AU1 x){return AF2(f16tof32(x&0xFFFF),f16tof32(x>>16));} + #define AF2_AH2_AU1(x) AF2_AH2_AU1_x(AU1(x)) +//============================================================================================================================== + AF1 AF1_x(AF1 a){return AF1(a);} + AF2 AF2_x(AF1 a){return AF2(a,a);} + AF3 AF3_x(AF1 a){return AF3(a,a,a);} + AF4 AF4_x(AF1 a){return AF4(a,a,a,a);} + #define AF1_(a) AF1_x(AF1(a)) + #define AF2_(a) AF2_x(AF1(a)) + #define AF3_(a) AF3_x(AF1(a)) + #define AF4_(a) AF4_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_x(AU1 a){return AU1(a);} + AU2 AU2_x(AU1 a){return AU2(a,a);} + AU3 AU3_x(AU1 a){return AU3(a,a,a);} + AU4 AU4_x(AU1 a){return AU4(a,a,a,a);} + #define AU1_(a) AU1_x(AU1(a)) + #define AU2_(a) AU2_x(AU1(a)) + #define AU3_(a) AU3_x(AU1(a)) + #define AU4_(a) AU4_x(AU1(a)) +//============================================================================================================================== + AU1 AAbsSU1(AU1 a){return AU1(abs(ASU1(a)));} + AU2 AAbsSU2(AU2 a){return AU2(abs(ASU2(a)));} + AU3 AAbsSU3(AU3 a){return AU3(abs(ASU3(a)));} + AU4 AAbsSU4(AU4 a){return AU4(abs(ASU4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 ABfe(AU1 src,AU1 off,AU1 bits){AU1 mask=(1u<>off)&mask;} + AU1 ABfi(AU1 src,AU1 ins,AU1 mask){return (ins&mask)|(src&(~mask));} + AU1 ABfiM(AU1 src,AU1 ins,AU1 bits){AU1 mask=(1u<>ASU1(b));} + AU2 AShrSU2(AU2 a,AU2 b){return AU2(ASU2(a)>>ASU2(b));} + AU3 AShrSU3(AU3 a,AU3 b){return AU3(ASU3(a)>>ASU3(b));} + AU4 AShrSU4(AU4 a,AU4 b){return AU4(ASU4(a)>>ASU4(b));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HLSL BYTE +//============================================================================================================================== + #ifdef A_BYTE + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HLSL HALF +//============================================================================================================================== + #ifdef A_HALF + #ifdef A_HLSL_6_2 + #define AH1 float16_t + #define AH2 float16_t2 + #define AH3 float16_t3 + #define AH4 float16_t4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AW1 uint16_t + #define AW2 uint16_t2 + #define AW3 uint16_t3 + #define AW4 uint16_t4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASW1 int16_t + #define ASW2 int16_t2 + #define ASW3 int16_t3 + #define ASW4 int16_t4 + #else + #define AH1 min16float + #define AH2 min16float2 + #define AH3 min16float3 + #define AH4 min16float4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AW1 min16uint + #define AW2 min16uint2 + #define AW3 min16uint3 + #define AW4 min16uint4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASW1 min16int + #define ASW2 min16int2 + #define ASW3 min16int3 + #define ASW4 min16int4 + #endif +//============================================================================================================================== + // Need to use manual unpack to get optimal execution (don't use packed types in buffers directly). + // Unpack requires this pattern: https://gpuopen.com/first-steps-implementing-fp16/ + AH2 AH2_AU1_x(AU1 x){AF2 t=f16tof32(AU2(x&0xFFFF,x>>16));return AH2(t);} + AH4 AH4_AU2_x(AU2 x){return AH4(AH2_AU1_x(x.x),AH2_AU1_x(x.y));} + AW2 AW2_AU1_x(AU1 x){AU2 t=AU2(x&0xFFFF,x>>16);return AW2(t);} + AW4 AW4_AU2_x(AU2 x){return AW4(AW2_AU1_x(x.x),AW2_AU1_x(x.y));} + #define AH2_AU1(x) AH2_AU1_x(AU1(x)) + #define AH4_AU2(x) AH4_AU2_x(AU2(x)) + #define AW2_AU1(x) AW2_AU1_x(AU1(x)) + #define AW4_AU2(x) AW4_AU2_x(AU2(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH2_x(AH2 x){return f32tof16(x.x)+(f32tof16(x.y)<<16);} + AU2 AU2_AH4_x(AH4 x){return AU2(AU1_AH2_x(x.xy),AU1_AH2_x(x.zw));} + AU1 AU1_AW2_x(AW2 x){return AU1(x.x)+(AU1(x.y)<<16);} + AU2 AU2_AW4_x(AW4 x){return AU2(AU1_AW2_x(x.xy),AU1_AW2_x(x.zw));} + #define AU1_AH2(x) AU1_AH2_x(AH2(x)) + #define AU2_AH4(x) AU2_AH4_x(AH4(x)) + #define AU1_AW2(x) AU1_AW2_x(AW2(x)) + #define AU2_AW4(x) AU2_AW4_x(AW4(x)) +//============================================================================================================================== + #if defined(A_HLSL_6_2) && !defined(A_NO_16_BIT_CAST) + #define AW1_AH1(x) asuint16(x) + #define AW2_AH2(x) asuint16(x) + #define AW3_AH3(x) asuint16(x) + #define AW4_AH4(x) asuint16(x) + #else + #define AW1_AH1(a) AW1(f32tof16(AF1(a))) + #define AW2_AH2(a) AW2(AW1_AH1((a).x),AW1_AH1((a).y)) + #define AW3_AH3(a) AW3(AW1_AH1((a).x),AW1_AH1((a).y),AW1_AH1((a).z)) + #define AW4_AH4(a) AW4(AW1_AH1((a).x),AW1_AH1((a).y),AW1_AH1((a).z),AW1_AH1((a).w)) + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #if defined(A_HLSL_6_2) && !defined(A_NO_16_BIT_CAST) + #define AH1_AW1(x) asfloat16(x) + #define AH2_AW2(x) asfloat16(x) + #define AH3_AW3(x) asfloat16(x) + #define AH4_AW4(x) asfloat16(x) + #else + #define AH1_AW1(a) AH1(f16tof32(AU1(a))) + #define AH2_AW2(a) AH2(AH1_AW1((a).x),AH1_AW1((a).y)) + #define AH3_AW3(a) AH3(AH1_AW1((a).x),AH1_AW1((a).y),AH1_AW1((a).z)) + #define AH4_AW4(a) AH4(AH1_AW1((a).x),AH1_AW1((a).y),AH1_AW1((a).z),AH1_AW1((a).w)) + #endif +//============================================================================================================================== + AH1 AH1_x(AH1 a){return AH1(a);} + AH2 AH2_x(AH1 a){return AH2(a,a);} + AH3 AH3_x(AH1 a){return AH3(a,a,a);} + AH4 AH4_x(AH1 a){return AH4(a,a,a,a);} + #define AH1_(a) AH1_x(AH1(a)) + #define AH2_(a) AH2_x(AH1(a)) + #define AH3_(a) AH3_x(AH1(a)) + #define AH4_(a) AH4_x(AH1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AW1_x(AW1 a){return AW1(a);} + AW2 AW2_x(AW1 a){return AW2(a,a);} + AW3 AW3_x(AW1 a){return AW3(a,a,a);} + AW4 AW4_x(AW1 a){return AW4(a,a,a,a);} + #define AW1_(a) AW1_x(AW1(a)) + #define AW2_(a) AW2_x(AW1(a)) + #define AW3_(a) AW3_x(AW1(a)) + #define AW4_(a) AW4_x(AW1(a)) +//============================================================================================================================== + AW1 AAbsSW1(AW1 a){return AW1(abs(ASW1(a)));} + AW2 AAbsSW2(AW2 a){return AW2(abs(ASW2(a)));} + AW3 AAbsSW3(AW3 a){return AW3(abs(ASW3(a)));} + AW4 AAbsSW4(AW4 a){return AW4(abs(ASW4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AClampH1(AH1 x,AH1 n,AH1 m){return max(n,min(x,m));} + AH2 AClampH2(AH2 x,AH2 n,AH2 m){return max(n,min(x,m));} + AH3 AClampH3(AH3 x,AH3 n,AH3 m){return max(n,min(x,m));} + AH4 AClampH4(AH4 x,AH4 n,AH4 m){return max(n,min(x,m));} +//------------------------------------------------------------------------------------------------------------------------------ + // V_FRACT_F16 (note DX frac() is different). + AH1 AFractH1(AH1 x){return x-floor(x);} + AH2 AFractH2(AH2 x){return x-floor(x);} + AH3 AFractH3(AH3 x){return x-floor(x);} + AH4 AFractH4(AH4 x){return x-floor(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ALerpH1(AH1 x,AH1 y,AH1 a){return lerp(x,y,a);} + AH2 ALerpH2(AH2 x,AH2 y,AH2 a){return lerp(x,y,a);} + AH3 ALerpH3(AH3 x,AH3 y,AH3 a){return lerp(x,y,a);} + AH4 ALerpH4(AH4 x,AH4 y,AH4 a){return lerp(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AMax3H1(AH1 x,AH1 y,AH1 z){return max(x,max(y,z));} + AH2 AMax3H2(AH2 x,AH2 y,AH2 z){return max(x,max(y,z));} + AH3 AMax3H3(AH3 x,AH3 y,AH3 z){return max(x,max(y,z));} + AH4 AMax3H4(AH4 x,AH4 y,AH4 z){return max(x,max(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMaxSW1(AW1 a,AW1 b){return AW1(max(ASU1(a),ASU1(b)));} + AW2 AMaxSW2(AW2 a,AW2 b){return AW2(max(ASU2(a),ASU2(b)));} + AW3 AMaxSW3(AW3 a,AW3 b){return AW3(max(ASU3(a),ASU3(b)));} + AW4 AMaxSW4(AW4 a,AW4 b){return AW4(max(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AMin3H1(AH1 x,AH1 y,AH1 z){return min(x,min(y,z));} + AH2 AMin3H2(AH2 x,AH2 y,AH2 z){return min(x,min(y,z));} + AH3 AMin3H3(AH3 x,AH3 y,AH3 z){return min(x,min(y,z));} + AH4 AMin3H4(AH4 x,AH4 y,AH4 z){return min(x,min(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMinSW1(AW1 a,AW1 b){return AW1(min(ASU1(a),ASU1(b)));} + AW2 AMinSW2(AW2 a,AW2 b){return AW2(min(ASU2(a),ASU2(b)));} + AW3 AMinSW3(AW3 a,AW3 b){return AW3(min(ASU3(a),ASU3(b)));} + AW4 AMinSW4(AW4 a,AW4 b){return AW4(min(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARcpH1(AH1 x){return rcp(x);} + AH2 ARcpH2(AH2 x){return rcp(x);} + AH3 ARcpH3(AH3 x){return rcp(x);} + AH4 ARcpH4(AH4 x){return rcp(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARsqH1(AH1 x){return rsqrt(x);} + AH2 ARsqH2(AH2 x){return rsqrt(x);} + AH3 ARsqH3(AH3 x){return rsqrt(x);} + AH4 ARsqH4(AH4 x){return rsqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASatH1(AH1 x){return saturate(x);} + AH2 ASatH2(AH2 x){return saturate(x);} + AH3 ASatH3(AH3 x){return saturate(x);} + AH4 ASatH4(AH4 x){return saturate(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AShrSW1(AW1 a,AW1 b){return AW1(ASW1(a)>>ASW1(b));} + AW2 AShrSW2(AW2 a,AW2 b){return AW2(ASW2(a)>>ASW2(b));} + AW3 AShrSW3(AW3 a,AW3 b){return AW3(ASW3(a)>>ASW3(b));} + AW4 AShrSW4(AW4 a,AW4 b){return AW4(ASW4(a)>>ASW4(b));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HLSL DOUBLE +//============================================================================================================================== + #ifdef A_DUBL + #ifdef A_HLSL_6_2 + #define AD1 float64_t + #define AD2 float64_t2 + #define AD3 float64_t3 + #define AD4 float64_t4 + #else + #define AD1 double + #define AD2 double2 + #define AD3 double3 + #define AD4 double4 + #endif +//------------------------------------------------------------------------------------------------------------------------------ + AD1 AD1_x(AD1 a){return AD1(a);} + AD2 AD2_x(AD1 a){return AD2(a,a);} + AD3 AD3_x(AD1 a){return AD3(a,a,a);} + AD4 AD4_x(AD1 a){return AD4(a,a,a,a);} + #define AD1_(a) AD1_x(AD1(a)) + #define AD2_(a) AD2_x(AD1(a)) + #define AD3_(a) AD3_x(AD1(a)) + #define AD4_(a) AD4_x(AD1(a)) +//============================================================================================================================== + AD1 AFractD1(AD1 a){return a-floor(a);} + AD2 AFractD2(AD2 a){return a-floor(a);} + AD3 AFractD3(AD3 a){return a-floor(a);} + AD4 AFractD4(AD4 a){return a-floor(a);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ALerpD1(AD1 x,AD1 y,AD1 a){return lerp(x,y,a);} + AD2 ALerpD2(AD2 x,AD2 y,AD2 a){return lerp(x,y,a);} + AD3 ALerpD3(AD3 x,AD3 y,AD3 a){return lerp(x,y,a);} + AD4 ALerpD4(AD4 x,AD4 y,AD4 a){return lerp(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARcpD1(AD1 x){return rcp(x);} + AD2 ARcpD2(AD2 x){return rcp(x);} + AD3 ARcpD3(AD3 x){return rcp(x);} + AD4 ARcpD4(AD4 x){return rcp(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARsqD1(AD1 x){return rsqrt(x);} + AD2 ARsqD2(AD2 x){return rsqrt(x);} + AD3 ARsqD3(AD3 x){return rsqrt(x);} + AD4 ARsqD4(AD4 x){return rsqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ASatD1(AD1 x){return saturate(x);} + AD2 ASatD2(AD2 x){return saturate(x);} + AD3 ASatD3(AD3 x){return saturate(x);} + AD4 ASatD4(AD4 x){return saturate(x);} + #endif +//============================================================================================================================== +// HLSL WAVE +//============================================================================================================================== + #ifdef A_WAVE + // Where 'x' must be a compile time literal. + AF1 AWaveXorF1(AF1 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AF2 AWaveXorF2(AF2 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AF3 AWaveXorF3(AF3 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AF4 AWaveXorF4(AF4 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AU1 AWaveXorU1(AU1 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AU2 AWaveXorU1(AU2 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AU3 AWaveXorU1(AU3 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AU4 AWaveXorU1(AU4 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + AH2 AWaveXorH2(AH2 v,AU1 x){return AH2_AU1(WaveReadLaneAt(AU1_AH2(v),WaveGetLaneIndex()^x));} + AH4 AWaveXorH4(AH4 v,AU1 x){return AH4_AU2(WaveReadLaneAt(AU2_AH4(v),WaveGetLaneIndex()^x));} + AW2 AWaveXorW2(AW2 v,AU1 x){return AW2_AU1(WaveReadLaneAt(AU1_AW2(v),WaveGetLaneIndex()^x));} + AW4 AWaveXorW4(AW4 v,AU1 x){return AW4_AU1(WaveReadLaneAt(AU1_AW4(v),WaveGetLaneIndex()^x));} + #endif + #endif +//============================================================================================================================== +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// GPU COMMON +// +// +//============================================================================================================================== +#ifdef A_GPU + // Negative and positive infinity. + #define A_INFP_F AF1_AU1(0x7f800000u) + #define A_INFN_F AF1_AU1(0xff800000u) +//------------------------------------------------------------------------------------------------------------------------------ + // Copy sign from 's' to positive 'd'. + AF1 ACpySgnF1(AF1 d,AF1 s){return AF1_AU1(AU1_AF1(d)|(AU1_AF1(s)&AU1_(0x80000000u)));} + AF2 ACpySgnF2(AF2 d,AF2 s){return AF2_AU2(AU2_AF2(d)|(AU2_AF2(s)&AU2_(0x80000000u)));} + AF3 ACpySgnF3(AF3 d,AF3 s){return AF3_AU3(AU3_AF3(d)|(AU3_AF3(s)&AU3_(0x80000000u)));} + AF4 ACpySgnF4(AF4 d,AF4 s){return AF4_AU4(AU4_AF4(d)|(AU4_AF4(s)&AU4_(0x80000000u)));} +//------------------------------------------------------------------------------------------------------------------------------ + // Single operation to return (useful to create a mask to use in lerp for branch free logic), + // m=NaN := 0 + // m>=0 := 0 + // m<0 := 1 + // Uses the following useful floating point logic, + // saturate(+a*(-INF)==-INF) := 0 + // saturate( 0*(-INF)== NaN) := 0 + // saturate(-a*(-INF)==+INF) := 1 + AF1 ASignedF1(AF1 m){return ASatF1(m*AF1_(A_INFN_F));} + AF2 ASignedF2(AF2 m){return ASatF2(m*AF2_(A_INFN_F));} + AF3 ASignedF3(AF3 m){return ASatF3(m*AF3_(A_INFN_F));} + AF4 ASignedF4(AF4 m){return ASatF4(m*AF4_(A_INFN_F));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AGtZeroF1(AF1 m){return ASatF1(m*AF1_(A_INFP_F));} + AF2 AGtZeroF2(AF2 m){return ASatF2(m*AF2_(A_INFP_F));} + AF3 AGtZeroF3(AF3 m){return ASatF3(m*AF3_(A_INFP_F));} + AF4 AGtZeroF4(AF4 m){return ASatF4(m*AF4_(A_INFP_F));} +//============================================================================================================================== + #ifdef A_HALF + #ifdef A_HLSL_6_2 + #define A_INFP_H AH1_AW1((uint16_t)0x7c00u) + #define A_INFN_H AH1_AW1((uint16_t)0xfc00u) + #else + #define A_INFP_H AH1_AW1(0x7c00u) + #define A_INFN_H AH1_AW1(0xfc00u) + #endif + +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ACpySgnH1(AH1 d,AH1 s){return AH1_AW1(AW1_AH1(d)|(AW1_AH1(s)&AW1_(0x8000u)));} + AH2 ACpySgnH2(AH2 d,AH2 s){return AH2_AW2(AW2_AH2(d)|(AW2_AH2(s)&AW2_(0x8000u)));} + AH3 ACpySgnH3(AH3 d,AH3 s){return AH3_AW3(AW3_AH3(d)|(AW3_AH3(s)&AW3_(0x8000u)));} + AH4 ACpySgnH4(AH4 d,AH4 s){return AH4_AW4(AW4_AH4(d)|(AW4_AH4(s)&AW4_(0x8000u)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASignedH1(AH1 m){return ASatH1(m*AH1_(A_INFN_H));} + AH2 ASignedH2(AH2 m){return ASatH2(m*AH2_(A_INFN_H));} + AH3 ASignedH3(AH3 m){return ASatH3(m*AH3_(A_INFN_H));} + AH4 ASignedH4(AH4 m){return ASatH4(m*AH4_(A_INFN_H));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AGtZeroH1(AH1 m){return ASatH1(m*AH1_(A_INFP_H));} + AH2 AGtZeroH2(AH2 m){return ASatH2(m*AH2_(A_INFP_H));} + AH3 AGtZeroH3(AH3 m){return ASatH3(m*AH3_(A_INFP_H));} + AH4 AGtZeroH4(AH4 m){return ASatH4(m*AH4_(A_INFP_H));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [FIS] FLOAT INTEGER SORTABLE +//------------------------------------------------------------------------------------------------------------------------------ +// Float to integer sortable. +// - If sign bit=0, flip the sign bit (positives). +// - If sign bit=1, flip all bits (negatives). +// Integer sortable to float. +// - If sign bit=1, flip the sign bit (positives). +// - If sign bit=0, flip all bits (negatives). +// Has nice side effects. +// - Larger integers are more positive values. +// - Float zero is mapped to center of integers (so clear to integer zero is a nice default for atomic max usage). +// Burns 3 ops for conversion {shift,or,xor}. +//============================================================================================================================== + AU1 AFisToU1(AU1 x){return x^(( AShrSU1(x,AU1_(31)))|AU1_(0x80000000));} + AU1 AFisFromU1(AU1 x){return x^((~AShrSU1(x,AU1_(31)))|AU1_(0x80000000));} +//------------------------------------------------------------------------------------------------------------------------------ + // Just adjust high 16-bit value (useful when upper part of 32-bit word is a 16-bit float value). + AU1 AFisToHiU1(AU1 x){return x^(( AShrSU1(x,AU1_(15)))|AU1_(0x80000000));} + AU1 AFisFromHiU1(AU1 x){return x^((~AShrSU1(x,AU1_(15)))|AU1_(0x80000000));} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + AW1 AFisToW1(AW1 x){return x^(( AShrSW1(x,AW1_(15)))|AW1_(0x8000));} + AW1 AFisFromW1(AW1 x){return x^((~AShrSW1(x,AW1_(15)))|AW1_(0x8000));} +//------------------------------------------------------------------------------------------------------------------------------ + AW2 AFisToW2(AW2 x){return x^(( AShrSW2(x,AW2_(15)))|AW2_(0x8000));} + AW2 AFisFromW2(AW2 x){return x^((~AShrSW2(x,AW2_(15)))|AW2_(0x8000));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [PERM] V_PERM_B32 +//------------------------------------------------------------------------------------------------------------------------------ +// Support for V_PERM_B32 started in the 3rd generation of GCN. +//------------------------------------------------------------------------------------------------------------------------------ +// yyyyxxxx - The 'i' input. +// 76543210 +// ======== +// HGFEDCBA - Naming on permutation. +//------------------------------------------------------------------------------------------------------------------------------ +// TODO +// ==== +// - Make sure compiler optimizes this. +//============================================================================================================================== + #ifdef A_HALF + AU1 APerm0E0A(AU2 i){return((i.x )&0xffu)|((i.y<<16)&0xff0000u);} + AU1 APerm0F0B(AU2 i){return((i.x>> 8)&0xffu)|((i.y<< 8)&0xff0000u);} + AU1 APerm0G0C(AU2 i){return((i.x>>16)&0xffu)|((i.y )&0xff0000u);} + AU1 APerm0H0D(AU2 i){return((i.x>>24)&0xffu)|((i.y>> 8)&0xff0000u);} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 APermHGFA(AU2 i){return((i.x )&0x000000ffu)|(i.y&0xffffff00u);} + AU1 APermHGFC(AU2 i){return((i.x>>16)&0x000000ffu)|(i.y&0xffffff00u);} + AU1 APermHGAE(AU2 i){return((i.x<< 8)&0x0000ff00u)|(i.y&0xffff00ffu);} + AU1 APermHGCE(AU2 i){return((i.x>> 8)&0x0000ff00u)|(i.y&0xffff00ffu);} + AU1 APermHAFE(AU2 i){return((i.x<<16)&0x00ff0000u)|(i.y&0xff00ffffu);} + AU1 APermHCFE(AU2 i){return((i.x )&0x00ff0000u)|(i.y&0xff00ffffu);} + AU1 APermAGFE(AU2 i){return((i.x<<24)&0xff000000u)|(i.y&0x00ffffffu);} + AU1 APermCGFE(AU2 i){return((i.x<< 8)&0xff000000u)|(i.y&0x00ffffffu);} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 APermGCEA(AU2 i){return((i.x)&0x00ff00ffu)|((i.y<<8)&0xff00ff00u);} + AU1 APermGECA(AU2 i){return(((i.x)&0xffu)|((i.x>>8)&0xff00u)|((i.y<<16)&0xff0000u)|((i.y<<8)&0xff000000u));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [BUC] BYTE UNSIGNED CONVERSION +//------------------------------------------------------------------------------------------------------------------------------ +// Designed to use the optimal conversion, enables the scaling to possibly be factored into other computation. +// Works on a range of {0 to A_BUC_<32,16>}, for <32-bit, and 16-bit> respectively. +//------------------------------------------------------------------------------------------------------------------------------ +// OPCODE NOTES +// ============ +// GCN does not do UNORM or SNORM for bytes in opcodes. +// - V_CVT_F32_UBYTE{0,1,2,3} - Unsigned byte to float. +// - V_CVT_PKACC_U8_F32 - Float to unsigned byte (does bit-field insert into 32-bit integer). +// V_PERM_B32 does byte packing with ability to zero fill bytes as well. +// - Can pull out byte values from two sources, and zero fill upper 8-bits of packed hi and lo. +//------------------------------------------------------------------------------------------------------------------------------ +// BYTE : FLOAT - ABuc{0,1,2,3}{To,From}U1() - Designed for V_CVT_F32_UBYTE* and V_CVT_PKACCUM_U8_F32 ops. +// ==== ===== +// 0 : 0 +// 1 : 1 +// ... +// 255 : 255 +// : 256 (just outside the encoding range) +//------------------------------------------------------------------------------------------------------------------------------ +// BYTE : FLOAT - ABuc{0,1,2,3}{To,From}U2() - Designed for 16-bit denormal tricks and V_PERM_B32. +// ==== ===== +// 0 : 0 +// 1 : 1/512 +// 2 : 1/256 +// ... +// 64 : 1/8 +// 128 : 1/4 +// 255 : 255/512 +// : 1/2 (just outside the encoding range) +//------------------------------------------------------------------------------------------------------------------------------ +// OPTIMAL IMPLEMENTATIONS ON AMD ARCHITECTURES +// ============================================ +// r=ABuc0FromU1(i) +// V_CVT_F32_UBYTE0 r,i +// -------------------------------------------- +// r=ABuc0ToU1(d,i) +// V_CVT_PKACCUM_U8_F32 r,i,0,d +// -------------------------------------------- +// d=ABuc0FromU2(i) +// Where 'k0' is an SGPR with 0x0E0A +// Where 'k1' is an SGPR with {32768.0} packed into the lower 16-bits +// V_PERM_B32 d,i.x,i.y,k0 +// V_PK_FMA_F16 d,d,k1.x,0 +// -------------------------------------------- +// r=ABuc0ToU2(d,i) +// Where 'k0' is an SGPR with {1.0/32768.0} packed into the lower 16-bits +// Where 'k1' is an SGPR with 0x???? +// Where 'k2' is an SGPR with 0x???? +// V_PK_FMA_F16 i,i,k0.x,0 +// V_PERM_B32 r.x,i,i,k1 +// V_PERM_B32 r.y,i,i,k2 +//============================================================================================================================== + // Peak range for 32-bit and 16-bit operations. + #define A_BUC_32 (255.0) + #define A_BUC_16 (255.0/512.0) +//============================================================================================================================== + #if 1 + // Designed to be one V_CVT_PKACCUM_U8_F32. + // The extra min is required to pattern match to V_CVT_PKACCUM_U8_F32. + AU1 ABuc0ToU1(AU1 d,AF1 i){return (d&0xffffff00u)|((min(AU1(i),255u) )&(0x000000ffu));} + AU1 ABuc1ToU1(AU1 d,AF1 i){return (d&0xffff00ffu)|((min(AU1(i),255u)<< 8)&(0x0000ff00u));} + AU1 ABuc2ToU1(AU1 d,AF1 i){return (d&0xff00ffffu)|((min(AU1(i),255u)<<16)&(0x00ff0000u));} + AU1 ABuc3ToU1(AU1 d,AF1 i){return (d&0x00ffffffu)|((min(AU1(i),255u)<<24)&(0xff000000u));} +//------------------------------------------------------------------------------------------------------------------------------ + // Designed to be one V_CVT_F32_UBYTE*. + AF1 ABuc0FromU1(AU1 i){return AF1((i )&255u);} + AF1 ABuc1FromU1(AU1 i){return AF1((i>> 8)&255u);} + AF1 ABuc2FromU1(AU1 i){return AF1((i>>16)&255u);} + AF1 ABuc3FromU1(AU1 i){return AF1((i>>24)&255u);} + #endif +//============================================================================================================================== + #ifdef A_HALF + // Takes {x0,x1} and {y0,y1} and builds {{x0,y0},{x1,y1}}. + AW2 ABuc01ToW2(AH2 x,AH2 y){x*=AH2_(1.0/32768.0);y*=AH2_(1.0/32768.0); + return AW2_AU1(APermGCEA(AU2(AU1_AW2(AW2_AH2(x)),AU1_AW2(AW2_AH2(y)))));} +//------------------------------------------------------------------------------------------------------------------------------ + // Designed for 3 ops to do SOA to AOS and conversion. + AU2 ABuc0ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); + return AU2(APermHGFA(AU2(d.x,b)),APermHGFC(AU2(d.y,b)));} + AU2 ABuc1ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); + return AU2(APermHGAE(AU2(d.x,b)),APermHGCE(AU2(d.y,b)));} + AU2 ABuc2ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); + return AU2(APermHAFE(AU2(d.x,b)),APermHCFE(AU2(d.y,b)));} + AU2 ABuc3ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); + return AU2(APermAGFE(AU2(d.x,b)),APermCGFE(AU2(d.y,b)));} +//------------------------------------------------------------------------------------------------------------------------------ + // Designed for 2 ops to do both AOS to SOA, and conversion. + AH2 ABuc0FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0E0A(i)))*AH2_(32768.0);} + AH2 ABuc1FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0F0B(i)))*AH2_(32768.0);} + AH2 ABuc2FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0G0C(i)))*AH2_(32768.0);} + AH2 ABuc3FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0H0D(i)))*AH2_(32768.0);} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [BSC] BYTE SIGNED CONVERSION +//------------------------------------------------------------------------------------------------------------------------------ +// Similar to [BUC]. +// Works on a range of {-/+ A_BSC_<32,16>}, for <32-bit, and 16-bit> respectively. +//------------------------------------------------------------------------------------------------------------------------------ +// ENCODING (without zero-based encoding) +// ======== +// 0 = unused (can be used to mean something else) +// 1 = lowest value +// 128 = exact zero center (zero based encoding +// 255 = highest value +//------------------------------------------------------------------------------------------------------------------------------ +// Zero-based [Zb] flips the MSB bit of the byte (making 128 "exact zero" actually zero). +// This is useful if there is a desire for cleared values to decode as zero. +//------------------------------------------------------------------------------------------------------------------------------ +// BYTE : FLOAT - ABsc{0,1,2,3}{To,From}U2() - Designed for 16-bit denormal tricks and V_PERM_B32. +// ==== ===== +// 0 : -127/512 (unused) +// 1 : -126/512 +// 2 : -125/512 +// ... +// 128 : 0 +// ... +// 255 : 127/512 +// : 1/4 (just outside the encoding range) +//============================================================================================================================== + // Peak range for 32-bit and 16-bit operations. + #define A_BSC_32 (127.0) + #define A_BSC_16 (127.0/512.0) +//============================================================================================================================== + #if 1 + AU1 ABsc0ToU1(AU1 d,AF1 i){return (d&0xffffff00u)|((min(AU1(i+128.0),255u) )&(0x000000ffu));} + AU1 ABsc1ToU1(AU1 d,AF1 i){return (d&0xffff00ffu)|((min(AU1(i+128.0),255u)<< 8)&(0x0000ff00u));} + AU1 ABsc2ToU1(AU1 d,AF1 i){return (d&0xff00ffffu)|((min(AU1(i+128.0),255u)<<16)&(0x00ff0000u));} + AU1 ABsc3ToU1(AU1 d,AF1 i){return (d&0x00ffffffu)|((min(AU1(i+128.0),255u)<<24)&(0xff000000u));} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 ABsc0ToZbU1(AU1 d,AF1 i){return ((d&0xffffff00u)|((min(AU1(trunc(i)+128.0),255u) )&(0x000000ffu)))^0x00000080u;} + AU1 ABsc1ToZbU1(AU1 d,AF1 i){return ((d&0xffff00ffu)|((min(AU1(trunc(i)+128.0),255u)<< 8)&(0x0000ff00u)))^0x00008000u;} + AU1 ABsc2ToZbU1(AU1 d,AF1 i){return ((d&0xff00ffffu)|((min(AU1(trunc(i)+128.0),255u)<<16)&(0x00ff0000u)))^0x00800000u;} + AU1 ABsc3ToZbU1(AU1 d,AF1 i){return ((d&0x00ffffffu)|((min(AU1(trunc(i)+128.0),255u)<<24)&(0xff000000u)))^0x80000000u;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 ABsc0FromU1(AU1 i){return AF1((i )&255u)-128.0;} + AF1 ABsc1FromU1(AU1 i){return AF1((i>> 8)&255u)-128.0;} + AF1 ABsc2FromU1(AU1 i){return AF1((i>>16)&255u)-128.0;} + AF1 ABsc3FromU1(AU1 i){return AF1((i>>24)&255u)-128.0;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 ABsc0FromZbU1(AU1 i){return AF1(((i )&255u)^0x80u)-128.0;} + AF1 ABsc1FromZbU1(AU1 i){return AF1(((i>> 8)&255u)^0x80u)-128.0;} + AF1 ABsc2FromZbU1(AU1 i){return AF1(((i>>16)&255u)^0x80u)-128.0;} + AF1 ABsc3FromZbU1(AU1 i){return AF1(((i>>24)&255u)^0x80u)-128.0;} + #endif +//============================================================================================================================== + #ifdef A_HALF + // Takes {x0,x1} and {y0,y1} and builds {{x0,y0},{x1,y1}}. + AW2 ABsc01ToW2(AH2 x,AH2 y){x=x*AH2_(1.0/32768.0)+AH2_(0.25/32768.0);y=y*AH2_(1.0/32768.0)+AH2_(0.25/32768.0); + return AW2_AU1(APermGCEA(AU2(AU1_AW2(AW2_AH2(x)),AU1_AW2(AW2_AH2(y)))));} +//------------------------------------------------------------------------------------------------------------------------------ + AU2 ABsc0ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); + return AU2(APermHGFA(AU2(d.x,b)),APermHGFC(AU2(d.y,b)));} + AU2 ABsc1ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); + return AU2(APermHGAE(AU2(d.x,b)),APermHGCE(AU2(d.y,b)));} + AU2 ABsc2ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); + return AU2(APermHAFE(AU2(d.x,b)),APermHCFE(AU2(d.y,b)));} + AU2 ABsc3ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); + return AU2(APermAGFE(AU2(d.x,b)),APermCGFE(AU2(d.y,b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AU2 ABsc0ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; + return AU2(APermHGFA(AU2(d.x,b)),APermHGFC(AU2(d.y,b)));} + AU2 ABsc1ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; + return AU2(APermHGAE(AU2(d.x,b)),APermHGCE(AU2(d.y,b)));} + AU2 ABsc2ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; + return AU2(APermHAFE(AU2(d.x,b)),APermHCFE(AU2(d.y,b)));} + AU2 ABsc3ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; + return AU2(APermAGFE(AU2(d.x,b)),APermCGFE(AU2(d.y,b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH2 ABsc0FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0E0A(i)))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc1FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0F0B(i)))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc2FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0G0C(i)))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc3FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0H0D(i)))*AH2_(32768.0)-AH2_(0.25);} +//------------------------------------------------------------------------------------------------------------------------------ + AH2 ABsc0FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0E0A(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc1FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0F0B(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc2FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0G0C(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc3FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0H0D(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HALF APPROXIMATIONS +//------------------------------------------------------------------------------------------------------------------------------ +// These support only positive inputs. +// Did not see value yet in specialization for range. +// Using quick testing, ended up mostly getting the same "best" approximation for various ranges. +// With hardware that can co-execute transcendentals, the value in approximations could be less than expected. +// However from a latency perspective, if execution of a transcendental is 4 clk, with no packed support, -> 8 clk total. +// And co-execution would require a compiler interleaving a lot of independent work for packed usage. +//------------------------------------------------------------------------------------------------------------------------------ +// The one Newton Raphson iteration form of rsq() was skipped (requires 6 ops total). +// Same with sqrt(), as this could be x*rsq() (7 ops). +//============================================================================================================================== + #ifdef A_HALF + // Minimize squared error across full positive range, 2 ops. + // The 0x1de2 based approximation maps {0 to 1} input maps to < 1 output. + AH1 APrxLoSqrtH1(AH1 a){return AH1_AW1((AW1_AH1(a)>>AW1_(1))+AW1_(0x1de2));} + AH2 APrxLoSqrtH2(AH2 a){return AH2_AW2((AW2_AH2(a)>>AW2_(1))+AW2_(0x1de2));} + AH3 APrxLoSqrtH3(AH3 a){return AH3_AW3((AW3_AH3(a)>>AW3_(1))+AW3_(0x1de2));} + AH4 APrxLoSqrtH4(AH4 a){return AH4_AW4((AW4_AH4(a)>>AW4_(1))+AW4_(0x1de2));} +//------------------------------------------------------------------------------------------------------------------------------ + // Lower precision estimation, 1 op. + // Minimize squared error across {smallest normal to 16384.0}. + AH1 APrxLoRcpH1(AH1 a){return AH1_AW1(AW1_(0x7784)-AW1_AH1(a));} + AH2 APrxLoRcpH2(AH2 a){return AH2_AW2(AW2_(0x7784)-AW2_AH2(a));} + AH3 APrxLoRcpH3(AH3 a){return AH3_AW3(AW3_(0x7784)-AW3_AH3(a));} + AH4 APrxLoRcpH4(AH4 a){return AH4_AW4(AW4_(0x7784)-AW4_AH4(a));} +//------------------------------------------------------------------------------------------------------------------------------ + // Medium precision estimation, one Newton Raphson iteration, 3 ops. + AH1 APrxMedRcpH1(AH1 a){AH1 b=AH1_AW1(AW1_(0x778d)-AW1_AH1(a));return b*(-b*a+AH1_(2.0));} + AH2 APrxMedRcpH2(AH2 a){AH2 b=AH2_AW2(AW2_(0x778d)-AW2_AH2(a));return b*(-b*a+AH2_(2.0));} + AH3 APrxMedRcpH3(AH3 a){AH3 b=AH3_AW3(AW3_(0x778d)-AW3_AH3(a));return b*(-b*a+AH3_(2.0));} + AH4 APrxMedRcpH4(AH4 a){AH4 b=AH4_AW4(AW4_(0x778d)-AW4_AH4(a));return b*(-b*a+AH4_(2.0));} +//------------------------------------------------------------------------------------------------------------------------------ + // Minimize squared error across {smallest normal to 16384.0}, 2 ops. + AH1 APrxLoRsqH1(AH1 a){return AH1_AW1(AW1_(0x59a3)-(AW1_AH1(a)>>AW1_(1)));} + AH2 APrxLoRsqH2(AH2 a){return AH2_AW2(AW2_(0x59a3)-(AW2_AH2(a)>>AW2_(1)));} + AH3 APrxLoRsqH3(AH3 a){return AH3_AW3(AW3_(0x59a3)-(AW3_AH3(a)>>AW3_(1)));} + AH4 APrxLoRsqH4(AH4 a){return AH4_AW4(AW4_(0x59a3)-(AW4_AH4(a)>>AW4_(1)));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// FLOAT APPROXIMATIONS +//------------------------------------------------------------------------------------------------------------------------------ +// Michal Drobot has an excellent presentation on these: "Low Level Optimizations For GCN", +// - Idea dates back to SGI, then to Quake 3, etc. +// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf +// - sqrt(x)=rsqrt(x)*x +// - rcp(x)=rsqrt(x)*rsqrt(x) for positive x +// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h +//------------------------------------------------------------------------------------------------------------------------------ +// These below are from perhaps less complete searching for optimal. +// Used FP16 normal range for testing with +4096 32-bit step size for sampling error. +// So these match up well with the half approximations. +//============================================================================================================================== + AF1 APrxLoSqrtF1(AF1 a){return AF1_AU1((AU1_AF1(a)>>AU1_(1))+AU1_(0x1fbc4639));} + AF1 APrxLoRcpF1(AF1 a){return AF1_AU1(AU1_(0x7ef07ebb)-AU1_AF1(a));} + AF1 APrxMedRcpF1(AF1 a){AF1 b=AF1_AU1(AU1_(0x7ef19fff)-AU1_AF1(a));return b*(-b*a+AF1_(2.0));} + AF1 APrxLoRsqF1(AF1 a){return AF1_AU1(AU1_(0x5f347d74)-(AU1_AF1(a)>>AU1_(1)));} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 APrxLoSqrtF2(AF2 a){return AF2_AU2((AU2_AF2(a)>>AU2_(1))+AU2_(0x1fbc4639));} + AF2 APrxLoRcpF2(AF2 a){return AF2_AU2(AU2_(0x7ef07ebb)-AU2_AF2(a));} + AF2 APrxMedRcpF2(AF2 a){AF2 b=AF2_AU2(AU2_(0x7ef19fff)-AU2_AF2(a));return b*(-b*a+AF2_(2.0));} + AF2 APrxLoRsqF2(AF2 a){return AF2_AU2(AU2_(0x5f347d74)-(AU2_AF2(a)>>AU2_(1)));} +//------------------------------------------------------------------------------------------------------------------------------ + AF3 APrxLoSqrtF3(AF3 a){return AF3_AU3((AU3_AF3(a)>>AU3_(1))+AU3_(0x1fbc4639));} + AF3 APrxLoRcpF3(AF3 a){return AF3_AU3(AU3_(0x7ef07ebb)-AU3_AF3(a));} + AF3 APrxMedRcpF3(AF3 a){AF3 b=AF3_AU3(AU3_(0x7ef19fff)-AU3_AF3(a));return b*(-b*a+AF3_(2.0));} + AF3 APrxLoRsqF3(AF3 a){return AF3_AU3(AU3_(0x5f347d74)-(AU3_AF3(a)>>AU3_(1)));} +//------------------------------------------------------------------------------------------------------------------------------ + AF4 APrxLoSqrtF4(AF4 a){return AF4_AU4((AU4_AF4(a)>>AU4_(1))+AU4_(0x1fbc4639));} + AF4 APrxLoRcpF4(AF4 a){return AF4_AU4(AU4_(0x7ef07ebb)-AU4_AF4(a));} + AF4 APrxMedRcpF4(AF4 a){AF4 b=AF4_AU4(AU4_(0x7ef19fff)-AU4_AF4(a));return b*(-b*a+AF4_(2.0));} + AF4 APrxLoRsqF4(AF4 a){return AF4_AU4(AU4_(0x5f347d74)-(AU4_AF4(a)>>AU4_(1)));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// PQ APPROXIMATIONS +//------------------------------------------------------------------------------------------------------------------------------ +// PQ is very close to x^(1/8). The functions below Use the fast float approximation method to do +// PQ<~>Gamma2 (4th power and fast 4th root) and PQ<~>Linear (8th power and fast 8th root). Maximum error is ~0.2%. +//============================================================================================================================== +// Helpers + AF1 Quart(AF1 a) { a = a * a; return a * a;} + AF1 Oct(AF1 a) { a = a * a; a = a * a; return a * a; } + AF2 Quart(AF2 a) { a = a * a; return a * a; } + AF2 Oct(AF2 a) { a = a * a; a = a * a; return a * a; } + AF3 Quart(AF3 a) { a = a * a; return a * a; } + AF3 Oct(AF3 a) { a = a * a; a = a * a; return a * a; } + AF4 Quart(AF4 a) { a = a * a; return a * a; } + AF4 Oct(AF4 a) { a = a * a; a = a * a; return a * a; } + //------------------------------------------------------------------------------------------------------------------------------ + AF1 APrxPQToGamma2(AF1 a) { return Quart(a); } + AF1 APrxPQToLinear(AF1 a) { return Oct(a); } + AF1 APrxLoGamma2ToPQ(AF1 a) { return AF1_AU1((AU1_AF1(a) >> AU1_(2)) + AU1_(0x2F9A4E46)); } + AF1 APrxMedGamma2ToPQ(AF1 a) { AF1 b = AF1_AU1((AU1_AF1(a) >> AU1_(2)) + AU1_(0x2F9A4E46)); AF1 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } + AF1 APrxHighGamma2ToPQ(AF1 a) { return sqrt(sqrt(a)); } + AF1 APrxLoLinearToPQ(AF1 a) { return AF1_AU1((AU1_AF1(a) >> AU1_(3)) + AU1_(0x378D8723)); } + AF1 APrxMedLinearToPQ(AF1 a) { AF1 b = AF1_AU1((AU1_AF1(a) >> AU1_(3)) + AU1_(0x378D8723)); AF1 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } + AF1 APrxHighLinearToPQ(AF1 a) { return sqrt(sqrt(sqrt(a))); } + //------------------------------------------------------------------------------------------------------------------------------ + AF2 APrxPQToGamma2(AF2 a) { return Quart(a); } + AF2 APrxPQToLinear(AF2 a) { return Oct(a); } + AF2 APrxLoGamma2ToPQ(AF2 a) { return AF2_AU2((AU2_AF2(a) >> AU2_(2)) + AU2_(0x2F9A4E46)); } + AF2 APrxMedGamma2ToPQ(AF2 a) { AF2 b = AF2_AU2((AU2_AF2(a) >> AU2_(2)) + AU2_(0x2F9A4E46)); AF2 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } + AF2 APrxHighGamma2ToPQ(AF2 a) { return sqrt(sqrt(a)); } + AF2 APrxLoLinearToPQ(AF2 a) { return AF2_AU2((AU2_AF2(a) >> AU2_(3)) + AU2_(0x378D8723)); } + AF2 APrxMedLinearToPQ(AF2 a) { AF2 b = AF2_AU2((AU2_AF2(a) >> AU2_(3)) + AU2_(0x378D8723)); AF2 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } + AF2 APrxHighLinearToPQ(AF2 a) { return sqrt(sqrt(sqrt(a))); } + //------------------------------------------------------------------------------------------------------------------------------ + AF3 APrxPQToGamma2(AF3 a) { return Quart(a); } + AF3 APrxPQToLinear(AF3 a) { return Oct(a); } + AF3 APrxLoGamma2ToPQ(AF3 a) { return AF3_AU3((AU3_AF3(a) >> AU3_(2)) + AU3_(0x2F9A4E46)); } + AF3 APrxMedGamma2ToPQ(AF3 a) { AF3 b = AF3_AU3((AU3_AF3(a) >> AU3_(2)) + AU3_(0x2F9A4E46)); AF3 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } + AF3 APrxHighGamma2ToPQ(AF3 a) { return sqrt(sqrt(a)); } + AF3 APrxLoLinearToPQ(AF3 a) { return AF3_AU3((AU3_AF3(a) >> AU3_(3)) + AU3_(0x378D8723)); } + AF3 APrxMedLinearToPQ(AF3 a) { AF3 b = AF3_AU3((AU3_AF3(a) >> AU3_(3)) + AU3_(0x378D8723)); AF3 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } + AF3 APrxHighLinearToPQ(AF3 a) { return sqrt(sqrt(sqrt(a))); } + //------------------------------------------------------------------------------------------------------------------------------ + AF4 APrxPQToGamma2(AF4 a) { return Quart(a); } + AF4 APrxPQToLinear(AF4 a) { return Oct(a); } + AF4 APrxLoGamma2ToPQ(AF4 a) { return AF4_AU4((AU4_AF4(a) >> AU4_(2)) + AU4_(0x2F9A4E46)); } + AF4 APrxMedGamma2ToPQ(AF4 a) { AF4 b = AF4_AU4((AU4_AF4(a) >> AU4_(2)) + AU4_(0x2F9A4E46)); AF4 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } + AF4 APrxHighGamma2ToPQ(AF4 a) { return sqrt(sqrt(a)); } + AF4 APrxLoLinearToPQ(AF4 a) { return AF4_AU4((AU4_AF4(a) >> AU4_(3)) + AU4_(0x378D8723)); } + AF4 APrxMedLinearToPQ(AF4 a) { AF4 b = AF4_AU4((AU4_AF4(a) >> AU4_(3)) + AU4_(0x378D8723)); AF4 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } + AF4 APrxHighLinearToPQ(AF4 a) { return sqrt(sqrt(sqrt(a))); } +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// PARABOLIC SIN & COS +//------------------------------------------------------------------------------------------------------------------------------ +// Approximate answers to transcendental questions. +//------------------------------------------------------------------------------------------------------------------------------ +//============================================================================================================================== + #if 1 + // Valid input range is {-1 to 1} representing {0 to 2 pi}. + // Output range is {-1/4 to 1/4} representing {-1 to 1}. + AF1 APSinF1(AF1 x){return x*abs(x)-x;} // MAD. + AF2 APSinF2(AF2 x){return x*abs(x)-x;} + AF1 APCosF1(AF1 x){x=AFractF1(x*AF1_(0.5)+AF1_(0.75));x=x*AF1_(2.0)-AF1_(1.0);return APSinF1(x);} // 3x MAD, FRACT + AF2 APCosF2(AF2 x){x=AFractF2(x*AF2_(0.5)+AF2_(0.75));x=x*AF2_(2.0)-AF2_(1.0);return APSinF2(x);} + AF2 APSinCosF1(AF1 x){AF1 y=AFractF1(x*AF1_(0.5)+AF1_(0.75));y=y*AF1_(2.0)-AF1_(1.0);return APSinF2(AF2(x,y));} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + // For a packed {sin,cos} pair, + // - Native takes 16 clocks and 4 issue slots (no packed transcendentals). + // - Parabolic takes 8 clocks and 8 issue slots (only fract is non-packed). + AH1 APSinH1(AH1 x){return x*abs(x)-x;} + AH2 APSinH2(AH2 x){return x*abs(x)-x;} // AND,FMA + AH1 APCosH1(AH1 x){x=AFractH1(x*AH1_(0.5)+AH1_(0.75));x=x*AH1_(2.0)-AH1_(1.0);return APSinH1(x);} + AH2 APCosH2(AH2 x){x=AFractH2(x*AH2_(0.5)+AH2_(0.75));x=x*AH2_(2.0)-AH2_(1.0);return APSinH2(x);} // 3x FMA, 2xFRACT, AND + AH2 APSinCosH1(AH1 x){AH1 y=AFractH1(x*AH1_(0.5)+AH1_(0.75));y=y*AH1_(2.0)-AH1_(1.0);return APSinH2(AH2(x,y));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [ZOL] ZERO ONE LOGIC +//------------------------------------------------------------------------------------------------------------------------------ +// Conditional free logic designed for easy 16-bit packing, and backwards porting to 32-bit. +//------------------------------------------------------------------------------------------------------------------------------ +// 0 := false +// 1 := true +//------------------------------------------------------------------------------------------------------------------------------ +// AndNot(x,y) -> !(x&y) .... One op. +// AndOr(x,y,z) -> (x&y)|z ... One op. +// GtZero(x) -> x>0.0 ..... One op. +// Sel(x,y,z) -> x?y:z ..... Two ops, has no precision loss. +// Signed(x) -> x<0.0 ..... One op. +// ZeroPass(x,y) -> x?0:y ..... Two ops, 'y' is a pass through safe for aliasing as integer. +//------------------------------------------------------------------------------------------------------------------------------ +// OPTIMIZATION NOTES +// ================== +// - On Vega to use 2 constants in a packed op, pass in as one AW2 or one AH2 'k.xy' and use as 'k.xx' and 'k.yy'. +// For example 'a.xy*k.xx+k.yy'. +//============================================================================================================================== + #if 1 + AU1 AZolAndU1(AU1 x,AU1 y){return min(x,y);} + AU2 AZolAndU2(AU2 x,AU2 y){return min(x,y);} + AU3 AZolAndU3(AU3 x,AU3 y){return min(x,y);} + AU4 AZolAndU4(AU4 x,AU4 y){return min(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AZolNotU1(AU1 x){return x^AU1_(1);} + AU2 AZolNotU2(AU2 x){return x^AU2_(1);} + AU3 AZolNotU3(AU3 x){return x^AU3_(1);} + AU4 AZolNotU4(AU4 x){return x^AU4_(1);} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AZolOrU1(AU1 x,AU1 y){return max(x,y);} + AU2 AZolOrU2(AU2 x,AU2 y){return max(x,y);} + AU3 AZolOrU3(AU3 x,AU3 y){return max(x,y);} + AU4 AZolOrU4(AU4 x,AU4 y){return max(x,y);} +//============================================================================================================================== + AU1 AZolF1ToU1(AF1 x){return AU1(x);} + AU2 AZolF2ToU2(AF2 x){return AU2(x);} + AU3 AZolF3ToU3(AF3 x){return AU3(x);} + AU4 AZolF4ToU4(AF4 x){return AU4(x);} +//------------------------------------------------------------------------------------------------------------------------------ + // 2 ops, denormals don't work in 32-bit on PC (and if they are enabled, OMOD is disabled). + AU1 AZolNotF1ToU1(AF1 x){return AU1(AF1_(1.0)-x);} + AU2 AZolNotF2ToU2(AF2 x){return AU2(AF2_(1.0)-x);} + AU3 AZolNotF3ToU3(AF3 x){return AU3(AF3_(1.0)-x);} + AU4 AZolNotF4ToU4(AF4 x){return AU4(AF4_(1.0)-x);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolU1ToF1(AU1 x){return AF1(x);} + AF2 AZolU2ToF2(AU2 x){return AF2(x);} + AF3 AZolU3ToF3(AU3 x){return AF3(x);} + AF4 AZolU4ToF4(AU4 x){return AF4(x);} +//============================================================================================================================== + AF1 AZolAndF1(AF1 x,AF1 y){return min(x,y);} + AF2 AZolAndF2(AF2 x,AF2 y){return min(x,y);} + AF3 AZolAndF3(AF3 x,AF3 y){return min(x,y);} + AF4 AZolAndF4(AF4 x,AF4 y){return min(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 ASolAndNotF1(AF1 x,AF1 y){return (-x)*y+AF1_(1.0);} + AF2 ASolAndNotF2(AF2 x,AF2 y){return (-x)*y+AF2_(1.0);} + AF3 ASolAndNotF3(AF3 x,AF3 y){return (-x)*y+AF3_(1.0);} + AF4 ASolAndNotF4(AF4 x,AF4 y){return (-x)*y+AF4_(1.0);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolAndOrF1(AF1 x,AF1 y,AF1 z){return ASatF1(x*y+z);} + AF2 AZolAndOrF2(AF2 x,AF2 y,AF2 z){return ASatF2(x*y+z);} + AF3 AZolAndOrF3(AF3 x,AF3 y,AF3 z){return ASatF3(x*y+z);} + AF4 AZolAndOrF4(AF4 x,AF4 y,AF4 z){return ASatF4(x*y+z);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolGtZeroF1(AF1 x){return ASatF1(x*AF1_(A_INFP_F));} + AF2 AZolGtZeroF2(AF2 x){return ASatF2(x*AF2_(A_INFP_F));} + AF3 AZolGtZeroF3(AF3 x){return ASatF3(x*AF3_(A_INFP_F));} + AF4 AZolGtZeroF4(AF4 x){return ASatF4(x*AF4_(A_INFP_F));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolNotF1(AF1 x){return AF1_(1.0)-x;} + AF2 AZolNotF2(AF2 x){return AF2_(1.0)-x;} + AF3 AZolNotF3(AF3 x){return AF3_(1.0)-x;} + AF4 AZolNotF4(AF4 x){return AF4_(1.0)-x;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolOrF1(AF1 x,AF1 y){return max(x,y);} + AF2 AZolOrF2(AF2 x,AF2 y){return max(x,y);} + AF3 AZolOrF3(AF3 x,AF3 y){return max(x,y);} + AF4 AZolOrF4(AF4 x,AF4 y){return max(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolSelF1(AF1 x,AF1 y,AF1 z){AF1 r=(-x)*z+z;return x*y+r;} + AF2 AZolSelF2(AF2 x,AF2 y,AF2 z){AF2 r=(-x)*z+z;return x*y+r;} + AF3 AZolSelF3(AF3 x,AF3 y,AF3 z){AF3 r=(-x)*z+z;return x*y+r;} + AF4 AZolSelF4(AF4 x,AF4 y,AF4 z){AF4 r=(-x)*z+z;return x*y+r;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolSignedF1(AF1 x){return ASatF1(x*AF1_(A_INFN_F));} + AF2 AZolSignedF2(AF2 x){return ASatF2(x*AF2_(A_INFN_F));} + AF3 AZolSignedF3(AF3 x){return ASatF3(x*AF3_(A_INFN_F));} + AF4 AZolSignedF4(AF4 x){return ASatF4(x*AF4_(A_INFN_F));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolZeroPassF1(AF1 x,AF1 y){return AF1_AU1((AU1_AF1(x)!=AU1_(0))?AU1_(0):AU1_AF1(y));} + AF2 AZolZeroPassF2(AF2 x,AF2 y){return AF2_AU2((AU2_AF2(x)!=AU2_(0))?AU2_(0):AU2_AF2(y));} + AF3 AZolZeroPassF3(AF3 x,AF3 y){return AF3_AU3((AU3_AF3(x)!=AU3_(0))?AU3_(0):AU3_AF3(y));} + AF4 AZolZeroPassF4(AF4 x,AF4 y){return AF4_AU4((AU4_AF4(x)!=AU4_(0))?AU4_(0):AU4_AF4(y));} + #endif +//============================================================================================================================== + #ifdef A_HALF + AW1 AZolAndW1(AW1 x,AW1 y){return min(x,y);} + AW2 AZolAndW2(AW2 x,AW2 y){return min(x,y);} + AW3 AZolAndW3(AW3 x,AW3 y){return min(x,y);} + AW4 AZolAndW4(AW4 x,AW4 y){return min(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AZolNotW1(AW1 x){return x^AW1_(1);} + AW2 AZolNotW2(AW2 x){return x^AW2_(1);} + AW3 AZolNotW3(AW3 x){return x^AW3_(1);} + AW4 AZolNotW4(AW4 x){return x^AW4_(1);} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AZolOrW1(AW1 x,AW1 y){return max(x,y);} + AW2 AZolOrW2(AW2 x,AW2 y){return max(x,y);} + AW3 AZolOrW3(AW3 x,AW3 y){return max(x,y);} + AW4 AZolOrW4(AW4 x,AW4 y){return max(x,y);} +//============================================================================================================================== + // Uses denormal trick. + AW1 AZolH1ToW1(AH1 x){return AW1_AH1(x*AH1_AW1(AW1_(1)));} + AW2 AZolH2ToW2(AH2 x){return AW2_AH2(x*AH2_AW2(AW2_(1)));} + AW3 AZolH3ToW3(AH3 x){return AW3_AH3(x*AH3_AW3(AW3_(1)));} + AW4 AZolH4ToW4(AH4 x){return AW4_AH4(x*AH4_AW4(AW4_(1)));} +//------------------------------------------------------------------------------------------------------------------------------ + // AMD arch lacks a packed conversion opcode. + AH1 AZolW1ToH1(AW1 x){return AH1_AW1(x*AW1_AH1(AH1_(1.0)));} + AH2 AZolW2ToH2(AW2 x){return AH2_AW2(x*AW2_AH2(AH2_(1.0)));} + AH3 AZolW1ToH3(AW3 x){return AH3_AW3(x*AW3_AH3(AH3_(1.0)));} + AH4 AZolW2ToH4(AW4 x){return AH4_AW4(x*AW4_AH4(AH4_(1.0)));} +//============================================================================================================================== + AH1 AZolAndH1(AH1 x,AH1 y){return min(x,y);} + AH2 AZolAndH2(AH2 x,AH2 y){return min(x,y);} + AH3 AZolAndH3(AH3 x,AH3 y){return min(x,y);} + AH4 AZolAndH4(AH4 x,AH4 y){return min(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASolAndNotH1(AH1 x,AH1 y){return (-x)*y+AH1_(1.0);} + AH2 ASolAndNotH2(AH2 x,AH2 y){return (-x)*y+AH2_(1.0);} + AH3 ASolAndNotH3(AH3 x,AH3 y){return (-x)*y+AH3_(1.0);} + AH4 ASolAndNotH4(AH4 x,AH4 y){return (-x)*y+AH4_(1.0);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolAndOrH1(AH1 x,AH1 y,AH1 z){return ASatH1(x*y+z);} + AH2 AZolAndOrH2(AH2 x,AH2 y,AH2 z){return ASatH2(x*y+z);} + AH3 AZolAndOrH3(AH3 x,AH3 y,AH3 z){return ASatH3(x*y+z);} + AH4 AZolAndOrH4(AH4 x,AH4 y,AH4 z){return ASatH4(x*y+z);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolGtZeroH1(AH1 x){return ASatH1(x*AH1_(A_INFP_H));} + AH2 AZolGtZeroH2(AH2 x){return ASatH2(x*AH2_(A_INFP_H));} + AH3 AZolGtZeroH3(AH3 x){return ASatH3(x*AH3_(A_INFP_H));} + AH4 AZolGtZeroH4(AH4 x){return ASatH4(x*AH4_(A_INFP_H));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolNotH1(AH1 x){return AH1_(1.0)-x;} + AH2 AZolNotH2(AH2 x){return AH2_(1.0)-x;} + AH3 AZolNotH3(AH3 x){return AH3_(1.0)-x;} + AH4 AZolNotH4(AH4 x){return AH4_(1.0)-x;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolOrH1(AH1 x,AH1 y){return max(x,y);} + AH2 AZolOrH2(AH2 x,AH2 y){return max(x,y);} + AH3 AZolOrH3(AH3 x,AH3 y){return max(x,y);} + AH4 AZolOrH4(AH4 x,AH4 y){return max(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolSelH1(AH1 x,AH1 y,AH1 z){AH1 r=(-x)*z+z;return x*y+r;} + AH2 AZolSelH2(AH2 x,AH2 y,AH2 z){AH2 r=(-x)*z+z;return x*y+r;} + AH3 AZolSelH3(AH3 x,AH3 y,AH3 z){AH3 r=(-x)*z+z;return x*y+r;} + AH4 AZolSelH4(AH4 x,AH4 y,AH4 z){AH4 r=(-x)*z+z;return x*y+r;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolSignedH1(AH1 x){return ASatH1(x*AH1_(A_INFN_H));} + AH2 AZolSignedH2(AH2 x){return ASatH2(x*AH2_(A_INFN_H));} + AH3 AZolSignedH3(AH3 x){return ASatH3(x*AH3_(A_INFN_H));} + AH4 AZolSignedH4(AH4 x){return ASatH4(x*AH4_(A_INFN_H));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// COLOR CONVERSIONS +//------------------------------------------------------------------------------------------------------------------------------ +// These are all linear to/from some other space (where 'linear' has been shortened out of the function name). +// So 'ToGamma' is 'LinearToGamma', and 'FromGamma' is 'LinearFromGamma'. +// These are branch free implementations. +// The AToSrgbF1() function is useful for stores for compute shaders for GPUs without hardware linear->sRGB store conversion. +//------------------------------------------------------------------------------------------------------------------------------ +// TRANSFER FUNCTIONS +// ================== +// 709 ..... Rec709 used for some HDTVs +// Gamma ... Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native +// Pq ...... PQ native for HDR10 +// Srgb .... The sRGB output, typical of PC displays, useful for 10-bit output, or storing to 8-bit UNORM without SRGB type +// Two ..... Gamma 2.0, fastest conversion (useful for intermediate pass approximations) +// Three ... Gamma 3.0, less fast, but good for HDR. +//------------------------------------------------------------------------------------------------------------------------------ +// KEEPING TO SPEC +// =============== +// Both Rec.709 and sRGB have a linear segment which as spec'ed would intersect the curved segment 2 times. +// (a.) For 8-bit sRGB, steps {0 to 10.3} are in the linear region (4% of the encoding range). +// (b.) For 8-bit 709, steps {0 to 20.7} are in the linear region (8% of the encoding range). +// Also there is a slight step in the transition regions. +// Precision of the coefficients in the spec being the likely cause. +// Main usage case of the sRGB code is to do the linear->sRGB converstion in a compute shader before store. +// This is to work around lack of hardware (typically only ROP does the conversion for free). +// To "correct" the linear segment, would be to introduce error, because hardware decode of sRGB->linear is fixed (and free). +// So this header keeps with the spec. +// For linear->sRGB transforms, the linear segment in some respects reduces error, because rounding in that region is linear. +// Rounding in the curved region in hardware (and fast software code) introduces error due to rounding in non-linear. +//------------------------------------------------------------------------------------------------------------------------------ +// FOR PQ +// ====== +// Both input and output is {0.0-1.0}, and where output 1.0 represents 10000.0 cd/m^2. +// All constants are only specified to FP32 precision. +// External PQ source reference, +// - https://github.com/ampas/aces-dev/blob/master/transforms/ctl/utilities/ACESlib.Utilities_Color.a1.0.1.ctl +//------------------------------------------------------------------------------------------------------------------------------ +// PACKED VERSIONS +// =============== +// These are the A*H2() functions. +// There is no PQ functions as FP16 seemed to not have enough precision for the conversion. +// The remaining functions are "good enough" for 8-bit, and maybe 10-bit if not concerned about a few 1-bit errors. +// Precision is lowest in the 709 conversion, higher in sRGB, higher still in Two and Gamma (when using 2.2 at least). +//------------------------------------------------------------------------------------------------------------------------------ +// NOTES +// ===== +// Could be faster for PQ conversions to be in ALU or a texture lookup depending on usage case. +//============================================================================================================================== + #if 1 + AF1 ATo709F1(AF1 c){AF3 j=AF3(0.018*4.5,4.5,0.45);AF2 k=AF2(1.099,-0.099); + return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} + AF2 ATo709F2(AF2 c){AF3 j=AF3(0.018*4.5,4.5,0.45);AF2 k=AF2(1.099,-0.099); + return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} + AF3 ATo709F3(AF3 c){AF3 j=AF3(0.018*4.5,4.5,0.45);AF2 k=AF2(1.099,-0.099); + return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} +//------------------------------------------------------------------------------------------------------------------------------ + // Note 'rcpX' is '1/x', where the 'x' is what would be used in AFromGamma(). + AF1 AToGammaF1(AF1 c,AF1 rcpX){return pow(c,AF1_(rcpX));} + AF2 AToGammaF2(AF2 c,AF1 rcpX){return pow(c,AF2_(rcpX));} + AF3 AToGammaF3(AF3 c,AF1 rcpX){return pow(c,AF3_(rcpX));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToPqF1(AF1 x){AF1 p=pow(x,AF1_(0.159302)); + return pow((AF1_(0.835938)+AF1_(18.8516)*p)/(AF1_(1.0)+AF1_(18.6875)*p),AF1_(78.8438));} + AF2 AToPqF1(AF2 x){AF2 p=pow(x,AF2_(0.159302)); + return pow((AF2_(0.835938)+AF2_(18.8516)*p)/(AF2_(1.0)+AF2_(18.6875)*p),AF2_(78.8438));} + AF3 AToPqF1(AF3 x){AF3 p=pow(x,AF3_(0.159302)); + return pow((AF3_(0.835938)+AF3_(18.8516)*p)/(AF3_(1.0)+AF3_(18.6875)*p),AF3_(78.8438));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToSrgbF1(AF1 c){AF3 j=AF3(0.0031308*12.92,12.92,1.0/2.4);AF2 k=AF2(1.055,-0.055); + return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} + AF2 AToSrgbF2(AF2 c){AF3 j=AF3(0.0031308*12.92,12.92,1.0/2.4);AF2 k=AF2(1.055,-0.055); + return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} + AF3 AToSrgbF3(AF3 c){AF3 j=AF3(0.0031308*12.92,12.92,1.0/2.4);AF2 k=AF2(1.055,-0.055); + return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToTwoF1(AF1 c){return sqrt(c);} + AF2 AToTwoF2(AF2 c){return sqrt(c);} + AF3 AToTwoF3(AF3 c){return sqrt(c);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToThreeF1(AF1 c){return pow(c,AF1_(1.0/3.0));} + AF2 AToThreeF2(AF2 c){return pow(c,AF2_(1.0/3.0));} + AF3 AToThreeF3(AF3 c){return pow(c,AF3_(1.0/3.0));} + #endif +//============================================================================================================================== + #if 1 + // Unfortunately median won't work here. + AF1 AFrom709F1(AF1 c){AF3 j=AF3(0.081/4.5,1.0/4.5,1.0/0.45);AF2 k=AF2(1.0/1.099,0.099/1.099); + return AZolSelF1(AZolSignedF1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} + AF2 AFrom709F2(AF2 c){AF3 j=AF3(0.081/4.5,1.0/4.5,1.0/0.45);AF2 k=AF2(1.0/1.099,0.099/1.099); + return AZolSelF2(AZolSignedF2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} + AF3 AFrom709F3(AF3 c){AF3 j=AF3(0.081/4.5,1.0/4.5,1.0/0.45);AF2 k=AF2(1.0/1.099,0.099/1.099); + return AZolSelF3(AZolSignedF3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromGammaF1(AF1 c,AF1 x){return pow(c,AF1_(x));} + AF2 AFromGammaF2(AF2 c,AF1 x){return pow(c,AF2_(x));} + AF3 AFromGammaF3(AF3 c,AF1 x){return pow(c,AF3_(x));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromPqF1(AF1 x){AF1 p=pow(x,AF1_(0.0126833)); + return pow(ASatF1(p-AF1_(0.835938))/(AF1_(18.8516)-AF1_(18.6875)*p),AF1_(6.27739));} + AF2 AFromPqF1(AF2 x){AF2 p=pow(x,AF2_(0.0126833)); + return pow(ASatF2(p-AF2_(0.835938))/(AF2_(18.8516)-AF2_(18.6875)*p),AF2_(6.27739));} + AF3 AFromPqF1(AF3 x){AF3 p=pow(x,AF3_(0.0126833)); + return pow(ASatF3(p-AF3_(0.835938))/(AF3_(18.8516)-AF3_(18.6875)*p),AF3_(6.27739));} +//------------------------------------------------------------------------------------------------------------------------------ + // Unfortunately median won't work here. + AF1 AFromSrgbF1(AF1 c){AF3 j=AF3(0.04045/12.92,1.0/12.92,2.4);AF2 k=AF2(1.0/1.055,0.055/1.055); + return AZolSelF1(AZolSignedF1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} + AF2 AFromSrgbF2(AF2 c){AF3 j=AF3(0.04045/12.92,1.0/12.92,2.4);AF2 k=AF2(1.0/1.055,0.055/1.055); + return AZolSelF2(AZolSignedF2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} + AF3 AFromSrgbF3(AF3 c){AF3 j=AF3(0.04045/12.92,1.0/12.92,2.4);AF2 k=AF2(1.0/1.055,0.055/1.055); + return AZolSelF3(AZolSignedF3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromTwoF1(AF1 c){return c*c;} + AF2 AFromTwoF2(AF2 c){return c*c;} + AF3 AFromTwoF3(AF3 c){return c*c;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromThreeF1(AF1 c){return c*c*c;} + AF2 AFromThreeF2(AF2 c){return c*c*c;} + AF3 AFromThreeF3(AF3 c){return c*c*c;} + #endif +//============================================================================================================================== + #ifdef A_HALF + AH1 ATo709H1(AH1 c){AH3 j=AH3(0.018*4.5,4.5,0.45);AH2 k=AH2(1.099,-0.099); + return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} + AH2 ATo709H2(AH2 c){AH3 j=AH3(0.018*4.5,4.5,0.45);AH2 k=AH2(1.099,-0.099); + return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} + AH3 ATo709H3(AH3 c){AH3 j=AH3(0.018*4.5,4.5,0.45);AH2 k=AH2(1.099,-0.099); + return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AToGammaH1(AH1 c,AH1 rcpX){return pow(c,AH1_(rcpX));} + AH2 AToGammaH2(AH2 c,AH1 rcpX){return pow(c,AH2_(rcpX));} + AH3 AToGammaH3(AH3 c,AH1 rcpX){return pow(c,AH3_(rcpX));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AToSrgbH1(AH1 c){AH3 j=AH3(0.0031308*12.92,12.92,1.0/2.4);AH2 k=AH2(1.055,-0.055); + return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} + AH2 AToSrgbH2(AH2 c){AH3 j=AH3(0.0031308*12.92,12.92,1.0/2.4);AH2 k=AH2(1.055,-0.055); + return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} + AH3 AToSrgbH3(AH3 c){AH3 j=AH3(0.0031308*12.92,12.92,1.0/2.4);AH2 k=AH2(1.055,-0.055); + return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AToTwoH1(AH1 c){return sqrt(c);} + AH2 AToTwoH2(AH2 c){return sqrt(c);} + AH3 AToTwoH3(AH3 c){return sqrt(c);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AToThreeF1(AH1 c){return pow(c,AH1_(1.0/3.0));} + AH2 AToThreeF2(AH2 c){return pow(c,AH2_(1.0/3.0));} + AH3 AToThreeF3(AH3 c){return pow(c,AH3_(1.0/3.0));} + #endif +//============================================================================================================================== + #ifdef A_HALF + AH1 AFrom709H1(AH1 c){AH3 j=AH3(0.081/4.5,1.0/4.5,1.0/0.45);AH2 k=AH2(1.0/1.099,0.099/1.099); + return AZolSelH1(AZolSignedH1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} + AH2 AFrom709H2(AH2 c){AH3 j=AH3(0.081/4.5,1.0/4.5,1.0/0.45);AH2 k=AH2(1.0/1.099,0.099/1.099); + return AZolSelH2(AZolSignedH2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} + AH3 AFrom709H3(AH3 c){AH3 j=AH3(0.081/4.5,1.0/4.5,1.0/0.45);AH2 k=AH2(1.0/1.099,0.099/1.099); + return AZolSelH3(AZolSignedH3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFromGammaH1(AH1 c,AH1 x){return pow(c,AH1_(x));} + AH2 AFromGammaH2(AH2 c,AH1 x){return pow(c,AH2_(x));} + AH3 AFromGammaH3(AH3 c,AH1 x){return pow(c,AH3_(x));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AHromSrgbF1(AH1 c){AH3 j=AH3(0.04045/12.92,1.0/12.92,2.4);AH2 k=AH2(1.0/1.055,0.055/1.055); + return AZolSelH1(AZolSignedH1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} + AH2 AHromSrgbF2(AH2 c){AH3 j=AH3(0.04045/12.92,1.0/12.92,2.4);AH2 k=AH2(1.0/1.055,0.055/1.055); + return AZolSelH2(AZolSignedH2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} + AH3 AHromSrgbF3(AH3 c){AH3 j=AH3(0.04045/12.92,1.0/12.92,2.4);AH2 k=AH2(1.0/1.055,0.055/1.055); + return AZolSelH3(AZolSignedH3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFromTwoH1(AH1 c){return c*c;} + AH2 AFromTwoH2(AH2 c){return c*c;} + AH3 AFromTwoH3(AH3 c){return c*c;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFromThreeH1(AH1 c){return c*c*c;} + AH2 AFromThreeH2(AH2 c){return c*c*c;} + AH3 AFromThreeH3(AH3 c){return c*c*c;} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// CS REMAP +//============================================================================================================================== + // Simple remap 64x1 to 8x8 with rotated 2x2 pixel quads in quad linear. + // 543210 + // ====== + // ..xxx. + // yy...y + AU2 ARmp8x8(AU1 a){return AU2(ABfe(a,1u,3u),ABfiM(ABfe(a,3u,3u),a,1u));} +//============================================================================================================================== + // More complex remap 64x1 to 8x8 which is necessary for 2D wave reductions. + // 543210 + // ====== + // .xx..x + // y..yy. + // Details, + // LANE TO 8x8 MAPPING + // =================== + // 00 01 08 09 10 11 18 19 + // 02 03 0a 0b 12 13 1a 1b + // 04 05 0c 0d 14 15 1c 1d + // 06 07 0e 0f 16 17 1e 1f + // 20 21 28 29 30 31 38 39 + // 22 23 2a 2b 32 33 3a 3b + // 24 25 2c 2d 34 35 3c 3d + // 26 27 2e 2f 36 37 3e 3f + AU2 ARmpRed8x8(AU1 a){return AU2(ABfiM(ABfe(a,2u,3u),a,1u),ABfiM(ABfe(a,3u,3u),ABfe(a,1u,2u),2u));} +//============================================================================================================================== + #ifdef A_HALF + AW2 ARmp8x8H(AU1 a){return AW2(ABfe(a,1u,3u),ABfiM(ABfe(a,3u,3u),a,1u));} + AW2 ARmpRed8x8H(AU1 a){return AW2(ABfiM(ABfe(a,2u,3u),a,1u),ABfiM(ABfe(a,3u,3u),ABfe(a,1u,2u),2u));} + #endif +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// REFERENCE +// +//------------------------------------------------------------------------------------------------------------------------------ +// IEEE FLOAT RULES +// ================ +// - saturate(NaN)=0, saturate(-INF)=0, saturate(+INF)=1 +// - {+/-}0 * {+/-}INF = NaN +// - -INF + (+INF) = NaN +// - {+/-}0 / {+/-}0 = NaN +// - {+/-}INF / {+/-}INF = NaN +// - a<(-0) := sqrt(a) = NaN (a=-0.0 won't NaN) +// - 0 == -0 +// - 4/0 = +INF +// - 4/-0 = -INF +// - 4+INF = +INF +// - 4-INF = -INF +// - 4*(+INF) = +INF +// - 4*(-INF) = -INF +// - -4*(+INF) = -INF +// - sqrt(+INF) = +INF +//------------------------------------------------------------------------------------------------------------------------------ +// FP16 ENCODING +// ============= +// fedcba9876543210 +// ---------------- +// ......mmmmmmmmmm 10-bit mantissa (encodes 11-bit 0.5 to 1.0 except for denormals) +// .eeeee.......... 5-bit exponent +// .00000.......... denormals +// .00001.......... -14 exponent +// .11110.......... 15 exponent +// .111110000000000 infinity +// .11111nnnnnnnnnn NaN with n!=0 +// s............... sign +//------------------------------------------------------------------------------------------------------------------------------ +// FP16/INT16 ALIASING DENORMAL +// ============================ +// 11-bit unsigned integers alias with half float denormal/normal values, +// 1 = 2^(-24) = 1/16777216 ....................... first denormal value +// 2 = 2^(-23) +// ... +// 1023 = 2^(-14)*(1-2^(-10)) = 2^(-14)*(1-1/1024) ... last denormal value +// 1024 = 2^(-14) = 1/16384 .......................... first normal value that still maps to integers +// 2047 .............................................. last normal value that still maps to integers +// Scaling limits, +// 2^15 = 32768 ...................................... largest power of 2 scaling +// Largest pow2 conversion mapping is at *32768, +// 1 : 2^(-9) = 1/512 +// 2 : 1/256 +// 4 : 1/128 +// 8 : 1/64 +// 16 : 1/32 +// 32 : 1/16 +// 64 : 1/8 +// 128 : 1/4 +// 256 : 1/2 +// 512 : 1 +// 1024 : 2 +// 2047 : a little less than 4 +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// GPU/CPU PORTABILITY +// +// +//------------------------------------------------------------------------------------------------------------------------------ +// This is the GPU implementation. +// See the CPU implementation for docs. +//============================================================================================================================== +#ifdef A_GPU + #define A_TRUE true + #define A_FALSE false + #define A_STATIC +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR ARGUMENT/RETURN/INITIALIZATION PORTABILITY +//============================================================================================================================== + #define retAD2 AD2 + #define retAD3 AD3 + #define retAD4 AD4 + #define retAF2 AF2 + #define retAF3 AF3 + #define retAF4 AF4 + #define retAL2 AL2 + #define retAL3 AL3 + #define retAL4 AL4 + #define retAU2 AU2 + #define retAU3 AU3 + #define retAU4 AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define inAD2 in AD2 + #define inAD3 in AD3 + #define inAD4 in AD4 + #define inAF2 in AF2 + #define inAF3 in AF3 + #define inAF4 in AF4 + #define inAL2 in AL2 + #define inAL3 in AL3 + #define inAL4 in AL4 + #define inAU2 in AU2 + #define inAU3 in AU3 + #define inAU4 in AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define inoutAD2 inout AD2 + #define inoutAD3 inout AD3 + #define inoutAD4 inout AD4 + #define inoutAF2 inout AF2 + #define inoutAF3 inout AF3 + #define inoutAF4 inout AF4 + #define inoutAL2 inout AL2 + #define inoutAL3 inout AL3 + #define inoutAL4 inout AL4 + #define inoutAU2 inout AU2 + #define inoutAU3 inout AU3 + #define inoutAU4 inout AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define outAD2 out AD2 + #define outAD3 out AD3 + #define outAD4 out AD4 + #define outAF2 out AF2 + #define outAF3 out AF3 + #define outAF4 out AF4 + #define outAL2 out AL2 + #define outAL3 out AL3 + #define outAL4 out AL4 + #define outAU2 out AU2 + #define outAU3 out AU3 + #define outAU4 out AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define varAD2(x) AD2 x + #define varAD3(x) AD3 x + #define varAD4(x) AD4 x + #define varAF2(x) AF2 x + #define varAF3(x) AF3 x + #define varAF4(x) AF4 x + #define varAL2(x) AL2 x + #define varAL3(x) AL3 x + #define varAL4(x) AL4 x + #define varAU2(x) AU2 x + #define varAU3(x) AU3 x + #define varAU4(x) AU4 x +//------------------------------------------------------------------------------------------------------------------------------ + #define initAD2(x,y) AD2(x,y) + #define initAD3(x,y,z) AD3(x,y,z) + #define initAD4(x,y,z,w) AD4(x,y,z,w) + #define initAF2(x,y) AF2(x,y) + #define initAF3(x,y,z) AF3(x,y,z) + #define initAF4(x,y,z,w) AF4(x,y,z,w) + #define initAL2(x,y) AL2(x,y) + #define initAL3(x,y,z) AL3(x,y,z) + #define initAL4(x,y,z,w) AL4(x,y,z,w) + #define initAU2(x,y) AU2(x,y) + #define initAU3(x,y,z) AU3(x,y,z) + #define initAU4(x,y,z,w) AU4(x,y,z,w) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS +//============================================================================================================================== + #define AAbsD1(a) abs(AD1(a)) + #define AAbsF1(a) abs(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ACosD1(a) cos(AD1(a)) + #define ACosF1(a) cos(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ADotD2(a,b) dot(AD2(a),AD2(b)) + #define ADotD3(a,b) dot(AD3(a),AD3(b)) + #define ADotD4(a,b) dot(AD4(a),AD4(b)) + #define ADotF2(a,b) dot(AF2(a),AF2(b)) + #define ADotF3(a,b) dot(AF3(a),AF3(b)) + #define ADotF4(a,b) dot(AF4(a),AF4(b)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AExp2D1(a) exp2(AD1(a)) + #define AExp2F1(a) exp2(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AFloorD1(a) floor(AD1(a)) + #define AFloorF1(a) floor(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ALog2D1(a) log2(AD1(a)) + #define ALog2F1(a) log2(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AMaxD1(a,b) max(a,b) + #define AMaxF1(a,b) max(a,b) + #define AMaxL1(a,b) max(a,b) + #define AMaxU1(a,b) max(a,b) +//------------------------------------------------------------------------------------------------------------------------------ + #define AMinD1(a,b) min(a,b) + #define AMinF1(a,b) min(a,b) + #define AMinL1(a,b) min(a,b) + #define AMinU1(a,b) min(a,b) +//------------------------------------------------------------------------------------------------------------------------------ + #define ASinD1(a) sin(AD1(a)) + #define ASinF1(a) sin(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ASqrtD1(a) sqrt(AD1(a)) + #define ASqrtF1(a) sqrt(AF1(a)) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS - DEPENDENT +//============================================================================================================================== + #define APowD1(a,b) pow(AD1(a),AF1(b)) + #define APowF1(a,b) pow(AF1(a),AF1(b)) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR OPS +//------------------------------------------------------------------------------------------------------------------------------ +// These are added as needed for production or prototyping, so not necessarily a complete set. +// They follow a convention of taking in a destination and also returning the destination value to increase utility. +//============================================================================================================================== + #ifdef A_DUBL + AD2 opAAbsD2(outAD2 d,inAD2 a){d=abs(a);return d;} + AD3 opAAbsD3(outAD3 d,inAD3 a){d=abs(a);return d;} + AD4 opAAbsD4(outAD4 d,inAD4 a){d=abs(a);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAAddD2(outAD2 d,inAD2 a,inAD2 b){d=a+b;return d;} + AD3 opAAddD3(outAD3 d,inAD3 a,inAD3 b){d=a+b;return d;} + AD4 opAAddD4(outAD4 d,inAD4 a,inAD4 b){d=a+b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAAddOneD2(outAD2 d,inAD2 a,AD1 b){d=a+AD2_(b);return d;} + AD3 opAAddOneD3(outAD3 d,inAD3 a,AD1 b){d=a+AD3_(b);return d;} + AD4 opAAddOneD4(outAD4 d,inAD4 a,AD1 b){d=a+AD4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opACpyD2(outAD2 d,inAD2 a){d=a;return d;} + AD3 opACpyD3(outAD3 d,inAD3 a){d=a;return d;} + AD4 opACpyD4(outAD4 d,inAD4 a){d=a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opALerpD2(outAD2 d,inAD2 a,inAD2 b,inAD2 c){d=ALerpD2(a,b,c);return d;} + AD3 opALerpD3(outAD3 d,inAD3 a,inAD3 b,inAD3 c){d=ALerpD3(a,b,c);return d;} + AD4 opALerpD4(outAD4 d,inAD4 a,inAD4 b,inAD4 c){d=ALerpD4(a,b,c);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opALerpOneD2(outAD2 d,inAD2 a,inAD2 b,AD1 c){d=ALerpD2(a,b,AD2_(c));return d;} + AD3 opALerpOneD3(outAD3 d,inAD3 a,inAD3 b,AD1 c){d=ALerpD3(a,b,AD3_(c));return d;} + AD4 opALerpOneD4(outAD4 d,inAD4 a,inAD4 b,AD1 c){d=ALerpD4(a,b,AD4_(c));return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMaxD2(outAD2 d,inAD2 a,inAD2 b){d=max(a,b);return d;} + AD3 opAMaxD3(outAD3 d,inAD3 a,inAD3 b){d=max(a,b);return d;} + AD4 opAMaxD4(outAD4 d,inAD4 a,inAD4 b){d=max(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMinD2(outAD2 d,inAD2 a,inAD2 b){d=min(a,b);return d;} + AD3 opAMinD3(outAD3 d,inAD3 a,inAD3 b){d=min(a,b);return d;} + AD4 opAMinD4(outAD4 d,inAD4 a,inAD4 b){d=min(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMulD2(outAD2 d,inAD2 a,inAD2 b){d=a*b;return d;} + AD3 opAMulD3(outAD3 d,inAD3 a,inAD3 b){d=a*b;return d;} + AD4 opAMulD4(outAD4 d,inAD4 a,inAD4 b){d=a*b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMulOneD2(outAD2 d,inAD2 a,AD1 b){d=a*AD2_(b);return d;} + AD3 opAMulOneD3(outAD3 d,inAD3 a,AD1 b){d=a*AD3_(b);return d;} + AD4 opAMulOneD4(outAD4 d,inAD4 a,AD1 b){d=a*AD4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opANegD2(outAD2 d,inAD2 a){d=-a;return d;} + AD3 opANegD3(outAD3 d,inAD3 a){d=-a;return d;} + AD4 opANegD4(outAD4 d,inAD4 a){d=-a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opARcpD2(outAD2 d,inAD2 a){d=ARcpD2(a);return d;} + AD3 opARcpD3(outAD3 d,inAD3 a){d=ARcpD3(a);return d;} + AD4 opARcpD4(outAD4 d,inAD4 a){d=ARcpD4(a);return d;} + #endif +//============================================================================================================================== + AF2 opAAbsF2(outAF2 d,inAF2 a){d=abs(a);return d;} + AF3 opAAbsF3(outAF3 d,inAF3 a){d=abs(a);return d;} + AF4 opAAbsF4(outAF4 d,inAF4 a){d=abs(a);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAAddF2(outAF2 d,inAF2 a,inAF2 b){d=a+b;return d;} + AF3 opAAddF3(outAF3 d,inAF3 a,inAF3 b){d=a+b;return d;} + AF4 opAAddF4(outAF4 d,inAF4 a,inAF4 b){d=a+b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAAddOneF2(outAF2 d,inAF2 a,AF1 b){d=a+AF2_(b);return d;} + AF3 opAAddOneF3(outAF3 d,inAF3 a,AF1 b){d=a+AF3_(b);return d;} + AF4 opAAddOneF4(outAF4 d,inAF4 a,AF1 b){d=a+AF4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opACpyF2(outAF2 d,inAF2 a){d=a;return d;} + AF3 opACpyF3(outAF3 d,inAF3 a){d=a;return d;} + AF4 opACpyF4(outAF4 d,inAF4 a){d=a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opALerpF2(outAF2 d,inAF2 a,inAF2 b,inAF2 c){d=ALerpF2(a,b,c);return d;} + AF3 opALerpF3(outAF3 d,inAF3 a,inAF3 b,inAF3 c){d=ALerpF3(a,b,c);return d;} + AF4 opALerpF4(outAF4 d,inAF4 a,inAF4 b,inAF4 c){d=ALerpF4(a,b,c);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opALerpOneF2(outAF2 d,inAF2 a,inAF2 b,AF1 c){d=ALerpF2(a,b,AF2_(c));return d;} + AF3 opALerpOneF3(outAF3 d,inAF3 a,inAF3 b,AF1 c){d=ALerpF3(a,b,AF3_(c));return d;} + AF4 opALerpOneF4(outAF4 d,inAF4 a,inAF4 b,AF1 c){d=ALerpF4(a,b,AF4_(c));return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMaxF2(outAF2 d,inAF2 a,inAF2 b){d=max(a,b);return d;} + AF3 opAMaxF3(outAF3 d,inAF3 a,inAF3 b){d=max(a,b);return d;} + AF4 opAMaxF4(outAF4 d,inAF4 a,inAF4 b){d=max(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMinF2(outAF2 d,inAF2 a,inAF2 b){d=min(a,b);return d;} + AF3 opAMinF3(outAF3 d,inAF3 a,inAF3 b){d=min(a,b);return d;} + AF4 opAMinF4(outAF4 d,inAF4 a,inAF4 b){d=min(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMulF2(outAF2 d,inAF2 a,inAF2 b){d=a*b;return d;} + AF3 opAMulF3(outAF3 d,inAF3 a,inAF3 b){d=a*b;return d;} + AF4 opAMulF4(outAF4 d,inAF4 a,inAF4 b){d=a*b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMulOneF2(outAF2 d,inAF2 a,AF1 b){d=a*AF2_(b);return d;} + AF3 opAMulOneF3(outAF3 d,inAF3 a,AF1 b){d=a*AF3_(b);return d;} + AF4 opAMulOneF4(outAF4 d,inAF4 a,AF1 b){d=a*AF4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opANegF2(outAF2 d,inAF2 a){d=-a;return d;} + AF3 opANegF3(outAF3 d,inAF3 a){d=-a;return d;} + AF4 opANegF4(outAF4 d,inAF4 a){d=-a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opARcpF2(outAF2 d,inAF2 a){d=ARcpF2(a);return d;} + AF3 opARcpF3(outAF3 d,inAF3 a){d=ARcpF3(a);return d;} + AF4 opARcpF4(outAF4 d,inAF4 a){d=ARcpF4(a);return d;} +#endif + +// FSR input callbacks +AF4 FsrEasuRF(AF2 p) +{ + return textureGather(texture0, p, 0); +} + +AF4 FsrEasuGF(AF2 p) +{ + return textureGather(texture0, p, 1); +} + +AF4 FsrEasuBF(AF2 p) +{ + return textureGather(texture0, p, 2); +} + +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// AMD FidelityFX SUPER RESOLUTION [FSR 1] ::: SPATIAL SCALING & EXTRAS - v1.20210629 +// +// +//------------------------------------------------------------------------------------------------------------------------------ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//------------------------------------------------------------------------------------------------------------------------------ +// FidelityFX Super Resolution Sample +// +// Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved. +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//------------------------------------------------------------------------------------------------------------------------------ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//------------------------------------------------------------------------------------------------------------------------------ +// ABOUT +// ===== +// FSR is a collection of algorithms relating to generating a higher resolution image. +// This specific header focuses on single-image non-temporal image scaling, and related tools. +// +// The core functions are EASU and RCAS: +// [EASU] Edge Adaptive Spatial Upsampling ....... 1x to 4x area range spatial scaling, clamped adaptive elliptical filter. +// [RCAS] Robust Contrast Adaptive Sharpening .... A non-scaling variation on CAS. +// RCAS needs to be applied after EASU as a separate pass. +// +// Optional utility functions are: +// [LFGA] Linear Film Grain Applicator ........... Tool to apply film grain after scaling. +// [SRTM] Simple Reversible Tone-Mapper .......... Linear HDR {0 to FP16_MAX} to {0 to 1} and back. +// [TEPD] Temporal Energy Preserving Dither ...... Temporally energy preserving dithered {0 to 1} linear to gamma 2.0 conversion. +// See each individual sub-section for inline documentation. +//------------------------------------------------------------------------------------------------------------------------------ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//------------------------------------------------------------------------------------------------------------------------------ +// FUNCTION PERMUTATIONS +// ===================== +// *F() ..... Single item computation with 32-bit. +// *H() ..... Single item computation with 16-bit, with packing (aka two 16-bit ops in parallel) when possible. +// *Hx2() ... Processing two items in parallel with 16-bit, easier packing. +// Not all interfaces in this file have a *Hx2() form. +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// FSR - [EASU] EDGE ADAPTIVE SPATIAL UPSAMPLING +// +//------------------------------------------------------------------------------------------------------------------------------ +// EASU provides a high quality spatial-only scaling at relatively low cost. +// Meaning EASU is appropiate for laptops and other low-end GPUs. +// Quality from 1x to 4x area scaling is good. +//------------------------------------------------------------------------------------------------------------------------------ +// The scalar uses a modified fast approximation to the standard lanczos(size=2) kernel. +// EASU runs in a single pass, so it applies a directionally and anisotropically adaptive radial lanczos. +// This is also kept as simple as possible to have minimum runtime. +//------------------------------------------------------------------------------------------------------------------------------ +// The lanzcos filter has negative lobes, so by itself it will introduce ringing. +// To remove all ringing, the algorithm uses the nearest 2x2 input texels as a neighborhood, +// and limits output to the minimum and maximum of that neighborhood. +//------------------------------------------------------------------------------------------------------------------------------ +// Input image requirements: +// +// Color needs to be encoded as 3 channel[red, green, blue](e.g.XYZ not supported) +// Each channel needs to be in the range[0, 1] +// Any color primaries are supported +// Display / tonemapping curve needs to be as if presenting to sRGB display or similar(e.g.Gamma 2.0) +// There should be no banding in the input +// There should be no high amplitude noise in the input +// There should be no noise in the input that is not at input pixel granularity +// For performance purposes, use 32bpp formats +//------------------------------------------------------------------------------------------------------------------------------ +// Best to apply EASU at the end of the frame after tonemapping +// but before film grain or composite of the UI. +//------------------------------------------------------------------------------------------------------------------------------ +// Example of including this header for D3D HLSL : +// +// #define A_GPU 1 +// #define A_HLSL 1 +// #define A_HALF 1 +// #include "ffx_a.h" +// #define FSR_EASU_H 1 +// #define FSR_RCAS_H 1 +// //declare input callbacks +// #include "ffx_fsr1.h" +// +// Example of including this header for Vulkan GLSL : +// +// #define A_GPU 1 +// #define A_GLSL 1 +// #define A_HALF 1 +// #include "ffx_a.h" +// #define FSR_EASU_H 1 +// #define FSR_RCAS_H 1 +// //declare input callbacks +// #include "ffx_fsr1.h" +// +// Example of including this header for Vulkan HLSL : +// +// #define A_GPU 1 +// #define A_HLSL 1 +// #define A_HLSL_6_2 1 +// #define A_NO_16_BIT_CAST 1 +// #define A_HALF 1 +// #include "ffx_a.h" +// #define FSR_EASU_H 1 +// #define FSR_RCAS_H 1 +// //declare input callbacks +// #include "ffx_fsr1.h" +// +// Example of declaring the required input callbacks for GLSL : +// The callbacks need to gather4 for each color channel using the specified texture coordinate 'p'. +// EASU uses gather4 to reduce position computation logic and for free Arrays of Structures to Structures of Arrays conversion. +// +// AH4 FsrEasuRH(AF2 p){return AH4(textureGather(sampler2D(tex,sam),p,0));} +// AH4 FsrEasuGH(AF2 p){return AH4(textureGather(sampler2D(tex,sam),p,1));} +// AH4 FsrEasuBH(AF2 p){return AH4(textureGather(sampler2D(tex,sam),p,2));} +// ... +// The FsrEasuCon function needs to be called from the CPU or GPU to set up constants. +// The difference in viewport and input image size is there to support Dynamic Resolution Scaling. +// To use FsrEasuCon() on the CPU, define A_CPU before including ffx_a and ffx_fsr1. +// Including a GPU example here, the 'con0' through 'con3' values would be stored out to a constant buffer. +// AU4 con0,con1,con2,con3; +// FsrEasuCon(con0,con1,con2,con3, +// 1920.0,1080.0, // Viewport size (top left aligned) in the input image which is to be scaled. +// 3840.0,2160.0, // The size of the input image. +// 2560.0,1440.0); // The output resolution. +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// CONSTANT SETUP +//============================================================================================================================== +// Call to setup required constant values (works on CPU or GPU). +A_STATIC void FsrEasuCon( +outAU4 con0, +outAU4 con1, +outAU4 con2, +outAU4 con3, +// This the rendered image resolution being upscaled +AF1 inputViewportInPixelsX, +AF1 inputViewportInPixelsY, +// This is the resolution of the resource containing the input image (useful for dynamic resolution) +AF1 inputSizeInPixelsX, +AF1 inputSizeInPixelsY, +// This is the display resolution which the input image gets upscaled to +AF1 outputSizeInPixelsX, +AF1 outputSizeInPixelsY){ + // Output integer position to a pixel position in viewport. + con0[0]=AU1_AF1(inputViewportInPixelsX*ARcpF1(outputSizeInPixelsX)); + con0[1]=AU1_AF1(inputViewportInPixelsY*ARcpF1(outputSizeInPixelsY)); + con0[2]=AU1_AF1(AF1_(0.5)*inputViewportInPixelsX*ARcpF1(outputSizeInPixelsX)-AF1_(0.5)); + con0[3]=AU1_AF1(AF1_(0.5)*inputViewportInPixelsY*ARcpF1(outputSizeInPixelsY)-AF1_(0.5)); + // Viewport pixel position to normalized image space. + // This is used to get upper-left of 'F' tap. + con1[0]=AU1_AF1(ARcpF1(inputSizeInPixelsX)); + con1[1]=AU1_AF1(ARcpF1(inputSizeInPixelsY)); + // Centers of gather4, first offset from upper-left of 'F'. + // +---+---+ + // | | | + // +--(0)--+ + // | b | c | + // +---F---+---+---+ + // | e | f | g | h | + // +--(1)--+--(2)--+ + // | i | j | k | l | + // +---+---+---+---+ + // | n | o | + // +--(3)--+ + // | | | + // +---+---+ + con1[2]=AU1_AF1(AF1_( 1.0)*ARcpF1(inputSizeInPixelsX)); + con1[3]=AU1_AF1(AF1_(-1.0)*ARcpF1(inputSizeInPixelsY)); + // These are from (0) instead of 'F'. + con2[0]=AU1_AF1(AF1_(-1.0)*ARcpF1(inputSizeInPixelsX)); + con2[1]=AU1_AF1(AF1_( 2.0)*ARcpF1(inputSizeInPixelsY)); + con2[2]=AU1_AF1(AF1_( 1.0)*ARcpF1(inputSizeInPixelsX)); + con2[3]=AU1_AF1(AF1_( 2.0)*ARcpF1(inputSizeInPixelsY)); + con3[0]=AU1_AF1(AF1_( 0.0)*ARcpF1(inputSizeInPixelsX)); + con3[1]=AU1_AF1(AF1_( 4.0)*ARcpF1(inputSizeInPixelsY)); + con3[2]=con3[3]=0;} + +//If the an offset into the input image resource +A_STATIC void FsrEasuConOffset( + outAU4 con0, + outAU4 con1, + outAU4 con2, + outAU4 con3, + // This the rendered image resolution being upscaled + AF1 inputViewportInPixelsX, + AF1 inputViewportInPixelsY, + // This is the resolution of the resource containing the input image (useful for dynamic resolution) + AF1 inputSizeInPixelsX, + AF1 inputSizeInPixelsY, + // This is the display resolution which the input image gets upscaled to + AF1 outputSizeInPixelsX, + AF1 outputSizeInPixelsY, + // This is the input image offset into the resource containing it (useful for dynamic resolution) + AF1 inputOffsetInPixelsX, + AF1 inputOffsetInPixelsY) { + FsrEasuCon(con0, con1, con2, con3, inputViewportInPixelsX, inputViewportInPixelsY, inputSizeInPixelsX, inputSizeInPixelsY, outputSizeInPixelsX, outputSizeInPixelsY); + con0[2] = AU1_AF1(AF1_(0.5) * inputViewportInPixelsX * ARcpF1(outputSizeInPixelsX) - AF1_(0.5) + inputOffsetInPixelsX); + con0[3] = AU1_AF1(AF1_(0.5) * inputViewportInPixelsY * ARcpF1(outputSizeInPixelsY) - AF1_(0.5) + inputOffsetInPixelsY); +} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// NON-PACKED 32-BIT VERSION +//============================================================================================================================== +#if defined(A_GPU)&&defined(FSR_EASU_F) + // Input callback prototypes, need to be implemented by calling shader + AF4 FsrEasuRF(AF2 p); + AF4 FsrEasuGF(AF2 p); + AF4 FsrEasuBF(AF2 p); +//------------------------------------------------------------------------------------------------------------------------------ + // Filtering for a given tap for the scalar. + void FsrEasuTapF( + inout AF3 aC, // Accumulated color, with negative lobe. + inout AF1 aW, // Accumulated weight. + AF2 off, // Pixel offset from resolve position to tap. + AF2 dir, // Gradient direction. + AF2 len, // Length. + AF1 lob, // Negative lobe strength. + AF1 clp, // Clipping point. + AF3 c){ // Tap color. + // Rotate offset by direction. + AF2 v; + v.x=(off.x*( dir.x))+(off.y*dir.y); + v.y=(off.x*(-dir.y))+(off.y*dir.x); + // Anisotropy. + v*=len; + // Compute distance^2. + AF1 d2=v.x*v.x+v.y*v.y; + // Limit to the window as at corner, 2 taps can easily be outside. + d2=min(d2,clp); + // Approximation of lancos2 without sin() or rcp(), or sqrt() to get x. + // (25/16 * (2/5 * x^2 - 1)^2 - (25/16 - 1)) * (1/4 * x^2 - 1)^2 + // |_______________________________________| |_______________| + // base window + // The general form of the 'base' is, + // (a*(b*x^2-1)^2-(a-1)) + // Where 'a=1/(2*b-b^2)' and 'b' moves around the negative lobe. + AF1 wB=AF1_(2.0/5.0)*d2+AF1_(-1.0); + AF1 wA=lob*d2+AF1_(-1.0); + wB*=wB; + wA*=wA; + wB=AF1_(25.0/16.0)*wB+AF1_(-(25.0/16.0-1.0)); + AF1 w=wB*wA; + // Do weighted average. + aC+=c*w;aW+=w;} +//------------------------------------------------------------------------------------------------------------------------------ + // Accumulate direction and length. + void FsrEasuSetF( + inout AF2 dir, + inout AF1 len, + AF2 pp, + AP1 biS,AP1 biT,AP1 biU,AP1 biV, + AF1 lA,AF1 lB,AF1 lC,AF1 lD,AF1 lE){ + // Compute bilinear weight, branches factor out as predicates are compiler time immediates. + // s t + // u v + AF1 w = AF1_(0.0); + if(biS)w=(AF1_(1.0)-pp.x)*(AF1_(1.0)-pp.y); + if(biT)w= pp.x *(AF1_(1.0)-pp.y); + if(biU)w=(AF1_(1.0)-pp.x)* pp.y ; + if(biV)w= pp.x * pp.y ; + // Direction is the '+' diff. + // a + // b c d + // e + // Then takes magnitude from abs average of both sides of 'c'. + // Length converts gradient reversal to 0, smoothly to non-reversal at 1, shaped, then adding horz and vert terms. + AF1 dc=lD-lC; + AF1 cb=lC-lB; + AF1 lenX=max(abs(dc),abs(cb)); + lenX=APrxLoRcpF1(lenX); + AF1 dirX=lD-lB; + dir.x+=dirX*w; + lenX=ASatF1(abs(dirX)*lenX); + lenX*=lenX; + len+=lenX*w; + // Repeat for the y axis. + AF1 ec=lE-lC; + AF1 ca=lC-lA; + AF1 lenY=max(abs(ec),abs(ca)); + lenY=APrxLoRcpF1(lenY); + AF1 dirY=lE-lA; + dir.y+=dirY*w; + lenY=ASatF1(abs(dirY)*lenY); + lenY*=lenY; + len+=lenY*w;} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrEasuF( + out AF3 pix, + AU2 ip, // Integer pixel position in output. + AU4 con0, // Constants generated by FsrEasuCon(). + AU4 con1, + AU4 con2, + AU4 con3){ +//------------------------------------------------------------------------------------------------------------------------------ + // Get position of 'f'. + AF2 pp=AF2(ip)*AF2_AU2(con0.xy)+AF2_AU2(con0.zw); + AF2 fp=floor(pp); + pp-=fp; +//------------------------------------------------------------------------------------------------------------------------------ + // 12-tap kernel. + // b c + // e f g h + // i j k l + // n o + // Gather 4 ordering. + // a b + // r g + // For packed FP16, need either {rg} or {ab} so using the following setup for gather in all versions, + // a b <- unused (z) + // r g + // a b a b + // r g r g + // a b + // r g <- unused (z) + // Allowing dead-code removal to remove the 'z's. + AF2 p0=fp*AF2_AU2(con1.xy)+AF2_AU2(con1.zw); + // These are from p0 to avoid pulling two constants on pre-Navi hardware. + AF2 p1=p0+AF2_AU2(con2.xy); + AF2 p2=p0+AF2_AU2(con2.zw); + AF2 p3=p0+AF2_AU2(con3.xy); + AF4 bczzR=FsrEasuRF(p0); + AF4 bczzG=FsrEasuGF(p0); + AF4 bczzB=FsrEasuBF(p0); + AF4 ijfeR=FsrEasuRF(p1); + AF4 ijfeG=FsrEasuGF(p1); + AF4 ijfeB=FsrEasuBF(p1); + AF4 klhgR=FsrEasuRF(p2); + AF4 klhgG=FsrEasuGF(p2); + AF4 klhgB=FsrEasuBF(p2); + AF4 zzonR=FsrEasuRF(p3); + AF4 zzonG=FsrEasuGF(p3); + AF4 zzonB=FsrEasuBF(p3); +//------------------------------------------------------------------------------------------------------------------------------ + // Simplest multi-channel approximate luma possible (luma times 2, in 2 FMA/MAD). + AF4 bczzL=bczzB*AF4_(0.5)+(bczzR*AF4_(0.5)+bczzG); + AF4 ijfeL=ijfeB*AF4_(0.5)+(ijfeR*AF4_(0.5)+ijfeG); + AF4 klhgL=klhgB*AF4_(0.5)+(klhgR*AF4_(0.5)+klhgG); + AF4 zzonL=zzonB*AF4_(0.5)+(zzonR*AF4_(0.5)+zzonG); + // Rename. + AF1 bL=bczzL.x; + AF1 cL=bczzL.y; + AF1 iL=ijfeL.x; + AF1 jL=ijfeL.y; + AF1 fL=ijfeL.z; + AF1 eL=ijfeL.w; + AF1 kL=klhgL.x; + AF1 lL=klhgL.y; + AF1 hL=klhgL.z; + AF1 gL=klhgL.w; + AF1 oL=zzonL.z; + AF1 nL=zzonL.w; + // Accumulate for bilinear interpolation. + AF2 dir=AF2_(0.0); + AF1 len=AF1_(0.0); + FsrEasuSetF(dir,len,pp,true, false,false,false,bL,eL,fL,gL,jL); + FsrEasuSetF(dir,len,pp,false,true ,false,false,cL,fL,gL,hL,kL); + FsrEasuSetF(dir,len,pp,false,false,true ,false,fL,iL,jL,kL,nL); + FsrEasuSetF(dir,len,pp,false,false,false,true ,gL,jL,kL,lL,oL); +//------------------------------------------------------------------------------------------------------------------------------ + // Normalize with approximation, and cleanup close to zero. + AF2 dir2=dir*dir; + AF1 dirR=dir2.x+dir2.y; + AP1 zro=dirR w = -m/(n+e+w+s) +// 1 == (w*(n+e+w+s)+m)/(4*w+1) -> w = (1-m)/(n+e+w+s-4*1) +// Then chooses the 'w' which results in no clipping, limits 'w', and multiplies by the 'sharp' amount. +// This solution above has issues with MSAA input as the steps along the gradient cause edge detection issues. +// So RCAS uses 4x the maximum and 4x the minimum (depending on equation)in place of the individual taps. +// As well as switching from 'm' to either the minimum or maximum (depending on side), to help in energy conservation. +// This stabilizes RCAS. +// RCAS does a simple highpass which is normalized against the local contrast then shaped, +// 0.25 +// 0.25 -1 0.25 +// 0.25 +// This is used as a noise detection filter, to reduce the effect of RCAS on grain, and focus on real edges. +// +// GLSL example for the required callbacks : +// +// AH4 FsrRcasLoadH(ASW2 p){return AH4(imageLoad(imgSrc,ASU2(p)));} +// void FsrRcasInputH(inout AH1 r,inout AH1 g,inout AH1 b) +// { +// //do any simple input color conversions here or leave empty if none needed +// } +// +// FsrRcasCon need to be called from the CPU or GPU to set up constants. +// Including a GPU example here, the 'con' value would be stored out to a constant buffer. +// +// AU4 con; +// FsrRcasCon(con, +// 0.0); // The scale is {0.0 := maximum sharpness, to N>0, where N is the number of stops (halving) of the reduction of sharpness}. +// --------------- +// RCAS sharpening supports a CAS-like pass-through alpha via, +// #define FSR_RCAS_PASSTHROUGH_ALPHA 1 +// RCAS also supports a define to enable a more expensive path to avoid some sharpening of noise. +// Would suggest it is better to apply film grain after RCAS sharpening (and after scaling) instead of using this define, +// #define FSR_RCAS_DENOISE 1 +//============================================================================================================================== +// This is set at the limit of providing unnatural results for sharpening. +#define FSR_RCAS_LIMIT (0.25-(1.0/16.0)) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// CONSTANT SETUP +//============================================================================================================================== +// Call to setup required constant values (works on CPU or GPU). +A_STATIC void FsrRcasCon( +outAU4 con, +// The scale is {0.0 := maximum, to N>0, where N is the number of stops (halving) of the reduction of sharpness}. +AF1 sharpness){ + // Transform from stops to linear value. + sharpness=AExp2F1(-sharpness); + varAF2(hSharp)=initAF2(sharpness,sharpness); + con[0]=AU1_AF1(sharpness); + con[1]=AU1_AH2_AF2(hSharp); + con[2]=0; + con[3]=0;} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// NON-PACKED 32-BIT VERSION +//============================================================================================================================== +#if defined(A_GPU)&&defined(FSR_RCAS_F) + // Input callback prototypes that need to be implemented by calling shader + AF4 FsrRcasLoadF(ASU2 p); + void FsrRcasInputF(inout AF1 r,inout AF1 g,inout AF1 b); +//------------------------------------------------------------------------------------------------------------------------------ + void FsrRcasF( + out AF1 pixR, // Output values, non-vector so port between RcasFilter() and RcasFilterH() is easy. + out AF1 pixG, + out AF1 pixB, + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + out AF1 pixA, + #endif + AU2 ip, // Integer pixel position in output. + AU4 con){ // Constant generated by RcasSetup(). + // Algorithm uses minimal 3x3 pixel neighborhood. + // b + // d e f + // h + ASU2 sp=ASU2(ip); + AF3 b=FsrRcasLoadF(sp+ASU2( 0,-1)).rgb; + AF3 d=FsrRcasLoadF(sp+ASU2(-1, 0)).rgb; + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + AF4 ee=FsrRcasLoadF(sp); + AF3 e=ee.rgb;pixA=ee.a; + #else + AF3 e=FsrRcasLoadF(sp).rgb; + #endif + AF3 f=FsrRcasLoadF(sp+ASU2( 1, 0)).rgb; + AF3 h=FsrRcasLoadF(sp+ASU2( 0, 1)).rgb; + // Rename (32-bit) or regroup (16-bit). + AF1 bR=b.r; + AF1 bG=b.g; + AF1 bB=b.b; + AF1 dR=d.r; + AF1 dG=d.g; + AF1 dB=d.b; + AF1 eR=e.r; + AF1 eG=e.g; + AF1 eB=e.b; + AF1 fR=f.r; + AF1 fG=f.g; + AF1 fB=f.b; + AF1 hR=h.r; + AF1 hG=h.g; + AF1 hB=h.b; + // Run optional input transform. + FsrRcasInputF(bR,bG,bB); + FsrRcasInputF(dR,dG,dB); + FsrRcasInputF(eR,eG,eB); + FsrRcasInputF(fR,fG,fB); + FsrRcasInputF(hR,hG,hB); + // Luma times 2. + AF1 bL=bB*AF1_(0.5)+(bR*AF1_(0.5)+bG); + AF1 dL=dB*AF1_(0.5)+(dR*AF1_(0.5)+dG); + AF1 eL=eB*AF1_(0.5)+(eR*AF1_(0.5)+eG); + AF1 fL=fB*AF1_(0.5)+(fR*AF1_(0.5)+fG); + AF1 hL=hB*AF1_(0.5)+(hR*AF1_(0.5)+hG); + // Noise detection. + AF1 nz=AF1_(0.25)*bL+AF1_(0.25)*dL+AF1_(0.25)*fL+AF1_(0.25)*hL-eL; + nz=ASatF1(abs(nz)*APrxMedRcpF1(AMax3F1(AMax3F1(bL,dL,eL),fL,hL)-AMin3F1(AMin3F1(bL,dL,eL),fL,hL))); + nz=AF1_(-0.5)*nz+AF1_(1.0); + // Min and max of ring. + AF1 mn4R=min(AMin3F1(bR,dR,fR),hR); + AF1 mn4G=min(AMin3F1(bG,dG,fG),hG); + AF1 mn4B=min(AMin3F1(bB,dB,fB),hB); + AF1 mx4R=max(AMax3F1(bR,dR,fR),hR); + AF1 mx4G=max(AMax3F1(bG,dG,fG),hG); + AF1 mx4B=max(AMax3F1(bB,dB,fB),hB); + // Immediate constants for peak range. + AF2 peakC=AF2(1.0,-1.0*4.0); + // Limiters, these need to be high precision RCPs. + AF1 hitMinR=mn4R*ARcpF1(AF1_(4.0)*mx4R); + AF1 hitMinG=mn4G*ARcpF1(AF1_(4.0)*mx4G); + AF1 hitMinB=mn4B*ARcpF1(AF1_(4.0)*mx4B); + AF1 hitMaxR=(peakC.x-mx4R)*ARcpF1(AF1_(4.0)*mn4R+peakC.y); + AF1 hitMaxG=(peakC.x-mx4G)*ARcpF1(AF1_(4.0)*mn4G+peakC.y); + AF1 hitMaxB=(peakC.x-mx4B)*ARcpF1(AF1_(4.0)*mn4B+peakC.y); + AF1 lobeR=max(-hitMinR,hitMaxR); + AF1 lobeG=max(-hitMinG,hitMaxG); + AF1 lobeB=max(-hitMinB,hitMaxB); + AF1 lobe=max(AF1_(-FSR_RCAS_LIMIT),min(AMax3F1(lobeR,lobeG,lobeB),AF1_(0.0)))*AF1_AU1(con.x); + // Apply noise removal. + #ifdef FSR_RCAS_DENOISE + lobe*=nz; + #endif + // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes. + AF1 rcpL=APrxMedRcpF1(AF1_(4.0)*lobe+AF1_(1.0)); + pixR=(lobe*bR+lobe*dR+lobe*hR+lobe*fR+eR)*rcpL; + pixG=(lobe*bG+lobe*dG+lobe*hG+lobe*fG+eG)*rcpL; + pixB=(lobe*bB+lobe*dB+lobe*hB+lobe*fB+eB)*rcpL; + return;} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// NON-PACKED 16-BIT VERSION +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF)&&defined(FSR_RCAS_H) + // Input callback prototypes that need to be implemented by calling shader + AH4 FsrRcasLoadH(ASW2 p); + void FsrRcasInputH(inout AH1 r,inout AH1 g,inout AH1 b); +//------------------------------------------------------------------------------------------------------------------------------ + void FsrRcasH( + out AH1 pixR, // Output values, non-vector so port between RcasFilter() and RcasFilterH() is easy. + out AH1 pixG, + out AH1 pixB, + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + out AH1 pixA, + #endif + AU2 ip, // Integer pixel position in output. + AU4 con){ // Constant generated by RcasSetup(). + // Sharpening algorithm uses minimal 3x3 pixel neighborhood. + // b + // d e f + // h + ASW2 sp=ASW2(ip); + AH3 b=FsrRcasLoadH(sp+ASW2( 0,-1)).rgb; + AH3 d=FsrRcasLoadH(sp+ASW2(-1, 0)).rgb; + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + AH4 ee=FsrRcasLoadH(sp); + AH3 e=ee.rgb;pixA=ee.a; + #else + AH3 e=FsrRcasLoadH(sp).rgb; + #endif + AH3 f=FsrRcasLoadH(sp+ASW2( 1, 0)).rgb; + AH3 h=FsrRcasLoadH(sp+ASW2( 0, 1)).rgb; + // Rename (32-bit) or regroup (16-bit). + AH1 bR=b.r; + AH1 bG=b.g; + AH1 bB=b.b; + AH1 dR=d.r; + AH1 dG=d.g; + AH1 dB=d.b; + AH1 eR=e.r; + AH1 eG=e.g; + AH1 eB=e.b; + AH1 fR=f.r; + AH1 fG=f.g; + AH1 fB=f.b; + AH1 hR=h.r; + AH1 hG=h.g; + AH1 hB=h.b; + // Run optional input transform. + FsrRcasInputH(bR,bG,bB); + FsrRcasInputH(dR,dG,dB); + FsrRcasInputH(eR,eG,eB); + FsrRcasInputH(fR,fG,fB); + FsrRcasInputH(hR,hG,hB); + // Luma times 2. + AH1 bL=bB*AH1_(0.5)+(bR*AH1_(0.5)+bG); + AH1 dL=dB*AH1_(0.5)+(dR*AH1_(0.5)+dG); + AH1 eL=eB*AH1_(0.5)+(eR*AH1_(0.5)+eG); + AH1 fL=fB*AH1_(0.5)+(fR*AH1_(0.5)+fG); + AH1 hL=hB*AH1_(0.5)+(hR*AH1_(0.5)+hG); + // Noise detection. + AH1 nz=AH1_(0.25)*bL+AH1_(0.25)*dL+AH1_(0.25)*fL+AH1_(0.25)*hL-eL; + nz=ASatH1(abs(nz)*APrxMedRcpH1(AMax3H1(AMax3H1(bL,dL,eL),fL,hL)-AMin3H1(AMin3H1(bL,dL,eL),fL,hL))); + nz=AH1_(-0.5)*nz+AH1_(1.0); + // Min and max of ring. + AH1 mn4R=min(AMin3H1(bR,dR,fR),hR); + AH1 mn4G=min(AMin3H1(bG,dG,fG),hG); + AH1 mn4B=min(AMin3H1(bB,dB,fB),hB); + AH1 mx4R=max(AMax3H1(bR,dR,fR),hR); + AH1 mx4G=max(AMax3H1(bG,dG,fG),hG); + AH1 mx4B=max(AMax3H1(bB,dB,fB),hB); + // Immediate constants for peak range. + AH2 peakC=AH2(1.0,-1.0*4.0); + // Limiters, these need to be high precision RCPs. + AH1 hitMinR=mn4R*ARcpH1(AH1_(4.0)*mx4R); + AH1 hitMinG=mn4G*ARcpH1(AH1_(4.0)*mx4G); + AH1 hitMinB=mn4B*ARcpH1(AH1_(4.0)*mx4B); + AH1 hitMaxR=(peakC.x-mx4R)*ARcpH1(AH1_(4.0)*mn4R+peakC.y); + AH1 hitMaxG=(peakC.x-mx4G)*ARcpH1(AH1_(4.0)*mn4G+peakC.y); + AH1 hitMaxB=(peakC.x-mx4B)*ARcpH1(AH1_(4.0)*mn4B+peakC.y); + AH1 lobeR=max(-hitMinR,hitMaxR); + AH1 lobeG=max(-hitMinG,hitMaxG); + AH1 lobeB=max(-hitMinB,hitMaxB); + AH1 lobe=max(AH1_(-FSR_RCAS_LIMIT),min(AMax3H1(lobeR,lobeG,lobeB),AH1_(0.0)))*AH2_AU1(con.y).x; + // Apply noise removal. + #ifdef FSR_RCAS_DENOISE + lobe*=nz; + #endif + // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes. + AH1 rcpL=APrxMedRcpH1(AH1_(4.0)*lobe+AH1_(1.0)); + pixR=(lobe*bR+lobe*dR+lobe*hR+lobe*fR+eR)*rcpL; + pixG=(lobe*bG+lobe*dG+lobe*hG+lobe*fG+eG)*rcpL; + pixB=(lobe*bB+lobe*dB+lobe*hB+lobe*fB+eB)*rcpL;} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// PACKED 16-BIT VERSION +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF)&&defined(FSR_RCAS_HX2) + // Input callback prototypes that need to be implemented by the calling shader + AH4 FsrRcasLoadHx2(ASW2 p); + void FsrRcasInputHx2(inout AH2 r,inout AH2 g,inout AH2 b); +//------------------------------------------------------------------------------------------------------------------------------ + // Can be used to convert from packed Structures of Arrays to Arrays of Structures for store. + void FsrRcasDepackHx2(out AH4 pix0,out AH4 pix1,AH2 pixR,AH2 pixG,AH2 pixB){ + #ifdef A_HLSL + // Invoke a slower path for DX only, since it won't allow uninitialized values. + pix0.a=pix1.a=0.0; + #endif + pix0.rgb=AH3(pixR.x,pixG.x,pixB.x); + pix1.rgb=AH3(pixR.y,pixG.y,pixB.y);} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrRcasHx2( + // Output values are for 2 8x8 tiles in a 16x8 region. + // pix.x = left 8x8 tile + // pix.y = right 8x8 tile + // This enables later processing to easily be packed as well. + out AH2 pixR, + out AH2 pixG, + out AH2 pixB, + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + out AH2 pixA, + #endif + AU2 ip, // Integer pixel position in output. + AU4 con){ // Constant generated by RcasSetup(). + // No scaling algorithm uses minimal 3x3 pixel neighborhood. + ASW2 sp0=ASW2(ip); + AH3 b0=FsrRcasLoadHx2(sp0+ASW2( 0,-1)).rgb; + AH3 d0=FsrRcasLoadHx2(sp0+ASW2(-1, 0)).rgb; + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + AH4 ee0=FsrRcasLoadHx2(sp0); + AH3 e0=ee0.rgb;pixA.r=ee0.a; + #else + AH3 e0=FsrRcasLoadHx2(sp0).rgb; + #endif + AH3 f0=FsrRcasLoadHx2(sp0+ASW2( 1, 0)).rgb; + AH3 h0=FsrRcasLoadHx2(sp0+ASW2( 0, 1)).rgb; + ASW2 sp1=sp0+ASW2(8,0); + AH3 b1=FsrRcasLoadHx2(sp1+ASW2( 0,-1)).rgb; + AH3 d1=FsrRcasLoadHx2(sp1+ASW2(-1, 0)).rgb; + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + AH4 ee1=FsrRcasLoadHx2(sp1); + AH3 e1=ee1.rgb;pixA.g=ee1.a; + #else + AH3 e1=FsrRcasLoadHx2(sp1).rgb; + #endif + AH3 f1=FsrRcasLoadHx2(sp1+ASW2( 1, 0)).rgb; + AH3 h1=FsrRcasLoadHx2(sp1+ASW2( 0, 1)).rgb; + // Arrays of Structures to Structures of Arrays conversion. + AH2 bR=AH2(b0.r,b1.r); + AH2 bG=AH2(b0.g,b1.g); + AH2 bB=AH2(b0.b,b1.b); + AH2 dR=AH2(d0.r,d1.r); + AH2 dG=AH2(d0.g,d1.g); + AH2 dB=AH2(d0.b,d1.b); + AH2 eR=AH2(e0.r,e1.r); + AH2 eG=AH2(e0.g,e1.g); + AH2 eB=AH2(e0.b,e1.b); + AH2 fR=AH2(f0.r,f1.r); + AH2 fG=AH2(f0.g,f1.g); + AH2 fB=AH2(f0.b,f1.b); + AH2 hR=AH2(h0.r,h1.r); + AH2 hG=AH2(h0.g,h1.g); + AH2 hB=AH2(h0.b,h1.b); + // Run optional input transform. + FsrRcasInputHx2(bR,bG,bB); + FsrRcasInputHx2(dR,dG,dB); + FsrRcasInputHx2(eR,eG,eB); + FsrRcasInputHx2(fR,fG,fB); + FsrRcasInputHx2(hR,hG,hB); + // Luma times 2. + AH2 bL=bB*AH2_(0.5)+(bR*AH2_(0.5)+bG); + AH2 dL=dB*AH2_(0.5)+(dR*AH2_(0.5)+dG); + AH2 eL=eB*AH2_(0.5)+(eR*AH2_(0.5)+eG); + AH2 fL=fB*AH2_(0.5)+(fR*AH2_(0.5)+fG); + AH2 hL=hB*AH2_(0.5)+(hR*AH2_(0.5)+hG); + // Noise detection. + AH2 nz=AH2_(0.25)*bL+AH2_(0.25)*dL+AH2_(0.25)*fL+AH2_(0.25)*hL-eL; + nz=ASatH2(abs(nz)*APrxMedRcpH2(AMax3H2(AMax3H2(bL,dL,eL),fL,hL)-AMin3H2(AMin3H2(bL,dL,eL),fL,hL))); + nz=AH2_(-0.5)*nz+AH2_(1.0); + // Min and max of ring. + AH2 mn4R=min(AMin3H2(bR,dR,fR),hR); + AH2 mn4G=min(AMin3H2(bG,dG,fG),hG); + AH2 mn4B=min(AMin3H2(bB,dB,fB),hB); + AH2 mx4R=max(AMax3H2(bR,dR,fR),hR); + AH2 mx4G=max(AMax3H2(bG,dG,fG),hG); + AH2 mx4B=max(AMax3H2(bB,dB,fB),hB); + // Immediate constants for peak range. + AH2 peakC=AH2(1.0,-1.0*4.0); + // Limiters, these need to be high precision RCPs. + AH2 hitMinR=mn4R*ARcpH2(AH2_(4.0)*mx4R); + AH2 hitMinG=mn4G*ARcpH2(AH2_(4.0)*mx4G); + AH2 hitMinB=mn4B*ARcpH2(AH2_(4.0)*mx4B); + AH2 hitMaxR=(peakC.x-mx4R)*ARcpH2(AH2_(4.0)*mn4R+peakC.y); + AH2 hitMaxG=(peakC.x-mx4G)*ARcpH2(AH2_(4.0)*mn4G+peakC.y); + AH2 hitMaxB=(peakC.x-mx4B)*ARcpH2(AH2_(4.0)*mn4B+peakC.y); + AH2 lobeR=max(-hitMinR,hitMaxR); + AH2 lobeG=max(-hitMinG,hitMaxG); + AH2 lobeB=max(-hitMinB,hitMaxB); + AH2 lobe=max(AH2_(-FSR_RCAS_LIMIT),min(AMax3H2(lobeR,lobeG,lobeB),AH2_(0.0)))*AH2_(AH2_AU1(con.y).x); + // Apply noise removal. + #ifdef FSR_RCAS_DENOISE + lobe*=nz; + #endif + // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes. + AH2 rcpL=APrxMedRcpH2(AH2_(4.0)*lobe+AH2_(1.0)); + pixR=(lobe*bR+lobe*dR+lobe*hR+lobe*fR+eR)*rcpL; + pixG=(lobe*bG+lobe*dG+lobe*hG+lobe*fG+eG)*rcpL; + pixB=(lobe*bB+lobe*dB+lobe*hB+lobe*fB+eB)*rcpL;} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// FSR - [LFGA] LINEAR FILM GRAIN APPLICATOR +// +//------------------------------------------------------------------------------------------------------------------------------ +// Adding output-resolution film grain after scaling is a good way to mask both rendering and scaling artifacts. +// Suggest using tiled blue noise as film grain input, with peak noise frequency set for a specific look and feel. +// The 'Lfga*()' functions provide a convenient way to introduce grain. +// These functions limit grain based on distance to signal limits. +// This is done so that the grain is temporally energy preserving, and thus won't modify image tonality. +// Grain application should be done in a linear colorspace. +// The grain should be temporally changing, but have a temporal sum per pixel that adds to zero (non-biased). +//------------------------------------------------------------------------------------------------------------------------------ +// Usage, +// FsrLfga*( +// color, // In/out linear colorspace color {0 to 1} ranged. +// grain, // Per pixel grain texture value {-0.5 to 0.5} ranged, input is 3-channel to support colored grain. +// amount); // Amount of grain (0 to 1} ranged. +//------------------------------------------------------------------------------------------------------------------------------ +// Example if grain texture is monochrome: 'FsrLfgaF(color,AF3_(grain),amount)' +//============================================================================================================================== +#if defined(A_GPU) + // Maximum grain is the minimum distance to the signal limit. + void FsrLfgaF(inout AF3 c,AF3 t,AF1 a){c+=(t*AF3_(a))*min(AF3_(1.0)-c,c);} +#endif +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF) + // Half precision version (slower). + void FsrLfgaH(inout AH3 c,AH3 t,AH1 a){c+=(t*AH3_(a))*min(AH3_(1.0)-c,c);} +//------------------------------------------------------------------------------------------------------------------------------ + // Packed half precision version (faster). + void FsrLfgaHx2(inout AH2 cR,inout AH2 cG,inout AH2 cB,AH2 tR,AH2 tG,AH2 tB,AH1 a){ + cR+=(tR*AH2_(a))*min(AH2_(1.0)-cR,cR);cG+=(tG*AH2_(a))*min(AH2_(1.0)-cG,cG);cB+=(tB*AH2_(a))*min(AH2_(1.0)-cB,cB);} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// FSR - [SRTM] SIMPLE REVERSIBLE TONE-MAPPER +// +//------------------------------------------------------------------------------------------------------------------------------ +// This provides a way to take linear HDR color {0 to FP16_MAX} and convert it into a temporary {0 to 1} ranged post-tonemapped linear. +// The tonemapper preserves RGB ratio, which helps maintain HDR color bleed during filtering. +//------------------------------------------------------------------------------------------------------------------------------ +// Reversible tonemapper usage, +// FsrSrtm*(color); // {0 to FP16_MAX} converted to {0 to 1}. +// FsrSrtmInv*(color); // {0 to 1} converted into {0 to 32768, output peak safe for FP16}. +//============================================================================================================================== +#if defined(A_GPU) + void FsrSrtmF(inout AF3 c){c*=AF3_(ARcpF1(AMax3F1(c.r,c.g,c.b)+AF1_(1.0)));} + // The extra max solves the c=1.0 case (which is a /0). + void FsrSrtmInvF(inout AF3 c){c*=AF3_(ARcpF1(max(AF1_(1.0/32768.0),AF1_(1.0)-AMax3F1(c.r,c.g,c.b))));} +#endif +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF) + void FsrSrtmH(inout AH3 c){c*=AH3_(ARcpH1(AMax3H1(c.r,c.g,c.b)+AH1_(1.0)));} + void FsrSrtmInvH(inout AH3 c){c*=AH3_(ARcpH1(max(AH1_(1.0/32768.0),AH1_(1.0)-AMax3H1(c.r,c.g,c.b))));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrSrtmHx2(inout AH2 cR,inout AH2 cG,inout AH2 cB){ + AH2 rcp=ARcpH2(AMax3H2(cR,cG,cB)+AH2_(1.0));cR*=rcp;cG*=rcp;cB*=rcp;} + void FsrSrtmInvHx2(inout AH2 cR,inout AH2 cG,inout AH2 cB){ + AH2 rcp=ARcpH2(max(AH2_(1.0/32768.0),AH2_(1.0)-AMax3H2(cR,cG,cB)));cR*=rcp;cG*=rcp;cB*=rcp;} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// FSR - [TEPD] TEMPORAL ENERGY PRESERVING DITHER +// +//------------------------------------------------------------------------------------------------------------------------------ +// Temporally energy preserving dithered {0 to 1} linear to gamma 2.0 conversion. +// Gamma 2.0 is used so that the conversion back to linear is just to square the color. +// The conversion comes in 8-bit and 10-bit modes, designed for output to 8-bit UNORM or 10:10:10:2 respectively. +// Given good non-biased temporal blue noise as dither input, +// the output dither will temporally conserve energy. +// This is done by choosing the linear nearest step point instead of perceptual nearest. +// See code below for details. +//------------------------------------------------------------------------------------------------------------------------------ +// DX SPEC RULES FOR FLOAT->UNORM 8-BIT CONVERSION +// =============================================== +// - Output is 'uint(floor(saturate(n)*255.0+0.5))'. +// - Thus rounding is to nearest. +// - NaN gets converted to zero. +// - INF is clamped to {0.0 to 1.0}. +//============================================================================================================================== +#if defined(A_GPU) + // Hand tuned integer position to dither value, with more values than simple checkerboard. + // Only 32-bit has enough precision for this compddation. + // Output is {0 to <1}. + AF1 FsrTepdDitF(AU2 p,AU1 f){ + AF1 x=AF1_(p.x+f); + AF1 y=AF1_(p.y); + // The 1.61803 golden ratio. + AF1 a=AF1_((1.0+sqrt(5.0))/2.0); + // Number designed to provide a good visual pattern. + AF1 b=AF1_(1.0/3.69); + x=x*a+(y*b); + return AFractF1(x);} +//------------------------------------------------------------------------------------------------------------------------------ + // This version is 8-bit gamma 2.0. + // The 'c' input is {0 to 1}. + // Output is {0 to 1} ready for image store. + void FsrTepdC8F(inout AF3 c,AF1 dit){ + AF3 n=sqrt(c); + n=floor(n*AF3_(255.0))*AF3_(1.0/255.0); + AF3 a=n*n; + AF3 b=n+AF3_(1.0/255.0);b=b*b; + // Ratio of 'a' to 'b' required to produce 'c'. + // APrxLoRcpF1() won't work here (at least for very high dynamic ranges). + // APrxMedRcpF1() is an IADD,FMA,MUL. + AF3 r=(c-b)*APrxMedRcpF3(a-b); + // Use the ratio as a cutoff to choose 'a' or 'b'. + // AGtZeroF1() is a MUL. + c=ASatF3(n+AGtZeroF3(AF3_(dit)-r)*AF3_(1.0/255.0));} +//------------------------------------------------------------------------------------------------------------------------------ + // This version is 10-bit gamma 2.0. + // The 'c' input is {0 to 1}. + // Output is {0 to 1} ready for image store. + void FsrTepdC10F(inout AF3 c,AF1 dit){ + AF3 n=sqrt(c); + n=floor(n*AF3_(1023.0))*AF3_(1.0/1023.0); + AF3 a=n*n; + AF3 b=n+AF3_(1.0/1023.0);b=b*b; + AF3 r=(c-b)*APrxMedRcpF3(a-b); + c=ASatF3(n+AGtZeroF3(AF3_(dit)-r)*AF3_(1.0/1023.0));} +#endif +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF) + AH1 FsrTepdDitH(AU2 p,AU1 f){ + AF1 x=AF1_(p.x+f); + AF1 y=AF1_(p.y); + AF1 a=AF1_((1.0+sqrt(5.0))/2.0); + AF1 b=AF1_(1.0/3.69); + x=x*a+(y*b); + return AH1(AFractF1(x));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrTepdC8H(inout AH3 c,AH1 dit){ + AH3 n=sqrt(c); + n=floor(n*AH3_(255.0))*AH3_(1.0/255.0); + AH3 a=n*n; + AH3 b=n+AH3_(1.0/255.0);b=b*b; + AH3 r=(c-b)*APrxMedRcpH3(a-b); + c=ASatH3(n+AGtZeroH3(AH3_(dit)-r)*AH3_(1.0/255.0));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrTepdC10H(inout AH3 c,AH1 dit){ + AH3 n=sqrt(c); + n=floor(n*AH3_(1023.0))*AH3_(1.0/1023.0); + AH3 a=n*n; + AH3 b=n+AH3_(1.0/1023.0);b=b*b; + AH3 r=(c-b)*APrxMedRcpH3(a-b); + c=ASatH3(n+AGtZeroH3(AH3_(dit)-r)*AH3_(1.0/1023.0));} +//============================================================================================================================== + // This computes dither for positions 'p' and 'p+{8,0}'. + AH2 FsrTepdDitHx2(AU2 p,AU1 f){ + AF2 x; + x.x=AF1_(p.x+f); + x.y=x.x+AF1_(8.0); + AF1 y=AF1_(p.y); + AF1 a=AF1_((1.0+sqrt(5.0))/2.0); + AF1 b=AF1_(1.0/3.69); + x=x*AF2_(a)+AF2_(y*b); + return AH2(AFractF2(x));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrTepdC8Hx2(inout AH2 cR,inout AH2 cG,inout AH2 cB,AH2 dit){ + AH2 nR=sqrt(cR); + AH2 nG=sqrt(cG); + AH2 nB=sqrt(cB); + nR=floor(nR*AH2_(255.0))*AH2_(1.0/255.0); + nG=floor(nG*AH2_(255.0))*AH2_(1.0/255.0); + nB=floor(nB*AH2_(255.0))*AH2_(1.0/255.0); + AH2 aR=nR*nR; + AH2 aG=nG*nG; + AH2 aB=nB*nB; + AH2 bR=nR+AH2_(1.0/255.0);bR=bR*bR; + AH2 bG=nG+AH2_(1.0/255.0);bG=bG*bG; + AH2 bB=nB+AH2_(1.0/255.0);bB=bB*bB; + AH2 rR=(cR-bR)*APrxMedRcpH2(aR-bR); + AH2 rG=(cG-bG)*APrxMedRcpH2(aG-bG); + AH2 rB=(cB-bB)*APrxMedRcpH2(aB-bB); + cR=ASatH2(nR+AGtZeroH2(dit-rR)*AH2_(1.0/255.0)); + cG=ASatH2(nG+AGtZeroH2(dit-rG)*AH2_(1.0/255.0)); + cB=ASatH2(nB+AGtZeroH2(dit-rB)*AH2_(1.0/255.0));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrTepdC10Hx2(inout AH2 cR,inout AH2 cG,inout AH2 cB,AH2 dit){ + AH2 nR=sqrt(cR); + AH2 nG=sqrt(cG); + AH2 nB=sqrt(cB); + nR=floor(nR*AH2_(1023.0))*AH2_(1.0/1023.0); + nG=floor(nG*AH2_(1023.0))*AH2_(1.0/1023.0); + nB=floor(nB*AH2_(1023.0))*AH2_(1.0/1023.0); + AH2 aR=nR*nR; + AH2 aG=nG*nG; + AH2 aB=nB*nB; + AH2 bR=nR+AH2_(1.0/1023.0);bR=bR*bR; + AH2 bG=nG+AH2_(1.0/1023.0);bG=bG*bG; + AH2 bB=nB+AH2_(1.0/1023.0);bB=bB*bB; + AH2 rR=(cR-bR)*APrxMedRcpH2(aR-bR); + AH2 rG=(cG-bG)*APrxMedRcpH2(aG-bG); + AH2 rB=(cB-bB)*APrxMedRcpH2(aB-bB); + cR=ASatH2(nR+AGtZeroH2(dit-rR)*AH2_(1.0/1023.0)); + cG=ASatH2(nG+AGtZeroH2(dit-rG)*AH2_(1.0/1023.0)); + cB=ASatH2(nB+AGtZeroH2(dit-rB)*AH2_(1.0/1023.0));} +#endif + +void main() +{ + AU4 con0, con1, con2, con3; + FsrEasuCon(con0, con1, con2, con3, srcSize.x, srcSize.y, + srcSize.x, srcSize.y, dstSize.x, dstSize.y); + + vec3 pout = vec3(0.0, 0.0, 0.0); + + FsrEasuF(pout, uvec2(fragTexCoord * dstSize), con0, con1, con2, con3); + + finalColor = vec4(pout, 1.0); +} + diff --git a/examples/resources/fsr/fsrRcas.frag b/examples/resources/fsr/fsrRcas.frag new file mode 100644 index 0000000..dd17be1 --- /dev/null +++ b/examples/resources/fsr/fsrRcas.frag @@ -0,0 +1,3897 @@ +#version 420 + +#define A_GPU 1 +#define A_GLSL 1 + +#define FSR_RCAS_F 1 +//#define FSR_RCAS_DENOISE 1 + +in vec2 fragTexCoord; +in vec4 fragColor; + +// Input uniform values +uniform sampler2D texture0; +uniform vec2 dstSize; +uniform float sharpness; + +// Output fragment color +out vec4 finalColor; + +//============================================================================================================================== +// +// [A] SHADER PORTABILITY 1.20210629 +// +//============================================================================================================================== +// FidelityFX Super Resolution Sample +// +// Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved. +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//------------------------------------------------------------------------------------------------------------------------------ +// MIT LICENSE +// =========== +// Copyright (c) 2014 Michal Drobot (for concepts used in "FLOAT APPROXIMATIONS"). +// ----------- +// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, +// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// ----------- +// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +// Software. +// ----------- +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +//------------------------------------------------------------------------------------------------------------------------------ +// ABOUT +// ===== +// Common central point for high-level shading language and C portability for various shader headers. +//------------------------------------------------------------------------------------------------------------------------------ +// DEFINES +// ======= +// A_CPU ..... Include the CPU related code. +// A_GPU ..... Include the GPU related code. +// A_GLSL .... Using GLSL. +// A_HLSL .... Using HLSL. +// A_HLSL_6_2 Using HLSL 6.2 with new 'uint16_t' and related types (requires '-enable-16bit-types'). +// A_NO_16_BIT_CAST Don't use instructions that are not availabe in SPIR-V (needed for running A_HLSL_6_2 on Vulkan) +// A_GCC ..... Using a GCC compatible compiler (else assume MSVC compatible compiler by default). +// ======= +// A_BYTE .... Support 8-bit integer. +// A_HALF .... Support 16-bit integer and floating point. +// A_LONG .... Support 64-bit integer. +// A_DUBL .... Support 64-bit floating point. +// ======= +// A_WAVE .... Support wave-wide operations. +//------------------------------------------------------------------------------------------------------------------------------ +// To get #include "ffx_a.h" working in GLSL use '#extension GL_GOOGLE_include_directive:require'. +//------------------------------------------------------------------------------------------------------------------------------ +// SIMPLIFIED TYPE SYSTEM +// ====================== +// - All ints will be unsigned with exception of when signed is required. +// - Type naming simplified and shortened "A<#components>", +// - H = 16-bit float (half) +// - F = 32-bit float (float) +// - D = 64-bit float (double) +// - P = 1-bit integer (predicate, not using bool because 'B' is used for byte) +// - B = 8-bit integer (byte) +// - W = 16-bit integer (word) +// - U = 32-bit integer (unsigned) +// - L = 64-bit integer (long) +// - Using "AS<#components>" for signed when required. +//------------------------------------------------------------------------------------------------------------------------------ +// TODO +// ==== +// - Make sure 'ALerp*(a,b,m)' does 'b*m+(-a*m+a)' (2 ops). +//------------------------------------------------------------------------------------------------------------------------------ +// CHANGE LOG +// ========== +// 20200914 - Expanded wave ops and prx code. +// 20200713 - Added [ZOL] section, fixed serious bugs in sRGB and Rec.709 color conversion code, etc. +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// COMMON +//============================================================================================================================== +#define A_2PI 6.28318530718 +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// CPU +// +// +//============================================================================================================================== +#ifdef A_CPU + // Supporting user defined overrides. + #ifndef A_RESTRICT + #define A_RESTRICT __restrict + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifndef A_STATIC + #define A_STATIC static + #endif +//------------------------------------------------------------------------------------------------------------------------------ + // Same types across CPU and GPU. + // Predicate uses 32-bit integer (C friendly bool). + typedef uint32_t AP1; + typedef float AF1; + typedef double AD1; + typedef uint8_t AB1; + typedef uint16_t AW1; + typedef uint32_t AU1; + typedef uint64_t AL1; + typedef int8_t ASB1; + typedef int16_t ASW1; + typedef int32_t ASU1; + typedef int64_t ASL1; +//------------------------------------------------------------------------------------------------------------------------------ + #define AD1_(a) ((AD1)(a)) + #define AF1_(a) ((AF1)(a)) + #define AL1_(a) ((AL1)(a)) + #define AU1_(a) ((AU1)(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ASL1_(a) ((ASL1)(a)) + #define ASU1_(a) ((ASU1)(a)) +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AU1 AU1_AF1(AF1 a){union{AF1 f;AU1 u;}bits;bits.f=a;return bits.u;} +//------------------------------------------------------------------------------------------------------------------------------ + #define A_TRUE 1 + #define A_FALSE 0 +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// CPU/GPU PORTING +// +//------------------------------------------------------------------------------------------------------------------------------ +// Get CPU and GPU to share all setup code, without duplicate code paths. +// This uses a lower-case prefix for special vector constructs. +// - In C restrict pointers are used. +// - In the shading language, in/inout/out arguments are used. +// This depends on the ability to access a vector value in both languages via array syntax (aka color[2]). +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR ARGUMENT/RETURN/INITIALIZATION PORTABILITY +//============================================================================================================================== + #define retAD2 AD1 *A_RESTRICT + #define retAD3 AD1 *A_RESTRICT + #define retAD4 AD1 *A_RESTRICT + #define retAF2 AF1 *A_RESTRICT + #define retAF3 AF1 *A_RESTRICT + #define retAF4 AF1 *A_RESTRICT + #define retAL2 AL1 *A_RESTRICT + #define retAL3 AL1 *A_RESTRICT + #define retAL4 AL1 *A_RESTRICT + #define retAU2 AU1 *A_RESTRICT + #define retAU3 AU1 *A_RESTRICT + #define retAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define inAD2 AD1 *A_RESTRICT + #define inAD3 AD1 *A_RESTRICT + #define inAD4 AD1 *A_RESTRICT + #define inAF2 AF1 *A_RESTRICT + #define inAF3 AF1 *A_RESTRICT + #define inAF4 AF1 *A_RESTRICT + #define inAL2 AL1 *A_RESTRICT + #define inAL3 AL1 *A_RESTRICT + #define inAL4 AL1 *A_RESTRICT + #define inAU2 AU1 *A_RESTRICT + #define inAU3 AU1 *A_RESTRICT + #define inAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define inoutAD2 AD1 *A_RESTRICT + #define inoutAD3 AD1 *A_RESTRICT + #define inoutAD4 AD1 *A_RESTRICT + #define inoutAF2 AF1 *A_RESTRICT + #define inoutAF3 AF1 *A_RESTRICT + #define inoutAF4 AF1 *A_RESTRICT + #define inoutAL2 AL1 *A_RESTRICT + #define inoutAL3 AL1 *A_RESTRICT + #define inoutAL4 AL1 *A_RESTRICT + #define inoutAU2 AU1 *A_RESTRICT + #define inoutAU3 AU1 *A_RESTRICT + #define inoutAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define outAD2 AD1 *A_RESTRICT + #define outAD3 AD1 *A_RESTRICT + #define outAD4 AD1 *A_RESTRICT + #define outAF2 AF1 *A_RESTRICT + #define outAF3 AF1 *A_RESTRICT + #define outAF4 AF1 *A_RESTRICT + #define outAL2 AL1 *A_RESTRICT + #define outAL3 AL1 *A_RESTRICT + #define outAL4 AL1 *A_RESTRICT + #define outAU2 AU1 *A_RESTRICT + #define outAU3 AU1 *A_RESTRICT + #define outAU4 AU1 *A_RESTRICT +//------------------------------------------------------------------------------------------------------------------------------ + #define varAD2(x) AD1 x[2] + #define varAD3(x) AD1 x[3] + #define varAD4(x) AD1 x[4] + #define varAF2(x) AF1 x[2] + #define varAF3(x) AF1 x[3] + #define varAF4(x) AF1 x[4] + #define varAL2(x) AL1 x[2] + #define varAL3(x) AL1 x[3] + #define varAL4(x) AL1 x[4] + #define varAU2(x) AU1 x[2] + #define varAU3(x) AU1 x[3] + #define varAU4(x) AU1 x[4] +//------------------------------------------------------------------------------------------------------------------------------ + #define initAD2(x,y) {x,y} + #define initAD3(x,y,z) {x,y,z} + #define initAD4(x,y,z,w) {x,y,z,w} + #define initAF2(x,y) {x,y} + #define initAF3(x,y,z) {x,y,z} + #define initAF4(x,y,z,w) {x,y,z,w} + #define initAL2(x,y) {x,y} + #define initAL3(x,y,z) {x,y,z} + #define initAL4(x,y,z,w) {x,y,z,w} + #define initAU2(x,y) {x,y} + #define initAU3(x,y,z) {x,y,z} + #define initAU4(x,y,z,w) {x,y,z,w} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS +//------------------------------------------------------------------------------------------------------------------------------ +// TODO +// ==== +// - Replace transcendentals with manual versions. +//============================================================================================================================== + #ifdef A_GCC + A_STATIC AD1 AAbsD1(AD1 a){return __builtin_fabs(a);} + A_STATIC AF1 AAbsF1(AF1 a){return __builtin_fabsf(a);} + A_STATIC AU1 AAbsSU1(AU1 a){return AU1_(__builtin_abs(ASU1_(a)));} + A_STATIC AL1 AAbsSL1(AL1 a){return AL1_(__builtin_llabs(ASL1_(a)));} + #else + A_STATIC AD1 AAbsD1(AD1 a){return fabs(a);} + A_STATIC AF1 AAbsF1(AF1 a){return fabsf(a);} + A_STATIC AU1 AAbsSU1(AU1 a){return AU1_(abs(ASU1_(a)));} + A_STATIC AL1 AAbsSL1(AL1 a){return AL1_(labs((long)ASL1_(a)));} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ACosD1(AD1 a){return __builtin_cos(a);} + A_STATIC AF1 ACosF1(AF1 a){return __builtin_cosf(a);} + #else + A_STATIC AD1 ACosD1(AD1 a){return cos(a);} + A_STATIC AF1 ACosF1(AF1 a){return cosf(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ADotD2(inAD2 a,inAD2 b){return a[0]*b[0]+a[1]*b[1];} + A_STATIC AD1 ADotD3(inAD3 a,inAD3 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];} + A_STATIC AD1 ADotD4(inAD4 a,inAD4 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3];} + A_STATIC AF1 ADotF2(inAF2 a,inAF2 b){return a[0]*b[0]+a[1]*b[1];} + A_STATIC AF1 ADotF3(inAF3 a,inAF3 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2];} + A_STATIC AF1 ADotF4(inAF4 a,inAF4 b){return a[0]*b[0]+a[1]*b[1]+a[2]*b[2]+a[3]*b[3];} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 AExp2D1(AD1 a){return __builtin_exp2(a);} + A_STATIC AF1 AExp2F1(AF1 a){return __builtin_exp2f(a);} + #else + A_STATIC AD1 AExp2D1(AD1 a){return exp2(a);} + A_STATIC AF1 AExp2F1(AF1 a){return exp2f(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 AFloorD1(AD1 a){return __builtin_floor(a);} + A_STATIC AF1 AFloorF1(AF1 a){return __builtin_floorf(a);} + #else + A_STATIC AD1 AFloorD1(AD1 a){return floor(a);} + A_STATIC AF1 AFloorF1(AF1 a){return floorf(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ALerpD1(AD1 a,AD1 b,AD1 c){return b*c+(-a*c+a);} + A_STATIC AF1 ALerpF1(AF1 a,AF1 b,AF1 c){return b*c+(-a*c+a);} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ALog2D1(AD1 a){return __builtin_log2(a);} + A_STATIC AF1 ALog2F1(AF1 a){return __builtin_log2f(a);} + #else + A_STATIC AD1 ALog2D1(AD1 a){return log2(a);} + A_STATIC AF1 ALog2F1(AF1 a){return log2f(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 AMaxD1(AD1 a,AD1 b){return a>b?a:b;} + A_STATIC AF1 AMaxF1(AF1 a,AF1 b){return a>b?a:b;} + A_STATIC AL1 AMaxL1(AL1 a,AL1 b){return a>b?a:b;} + A_STATIC AU1 AMaxU1(AU1 a,AU1 b){return a>b?a:b;} +//------------------------------------------------------------------------------------------------------------------------------ + // These follow the convention that A integer types don't have signage, until they are operated on. + A_STATIC AL1 AMaxSL1(AL1 a,AL1 b){return (ASL1_(a)>ASL1_(b))?a:b;} + A_STATIC AU1 AMaxSU1(AU1 a,AU1 b){return (ASU1_(a)>ASU1_(b))?a:b;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 AMinD1(AD1 a,AD1 b){return a>ASL1_(b));} + A_STATIC AU1 AShrSU1(AU1 a,AU1 b){return AU1_(ASU1_(a)>>ASU1_(b));} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ASinD1(AD1 a){return __builtin_sin(a);} + A_STATIC AF1 ASinF1(AF1 a){return __builtin_sinf(a);} + #else + A_STATIC AD1 ASinD1(AD1 a){return sin(a);} + A_STATIC AF1 ASinF1(AF1 a){return sinf(a);} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_GCC + A_STATIC AD1 ASqrtD1(AD1 a){return __builtin_sqrt(a);} + A_STATIC AF1 ASqrtF1(AF1 a){return __builtin_sqrtf(a);} + #else + A_STATIC AD1 ASqrtD1(AD1 a){return sqrt(a);} + A_STATIC AF1 ASqrtF1(AF1 a){return sqrtf(a);} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS - DEPENDENT +//============================================================================================================================== + A_STATIC AD1 AClampD1(AD1 x,AD1 n,AD1 m){return AMaxD1(n,AMinD1(x,m));} + A_STATIC AF1 AClampF1(AF1 x,AF1 n,AF1 m){return AMaxF1(n,AMinF1(x,m));} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 AFractD1(AD1 a){return a-AFloorD1(a);} + A_STATIC AF1 AFractF1(AF1 a){return a-AFloorF1(a);} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 APowD1(AD1 a,AD1 b){return AExp2D1(b*ALog2D1(a));} + A_STATIC AF1 APowF1(AF1 a,AF1 b){return AExp2F1(b*ALog2F1(a));} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ARsqD1(AD1 a){return ARcpD1(ASqrtD1(a));} + A_STATIC AF1 ARsqF1(AF1 a){return ARcpF1(ASqrtF1(a));} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC AD1 ASatD1(AD1 a){return AMinD1(1.0,AMaxD1(0.0,a));} + A_STATIC AF1 ASatF1(AF1 a){return AMinF1(1.0f,AMaxF1(0.0f,a));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR OPS +//------------------------------------------------------------------------------------------------------------------------------ +// These are added as needed for production or prototyping, so not necessarily a complete set. +// They follow a convention of taking in a destination and also returning the destination value to increase utility. +//============================================================================================================================== + A_STATIC retAD2 opAAbsD2(outAD2 d,inAD2 a){d[0]=AAbsD1(a[0]);d[1]=AAbsD1(a[1]);return d;} + A_STATIC retAD3 opAAbsD3(outAD3 d,inAD3 a){d[0]=AAbsD1(a[0]);d[1]=AAbsD1(a[1]);d[2]=AAbsD1(a[2]);return d;} + A_STATIC retAD4 opAAbsD4(outAD4 d,inAD4 a){d[0]=AAbsD1(a[0]);d[1]=AAbsD1(a[1]);d[2]=AAbsD1(a[2]);d[3]=AAbsD1(a[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAAbsF2(outAF2 d,inAF2 a){d[0]=AAbsF1(a[0]);d[1]=AAbsF1(a[1]);return d;} + A_STATIC retAF3 opAAbsF3(outAF3 d,inAF3 a){d[0]=AAbsF1(a[0]);d[1]=AAbsF1(a[1]);d[2]=AAbsF1(a[2]);return d;} + A_STATIC retAF4 opAAbsF4(outAF4 d,inAF4 a){d[0]=AAbsF1(a[0]);d[1]=AAbsF1(a[1]);d[2]=AAbsF1(a[2]);d[3]=AAbsF1(a[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAAddD2(outAD2 d,inAD2 a,inAD2 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];return d;} + A_STATIC retAD3 opAAddD3(outAD3 d,inAD3 a,inAD3 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];return d;} + A_STATIC retAD4 opAAddD4(outAD4 d,inAD4 a,inAD4 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];d[3]=a[3]+b[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAAddF2(outAF2 d,inAF2 a,inAF2 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];return d;} + A_STATIC retAF3 opAAddF3(outAF3 d,inAF3 a,inAF3 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];return d;} + A_STATIC retAF4 opAAddF4(outAF4 d,inAF4 a,inAF4 b){d[0]=a[0]+b[0];d[1]=a[1]+b[1];d[2]=a[2]+b[2];d[3]=a[3]+b[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opAAddOneD2(outAD2 d,inAD2 a,AD1 b){d[0]=a[0]+b;d[1]=a[1]+b;return d;} + A_STATIC retAD3 opAAddOneD3(outAD3 d,inAD3 a,AD1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;return d;} + A_STATIC retAD4 opAAddOneD4(outAD4 d,inAD4 a,AD1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;d[3]=a[3]+b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAAddOneF2(outAF2 d,inAF2 a,AF1 b){d[0]=a[0]+b;d[1]=a[1]+b;return d;} + A_STATIC retAF3 opAAddOneF3(outAF3 d,inAF3 a,AF1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;return d;} + A_STATIC retAF4 opAAddOneF4(outAF4 d,inAF4 a,AF1 b){d[0]=a[0]+b;d[1]=a[1]+b;d[2]=a[2]+b;d[3]=a[3]+b;return d;} +//============================================================================================================================== + A_STATIC retAD2 opACpyD2(outAD2 d,inAD2 a){d[0]=a[0];d[1]=a[1];return d;} + A_STATIC retAD3 opACpyD3(outAD3 d,inAD3 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];return d;} + A_STATIC retAD4 opACpyD4(outAD4 d,inAD4 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];d[3]=a[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opACpyF2(outAF2 d,inAF2 a){d[0]=a[0];d[1]=a[1];return d;} + A_STATIC retAF3 opACpyF3(outAF3 d,inAF3 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];return d;} + A_STATIC retAF4 opACpyF4(outAF4 d,inAF4 a){d[0]=a[0];d[1]=a[1];d[2]=a[2];d[3]=a[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opALerpD2(outAD2 d,inAD2 a,inAD2 b,inAD2 c){d[0]=ALerpD1(a[0],b[0],c[0]);d[1]=ALerpD1(a[1],b[1],c[1]);return d;} + A_STATIC retAD3 opALerpD3(outAD3 d,inAD3 a,inAD3 b,inAD3 c){d[0]=ALerpD1(a[0],b[0],c[0]);d[1]=ALerpD1(a[1],b[1],c[1]);d[2]=ALerpD1(a[2],b[2],c[2]);return d;} + A_STATIC retAD4 opALerpD4(outAD4 d,inAD4 a,inAD4 b,inAD4 c){d[0]=ALerpD1(a[0],b[0],c[0]);d[1]=ALerpD1(a[1],b[1],c[1]);d[2]=ALerpD1(a[2],b[2],c[2]);d[3]=ALerpD1(a[3],b[3],c[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opALerpF2(outAF2 d,inAF2 a,inAF2 b,inAF2 c){d[0]=ALerpF1(a[0],b[0],c[0]);d[1]=ALerpF1(a[1],b[1],c[1]);return d;} + A_STATIC retAF3 opALerpF3(outAF3 d,inAF3 a,inAF3 b,inAF3 c){d[0]=ALerpF1(a[0],b[0],c[0]);d[1]=ALerpF1(a[1],b[1],c[1]);d[2]=ALerpF1(a[2],b[2],c[2]);return d;} + A_STATIC retAF4 opALerpF4(outAF4 d,inAF4 a,inAF4 b,inAF4 c){d[0]=ALerpF1(a[0],b[0],c[0]);d[1]=ALerpF1(a[1],b[1],c[1]);d[2]=ALerpF1(a[2],b[2],c[2]);d[3]=ALerpF1(a[3],b[3],c[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opALerpOneD2(outAD2 d,inAD2 a,inAD2 b,AD1 c){d[0]=ALerpD1(a[0],b[0],c);d[1]=ALerpD1(a[1],b[1],c);return d;} + A_STATIC retAD3 opALerpOneD3(outAD3 d,inAD3 a,inAD3 b,AD1 c){d[0]=ALerpD1(a[0],b[0],c);d[1]=ALerpD1(a[1],b[1],c);d[2]=ALerpD1(a[2],b[2],c);return d;} + A_STATIC retAD4 opALerpOneD4(outAD4 d,inAD4 a,inAD4 b,AD1 c){d[0]=ALerpD1(a[0],b[0],c);d[1]=ALerpD1(a[1],b[1],c);d[2]=ALerpD1(a[2],b[2],c);d[3]=ALerpD1(a[3],b[3],c);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opALerpOneF2(outAF2 d,inAF2 a,inAF2 b,AF1 c){d[0]=ALerpF1(a[0],b[0],c);d[1]=ALerpF1(a[1],b[1],c);return d;} + A_STATIC retAF3 opALerpOneF3(outAF3 d,inAF3 a,inAF3 b,AF1 c){d[0]=ALerpF1(a[0],b[0],c);d[1]=ALerpF1(a[1],b[1],c);d[2]=ALerpF1(a[2],b[2],c);return d;} + A_STATIC retAF4 opALerpOneF4(outAF4 d,inAF4 a,inAF4 b,AF1 c){d[0]=ALerpF1(a[0],b[0],c);d[1]=ALerpF1(a[1],b[1],c);d[2]=ALerpF1(a[2],b[2],c);d[3]=ALerpF1(a[3],b[3],c);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMaxD2(outAD2 d,inAD2 a,inAD2 b){d[0]=AMaxD1(a[0],b[0]);d[1]=AMaxD1(a[1],b[1]);return d;} + A_STATIC retAD3 opAMaxD3(outAD3 d,inAD3 a,inAD3 b){d[0]=AMaxD1(a[0],b[0]);d[1]=AMaxD1(a[1],b[1]);d[2]=AMaxD1(a[2],b[2]);return d;} + A_STATIC retAD4 opAMaxD4(outAD4 d,inAD4 a,inAD4 b){d[0]=AMaxD1(a[0],b[0]);d[1]=AMaxD1(a[1],b[1]);d[2]=AMaxD1(a[2],b[2]);d[3]=AMaxD1(a[3],b[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMaxF2(outAF2 d,inAF2 a,inAF2 b){d[0]=AMaxF1(a[0],b[0]);d[1]=AMaxF1(a[1],b[1]);return d;} + A_STATIC retAF3 opAMaxF3(outAF3 d,inAF3 a,inAF3 b){d[0]=AMaxF1(a[0],b[0]);d[1]=AMaxF1(a[1],b[1]);d[2]=AMaxF1(a[2],b[2]);return d;} + A_STATIC retAF4 opAMaxF4(outAF4 d,inAF4 a,inAF4 b){d[0]=AMaxF1(a[0],b[0]);d[1]=AMaxF1(a[1],b[1]);d[2]=AMaxF1(a[2],b[2]);d[3]=AMaxF1(a[3],b[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMinD2(outAD2 d,inAD2 a,inAD2 b){d[0]=AMinD1(a[0],b[0]);d[1]=AMinD1(a[1],b[1]);return d;} + A_STATIC retAD3 opAMinD3(outAD3 d,inAD3 a,inAD3 b){d[0]=AMinD1(a[0],b[0]);d[1]=AMinD1(a[1],b[1]);d[2]=AMinD1(a[2],b[2]);return d;} + A_STATIC retAD4 opAMinD4(outAD4 d,inAD4 a,inAD4 b){d[0]=AMinD1(a[0],b[0]);d[1]=AMinD1(a[1],b[1]);d[2]=AMinD1(a[2],b[2]);d[3]=AMinD1(a[3],b[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMinF2(outAF2 d,inAF2 a,inAF2 b){d[0]=AMinF1(a[0],b[0]);d[1]=AMinF1(a[1],b[1]);return d;} + A_STATIC retAF3 opAMinF3(outAF3 d,inAF3 a,inAF3 b){d[0]=AMinF1(a[0],b[0]);d[1]=AMinF1(a[1],b[1]);d[2]=AMinF1(a[2],b[2]);return d;} + A_STATIC retAF4 opAMinF4(outAF4 d,inAF4 a,inAF4 b){d[0]=AMinF1(a[0],b[0]);d[1]=AMinF1(a[1],b[1]);d[2]=AMinF1(a[2],b[2]);d[3]=AMinF1(a[3],b[3]);return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMulD2(outAD2 d,inAD2 a,inAD2 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];return d;} + A_STATIC retAD3 opAMulD3(outAD3 d,inAD3 a,inAD3 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];return d;} + A_STATIC retAD4 opAMulD4(outAD4 d,inAD4 a,inAD4 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];d[3]=a[3]*b[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMulF2(outAF2 d,inAF2 a,inAF2 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];return d;} + A_STATIC retAF3 opAMulF3(outAF3 d,inAF3 a,inAF3 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];return d;} + A_STATIC retAF4 opAMulF4(outAF4 d,inAF4 a,inAF4 b){d[0]=a[0]*b[0];d[1]=a[1]*b[1];d[2]=a[2]*b[2];d[3]=a[3]*b[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opAMulOneD2(outAD2 d,inAD2 a,AD1 b){d[0]=a[0]*b;d[1]=a[1]*b;return d;} + A_STATIC retAD3 opAMulOneD3(outAD3 d,inAD3 a,AD1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;return d;} + A_STATIC retAD4 opAMulOneD4(outAD4 d,inAD4 a,AD1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;d[3]=a[3]*b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opAMulOneF2(outAF2 d,inAF2 a,AF1 b){d[0]=a[0]*b;d[1]=a[1]*b;return d;} + A_STATIC retAF3 opAMulOneF3(outAF3 d,inAF3 a,AF1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;return d;} + A_STATIC retAF4 opAMulOneF4(outAF4 d,inAF4 a,AF1 b){d[0]=a[0]*b;d[1]=a[1]*b;d[2]=a[2]*b;d[3]=a[3]*b;return d;} +//============================================================================================================================== + A_STATIC retAD2 opANegD2(outAD2 d,inAD2 a){d[0]=-a[0];d[1]=-a[1];return d;} + A_STATIC retAD3 opANegD3(outAD3 d,inAD3 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];return d;} + A_STATIC retAD4 opANegD4(outAD4 d,inAD4 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];d[3]=-a[3];return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opANegF2(outAF2 d,inAF2 a){d[0]=-a[0];d[1]=-a[1];return d;} + A_STATIC retAF3 opANegF3(outAF3 d,inAF3 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];return d;} + A_STATIC retAF4 opANegF4(outAF4 d,inAF4 a){d[0]=-a[0];d[1]=-a[1];d[2]=-a[2];d[3]=-a[3];return d;} +//============================================================================================================================== + A_STATIC retAD2 opARcpD2(outAD2 d,inAD2 a){d[0]=ARcpD1(a[0]);d[1]=ARcpD1(a[1]);return d;} + A_STATIC retAD3 opARcpD3(outAD3 d,inAD3 a){d[0]=ARcpD1(a[0]);d[1]=ARcpD1(a[1]);d[2]=ARcpD1(a[2]);return d;} + A_STATIC retAD4 opARcpD4(outAD4 d,inAD4 a){d[0]=ARcpD1(a[0]);d[1]=ARcpD1(a[1]);d[2]=ARcpD1(a[2]);d[3]=ARcpD1(a[3]);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + A_STATIC retAF2 opARcpF2(outAF2 d,inAF2 a){d[0]=ARcpF1(a[0]);d[1]=ARcpF1(a[1]);return d;} + A_STATIC retAF3 opARcpF3(outAF3 d,inAF3 a){d[0]=ARcpF1(a[0]);d[1]=ARcpF1(a[1]);d[2]=ARcpF1(a[2]);return d;} + A_STATIC retAF4 opARcpF4(outAF4 d,inAF4 a){d[0]=ARcpF1(a[0]);d[1]=ARcpF1(a[1]);d[2]=ARcpF1(a[2]);d[3]=ARcpF1(a[3]);return d;} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HALF FLOAT PACKING +//============================================================================================================================== + // Convert float to half (in lower 16-bits of output). + // Same fast technique as documented here: ftp://ftp.fox-toolkit.org/pub/fasthalffloatconversion.pdf + // Supports denormals. + // Conversion rules are to make computations possibly "safer" on the GPU, + // -INF & -NaN -> -65504 + // +INF & +NaN -> +65504 + A_STATIC AU1 AU1_AH1_AF1(AF1 f){ + static AW1 base[512]={ + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000, + 0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0000,0x0001,0x0002,0x0004,0x0008,0x0010,0x0020,0x0040,0x0080,0x0100, + 0x0200,0x0400,0x0800,0x0c00,0x1000,0x1400,0x1800,0x1c00,0x2000,0x2400,0x2800,0x2c00,0x3000,0x3400,0x3800,0x3c00, + 0x4000,0x4400,0x4800,0x4c00,0x5000,0x5400,0x5800,0x5c00,0x6000,0x6400,0x6800,0x6c00,0x7000,0x7400,0x7800,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff,0x7bff, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000, + 0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8000,0x8001,0x8002,0x8004,0x8008,0x8010,0x8020,0x8040,0x8080,0x8100, + 0x8200,0x8400,0x8800,0x8c00,0x9000,0x9400,0x9800,0x9c00,0xa000,0xa400,0xa800,0xac00,0xb000,0xb400,0xb800,0xbc00, + 0xc000,0xc400,0xc800,0xcc00,0xd000,0xd400,0xd800,0xdc00,0xe000,0xe400,0xe800,0xec00,0xf000,0xf400,0xf800,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff, + 0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff,0xfbff}; + static AB1 shift[512]={ + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x17,0x16,0x15,0x14,0x13,0x12,0x11,0x10,0x0f, + 0x0e,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d, + 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x17,0x16,0x15,0x14,0x13,0x12,0x11,0x10,0x0f, + 0x0e,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d, + 0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x0d,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18, + 0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18}; + union{AF1 f;AU1 u;}bits;bits.f=f;AU1 u=bits.u;AU1 i=u>>23;return (AU1)(base[i])+((u&0x7fffff)>>shift[i]);} +//------------------------------------------------------------------------------------------------------------------------------ + // Used to output packed constant. + A_STATIC AU1 AU1_AH2_AF2(inAF2 a){return AU1_AH1_AF1(a[0])+(AU1_AH1_AF1(a[1])<<16);} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// GLSL +// +// +//============================================================================================================================== +#if defined(A_GLSL) && defined(A_GPU) + #ifndef A_SKIP_EXT + #ifdef A_HALF + #extension GL_EXT_shader_16bit_storage:require + #extension GL_EXT_shader_explicit_arithmetic_types:require + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_LONG + #extension GL_ARB_gpu_shader_int64:require + #extension GL_NV_shader_atomic_int64:require + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_WAVE + #extension GL_KHR_shader_subgroup_arithmetic:require + #extension GL_KHR_shader_subgroup_ballot:require + #extension GL_KHR_shader_subgroup_quad:require + #extension GL_KHR_shader_subgroup_shuffle:require + #endif + #endif +//============================================================================================================================== + #define AP1 bool + #define AP2 bvec2 + #define AP3 bvec3 + #define AP4 bvec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF1 float + #define AF2 vec2 + #define AF3 vec3 + #define AF4 vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1 uint + #define AU2 uvec2 + #define AU3 uvec3 + #define AU4 uvec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASU1 int + #define ASU2 ivec2 + #define ASU3 ivec3 + #define ASU4 ivec4 +//============================================================================================================================== + #define AF1_AU1(x) uintBitsToFloat(AU1(x)) + #define AF2_AU2(x) uintBitsToFloat(AU2(x)) + #define AF3_AU3(x) uintBitsToFloat(AU3(x)) + #define AF4_AU4(x) uintBitsToFloat(AU4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AF1(x) floatBitsToUint(AF1(x)) + #define AU2_AF2(x) floatBitsToUint(AF2(x)) + #define AU3_AF3(x) floatBitsToUint(AF3(x)) + #define AU4_AF4(x) floatBitsToUint(AF4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH1_AF1_x(AF1 a){return packHalf2x16(AF2(a,0.0));} + #define AU1_AH1_AF1(a) AU1_AH1_AF1_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AH2_AF2 packHalf2x16 + #define AU1_AW2Unorm_AF2 packUnorm2x16 + #define AU1_AB4Unorm_AF4 packUnorm4x8 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF2_AH2_AU1 unpackHalf2x16 + #define AF2_AW2Unorm_AU1 unpackUnorm2x16 + #define AF4_AB4Unorm_AU1 unpackUnorm4x8 +//============================================================================================================================== + AF1 AF1_x(AF1 a){return AF1(a);} + AF2 AF2_x(AF1 a){return AF2(a,a);} + AF3 AF3_x(AF1 a){return AF3(a,a,a);} + AF4 AF4_x(AF1 a){return AF4(a,a,a,a);} + #define AF1_(a) AF1_x(AF1(a)) + #define AF2_(a) AF2_x(AF1(a)) + #define AF3_(a) AF3_x(AF1(a)) + #define AF4_(a) AF4_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_x(AU1 a){return AU1(a);} + AU2 AU2_x(AU1 a){return AU2(a,a);} + AU3 AU3_x(AU1 a){return AU3(a,a,a);} + AU4 AU4_x(AU1 a){return AU4(a,a,a,a);} + #define AU1_(a) AU1_x(AU1(a)) + #define AU2_(a) AU2_x(AU1(a)) + #define AU3_(a) AU3_x(AU1(a)) + #define AU4_(a) AU4_x(AU1(a)) +//============================================================================================================================== + AU1 AAbsSU1(AU1 a){return AU1(abs(ASU1(a)));} + AU2 AAbsSU2(AU2 a){return AU2(abs(ASU2(a)));} + AU3 AAbsSU3(AU3 a){return AU3(abs(ASU3(a)));} + AU4 AAbsSU4(AU4 a){return AU4(abs(ASU4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 ABfe(AU1 src,AU1 off,AU1 bits){return bitfieldExtract(src,ASU1(off),ASU1(bits));} + AU1 ABfi(AU1 src,AU1 ins,AU1 mask){return (ins&mask)|(src&(~mask));} + // Proxy for V_BFI_B32 where the 'mask' is set as 'bits', 'mask=(1<>ASU1(b));} + AU2 AShrSU2(AU2 a,AU2 b){return AU2(ASU2(a)>>ASU2(b));} + AU3 AShrSU3(AU3 a,AU3 b){return AU3(ASU3(a)>>ASU3(b));} + AU4 AShrSU4(AU4 a,AU4 b){return AU4(ASU4(a)>>ASU4(b));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL BYTE +//============================================================================================================================== + #ifdef A_BYTE + #define AB1 uint8_t + #define AB2 u8vec2 + #define AB3 u8vec3 + #define AB4 u8vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASB1 int8_t + #define ASB2 i8vec2 + #define ASB3 i8vec3 + #define ASB4 i8vec4 +//------------------------------------------------------------------------------------------------------------------------------ + AB1 AB1_x(AB1 a){return AB1(a);} + AB2 AB2_x(AB1 a){return AB2(a,a);} + AB3 AB3_x(AB1 a){return AB3(a,a,a);} + AB4 AB4_x(AB1 a){return AB4(a,a,a,a);} + #define AB1_(a) AB1_x(AB1(a)) + #define AB2_(a) AB2_x(AB1(a)) + #define AB3_(a) AB3_x(AB1(a)) + #define AB4_(a) AB4_x(AB1(a)) + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL HALF +//============================================================================================================================== + #ifdef A_HALF + #define AH1 float16_t + #define AH2 f16vec2 + #define AH3 f16vec3 + #define AH4 f16vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AW1 uint16_t + #define AW2 u16vec2 + #define AW3 u16vec3 + #define AW4 u16vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASW1 int16_t + #define ASW2 i16vec2 + #define ASW3 i16vec3 + #define ASW4 i16vec4 +//============================================================================================================================== + #define AH2_AU1(x) unpackFloat2x16(AU1(x)) + AH4 AH4_AU2_x(AU2 x){return AH4(unpackFloat2x16(x.x),unpackFloat2x16(x.y));} + #define AH4_AU2(x) AH4_AU2_x(AU2(x)) + #define AW2_AU1(x) unpackUint2x16(AU1(x)) + #define AW4_AU2(x) unpackUint4x16(pack64(AU2(x))) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AH2(x) packFloat2x16(AH2(x)) + AU2 AU2_AH4_x(AH4 x){return AU2(packFloat2x16(x.xy),packFloat2x16(x.zw));} + #define AU2_AH4(x) AU2_AH4_x(AH4(x)) + #define AU1_AW2(x) packUint2x16(AW2(x)) + #define AU2_AW4(x) unpack32(packUint4x16(AW4(x))) +//============================================================================================================================== + #define AW1_AH1(x) halfBitsToUint16(AH1(x)) + #define AW2_AH2(x) halfBitsToUint16(AH2(x)) + #define AW3_AH3(x) halfBitsToUint16(AH3(x)) + #define AW4_AH4(x) halfBitsToUint16(AH4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AH1_AW1(x) uint16BitsToHalf(AW1(x)) + #define AH2_AW2(x) uint16BitsToHalf(AW2(x)) + #define AH3_AW3(x) uint16BitsToHalf(AW3(x)) + #define AH4_AW4(x) uint16BitsToHalf(AW4(x)) +//============================================================================================================================== + AH1 AH1_x(AH1 a){return AH1(a);} + AH2 AH2_x(AH1 a){return AH2(a,a);} + AH3 AH3_x(AH1 a){return AH3(a,a,a);} + AH4 AH4_x(AH1 a){return AH4(a,a,a,a);} + #define AH1_(a) AH1_x(AH1(a)) + #define AH2_(a) AH2_x(AH1(a)) + #define AH3_(a) AH3_x(AH1(a)) + #define AH4_(a) AH4_x(AH1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AW1_x(AW1 a){return AW1(a);} + AW2 AW2_x(AW1 a){return AW2(a,a);} + AW3 AW3_x(AW1 a){return AW3(a,a,a);} + AW4 AW4_x(AW1 a){return AW4(a,a,a,a);} + #define AW1_(a) AW1_x(AW1(a)) + #define AW2_(a) AW2_x(AW1(a)) + #define AW3_(a) AW3_x(AW1(a)) + #define AW4_(a) AW4_x(AW1(a)) +//============================================================================================================================== + AW1 AAbsSW1(AW1 a){return AW1(abs(ASW1(a)));} + AW2 AAbsSW2(AW2 a){return AW2(abs(ASW2(a)));} + AW3 AAbsSW3(AW3 a){return AW3(abs(ASW3(a)));} + AW4 AAbsSW4(AW4 a){return AW4(abs(ASW4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AClampH1(AH1 x,AH1 n,AH1 m){return clamp(x,n,m);} + AH2 AClampH2(AH2 x,AH2 n,AH2 m){return clamp(x,n,m);} + AH3 AClampH3(AH3 x,AH3 n,AH3 m){return clamp(x,n,m);} + AH4 AClampH4(AH4 x,AH4 n,AH4 m){return clamp(x,n,m);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFractH1(AH1 x){return fract(x);} + AH2 AFractH2(AH2 x){return fract(x);} + AH3 AFractH3(AH3 x){return fract(x);} + AH4 AFractH4(AH4 x){return fract(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ALerpH1(AH1 x,AH1 y,AH1 a){return mix(x,y,a);} + AH2 ALerpH2(AH2 x,AH2 y,AH2 a){return mix(x,y,a);} + AH3 ALerpH3(AH3 x,AH3 y,AH3 a){return mix(x,y,a);} + AH4 ALerpH4(AH4 x,AH4 y,AH4 a){return mix(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + // No packed version of max3. + AH1 AMax3H1(AH1 x,AH1 y,AH1 z){return max(x,max(y,z));} + AH2 AMax3H2(AH2 x,AH2 y,AH2 z){return max(x,max(y,z));} + AH3 AMax3H3(AH3 x,AH3 y,AH3 z){return max(x,max(y,z));} + AH4 AMax3H4(AH4 x,AH4 y,AH4 z){return max(x,max(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMaxSW1(AW1 a,AW1 b){return AW1(max(ASU1(a),ASU1(b)));} + AW2 AMaxSW2(AW2 a,AW2 b){return AW2(max(ASU2(a),ASU2(b)));} + AW3 AMaxSW3(AW3 a,AW3 b){return AW3(max(ASU3(a),ASU3(b)));} + AW4 AMaxSW4(AW4 a,AW4 b){return AW4(max(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + // No packed version of min3. + AH1 AMin3H1(AH1 x,AH1 y,AH1 z){return min(x,min(y,z));} + AH2 AMin3H2(AH2 x,AH2 y,AH2 z){return min(x,min(y,z));} + AH3 AMin3H3(AH3 x,AH3 y,AH3 z){return min(x,min(y,z));} + AH4 AMin3H4(AH4 x,AH4 y,AH4 z){return min(x,min(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMinSW1(AW1 a,AW1 b){return AW1(min(ASU1(a),ASU1(b)));} + AW2 AMinSW2(AW2 a,AW2 b){return AW2(min(ASU2(a),ASU2(b)));} + AW3 AMinSW3(AW3 a,AW3 b){return AW3(min(ASU3(a),ASU3(b)));} + AW4 AMinSW4(AW4 a,AW4 b){return AW4(min(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARcpH1(AH1 x){return AH1_(1.0)/x;} + AH2 ARcpH2(AH2 x){return AH2_(1.0)/x;} + AH3 ARcpH3(AH3 x){return AH3_(1.0)/x;} + AH4 ARcpH4(AH4 x){return AH4_(1.0)/x;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARsqH1(AH1 x){return AH1_(1.0)/sqrt(x);} + AH2 ARsqH2(AH2 x){return AH2_(1.0)/sqrt(x);} + AH3 ARsqH3(AH3 x){return AH3_(1.0)/sqrt(x);} + AH4 ARsqH4(AH4 x){return AH4_(1.0)/sqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASatH1(AH1 x){return clamp(x,AH1_(0.0),AH1_(1.0));} + AH2 ASatH2(AH2 x){return clamp(x,AH2_(0.0),AH2_(1.0));} + AH3 ASatH3(AH3 x){return clamp(x,AH3_(0.0),AH3_(1.0));} + AH4 ASatH4(AH4 x){return clamp(x,AH4_(0.0),AH4_(1.0));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AShrSW1(AW1 a,AW1 b){return AW1(ASW1(a)>>ASW1(b));} + AW2 AShrSW2(AW2 a,AW2 b){return AW2(ASW2(a)>>ASW2(b));} + AW3 AShrSW3(AW3 a,AW3 b){return AW3(ASW3(a)>>ASW3(b));} + AW4 AShrSW4(AW4 a,AW4 b){return AW4(ASW4(a)>>ASW4(b));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL DOUBLE +//============================================================================================================================== + #ifdef A_DUBL + #define AD1 double + #define AD2 dvec2 + #define AD3 dvec3 + #define AD4 dvec4 +//------------------------------------------------------------------------------------------------------------------------------ + AD1 AD1_x(AD1 a){return AD1(a);} + AD2 AD2_x(AD1 a){return AD2(a,a);} + AD3 AD3_x(AD1 a){return AD3(a,a,a);} + AD4 AD4_x(AD1 a){return AD4(a,a,a,a);} + #define AD1_(a) AD1_x(AD1(a)) + #define AD2_(a) AD2_x(AD1(a)) + #define AD3_(a) AD3_x(AD1(a)) + #define AD4_(a) AD4_x(AD1(a)) +//============================================================================================================================== + AD1 AFractD1(AD1 x){return fract(x);} + AD2 AFractD2(AD2 x){return fract(x);} + AD3 AFractD3(AD3 x){return fract(x);} + AD4 AFractD4(AD4 x){return fract(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ALerpD1(AD1 x,AD1 y,AD1 a){return mix(x,y,a);} + AD2 ALerpD2(AD2 x,AD2 y,AD2 a){return mix(x,y,a);} + AD3 ALerpD3(AD3 x,AD3 y,AD3 a){return mix(x,y,a);} + AD4 ALerpD4(AD4 x,AD4 y,AD4 a){return mix(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARcpD1(AD1 x){return AD1_(1.0)/x;} + AD2 ARcpD2(AD2 x){return AD2_(1.0)/x;} + AD3 ARcpD3(AD3 x){return AD3_(1.0)/x;} + AD4 ARcpD4(AD4 x){return AD4_(1.0)/x;} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARsqD1(AD1 x){return AD1_(1.0)/sqrt(x);} + AD2 ARsqD2(AD2 x){return AD2_(1.0)/sqrt(x);} + AD3 ARsqD3(AD3 x){return AD3_(1.0)/sqrt(x);} + AD4 ARsqD4(AD4 x){return AD4_(1.0)/sqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ASatD1(AD1 x){return clamp(x,AD1_(0.0),AD1_(1.0));} + AD2 ASatD2(AD2 x){return clamp(x,AD2_(0.0),AD2_(1.0));} + AD3 ASatD3(AD3 x){return clamp(x,AD3_(0.0),AD3_(1.0));} + AD4 ASatD4(AD4 x){return clamp(x,AD4_(0.0),AD4_(1.0));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// GLSL LONG +//============================================================================================================================== + #ifdef A_LONG + #define AL1 uint64_t + #define AL2 u64vec2 + #define AL3 u64vec3 + #define AL4 u64vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASL1 int64_t + #define ASL2 i64vec2 + #define ASL3 i64vec3 + #define ASL4 i64vec4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AL1_AU2(x) packUint2x32(AU2(x)) + #define AU2_AL1(x) unpackUint2x32(AL1(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AL1 AL1_x(AL1 a){return AL1(a);} + AL2 AL2_x(AL1 a){return AL2(a,a);} + AL3 AL3_x(AL1 a){return AL3(a,a,a);} + AL4 AL4_x(AL1 a){return AL4(a,a,a,a);} + #define AL1_(a) AL1_x(AL1(a)) + #define AL2_(a) AL2_x(AL1(a)) + #define AL3_(a) AL3_x(AL1(a)) + #define AL4_(a) AL4_x(AL1(a)) +//============================================================================================================================== + AL1 AAbsSL1(AL1 a){return AL1(abs(ASL1(a)));} + AL2 AAbsSL2(AL2 a){return AL2(abs(ASL2(a)));} + AL3 AAbsSL3(AL3 a){return AL3(abs(ASL3(a)));} + AL4 AAbsSL4(AL4 a){return AL4(abs(ASL4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AL1 AMaxSL1(AL1 a,AL1 b){return AL1(max(ASU1(a),ASU1(b)));} + AL2 AMaxSL2(AL2 a,AL2 b){return AL2(max(ASU2(a),ASU2(b)));} + AL3 AMaxSL3(AL3 a,AL3 b){return AL3(max(ASU3(a),ASU3(b)));} + AL4 AMaxSL4(AL4 a,AL4 b){return AL4(max(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AL1 AMinSL1(AL1 a,AL1 b){return AL1(min(ASU1(a),ASU1(b)));} + AL2 AMinSL2(AL2 a,AL2 b){return AL2(min(ASU2(a),ASU2(b)));} + AL3 AMinSL3(AL3 a,AL3 b){return AL3(min(ASU3(a),ASU3(b)));} + AL4 AMinSL4(AL4 a,AL4 b){return AL4(min(ASU4(a),ASU4(b)));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// WAVE OPERATIONS +//============================================================================================================================== + #ifdef A_WAVE + // Where 'x' must be a compile time literal. + AF1 AWaveXorF1(AF1 v,AU1 x){return subgroupShuffleXor(v,x);} + AF2 AWaveXorF2(AF2 v,AU1 x){return subgroupShuffleXor(v,x);} + AF3 AWaveXorF3(AF3 v,AU1 x){return subgroupShuffleXor(v,x);} + AF4 AWaveXorF4(AF4 v,AU1 x){return subgroupShuffleXor(v,x);} + AU1 AWaveXorU1(AU1 v,AU1 x){return subgroupShuffleXor(v,x);} + AU2 AWaveXorU2(AU2 v,AU1 x){return subgroupShuffleXor(v,x);} + AU3 AWaveXorU3(AU3 v,AU1 x){return subgroupShuffleXor(v,x);} + AU4 AWaveXorU4(AU4 v,AU1 x){return subgroupShuffleXor(v,x);} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + AH2 AWaveXorH2(AH2 v,AU1 x){return AH2_AU1(subgroupShuffleXor(AU1_AH2(v),x));} + AH4 AWaveXorH4(AH4 v,AU1 x){return AH4_AU2(subgroupShuffleXor(AU2_AH4(v),x));} + AW2 AWaveXorW2(AW2 v,AU1 x){return AW2_AU1(subgroupShuffleXor(AU1_AW2(v),x));} + AW4 AWaveXorW4(AW4 v,AU1 x){return AW4_AU2(subgroupShuffleXor(AU2_AW4(v),x));} + #endif + #endif +//============================================================================================================================== +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// HLSL +// +// +//============================================================================================================================== +#if defined(A_HLSL) && defined(A_GPU) + #ifdef A_HLSL_6_2 + #define AP1 bool + #define AP2 bool2 + #define AP3 bool3 + #define AP4 bool4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF1 float32_t + #define AF2 float32_t2 + #define AF3 float32_t3 + #define AF4 float32_t4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1 uint32_t + #define AU2 uint32_t2 + #define AU3 uint32_t3 + #define AU4 uint32_t4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASU1 int32_t + #define ASU2 int32_t2 + #define ASU3 int32_t3 + #define ASU4 int32_t4 + #else + #define AP1 bool + #define AP2 bool2 + #define AP3 bool3 + #define AP4 bool4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AF1 float + #define AF2 float2 + #define AF3 float3 + #define AF4 float4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1 uint + #define AU2 uint2 + #define AU3 uint3 + #define AU4 uint4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASU1 int + #define ASU2 int2 + #define ASU3 int3 + #define ASU4 int4 + #endif +//============================================================================================================================== + #define AF1_AU1(x) asfloat(AU1(x)) + #define AF2_AU2(x) asfloat(AU2(x)) + #define AF3_AU3(x) asfloat(AU3(x)) + #define AF4_AU4(x) asfloat(AU4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AU1_AF1(x) asuint(AF1(x)) + #define AU2_AF2(x) asuint(AF2(x)) + #define AU3_AF3(x) asuint(AF3(x)) + #define AU4_AF4(x) asuint(AF4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH1_AF1_x(AF1 a){return f32tof16(a);} + #define AU1_AH1_AF1(a) AU1_AH1_AF1_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH2_AF2_x(AF2 a){return f32tof16(a.x)|(f32tof16(a.y)<<16);} + #define AU1_AH2_AF2(a) AU1_AH2_AF2_x(AF2(a)) + #define AU1_AB4Unorm_AF4(x) D3DCOLORtoUBYTE4(AF4(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AF2 AF2_AH2_AU1_x(AU1 x){return AF2(f16tof32(x&0xFFFF),f16tof32(x>>16));} + #define AF2_AH2_AU1(x) AF2_AH2_AU1_x(AU1(x)) +//============================================================================================================================== + AF1 AF1_x(AF1 a){return AF1(a);} + AF2 AF2_x(AF1 a){return AF2(a,a);} + AF3 AF3_x(AF1 a){return AF3(a,a,a);} + AF4 AF4_x(AF1 a){return AF4(a,a,a,a);} + #define AF1_(a) AF1_x(AF1(a)) + #define AF2_(a) AF2_x(AF1(a)) + #define AF3_(a) AF3_x(AF1(a)) + #define AF4_(a) AF4_x(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_x(AU1 a){return AU1(a);} + AU2 AU2_x(AU1 a){return AU2(a,a);} + AU3 AU3_x(AU1 a){return AU3(a,a,a);} + AU4 AU4_x(AU1 a){return AU4(a,a,a,a);} + #define AU1_(a) AU1_x(AU1(a)) + #define AU2_(a) AU2_x(AU1(a)) + #define AU3_(a) AU3_x(AU1(a)) + #define AU4_(a) AU4_x(AU1(a)) +//============================================================================================================================== + AU1 AAbsSU1(AU1 a){return AU1(abs(ASU1(a)));} + AU2 AAbsSU2(AU2 a){return AU2(abs(ASU2(a)));} + AU3 AAbsSU3(AU3 a){return AU3(abs(ASU3(a)));} + AU4 AAbsSU4(AU4 a){return AU4(abs(ASU4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 ABfe(AU1 src,AU1 off,AU1 bits){AU1 mask=(1u<>off)&mask;} + AU1 ABfi(AU1 src,AU1 ins,AU1 mask){return (ins&mask)|(src&(~mask));} + AU1 ABfiM(AU1 src,AU1 ins,AU1 bits){AU1 mask=(1u<>ASU1(b));} + AU2 AShrSU2(AU2 a,AU2 b){return AU2(ASU2(a)>>ASU2(b));} + AU3 AShrSU3(AU3 a,AU3 b){return AU3(ASU3(a)>>ASU3(b));} + AU4 AShrSU4(AU4 a,AU4 b){return AU4(ASU4(a)>>ASU4(b));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HLSL BYTE +//============================================================================================================================== + #ifdef A_BYTE + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HLSL HALF +//============================================================================================================================== + #ifdef A_HALF + #ifdef A_HLSL_6_2 + #define AH1 float16_t + #define AH2 float16_t2 + #define AH3 float16_t3 + #define AH4 float16_t4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AW1 uint16_t + #define AW2 uint16_t2 + #define AW3 uint16_t3 + #define AW4 uint16_t4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASW1 int16_t + #define ASW2 int16_t2 + #define ASW3 int16_t3 + #define ASW4 int16_t4 + #else + #define AH1 min16float + #define AH2 min16float2 + #define AH3 min16float3 + #define AH4 min16float4 +//------------------------------------------------------------------------------------------------------------------------------ + #define AW1 min16uint + #define AW2 min16uint2 + #define AW3 min16uint3 + #define AW4 min16uint4 +//------------------------------------------------------------------------------------------------------------------------------ + #define ASW1 min16int + #define ASW2 min16int2 + #define ASW3 min16int3 + #define ASW4 min16int4 + #endif +//============================================================================================================================== + // Need to use manual unpack to get optimal execution (don't use packed types in buffers directly). + // Unpack requires this pattern: https://gpuopen.com/first-steps-implementing-fp16/ + AH2 AH2_AU1_x(AU1 x){AF2 t=f16tof32(AU2(x&0xFFFF,x>>16));return AH2(t);} + AH4 AH4_AU2_x(AU2 x){return AH4(AH2_AU1_x(x.x),AH2_AU1_x(x.y));} + AW2 AW2_AU1_x(AU1 x){AU2 t=AU2(x&0xFFFF,x>>16);return AW2(t);} + AW4 AW4_AU2_x(AU2 x){return AW4(AW2_AU1_x(x.x),AW2_AU1_x(x.y));} + #define AH2_AU1(x) AH2_AU1_x(AU1(x)) + #define AH4_AU2(x) AH4_AU2_x(AU2(x)) + #define AW2_AU1(x) AW2_AU1_x(AU1(x)) + #define AW4_AU2(x) AW4_AU2_x(AU2(x)) +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AU1_AH2_x(AH2 x){return f32tof16(x.x)+(f32tof16(x.y)<<16);} + AU2 AU2_AH4_x(AH4 x){return AU2(AU1_AH2_x(x.xy),AU1_AH2_x(x.zw));} + AU1 AU1_AW2_x(AW2 x){return AU1(x.x)+(AU1(x.y)<<16);} + AU2 AU2_AW4_x(AW4 x){return AU2(AU1_AW2_x(x.xy),AU1_AW2_x(x.zw));} + #define AU1_AH2(x) AU1_AH2_x(AH2(x)) + #define AU2_AH4(x) AU2_AH4_x(AH4(x)) + #define AU1_AW2(x) AU1_AW2_x(AW2(x)) + #define AU2_AW4(x) AU2_AW4_x(AW4(x)) +//============================================================================================================================== + #if defined(A_HLSL_6_2) && !defined(A_NO_16_BIT_CAST) + #define AW1_AH1(x) asuint16(x) + #define AW2_AH2(x) asuint16(x) + #define AW3_AH3(x) asuint16(x) + #define AW4_AH4(x) asuint16(x) + #else + #define AW1_AH1(a) AW1(f32tof16(AF1(a))) + #define AW2_AH2(a) AW2(AW1_AH1((a).x),AW1_AH1((a).y)) + #define AW3_AH3(a) AW3(AW1_AH1((a).x),AW1_AH1((a).y),AW1_AH1((a).z)) + #define AW4_AH4(a) AW4(AW1_AH1((a).x),AW1_AH1((a).y),AW1_AH1((a).z),AW1_AH1((a).w)) + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #if defined(A_HLSL_6_2) && !defined(A_NO_16_BIT_CAST) + #define AH1_AW1(x) asfloat16(x) + #define AH2_AW2(x) asfloat16(x) + #define AH3_AW3(x) asfloat16(x) + #define AH4_AW4(x) asfloat16(x) + #else + #define AH1_AW1(a) AH1(f16tof32(AU1(a))) + #define AH2_AW2(a) AH2(AH1_AW1((a).x),AH1_AW1((a).y)) + #define AH3_AW3(a) AH3(AH1_AW1((a).x),AH1_AW1((a).y),AH1_AW1((a).z)) + #define AH4_AW4(a) AH4(AH1_AW1((a).x),AH1_AW1((a).y),AH1_AW1((a).z),AH1_AW1((a).w)) + #endif +//============================================================================================================================== + AH1 AH1_x(AH1 a){return AH1(a);} + AH2 AH2_x(AH1 a){return AH2(a,a);} + AH3 AH3_x(AH1 a){return AH3(a,a,a);} + AH4 AH4_x(AH1 a){return AH4(a,a,a,a);} + #define AH1_(a) AH1_x(AH1(a)) + #define AH2_(a) AH2_x(AH1(a)) + #define AH3_(a) AH3_x(AH1(a)) + #define AH4_(a) AH4_x(AH1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AW1_x(AW1 a){return AW1(a);} + AW2 AW2_x(AW1 a){return AW2(a,a);} + AW3 AW3_x(AW1 a){return AW3(a,a,a);} + AW4 AW4_x(AW1 a){return AW4(a,a,a,a);} + #define AW1_(a) AW1_x(AW1(a)) + #define AW2_(a) AW2_x(AW1(a)) + #define AW3_(a) AW3_x(AW1(a)) + #define AW4_(a) AW4_x(AW1(a)) +//============================================================================================================================== + AW1 AAbsSW1(AW1 a){return AW1(abs(ASW1(a)));} + AW2 AAbsSW2(AW2 a){return AW2(abs(ASW2(a)));} + AW3 AAbsSW3(AW3 a){return AW3(abs(ASW3(a)));} + AW4 AAbsSW4(AW4 a){return AW4(abs(ASW4(a)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AClampH1(AH1 x,AH1 n,AH1 m){return max(n,min(x,m));} + AH2 AClampH2(AH2 x,AH2 n,AH2 m){return max(n,min(x,m));} + AH3 AClampH3(AH3 x,AH3 n,AH3 m){return max(n,min(x,m));} + AH4 AClampH4(AH4 x,AH4 n,AH4 m){return max(n,min(x,m));} +//------------------------------------------------------------------------------------------------------------------------------ + // V_FRACT_F16 (note DX frac() is different). + AH1 AFractH1(AH1 x){return x-floor(x);} + AH2 AFractH2(AH2 x){return x-floor(x);} + AH3 AFractH3(AH3 x){return x-floor(x);} + AH4 AFractH4(AH4 x){return x-floor(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ALerpH1(AH1 x,AH1 y,AH1 a){return lerp(x,y,a);} + AH2 ALerpH2(AH2 x,AH2 y,AH2 a){return lerp(x,y,a);} + AH3 ALerpH3(AH3 x,AH3 y,AH3 a){return lerp(x,y,a);} + AH4 ALerpH4(AH4 x,AH4 y,AH4 a){return lerp(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AMax3H1(AH1 x,AH1 y,AH1 z){return max(x,max(y,z));} + AH2 AMax3H2(AH2 x,AH2 y,AH2 z){return max(x,max(y,z));} + AH3 AMax3H3(AH3 x,AH3 y,AH3 z){return max(x,max(y,z));} + AH4 AMax3H4(AH4 x,AH4 y,AH4 z){return max(x,max(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMaxSW1(AW1 a,AW1 b){return AW1(max(ASU1(a),ASU1(b)));} + AW2 AMaxSW2(AW2 a,AW2 b){return AW2(max(ASU2(a),ASU2(b)));} + AW3 AMaxSW3(AW3 a,AW3 b){return AW3(max(ASU3(a),ASU3(b)));} + AW4 AMaxSW4(AW4 a,AW4 b){return AW4(max(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AMin3H1(AH1 x,AH1 y,AH1 z){return min(x,min(y,z));} + AH2 AMin3H2(AH2 x,AH2 y,AH2 z){return min(x,min(y,z));} + AH3 AMin3H3(AH3 x,AH3 y,AH3 z){return min(x,min(y,z));} + AH4 AMin3H4(AH4 x,AH4 y,AH4 z){return min(x,min(y,z));} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AMinSW1(AW1 a,AW1 b){return AW1(min(ASU1(a),ASU1(b)));} + AW2 AMinSW2(AW2 a,AW2 b){return AW2(min(ASU2(a),ASU2(b)));} + AW3 AMinSW3(AW3 a,AW3 b){return AW3(min(ASU3(a),ASU3(b)));} + AW4 AMinSW4(AW4 a,AW4 b){return AW4(min(ASU4(a),ASU4(b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARcpH1(AH1 x){return rcp(x);} + AH2 ARcpH2(AH2 x){return rcp(x);} + AH3 ARcpH3(AH3 x){return rcp(x);} + AH4 ARcpH4(AH4 x){return rcp(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ARsqH1(AH1 x){return rsqrt(x);} + AH2 ARsqH2(AH2 x){return rsqrt(x);} + AH3 ARsqH3(AH3 x){return rsqrt(x);} + AH4 ARsqH4(AH4 x){return rsqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASatH1(AH1 x){return saturate(x);} + AH2 ASatH2(AH2 x){return saturate(x);} + AH3 ASatH3(AH3 x){return saturate(x);} + AH4 ASatH4(AH4 x){return saturate(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AShrSW1(AW1 a,AW1 b){return AW1(ASW1(a)>>ASW1(b));} + AW2 AShrSW2(AW2 a,AW2 b){return AW2(ASW2(a)>>ASW2(b));} + AW3 AShrSW3(AW3 a,AW3 b){return AW3(ASW3(a)>>ASW3(b));} + AW4 AShrSW4(AW4 a,AW4 b){return AW4(ASW4(a)>>ASW4(b));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HLSL DOUBLE +//============================================================================================================================== + #ifdef A_DUBL + #ifdef A_HLSL_6_2 + #define AD1 float64_t + #define AD2 float64_t2 + #define AD3 float64_t3 + #define AD4 float64_t4 + #else + #define AD1 double + #define AD2 double2 + #define AD3 double3 + #define AD4 double4 + #endif +//------------------------------------------------------------------------------------------------------------------------------ + AD1 AD1_x(AD1 a){return AD1(a);} + AD2 AD2_x(AD1 a){return AD2(a,a);} + AD3 AD3_x(AD1 a){return AD3(a,a,a);} + AD4 AD4_x(AD1 a){return AD4(a,a,a,a);} + #define AD1_(a) AD1_x(AD1(a)) + #define AD2_(a) AD2_x(AD1(a)) + #define AD3_(a) AD3_x(AD1(a)) + #define AD4_(a) AD4_x(AD1(a)) +//============================================================================================================================== + AD1 AFractD1(AD1 a){return a-floor(a);} + AD2 AFractD2(AD2 a){return a-floor(a);} + AD3 AFractD3(AD3 a){return a-floor(a);} + AD4 AFractD4(AD4 a){return a-floor(a);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ALerpD1(AD1 x,AD1 y,AD1 a){return lerp(x,y,a);} + AD2 ALerpD2(AD2 x,AD2 y,AD2 a){return lerp(x,y,a);} + AD3 ALerpD3(AD3 x,AD3 y,AD3 a){return lerp(x,y,a);} + AD4 ALerpD4(AD4 x,AD4 y,AD4 a){return lerp(x,y,a);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARcpD1(AD1 x){return rcp(x);} + AD2 ARcpD2(AD2 x){return rcp(x);} + AD3 ARcpD3(AD3 x){return rcp(x);} + AD4 ARcpD4(AD4 x){return rcp(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ARsqD1(AD1 x){return rsqrt(x);} + AD2 ARsqD2(AD2 x){return rsqrt(x);} + AD3 ARsqD3(AD3 x){return rsqrt(x);} + AD4 ARsqD4(AD4 x){return rsqrt(x);} +//------------------------------------------------------------------------------------------------------------------------------ + AD1 ASatD1(AD1 x){return saturate(x);} + AD2 ASatD2(AD2 x){return saturate(x);} + AD3 ASatD3(AD3 x){return saturate(x);} + AD4 ASatD4(AD4 x){return saturate(x);} + #endif +//============================================================================================================================== +// HLSL WAVE +//============================================================================================================================== + #ifdef A_WAVE + // Where 'x' must be a compile time literal. + AF1 AWaveXorF1(AF1 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AF2 AWaveXorF2(AF2 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AF3 AWaveXorF3(AF3 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AF4 AWaveXorF4(AF4 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AU1 AWaveXorU1(AU1 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AU2 AWaveXorU1(AU2 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AU3 AWaveXorU1(AU3 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} + AU4 AWaveXorU1(AU4 v,AU1 x){return WaveReadLaneAt(v,WaveGetLaneIndex()^x);} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + AH2 AWaveXorH2(AH2 v,AU1 x){return AH2_AU1(WaveReadLaneAt(AU1_AH2(v),WaveGetLaneIndex()^x));} + AH4 AWaveXorH4(AH4 v,AU1 x){return AH4_AU2(WaveReadLaneAt(AU2_AH4(v),WaveGetLaneIndex()^x));} + AW2 AWaveXorW2(AW2 v,AU1 x){return AW2_AU1(WaveReadLaneAt(AU1_AW2(v),WaveGetLaneIndex()^x));} + AW4 AWaveXorW4(AW4 v,AU1 x){return AW4_AU1(WaveReadLaneAt(AU1_AW4(v),WaveGetLaneIndex()^x));} + #endif + #endif +//============================================================================================================================== +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// GPU COMMON +// +// +//============================================================================================================================== +#ifdef A_GPU + // Negative and positive infinity. + #define A_INFP_F AF1_AU1(0x7f800000u) + #define A_INFN_F AF1_AU1(0xff800000u) +//------------------------------------------------------------------------------------------------------------------------------ + // Copy sign from 's' to positive 'd'. + AF1 ACpySgnF1(AF1 d,AF1 s){return AF1_AU1(AU1_AF1(d)|(AU1_AF1(s)&AU1_(0x80000000u)));} + AF2 ACpySgnF2(AF2 d,AF2 s){return AF2_AU2(AU2_AF2(d)|(AU2_AF2(s)&AU2_(0x80000000u)));} + AF3 ACpySgnF3(AF3 d,AF3 s){return AF3_AU3(AU3_AF3(d)|(AU3_AF3(s)&AU3_(0x80000000u)));} + AF4 ACpySgnF4(AF4 d,AF4 s){return AF4_AU4(AU4_AF4(d)|(AU4_AF4(s)&AU4_(0x80000000u)));} +//------------------------------------------------------------------------------------------------------------------------------ + // Single operation to return (useful to create a mask to use in lerp for branch free logic), + // m=NaN := 0 + // m>=0 := 0 + // m<0 := 1 + // Uses the following useful floating point logic, + // saturate(+a*(-INF)==-INF) := 0 + // saturate( 0*(-INF)== NaN) := 0 + // saturate(-a*(-INF)==+INF) := 1 + AF1 ASignedF1(AF1 m){return ASatF1(m*AF1_(A_INFN_F));} + AF2 ASignedF2(AF2 m){return ASatF2(m*AF2_(A_INFN_F));} + AF3 ASignedF3(AF3 m){return ASatF3(m*AF3_(A_INFN_F));} + AF4 ASignedF4(AF4 m){return ASatF4(m*AF4_(A_INFN_F));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AGtZeroF1(AF1 m){return ASatF1(m*AF1_(A_INFP_F));} + AF2 AGtZeroF2(AF2 m){return ASatF2(m*AF2_(A_INFP_F));} + AF3 AGtZeroF3(AF3 m){return ASatF3(m*AF3_(A_INFP_F));} + AF4 AGtZeroF4(AF4 m){return ASatF4(m*AF4_(A_INFP_F));} +//============================================================================================================================== + #ifdef A_HALF + #ifdef A_HLSL_6_2 + #define A_INFP_H AH1_AW1((uint16_t)0x7c00u) + #define A_INFN_H AH1_AW1((uint16_t)0xfc00u) + #else + #define A_INFP_H AH1_AW1(0x7c00u) + #define A_INFN_H AH1_AW1(0xfc00u) + #endif + +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ACpySgnH1(AH1 d,AH1 s){return AH1_AW1(AW1_AH1(d)|(AW1_AH1(s)&AW1_(0x8000u)));} + AH2 ACpySgnH2(AH2 d,AH2 s){return AH2_AW2(AW2_AH2(d)|(AW2_AH2(s)&AW2_(0x8000u)));} + AH3 ACpySgnH3(AH3 d,AH3 s){return AH3_AW3(AW3_AH3(d)|(AW3_AH3(s)&AW3_(0x8000u)));} + AH4 ACpySgnH4(AH4 d,AH4 s){return AH4_AW4(AW4_AH4(d)|(AW4_AH4(s)&AW4_(0x8000u)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASignedH1(AH1 m){return ASatH1(m*AH1_(A_INFN_H));} + AH2 ASignedH2(AH2 m){return ASatH2(m*AH2_(A_INFN_H));} + AH3 ASignedH3(AH3 m){return ASatH3(m*AH3_(A_INFN_H));} + AH4 ASignedH4(AH4 m){return ASatH4(m*AH4_(A_INFN_H));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AGtZeroH1(AH1 m){return ASatH1(m*AH1_(A_INFP_H));} + AH2 AGtZeroH2(AH2 m){return ASatH2(m*AH2_(A_INFP_H));} + AH3 AGtZeroH3(AH3 m){return ASatH3(m*AH3_(A_INFP_H));} + AH4 AGtZeroH4(AH4 m){return ASatH4(m*AH4_(A_INFP_H));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [FIS] FLOAT INTEGER SORTABLE +//------------------------------------------------------------------------------------------------------------------------------ +// Float to integer sortable. +// - If sign bit=0, flip the sign bit (positives). +// - If sign bit=1, flip all bits (negatives). +// Integer sortable to float. +// - If sign bit=1, flip the sign bit (positives). +// - If sign bit=0, flip all bits (negatives). +// Has nice side effects. +// - Larger integers are more positive values. +// - Float zero is mapped to center of integers (so clear to integer zero is a nice default for atomic max usage). +// Burns 3 ops for conversion {shift,or,xor}. +//============================================================================================================================== + AU1 AFisToU1(AU1 x){return x^(( AShrSU1(x,AU1_(31)))|AU1_(0x80000000));} + AU1 AFisFromU1(AU1 x){return x^((~AShrSU1(x,AU1_(31)))|AU1_(0x80000000));} +//------------------------------------------------------------------------------------------------------------------------------ + // Just adjust high 16-bit value (useful when upper part of 32-bit word is a 16-bit float value). + AU1 AFisToHiU1(AU1 x){return x^(( AShrSU1(x,AU1_(15)))|AU1_(0x80000000));} + AU1 AFisFromHiU1(AU1 x){return x^((~AShrSU1(x,AU1_(15)))|AU1_(0x80000000));} +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + AW1 AFisToW1(AW1 x){return x^(( AShrSW1(x,AW1_(15)))|AW1_(0x8000));} + AW1 AFisFromW1(AW1 x){return x^((~AShrSW1(x,AW1_(15)))|AW1_(0x8000));} +//------------------------------------------------------------------------------------------------------------------------------ + AW2 AFisToW2(AW2 x){return x^(( AShrSW2(x,AW2_(15)))|AW2_(0x8000));} + AW2 AFisFromW2(AW2 x){return x^((~AShrSW2(x,AW2_(15)))|AW2_(0x8000));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [PERM] V_PERM_B32 +//------------------------------------------------------------------------------------------------------------------------------ +// Support for V_PERM_B32 started in the 3rd generation of GCN. +//------------------------------------------------------------------------------------------------------------------------------ +// yyyyxxxx - The 'i' input. +// 76543210 +// ======== +// HGFEDCBA - Naming on permutation. +//------------------------------------------------------------------------------------------------------------------------------ +// TODO +// ==== +// - Make sure compiler optimizes this. +//============================================================================================================================== + #ifdef A_HALF + AU1 APerm0E0A(AU2 i){return((i.x )&0xffu)|((i.y<<16)&0xff0000u);} + AU1 APerm0F0B(AU2 i){return((i.x>> 8)&0xffu)|((i.y<< 8)&0xff0000u);} + AU1 APerm0G0C(AU2 i){return((i.x>>16)&0xffu)|((i.y )&0xff0000u);} + AU1 APerm0H0D(AU2 i){return((i.x>>24)&0xffu)|((i.y>> 8)&0xff0000u);} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 APermHGFA(AU2 i){return((i.x )&0x000000ffu)|(i.y&0xffffff00u);} + AU1 APermHGFC(AU2 i){return((i.x>>16)&0x000000ffu)|(i.y&0xffffff00u);} + AU1 APermHGAE(AU2 i){return((i.x<< 8)&0x0000ff00u)|(i.y&0xffff00ffu);} + AU1 APermHGCE(AU2 i){return((i.x>> 8)&0x0000ff00u)|(i.y&0xffff00ffu);} + AU1 APermHAFE(AU2 i){return((i.x<<16)&0x00ff0000u)|(i.y&0xff00ffffu);} + AU1 APermHCFE(AU2 i){return((i.x )&0x00ff0000u)|(i.y&0xff00ffffu);} + AU1 APermAGFE(AU2 i){return((i.x<<24)&0xff000000u)|(i.y&0x00ffffffu);} + AU1 APermCGFE(AU2 i){return((i.x<< 8)&0xff000000u)|(i.y&0x00ffffffu);} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 APermGCEA(AU2 i){return((i.x)&0x00ff00ffu)|((i.y<<8)&0xff00ff00u);} + AU1 APermGECA(AU2 i){return(((i.x)&0xffu)|((i.x>>8)&0xff00u)|((i.y<<16)&0xff0000u)|((i.y<<8)&0xff000000u));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [BUC] BYTE UNSIGNED CONVERSION +//------------------------------------------------------------------------------------------------------------------------------ +// Designed to use the optimal conversion, enables the scaling to possibly be factored into other computation. +// Works on a range of {0 to A_BUC_<32,16>}, for <32-bit, and 16-bit> respectively. +//------------------------------------------------------------------------------------------------------------------------------ +// OPCODE NOTES +// ============ +// GCN does not do UNORM or SNORM for bytes in opcodes. +// - V_CVT_F32_UBYTE{0,1,2,3} - Unsigned byte to float. +// - V_CVT_PKACC_U8_F32 - Float to unsigned byte (does bit-field insert into 32-bit integer). +// V_PERM_B32 does byte packing with ability to zero fill bytes as well. +// - Can pull out byte values from two sources, and zero fill upper 8-bits of packed hi and lo. +//------------------------------------------------------------------------------------------------------------------------------ +// BYTE : FLOAT - ABuc{0,1,2,3}{To,From}U1() - Designed for V_CVT_F32_UBYTE* and V_CVT_PKACCUM_U8_F32 ops. +// ==== ===== +// 0 : 0 +// 1 : 1 +// ... +// 255 : 255 +// : 256 (just outside the encoding range) +//------------------------------------------------------------------------------------------------------------------------------ +// BYTE : FLOAT - ABuc{0,1,2,3}{To,From}U2() - Designed for 16-bit denormal tricks and V_PERM_B32. +// ==== ===== +// 0 : 0 +// 1 : 1/512 +// 2 : 1/256 +// ... +// 64 : 1/8 +// 128 : 1/4 +// 255 : 255/512 +// : 1/2 (just outside the encoding range) +//------------------------------------------------------------------------------------------------------------------------------ +// OPTIMAL IMPLEMENTATIONS ON AMD ARCHITECTURES +// ============================================ +// r=ABuc0FromU1(i) +// V_CVT_F32_UBYTE0 r,i +// -------------------------------------------- +// r=ABuc0ToU1(d,i) +// V_CVT_PKACCUM_U8_F32 r,i,0,d +// -------------------------------------------- +// d=ABuc0FromU2(i) +// Where 'k0' is an SGPR with 0x0E0A +// Where 'k1' is an SGPR with {32768.0} packed into the lower 16-bits +// V_PERM_B32 d,i.x,i.y,k0 +// V_PK_FMA_F16 d,d,k1.x,0 +// -------------------------------------------- +// r=ABuc0ToU2(d,i) +// Where 'k0' is an SGPR with {1.0/32768.0} packed into the lower 16-bits +// Where 'k1' is an SGPR with 0x???? +// Where 'k2' is an SGPR with 0x???? +// V_PK_FMA_F16 i,i,k0.x,0 +// V_PERM_B32 r.x,i,i,k1 +// V_PERM_B32 r.y,i,i,k2 +//============================================================================================================================== + // Peak range for 32-bit and 16-bit operations. + #define A_BUC_32 (255.0) + #define A_BUC_16 (255.0/512.0) +//============================================================================================================================== + #if 1 + // Designed to be one V_CVT_PKACCUM_U8_F32. + // The extra min is required to pattern match to V_CVT_PKACCUM_U8_F32. + AU1 ABuc0ToU1(AU1 d,AF1 i){return (d&0xffffff00u)|((min(AU1(i),255u) )&(0x000000ffu));} + AU1 ABuc1ToU1(AU1 d,AF1 i){return (d&0xffff00ffu)|((min(AU1(i),255u)<< 8)&(0x0000ff00u));} + AU1 ABuc2ToU1(AU1 d,AF1 i){return (d&0xff00ffffu)|((min(AU1(i),255u)<<16)&(0x00ff0000u));} + AU1 ABuc3ToU1(AU1 d,AF1 i){return (d&0x00ffffffu)|((min(AU1(i),255u)<<24)&(0xff000000u));} +//------------------------------------------------------------------------------------------------------------------------------ + // Designed to be one V_CVT_F32_UBYTE*. + AF1 ABuc0FromU1(AU1 i){return AF1((i )&255u);} + AF1 ABuc1FromU1(AU1 i){return AF1((i>> 8)&255u);} + AF1 ABuc2FromU1(AU1 i){return AF1((i>>16)&255u);} + AF1 ABuc3FromU1(AU1 i){return AF1((i>>24)&255u);} + #endif +//============================================================================================================================== + #ifdef A_HALF + // Takes {x0,x1} and {y0,y1} and builds {{x0,y0},{x1,y1}}. + AW2 ABuc01ToW2(AH2 x,AH2 y){x*=AH2_(1.0/32768.0);y*=AH2_(1.0/32768.0); + return AW2_AU1(APermGCEA(AU2(AU1_AW2(AW2_AH2(x)),AU1_AW2(AW2_AH2(y)))));} +//------------------------------------------------------------------------------------------------------------------------------ + // Designed for 3 ops to do SOA to AOS and conversion. + AU2 ABuc0ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); + return AU2(APermHGFA(AU2(d.x,b)),APermHGFC(AU2(d.y,b)));} + AU2 ABuc1ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); + return AU2(APermHGAE(AU2(d.x,b)),APermHGCE(AU2(d.y,b)));} + AU2 ABuc2ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); + return AU2(APermHAFE(AU2(d.x,b)),APermHCFE(AU2(d.y,b)));} + AU2 ABuc3ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0))); + return AU2(APermAGFE(AU2(d.x,b)),APermCGFE(AU2(d.y,b)));} +//------------------------------------------------------------------------------------------------------------------------------ + // Designed for 2 ops to do both AOS to SOA, and conversion. + AH2 ABuc0FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0E0A(i)))*AH2_(32768.0);} + AH2 ABuc1FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0F0B(i)))*AH2_(32768.0);} + AH2 ABuc2FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0G0C(i)))*AH2_(32768.0);} + AH2 ABuc3FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0H0D(i)))*AH2_(32768.0);} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [BSC] BYTE SIGNED CONVERSION +//------------------------------------------------------------------------------------------------------------------------------ +// Similar to [BUC]. +// Works on a range of {-/+ A_BSC_<32,16>}, for <32-bit, and 16-bit> respectively. +//------------------------------------------------------------------------------------------------------------------------------ +// ENCODING (without zero-based encoding) +// ======== +// 0 = unused (can be used to mean something else) +// 1 = lowest value +// 128 = exact zero center (zero based encoding +// 255 = highest value +//------------------------------------------------------------------------------------------------------------------------------ +// Zero-based [Zb] flips the MSB bit of the byte (making 128 "exact zero" actually zero). +// This is useful if there is a desire for cleared values to decode as zero. +//------------------------------------------------------------------------------------------------------------------------------ +// BYTE : FLOAT - ABsc{0,1,2,3}{To,From}U2() - Designed for 16-bit denormal tricks and V_PERM_B32. +// ==== ===== +// 0 : -127/512 (unused) +// 1 : -126/512 +// 2 : -125/512 +// ... +// 128 : 0 +// ... +// 255 : 127/512 +// : 1/4 (just outside the encoding range) +//============================================================================================================================== + // Peak range for 32-bit and 16-bit operations. + #define A_BSC_32 (127.0) + #define A_BSC_16 (127.0/512.0) +//============================================================================================================================== + #if 1 + AU1 ABsc0ToU1(AU1 d,AF1 i){return (d&0xffffff00u)|((min(AU1(i+128.0),255u) )&(0x000000ffu));} + AU1 ABsc1ToU1(AU1 d,AF1 i){return (d&0xffff00ffu)|((min(AU1(i+128.0),255u)<< 8)&(0x0000ff00u));} + AU1 ABsc2ToU1(AU1 d,AF1 i){return (d&0xff00ffffu)|((min(AU1(i+128.0),255u)<<16)&(0x00ff0000u));} + AU1 ABsc3ToU1(AU1 d,AF1 i){return (d&0x00ffffffu)|((min(AU1(i+128.0),255u)<<24)&(0xff000000u));} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 ABsc0ToZbU1(AU1 d,AF1 i){return ((d&0xffffff00u)|((min(AU1(trunc(i)+128.0),255u) )&(0x000000ffu)))^0x00000080u;} + AU1 ABsc1ToZbU1(AU1 d,AF1 i){return ((d&0xffff00ffu)|((min(AU1(trunc(i)+128.0),255u)<< 8)&(0x0000ff00u)))^0x00008000u;} + AU1 ABsc2ToZbU1(AU1 d,AF1 i){return ((d&0xff00ffffu)|((min(AU1(trunc(i)+128.0),255u)<<16)&(0x00ff0000u)))^0x00800000u;} + AU1 ABsc3ToZbU1(AU1 d,AF1 i){return ((d&0x00ffffffu)|((min(AU1(trunc(i)+128.0),255u)<<24)&(0xff000000u)))^0x80000000u;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 ABsc0FromU1(AU1 i){return AF1((i )&255u)-128.0;} + AF1 ABsc1FromU1(AU1 i){return AF1((i>> 8)&255u)-128.0;} + AF1 ABsc2FromU1(AU1 i){return AF1((i>>16)&255u)-128.0;} + AF1 ABsc3FromU1(AU1 i){return AF1((i>>24)&255u)-128.0;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 ABsc0FromZbU1(AU1 i){return AF1(((i )&255u)^0x80u)-128.0;} + AF1 ABsc1FromZbU1(AU1 i){return AF1(((i>> 8)&255u)^0x80u)-128.0;} + AF1 ABsc2FromZbU1(AU1 i){return AF1(((i>>16)&255u)^0x80u)-128.0;} + AF1 ABsc3FromZbU1(AU1 i){return AF1(((i>>24)&255u)^0x80u)-128.0;} + #endif +//============================================================================================================================== + #ifdef A_HALF + // Takes {x0,x1} and {y0,y1} and builds {{x0,y0},{x1,y1}}. + AW2 ABsc01ToW2(AH2 x,AH2 y){x=x*AH2_(1.0/32768.0)+AH2_(0.25/32768.0);y=y*AH2_(1.0/32768.0)+AH2_(0.25/32768.0); + return AW2_AU1(APermGCEA(AU2(AU1_AW2(AW2_AH2(x)),AU1_AW2(AW2_AH2(y)))));} +//------------------------------------------------------------------------------------------------------------------------------ + AU2 ABsc0ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); + return AU2(APermHGFA(AU2(d.x,b)),APermHGFC(AU2(d.y,b)));} + AU2 ABsc1ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); + return AU2(APermHGAE(AU2(d.x,b)),APermHGCE(AU2(d.y,b)));} + AU2 ABsc2ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); + return AU2(APermHAFE(AU2(d.x,b)),APermHCFE(AU2(d.y,b)));} + AU2 ABsc3ToU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0))); + return AU2(APermAGFE(AU2(d.x,b)),APermCGFE(AU2(d.y,b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AU2 ABsc0ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; + return AU2(APermHGFA(AU2(d.x,b)),APermHGFC(AU2(d.y,b)));} + AU2 ABsc1ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; + return AU2(APermHGAE(AU2(d.x,b)),APermHGCE(AU2(d.y,b)));} + AU2 ABsc2ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; + return AU2(APermHAFE(AU2(d.x,b)),APermHCFE(AU2(d.y,b)));} + AU2 ABsc3ToZbU2(AU2 d,AH2 i){AU1 b=AU1_AW2(AW2_AH2(i*AH2_(1.0/32768.0)+AH2_(0.25/32768.0)))^0x00800080u; + return AU2(APermAGFE(AU2(d.x,b)),APermCGFE(AU2(d.y,b)));} +//------------------------------------------------------------------------------------------------------------------------------ + AH2 ABsc0FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0E0A(i)))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc1FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0F0B(i)))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc2FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0G0C(i)))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc3FromU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0H0D(i)))*AH2_(32768.0)-AH2_(0.25);} +//------------------------------------------------------------------------------------------------------------------------------ + AH2 ABsc0FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0E0A(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc1FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0F0B(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc2FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0G0C(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} + AH2 ABsc3FromZbU2(AU2 i){return AH2_AW2(AW2_AU1(APerm0H0D(i)^0x00800080u))*AH2_(32768.0)-AH2_(0.25);} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// HALF APPROXIMATIONS +//------------------------------------------------------------------------------------------------------------------------------ +// These support only positive inputs. +// Did not see value yet in specialization for range. +// Using quick testing, ended up mostly getting the same "best" approximation for various ranges. +// With hardware that can co-execute transcendentals, the value in approximations could be less than expected. +// However from a latency perspective, if execution of a transcendental is 4 clk, with no packed support, -> 8 clk total. +// And co-execution would require a compiler interleaving a lot of independent work for packed usage. +//------------------------------------------------------------------------------------------------------------------------------ +// The one Newton Raphson iteration form of rsq() was skipped (requires 6 ops total). +// Same with sqrt(), as this could be x*rsq() (7 ops). +//============================================================================================================================== + #ifdef A_HALF + // Minimize squared error across full positive range, 2 ops. + // The 0x1de2 based approximation maps {0 to 1} input maps to < 1 output. + AH1 APrxLoSqrtH1(AH1 a){return AH1_AW1((AW1_AH1(a)>>AW1_(1))+AW1_(0x1de2));} + AH2 APrxLoSqrtH2(AH2 a){return AH2_AW2((AW2_AH2(a)>>AW2_(1))+AW2_(0x1de2));} + AH3 APrxLoSqrtH3(AH3 a){return AH3_AW3((AW3_AH3(a)>>AW3_(1))+AW3_(0x1de2));} + AH4 APrxLoSqrtH4(AH4 a){return AH4_AW4((AW4_AH4(a)>>AW4_(1))+AW4_(0x1de2));} +//------------------------------------------------------------------------------------------------------------------------------ + // Lower precision estimation, 1 op. + // Minimize squared error across {smallest normal to 16384.0}. + AH1 APrxLoRcpH1(AH1 a){return AH1_AW1(AW1_(0x7784)-AW1_AH1(a));} + AH2 APrxLoRcpH2(AH2 a){return AH2_AW2(AW2_(0x7784)-AW2_AH2(a));} + AH3 APrxLoRcpH3(AH3 a){return AH3_AW3(AW3_(0x7784)-AW3_AH3(a));} + AH4 APrxLoRcpH4(AH4 a){return AH4_AW4(AW4_(0x7784)-AW4_AH4(a));} +//------------------------------------------------------------------------------------------------------------------------------ + // Medium precision estimation, one Newton Raphson iteration, 3 ops. + AH1 APrxMedRcpH1(AH1 a){AH1 b=AH1_AW1(AW1_(0x778d)-AW1_AH1(a));return b*(-b*a+AH1_(2.0));} + AH2 APrxMedRcpH2(AH2 a){AH2 b=AH2_AW2(AW2_(0x778d)-AW2_AH2(a));return b*(-b*a+AH2_(2.0));} + AH3 APrxMedRcpH3(AH3 a){AH3 b=AH3_AW3(AW3_(0x778d)-AW3_AH3(a));return b*(-b*a+AH3_(2.0));} + AH4 APrxMedRcpH4(AH4 a){AH4 b=AH4_AW4(AW4_(0x778d)-AW4_AH4(a));return b*(-b*a+AH4_(2.0));} +//------------------------------------------------------------------------------------------------------------------------------ + // Minimize squared error across {smallest normal to 16384.0}, 2 ops. + AH1 APrxLoRsqH1(AH1 a){return AH1_AW1(AW1_(0x59a3)-(AW1_AH1(a)>>AW1_(1)));} + AH2 APrxLoRsqH2(AH2 a){return AH2_AW2(AW2_(0x59a3)-(AW2_AH2(a)>>AW2_(1)));} + AH3 APrxLoRsqH3(AH3 a){return AH3_AW3(AW3_(0x59a3)-(AW3_AH3(a)>>AW3_(1)));} + AH4 APrxLoRsqH4(AH4 a){return AH4_AW4(AW4_(0x59a3)-(AW4_AH4(a)>>AW4_(1)));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// FLOAT APPROXIMATIONS +//------------------------------------------------------------------------------------------------------------------------------ +// Michal Drobot has an excellent presentation on these: "Low Level Optimizations For GCN", +// - Idea dates back to SGI, then to Quake 3, etc. +// - https://michaldrobot.files.wordpress.com/2014/05/gcn_alu_opt_digitaldragons2014.pdf +// - sqrt(x)=rsqrt(x)*x +// - rcp(x)=rsqrt(x)*rsqrt(x) for positive x +// - https://github.com/michaldrobot/ShaderFastLibs/blob/master/ShaderFastMathLib.h +//------------------------------------------------------------------------------------------------------------------------------ +// These below are from perhaps less complete searching for optimal. +// Used FP16 normal range for testing with +4096 32-bit step size for sampling error. +// So these match up well with the half approximations. +//============================================================================================================================== + AF1 APrxLoSqrtF1(AF1 a){return AF1_AU1((AU1_AF1(a)>>AU1_(1))+AU1_(0x1fbc4639));} + AF1 APrxLoRcpF1(AF1 a){return AF1_AU1(AU1_(0x7ef07ebb)-AU1_AF1(a));} + AF1 APrxMedRcpF1(AF1 a){AF1 b=AF1_AU1(AU1_(0x7ef19fff)-AU1_AF1(a));return b*(-b*a+AF1_(2.0));} + AF1 APrxLoRsqF1(AF1 a){return AF1_AU1(AU1_(0x5f347d74)-(AU1_AF1(a)>>AU1_(1)));} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 APrxLoSqrtF2(AF2 a){return AF2_AU2((AU2_AF2(a)>>AU2_(1))+AU2_(0x1fbc4639));} + AF2 APrxLoRcpF2(AF2 a){return AF2_AU2(AU2_(0x7ef07ebb)-AU2_AF2(a));} + AF2 APrxMedRcpF2(AF2 a){AF2 b=AF2_AU2(AU2_(0x7ef19fff)-AU2_AF2(a));return b*(-b*a+AF2_(2.0));} + AF2 APrxLoRsqF2(AF2 a){return AF2_AU2(AU2_(0x5f347d74)-(AU2_AF2(a)>>AU2_(1)));} +//------------------------------------------------------------------------------------------------------------------------------ + AF3 APrxLoSqrtF3(AF3 a){return AF3_AU3((AU3_AF3(a)>>AU3_(1))+AU3_(0x1fbc4639));} + AF3 APrxLoRcpF3(AF3 a){return AF3_AU3(AU3_(0x7ef07ebb)-AU3_AF3(a));} + AF3 APrxMedRcpF3(AF3 a){AF3 b=AF3_AU3(AU3_(0x7ef19fff)-AU3_AF3(a));return b*(-b*a+AF3_(2.0));} + AF3 APrxLoRsqF3(AF3 a){return AF3_AU3(AU3_(0x5f347d74)-(AU3_AF3(a)>>AU3_(1)));} +//------------------------------------------------------------------------------------------------------------------------------ + AF4 APrxLoSqrtF4(AF4 a){return AF4_AU4((AU4_AF4(a)>>AU4_(1))+AU4_(0x1fbc4639));} + AF4 APrxLoRcpF4(AF4 a){return AF4_AU4(AU4_(0x7ef07ebb)-AU4_AF4(a));} + AF4 APrxMedRcpF4(AF4 a){AF4 b=AF4_AU4(AU4_(0x7ef19fff)-AU4_AF4(a));return b*(-b*a+AF4_(2.0));} + AF4 APrxLoRsqF4(AF4 a){return AF4_AU4(AU4_(0x5f347d74)-(AU4_AF4(a)>>AU4_(1)));} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// PQ APPROXIMATIONS +//------------------------------------------------------------------------------------------------------------------------------ +// PQ is very close to x^(1/8). The functions below Use the fast float approximation method to do +// PQ<~>Gamma2 (4th power and fast 4th root) and PQ<~>Linear (8th power and fast 8th root). Maximum error is ~0.2%. +//============================================================================================================================== +// Helpers + AF1 Quart(AF1 a) { a = a * a; return a * a;} + AF1 Oct(AF1 a) { a = a * a; a = a * a; return a * a; } + AF2 Quart(AF2 a) { a = a * a; return a * a; } + AF2 Oct(AF2 a) { a = a * a; a = a * a; return a * a; } + AF3 Quart(AF3 a) { a = a * a; return a * a; } + AF3 Oct(AF3 a) { a = a * a; a = a * a; return a * a; } + AF4 Quart(AF4 a) { a = a * a; return a * a; } + AF4 Oct(AF4 a) { a = a * a; a = a * a; return a * a; } + //------------------------------------------------------------------------------------------------------------------------------ + AF1 APrxPQToGamma2(AF1 a) { return Quart(a); } + AF1 APrxPQToLinear(AF1 a) { return Oct(a); } + AF1 APrxLoGamma2ToPQ(AF1 a) { return AF1_AU1((AU1_AF1(a) >> AU1_(2)) + AU1_(0x2F9A4E46)); } + AF1 APrxMedGamma2ToPQ(AF1 a) { AF1 b = AF1_AU1((AU1_AF1(a) >> AU1_(2)) + AU1_(0x2F9A4E46)); AF1 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } + AF1 APrxHighGamma2ToPQ(AF1 a) { return sqrt(sqrt(a)); } + AF1 APrxLoLinearToPQ(AF1 a) { return AF1_AU1((AU1_AF1(a) >> AU1_(3)) + AU1_(0x378D8723)); } + AF1 APrxMedLinearToPQ(AF1 a) { AF1 b = AF1_AU1((AU1_AF1(a) >> AU1_(3)) + AU1_(0x378D8723)); AF1 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } + AF1 APrxHighLinearToPQ(AF1 a) { return sqrt(sqrt(sqrt(a))); } + //------------------------------------------------------------------------------------------------------------------------------ + AF2 APrxPQToGamma2(AF2 a) { return Quart(a); } + AF2 APrxPQToLinear(AF2 a) { return Oct(a); } + AF2 APrxLoGamma2ToPQ(AF2 a) { return AF2_AU2((AU2_AF2(a) >> AU2_(2)) + AU2_(0x2F9A4E46)); } + AF2 APrxMedGamma2ToPQ(AF2 a) { AF2 b = AF2_AU2((AU2_AF2(a) >> AU2_(2)) + AU2_(0x2F9A4E46)); AF2 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } + AF2 APrxHighGamma2ToPQ(AF2 a) { return sqrt(sqrt(a)); } + AF2 APrxLoLinearToPQ(AF2 a) { return AF2_AU2((AU2_AF2(a) >> AU2_(3)) + AU2_(0x378D8723)); } + AF2 APrxMedLinearToPQ(AF2 a) { AF2 b = AF2_AU2((AU2_AF2(a) >> AU2_(3)) + AU2_(0x378D8723)); AF2 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } + AF2 APrxHighLinearToPQ(AF2 a) { return sqrt(sqrt(sqrt(a))); } + //------------------------------------------------------------------------------------------------------------------------------ + AF3 APrxPQToGamma2(AF3 a) { return Quart(a); } + AF3 APrxPQToLinear(AF3 a) { return Oct(a); } + AF3 APrxLoGamma2ToPQ(AF3 a) { return AF3_AU3((AU3_AF3(a) >> AU3_(2)) + AU3_(0x2F9A4E46)); } + AF3 APrxMedGamma2ToPQ(AF3 a) { AF3 b = AF3_AU3((AU3_AF3(a) >> AU3_(2)) + AU3_(0x2F9A4E46)); AF3 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } + AF3 APrxHighGamma2ToPQ(AF3 a) { return sqrt(sqrt(a)); } + AF3 APrxLoLinearToPQ(AF3 a) { return AF3_AU3((AU3_AF3(a) >> AU3_(3)) + AU3_(0x378D8723)); } + AF3 APrxMedLinearToPQ(AF3 a) { AF3 b = AF3_AU3((AU3_AF3(a) >> AU3_(3)) + AU3_(0x378D8723)); AF3 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } + AF3 APrxHighLinearToPQ(AF3 a) { return sqrt(sqrt(sqrt(a))); } + //------------------------------------------------------------------------------------------------------------------------------ + AF4 APrxPQToGamma2(AF4 a) { return Quart(a); } + AF4 APrxPQToLinear(AF4 a) { return Oct(a); } + AF4 APrxLoGamma2ToPQ(AF4 a) { return AF4_AU4((AU4_AF4(a) >> AU4_(2)) + AU4_(0x2F9A4E46)); } + AF4 APrxMedGamma2ToPQ(AF4 a) { AF4 b = AF4_AU4((AU4_AF4(a) >> AU4_(2)) + AU4_(0x2F9A4E46)); AF4 b4 = Quart(b); return b - b * (b4 - a) / (AF1_(4.0) * b4); } + AF4 APrxHighGamma2ToPQ(AF4 a) { return sqrt(sqrt(a)); } + AF4 APrxLoLinearToPQ(AF4 a) { return AF4_AU4((AU4_AF4(a) >> AU4_(3)) + AU4_(0x378D8723)); } + AF4 APrxMedLinearToPQ(AF4 a) { AF4 b = AF4_AU4((AU4_AF4(a) >> AU4_(3)) + AU4_(0x378D8723)); AF4 b8 = Oct(b); return b - b * (b8 - a) / (AF1_(8.0) * b8); } + AF4 APrxHighLinearToPQ(AF4 a) { return sqrt(sqrt(sqrt(a))); } +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// PARABOLIC SIN & COS +//------------------------------------------------------------------------------------------------------------------------------ +// Approximate answers to transcendental questions. +//------------------------------------------------------------------------------------------------------------------------------ +//============================================================================================================================== + #if 1 + // Valid input range is {-1 to 1} representing {0 to 2 pi}. + // Output range is {-1/4 to 1/4} representing {-1 to 1}. + AF1 APSinF1(AF1 x){return x*abs(x)-x;} // MAD. + AF2 APSinF2(AF2 x){return x*abs(x)-x;} + AF1 APCosF1(AF1 x){x=AFractF1(x*AF1_(0.5)+AF1_(0.75));x=x*AF1_(2.0)-AF1_(1.0);return APSinF1(x);} // 3x MAD, FRACT + AF2 APCosF2(AF2 x){x=AFractF2(x*AF2_(0.5)+AF2_(0.75));x=x*AF2_(2.0)-AF2_(1.0);return APSinF2(x);} + AF2 APSinCosF1(AF1 x){AF1 y=AFractF1(x*AF1_(0.5)+AF1_(0.75));y=y*AF1_(2.0)-AF1_(1.0);return APSinF2(AF2(x,y));} + #endif +//------------------------------------------------------------------------------------------------------------------------------ + #ifdef A_HALF + // For a packed {sin,cos} pair, + // - Native takes 16 clocks and 4 issue slots (no packed transcendentals). + // - Parabolic takes 8 clocks and 8 issue slots (only fract is non-packed). + AH1 APSinH1(AH1 x){return x*abs(x)-x;} + AH2 APSinH2(AH2 x){return x*abs(x)-x;} // AND,FMA + AH1 APCosH1(AH1 x){x=AFractH1(x*AH1_(0.5)+AH1_(0.75));x=x*AH1_(2.0)-AH1_(1.0);return APSinH1(x);} + AH2 APCosH2(AH2 x){x=AFractH2(x*AH2_(0.5)+AH2_(0.75));x=x*AH2_(2.0)-AH2_(1.0);return APSinH2(x);} // 3x FMA, 2xFRACT, AND + AH2 APSinCosH1(AH1 x){AH1 y=AFractH1(x*AH1_(0.5)+AH1_(0.75));y=y*AH1_(2.0)-AH1_(1.0);return APSinH2(AH2(x,y));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// [ZOL] ZERO ONE LOGIC +//------------------------------------------------------------------------------------------------------------------------------ +// Conditional free logic designed for easy 16-bit packing, and backwards porting to 32-bit. +//------------------------------------------------------------------------------------------------------------------------------ +// 0 := false +// 1 := true +//------------------------------------------------------------------------------------------------------------------------------ +// AndNot(x,y) -> !(x&y) .... One op. +// AndOr(x,y,z) -> (x&y)|z ... One op. +// GtZero(x) -> x>0.0 ..... One op. +// Sel(x,y,z) -> x?y:z ..... Two ops, has no precision loss. +// Signed(x) -> x<0.0 ..... One op. +// ZeroPass(x,y) -> x?0:y ..... Two ops, 'y' is a pass through safe for aliasing as integer. +//------------------------------------------------------------------------------------------------------------------------------ +// OPTIMIZATION NOTES +// ================== +// - On Vega to use 2 constants in a packed op, pass in as one AW2 or one AH2 'k.xy' and use as 'k.xx' and 'k.yy'. +// For example 'a.xy*k.xx+k.yy'. +//============================================================================================================================== + #if 1 + AU1 AZolAndU1(AU1 x,AU1 y){return min(x,y);} + AU2 AZolAndU2(AU2 x,AU2 y){return min(x,y);} + AU3 AZolAndU3(AU3 x,AU3 y){return min(x,y);} + AU4 AZolAndU4(AU4 x,AU4 y){return min(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AZolNotU1(AU1 x){return x^AU1_(1);} + AU2 AZolNotU2(AU2 x){return x^AU2_(1);} + AU3 AZolNotU3(AU3 x){return x^AU3_(1);} + AU4 AZolNotU4(AU4 x){return x^AU4_(1);} +//------------------------------------------------------------------------------------------------------------------------------ + AU1 AZolOrU1(AU1 x,AU1 y){return max(x,y);} + AU2 AZolOrU2(AU2 x,AU2 y){return max(x,y);} + AU3 AZolOrU3(AU3 x,AU3 y){return max(x,y);} + AU4 AZolOrU4(AU4 x,AU4 y){return max(x,y);} +//============================================================================================================================== + AU1 AZolF1ToU1(AF1 x){return AU1(x);} + AU2 AZolF2ToU2(AF2 x){return AU2(x);} + AU3 AZolF3ToU3(AF3 x){return AU3(x);} + AU4 AZolF4ToU4(AF4 x){return AU4(x);} +//------------------------------------------------------------------------------------------------------------------------------ + // 2 ops, denormals don't work in 32-bit on PC (and if they are enabled, OMOD is disabled). + AU1 AZolNotF1ToU1(AF1 x){return AU1(AF1_(1.0)-x);} + AU2 AZolNotF2ToU2(AF2 x){return AU2(AF2_(1.0)-x);} + AU3 AZolNotF3ToU3(AF3 x){return AU3(AF3_(1.0)-x);} + AU4 AZolNotF4ToU4(AF4 x){return AU4(AF4_(1.0)-x);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolU1ToF1(AU1 x){return AF1(x);} + AF2 AZolU2ToF2(AU2 x){return AF2(x);} + AF3 AZolU3ToF3(AU3 x){return AF3(x);} + AF4 AZolU4ToF4(AU4 x){return AF4(x);} +//============================================================================================================================== + AF1 AZolAndF1(AF1 x,AF1 y){return min(x,y);} + AF2 AZolAndF2(AF2 x,AF2 y){return min(x,y);} + AF3 AZolAndF3(AF3 x,AF3 y){return min(x,y);} + AF4 AZolAndF4(AF4 x,AF4 y){return min(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 ASolAndNotF1(AF1 x,AF1 y){return (-x)*y+AF1_(1.0);} + AF2 ASolAndNotF2(AF2 x,AF2 y){return (-x)*y+AF2_(1.0);} + AF3 ASolAndNotF3(AF3 x,AF3 y){return (-x)*y+AF3_(1.0);} + AF4 ASolAndNotF4(AF4 x,AF4 y){return (-x)*y+AF4_(1.0);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolAndOrF1(AF1 x,AF1 y,AF1 z){return ASatF1(x*y+z);} + AF2 AZolAndOrF2(AF2 x,AF2 y,AF2 z){return ASatF2(x*y+z);} + AF3 AZolAndOrF3(AF3 x,AF3 y,AF3 z){return ASatF3(x*y+z);} + AF4 AZolAndOrF4(AF4 x,AF4 y,AF4 z){return ASatF4(x*y+z);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolGtZeroF1(AF1 x){return ASatF1(x*AF1_(A_INFP_F));} + AF2 AZolGtZeroF2(AF2 x){return ASatF2(x*AF2_(A_INFP_F));} + AF3 AZolGtZeroF3(AF3 x){return ASatF3(x*AF3_(A_INFP_F));} + AF4 AZolGtZeroF4(AF4 x){return ASatF4(x*AF4_(A_INFP_F));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolNotF1(AF1 x){return AF1_(1.0)-x;} + AF2 AZolNotF2(AF2 x){return AF2_(1.0)-x;} + AF3 AZolNotF3(AF3 x){return AF3_(1.0)-x;} + AF4 AZolNotF4(AF4 x){return AF4_(1.0)-x;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolOrF1(AF1 x,AF1 y){return max(x,y);} + AF2 AZolOrF2(AF2 x,AF2 y){return max(x,y);} + AF3 AZolOrF3(AF3 x,AF3 y){return max(x,y);} + AF4 AZolOrF4(AF4 x,AF4 y){return max(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolSelF1(AF1 x,AF1 y,AF1 z){AF1 r=(-x)*z+z;return x*y+r;} + AF2 AZolSelF2(AF2 x,AF2 y,AF2 z){AF2 r=(-x)*z+z;return x*y+r;} + AF3 AZolSelF3(AF3 x,AF3 y,AF3 z){AF3 r=(-x)*z+z;return x*y+r;} + AF4 AZolSelF4(AF4 x,AF4 y,AF4 z){AF4 r=(-x)*z+z;return x*y+r;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolSignedF1(AF1 x){return ASatF1(x*AF1_(A_INFN_F));} + AF2 AZolSignedF2(AF2 x){return ASatF2(x*AF2_(A_INFN_F));} + AF3 AZolSignedF3(AF3 x){return ASatF3(x*AF3_(A_INFN_F));} + AF4 AZolSignedF4(AF4 x){return ASatF4(x*AF4_(A_INFN_F));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AZolZeroPassF1(AF1 x,AF1 y){return AF1_AU1((AU1_AF1(x)!=AU1_(0))?AU1_(0):AU1_AF1(y));} + AF2 AZolZeroPassF2(AF2 x,AF2 y){return AF2_AU2((AU2_AF2(x)!=AU2_(0))?AU2_(0):AU2_AF2(y));} + AF3 AZolZeroPassF3(AF3 x,AF3 y){return AF3_AU3((AU3_AF3(x)!=AU3_(0))?AU3_(0):AU3_AF3(y));} + AF4 AZolZeroPassF4(AF4 x,AF4 y){return AF4_AU4((AU4_AF4(x)!=AU4_(0))?AU4_(0):AU4_AF4(y));} + #endif +//============================================================================================================================== + #ifdef A_HALF + AW1 AZolAndW1(AW1 x,AW1 y){return min(x,y);} + AW2 AZolAndW2(AW2 x,AW2 y){return min(x,y);} + AW3 AZolAndW3(AW3 x,AW3 y){return min(x,y);} + AW4 AZolAndW4(AW4 x,AW4 y){return min(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AZolNotW1(AW1 x){return x^AW1_(1);} + AW2 AZolNotW2(AW2 x){return x^AW2_(1);} + AW3 AZolNotW3(AW3 x){return x^AW3_(1);} + AW4 AZolNotW4(AW4 x){return x^AW4_(1);} +//------------------------------------------------------------------------------------------------------------------------------ + AW1 AZolOrW1(AW1 x,AW1 y){return max(x,y);} + AW2 AZolOrW2(AW2 x,AW2 y){return max(x,y);} + AW3 AZolOrW3(AW3 x,AW3 y){return max(x,y);} + AW4 AZolOrW4(AW4 x,AW4 y){return max(x,y);} +//============================================================================================================================== + // Uses denormal trick. + AW1 AZolH1ToW1(AH1 x){return AW1_AH1(x*AH1_AW1(AW1_(1)));} + AW2 AZolH2ToW2(AH2 x){return AW2_AH2(x*AH2_AW2(AW2_(1)));} + AW3 AZolH3ToW3(AH3 x){return AW3_AH3(x*AH3_AW3(AW3_(1)));} + AW4 AZolH4ToW4(AH4 x){return AW4_AH4(x*AH4_AW4(AW4_(1)));} +//------------------------------------------------------------------------------------------------------------------------------ + // AMD arch lacks a packed conversion opcode. + AH1 AZolW1ToH1(AW1 x){return AH1_AW1(x*AW1_AH1(AH1_(1.0)));} + AH2 AZolW2ToH2(AW2 x){return AH2_AW2(x*AW2_AH2(AH2_(1.0)));} + AH3 AZolW1ToH3(AW3 x){return AH3_AW3(x*AW3_AH3(AH3_(1.0)));} + AH4 AZolW2ToH4(AW4 x){return AH4_AW4(x*AW4_AH4(AH4_(1.0)));} +//============================================================================================================================== + AH1 AZolAndH1(AH1 x,AH1 y){return min(x,y);} + AH2 AZolAndH2(AH2 x,AH2 y){return min(x,y);} + AH3 AZolAndH3(AH3 x,AH3 y){return min(x,y);} + AH4 AZolAndH4(AH4 x,AH4 y){return min(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 ASolAndNotH1(AH1 x,AH1 y){return (-x)*y+AH1_(1.0);} + AH2 ASolAndNotH2(AH2 x,AH2 y){return (-x)*y+AH2_(1.0);} + AH3 ASolAndNotH3(AH3 x,AH3 y){return (-x)*y+AH3_(1.0);} + AH4 ASolAndNotH4(AH4 x,AH4 y){return (-x)*y+AH4_(1.0);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolAndOrH1(AH1 x,AH1 y,AH1 z){return ASatH1(x*y+z);} + AH2 AZolAndOrH2(AH2 x,AH2 y,AH2 z){return ASatH2(x*y+z);} + AH3 AZolAndOrH3(AH3 x,AH3 y,AH3 z){return ASatH3(x*y+z);} + AH4 AZolAndOrH4(AH4 x,AH4 y,AH4 z){return ASatH4(x*y+z);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolGtZeroH1(AH1 x){return ASatH1(x*AH1_(A_INFP_H));} + AH2 AZolGtZeroH2(AH2 x){return ASatH2(x*AH2_(A_INFP_H));} + AH3 AZolGtZeroH3(AH3 x){return ASatH3(x*AH3_(A_INFP_H));} + AH4 AZolGtZeroH4(AH4 x){return ASatH4(x*AH4_(A_INFP_H));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolNotH1(AH1 x){return AH1_(1.0)-x;} + AH2 AZolNotH2(AH2 x){return AH2_(1.0)-x;} + AH3 AZolNotH3(AH3 x){return AH3_(1.0)-x;} + AH4 AZolNotH4(AH4 x){return AH4_(1.0)-x;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolOrH1(AH1 x,AH1 y){return max(x,y);} + AH2 AZolOrH2(AH2 x,AH2 y){return max(x,y);} + AH3 AZolOrH3(AH3 x,AH3 y){return max(x,y);} + AH4 AZolOrH4(AH4 x,AH4 y){return max(x,y);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolSelH1(AH1 x,AH1 y,AH1 z){AH1 r=(-x)*z+z;return x*y+r;} + AH2 AZolSelH2(AH2 x,AH2 y,AH2 z){AH2 r=(-x)*z+z;return x*y+r;} + AH3 AZolSelH3(AH3 x,AH3 y,AH3 z){AH3 r=(-x)*z+z;return x*y+r;} + AH4 AZolSelH4(AH4 x,AH4 y,AH4 z){AH4 r=(-x)*z+z;return x*y+r;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AZolSignedH1(AH1 x){return ASatH1(x*AH1_(A_INFN_H));} + AH2 AZolSignedH2(AH2 x){return ASatH2(x*AH2_(A_INFN_H));} + AH3 AZolSignedH3(AH3 x){return ASatH3(x*AH3_(A_INFN_H));} + AH4 AZolSignedH4(AH4 x){return ASatH4(x*AH4_(A_INFN_H));} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// COLOR CONVERSIONS +//------------------------------------------------------------------------------------------------------------------------------ +// These are all linear to/from some other space (where 'linear' has been shortened out of the function name). +// So 'ToGamma' is 'LinearToGamma', and 'FromGamma' is 'LinearFromGamma'. +// These are branch free implementations. +// The AToSrgbF1() function is useful for stores for compute shaders for GPUs without hardware linear->sRGB store conversion. +//------------------------------------------------------------------------------------------------------------------------------ +// TRANSFER FUNCTIONS +// ================== +// 709 ..... Rec709 used for some HDTVs +// Gamma ... Typically 2.2 for some PC displays, or 2.4-2.5 for CRTs, or 2.2 FreeSync2 native +// Pq ...... PQ native for HDR10 +// Srgb .... The sRGB output, typical of PC displays, useful for 10-bit output, or storing to 8-bit UNORM without SRGB type +// Two ..... Gamma 2.0, fastest conversion (useful for intermediate pass approximations) +// Three ... Gamma 3.0, less fast, but good for HDR. +//------------------------------------------------------------------------------------------------------------------------------ +// KEEPING TO SPEC +// =============== +// Both Rec.709 and sRGB have a linear segment which as spec'ed would intersect the curved segment 2 times. +// (a.) For 8-bit sRGB, steps {0 to 10.3} are in the linear region (4% of the encoding range). +// (b.) For 8-bit 709, steps {0 to 20.7} are in the linear region (8% of the encoding range). +// Also there is a slight step in the transition regions. +// Precision of the coefficients in the spec being the likely cause. +// Main usage case of the sRGB code is to do the linear->sRGB converstion in a compute shader before store. +// This is to work around lack of hardware (typically only ROP does the conversion for free). +// To "correct" the linear segment, would be to introduce error, because hardware decode of sRGB->linear is fixed (and free). +// So this header keeps with the spec. +// For linear->sRGB transforms, the linear segment in some respects reduces error, because rounding in that region is linear. +// Rounding in the curved region in hardware (and fast software code) introduces error due to rounding in non-linear. +//------------------------------------------------------------------------------------------------------------------------------ +// FOR PQ +// ====== +// Both input and output is {0.0-1.0}, and where output 1.0 represents 10000.0 cd/m^2. +// All constants are only specified to FP32 precision. +// External PQ source reference, +// - https://github.com/ampas/aces-dev/blob/master/transforms/ctl/utilities/ACESlib.Utilities_Color.a1.0.1.ctl +//------------------------------------------------------------------------------------------------------------------------------ +// PACKED VERSIONS +// =============== +// These are the A*H2() functions. +// There is no PQ functions as FP16 seemed to not have enough precision for the conversion. +// The remaining functions are "good enough" for 8-bit, and maybe 10-bit if not concerned about a few 1-bit errors. +// Precision is lowest in the 709 conversion, higher in sRGB, higher still in Two and Gamma (when using 2.2 at least). +//------------------------------------------------------------------------------------------------------------------------------ +// NOTES +// ===== +// Could be faster for PQ conversions to be in ALU or a texture lookup depending on usage case. +//============================================================================================================================== + #if 1 + AF1 ATo709F1(AF1 c){AF3 j=AF3(0.018*4.5,4.5,0.45);AF2 k=AF2(1.099,-0.099); + return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} + AF2 ATo709F2(AF2 c){AF3 j=AF3(0.018*4.5,4.5,0.45);AF2 k=AF2(1.099,-0.099); + return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} + AF3 ATo709F3(AF3 c){AF3 j=AF3(0.018*4.5,4.5,0.45);AF2 k=AF2(1.099,-0.099); + return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} +//------------------------------------------------------------------------------------------------------------------------------ + // Note 'rcpX' is '1/x', where the 'x' is what would be used in AFromGamma(). + AF1 AToGammaF1(AF1 c,AF1 rcpX){return pow(c,AF1_(rcpX));} + AF2 AToGammaF2(AF2 c,AF1 rcpX){return pow(c,AF2_(rcpX));} + AF3 AToGammaF3(AF3 c,AF1 rcpX){return pow(c,AF3_(rcpX));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToPqF1(AF1 x){AF1 p=pow(x,AF1_(0.159302)); + return pow((AF1_(0.835938)+AF1_(18.8516)*p)/(AF1_(1.0)+AF1_(18.6875)*p),AF1_(78.8438));} + AF2 AToPqF1(AF2 x){AF2 p=pow(x,AF2_(0.159302)); + return pow((AF2_(0.835938)+AF2_(18.8516)*p)/(AF2_(1.0)+AF2_(18.6875)*p),AF2_(78.8438));} + AF3 AToPqF1(AF3 x){AF3 p=pow(x,AF3_(0.159302)); + return pow((AF3_(0.835938)+AF3_(18.8516)*p)/(AF3_(1.0)+AF3_(18.6875)*p),AF3_(78.8438));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToSrgbF1(AF1 c){AF3 j=AF3(0.0031308*12.92,12.92,1.0/2.4);AF2 k=AF2(1.055,-0.055); + return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} + AF2 AToSrgbF2(AF2 c){AF3 j=AF3(0.0031308*12.92,12.92,1.0/2.4);AF2 k=AF2(1.055,-0.055); + return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} + AF3 AToSrgbF3(AF3 c){AF3 j=AF3(0.0031308*12.92,12.92,1.0/2.4);AF2 k=AF2(1.055,-0.055); + return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToTwoF1(AF1 c){return sqrt(c);} + AF2 AToTwoF2(AF2 c){return sqrt(c);} + AF3 AToTwoF3(AF3 c){return sqrt(c);} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AToThreeF1(AF1 c){return pow(c,AF1_(1.0/3.0));} + AF2 AToThreeF2(AF2 c){return pow(c,AF2_(1.0/3.0));} + AF3 AToThreeF3(AF3 c){return pow(c,AF3_(1.0/3.0));} + #endif +//============================================================================================================================== + #if 1 + // Unfortunately median won't work here. + AF1 AFrom709F1(AF1 c){AF3 j=AF3(0.081/4.5,1.0/4.5,1.0/0.45);AF2 k=AF2(1.0/1.099,0.099/1.099); + return AZolSelF1(AZolSignedF1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} + AF2 AFrom709F2(AF2 c){AF3 j=AF3(0.081/4.5,1.0/4.5,1.0/0.45);AF2 k=AF2(1.0/1.099,0.099/1.099); + return AZolSelF2(AZolSignedF2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} + AF3 AFrom709F3(AF3 c){AF3 j=AF3(0.081/4.5,1.0/4.5,1.0/0.45);AF2 k=AF2(1.0/1.099,0.099/1.099); + return AZolSelF3(AZolSignedF3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromGammaF1(AF1 c,AF1 x){return pow(c,AF1_(x));} + AF2 AFromGammaF2(AF2 c,AF1 x){return pow(c,AF2_(x));} + AF3 AFromGammaF3(AF3 c,AF1 x){return pow(c,AF3_(x));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromPqF1(AF1 x){AF1 p=pow(x,AF1_(0.0126833)); + return pow(ASatF1(p-AF1_(0.835938))/(AF1_(18.8516)-AF1_(18.6875)*p),AF1_(6.27739));} + AF2 AFromPqF1(AF2 x){AF2 p=pow(x,AF2_(0.0126833)); + return pow(ASatF2(p-AF2_(0.835938))/(AF2_(18.8516)-AF2_(18.6875)*p),AF2_(6.27739));} + AF3 AFromPqF1(AF3 x){AF3 p=pow(x,AF3_(0.0126833)); + return pow(ASatF3(p-AF3_(0.835938))/(AF3_(18.8516)-AF3_(18.6875)*p),AF3_(6.27739));} +//------------------------------------------------------------------------------------------------------------------------------ + // Unfortunately median won't work here. + AF1 AFromSrgbF1(AF1 c){AF3 j=AF3(0.04045/12.92,1.0/12.92,2.4);AF2 k=AF2(1.0/1.055,0.055/1.055); + return AZolSelF1(AZolSignedF1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} + AF2 AFromSrgbF2(AF2 c){AF3 j=AF3(0.04045/12.92,1.0/12.92,2.4);AF2 k=AF2(1.0/1.055,0.055/1.055); + return AZolSelF2(AZolSignedF2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} + AF3 AFromSrgbF3(AF3 c){AF3 j=AF3(0.04045/12.92,1.0/12.92,2.4);AF2 k=AF2(1.0/1.055,0.055/1.055); + return AZolSelF3(AZolSignedF3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromTwoF1(AF1 c){return c*c;} + AF2 AFromTwoF2(AF2 c){return c*c;} + AF3 AFromTwoF3(AF3 c){return c*c;} +//------------------------------------------------------------------------------------------------------------------------------ + AF1 AFromThreeF1(AF1 c){return c*c*c;} + AF2 AFromThreeF2(AF2 c){return c*c*c;} + AF3 AFromThreeF3(AF3 c){return c*c*c;} + #endif +//============================================================================================================================== + #ifdef A_HALF + AH1 ATo709H1(AH1 c){AH3 j=AH3(0.018*4.5,4.5,0.45);AH2 k=AH2(1.099,-0.099); + return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} + AH2 ATo709H2(AH2 c){AH3 j=AH3(0.018*4.5,4.5,0.45);AH2 k=AH2(1.099,-0.099); + return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} + AH3 ATo709H3(AH3 c){AH3 j=AH3(0.018*4.5,4.5,0.45);AH2 k=AH2(1.099,-0.099); + return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AToGammaH1(AH1 c,AH1 rcpX){return pow(c,AH1_(rcpX));} + AH2 AToGammaH2(AH2 c,AH1 rcpX){return pow(c,AH2_(rcpX));} + AH3 AToGammaH3(AH3 c,AH1 rcpX){return pow(c,AH3_(rcpX));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AToSrgbH1(AH1 c){AH3 j=AH3(0.0031308*12.92,12.92,1.0/2.4);AH2 k=AH2(1.055,-0.055); + return clamp(j.x ,c*j.y ,pow(c,j.z )*k.x +k.y );} + AH2 AToSrgbH2(AH2 c){AH3 j=AH3(0.0031308*12.92,12.92,1.0/2.4);AH2 k=AH2(1.055,-0.055); + return clamp(j.xx ,c*j.yy ,pow(c,j.zz )*k.xx +k.yy );} + AH3 AToSrgbH3(AH3 c){AH3 j=AH3(0.0031308*12.92,12.92,1.0/2.4);AH2 k=AH2(1.055,-0.055); + return clamp(j.xxx,c*j.yyy,pow(c,j.zzz)*k.xxx+k.yyy);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AToTwoH1(AH1 c){return sqrt(c);} + AH2 AToTwoH2(AH2 c){return sqrt(c);} + AH3 AToTwoH3(AH3 c){return sqrt(c);} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AToThreeF1(AH1 c){return pow(c,AH1_(1.0/3.0));} + AH2 AToThreeF2(AH2 c){return pow(c,AH2_(1.0/3.0));} + AH3 AToThreeF3(AH3 c){return pow(c,AH3_(1.0/3.0));} + #endif +//============================================================================================================================== + #ifdef A_HALF + AH1 AFrom709H1(AH1 c){AH3 j=AH3(0.081/4.5,1.0/4.5,1.0/0.45);AH2 k=AH2(1.0/1.099,0.099/1.099); + return AZolSelH1(AZolSignedH1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} + AH2 AFrom709H2(AH2 c){AH3 j=AH3(0.081/4.5,1.0/4.5,1.0/0.45);AH2 k=AH2(1.0/1.099,0.099/1.099); + return AZolSelH2(AZolSignedH2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} + AH3 AFrom709H3(AH3 c){AH3 j=AH3(0.081/4.5,1.0/4.5,1.0/0.45);AH2 k=AH2(1.0/1.099,0.099/1.099); + return AZolSelH3(AZolSignedH3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFromGammaH1(AH1 c,AH1 x){return pow(c,AH1_(x));} + AH2 AFromGammaH2(AH2 c,AH1 x){return pow(c,AH2_(x));} + AH3 AFromGammaH3(AH3 c,AH1 x){return pow(c,AH3_(x));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AHromSrgbF1(AH1 c){AH3 j=AH3(0.04045/12.92,1.0/12.92,2.4);AH2 k=AH2(1.0/1.055,0.055/1.055); + return AZolSelH1(AZolSignedH1(c-j.x ),c*j.y ,pow(c*k.x +k.y ,j.z ));} + AH2 AHromSrgbF2(AH2 c){AH3 j=AH3(0.04045/12.92,1.0/12.92,2.4);AH2 k=AH2(1.0/1.055,0.055/1.055); + return AZolSelH2(AZolSignedH2(c-j.xx ),c*j.yy ,pow(c*k.xx +k.yy ,j.zz ));} + AH3 AHromSrgbF3(AH3 c){AH3 j=AH3(0.04045/12.92,1.0/12.92,2.4);AH2 k=AH2(1.0/1.055,0.055/1.055); + return AZolSelH3(AZolSignedH3(c-j.xxx),c*j.yyy,pow(c*k.xxx+k.yyy,j.zzz));} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFromTwoH1(AH1 c){return c*c;} + AH2 AFromTwoH2(AH2 c){return c*c;} + AH3 AFromTwoH3(AH3 c){return c*c;} +//------------------------------------------------------------------------------------------------------------------------------ + AH1 AFromThreeH1(AH1 c){return c*c*c;} + AH2 AFromThreeH2(AH2 c){return c*c*c;} + AH3 AFromThreeH3(AH3 c){return c*c*c;} + #endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// CS REMAP +//============================================================================================================================== + // Simple remap 64x1 to 8x8 with rotated 2x2 pixel quads in quad linear. + // 543210 + // ====== + // ..xxx. + // yy...y + AU2 ARmp8x8(AU1 a){return AU2(ABfe(a,1u,3u),ABfiM(ABfe(a,3u,3u),a,1u));} +//============================================================================================================================== + // More complex remap 64x1 to 8x8 which is necessary for 2D wave reductions. + // 543210 + // ====== + // .xx..x + // y..yy. + // Details, + // LANE TO 8x8 MAPPING + // =================== + // 00 01 08 09 10 11 18 19 + // 02 03 0a 0b 12 13 1a 1b + // 04 05 0c 0d 14 15 1c 1d + // 06 07 0e 0f 16 17 1e 1f + // 20 21 28 29 30 31 38 39 + // 22 23 2a 2b 32 33 3a 3b + // 24 25 2c 2d 34 35 3c 3d + // 26 27 2e 2f 36 37 3e 3f + AU2 ARmpRed8x8(AU1 a){return AU2(ABfiM(ABfe(a,2u,3u),a,1u),ABfiM(ABfe(a,3u,3u),ABfe(a,1u,2u),2u));} +//============================================================================================================================== + #ifdef A_HALF + AW2 ARmp8x8H(AU1 a){return AW2(ABfe(a,1u,3u),ABfiM(ABfe(a,3u,3u),a,1u));} + AW2 ARmpRed8x8H(AU1 a){return AW2(ABfiM(ABfe(a,2u,3u),a,1u),ABfiM(ABfe(a,3u,3u),ABfe(a,1u,2u),2u));} + #endif +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// REFERENCE +// +//------------------------------------------------------------------------------------------------------------------------------ +// IEEE FLOAT RULES +// ================ +// - saturate(NaN)=0, saturate(-INF)=0, saturate(+INF)=1 +// - {+/-}0 * {+/-}INF = NaN +// - -INF + (+INF) = NaN +// - {+/-}0 / {+/-}0 = NaN +// - {+/-}INF / {+/-}INF = NaN +// - a<(-0) := sqrt(a) = NaN (a=-0.0 won't NaN) +// - 0 == -0 +// - 4/0 = +INF +// - 4/-0 = -INF +// - 4+INF = +INF +// - 4-INF = -INF +// - 4*(+INF) = +INF +// - 4*(-INF) = -INF +// - -4*(+INF) = -INF +// - sqrt(+INF) = +INF +//------------------------------------------------------------------------------------------------------------------------------ +// FP16 ENCODING +// ============= +// fedcba9876543210 +// ---------------- +// ......mmmmmmmmmm 10-bit mantissa (encodes 11-bit 0.5 to 1.0 except for denormals) +// .eeeee.......... 5-bit exponent +// .00000.......... denormals +// .00001.......... -14 exponent +// .11110.......... 15 exponent +// .111110000000000 infinity +// .11111nnnnnnnnnn NaN with n!=0 +// s............... sign +//------------------------------------------------------------------------------------------------------------------------------ +// FP16/INT16 ALIASING DENORMAL +// ============================ +// 11-bit unsigned integers alias with half float denormal/normal values, +// 1 = 2^(-24) = 1/16777216 ....................... first denormal value +// 2 = 2^(-23) +// ... +// 1023 = 2^(-14)*(1-2^(-10)) = 2^(-14)*(1-1/1024) ... last denormal value +// 1024 = 2^(-14) = 1/16384 .......................... first normal value that still maps to integers +// 2047 .............................................. last normal value that still maps to integers +// Scaling limits, +// 2^15 = 32768 ...................................... largest power of 2 scaling +// Largest pow2 conversion mapping is at *32768, +// 1 : 2^(-9) = 1/512 +// 2 : 1/256 +// 4 : 1/128 +// 8 : 1/64 +// 16 : 1/32 +// 32 : 1/16 +// 64 : 1/8 +// 128 : 1/4 +// 256 : 1/2 +// 512 : 1 +// 1024 : 2 +// 2047 : a little less than 4 +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// GPU/CPU PORTABILITY +// +// +//------------------------------------------------------------------------------------------------------------------------------ +// This is the GPU implementation. +// See the CPU implementation for docs. +//============================================================================================================================== +#ifdef A_GPU + #define A_TRUE true + #define A_FALSE false + #define A_STATIC +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR ARGUMENT/RETURN/INITIALIZATION PORTABILITY +//============================================================================================================================== + #define retAD2 AD2 + #define retAD3 AD3 + #define retAD4 AD4 + #define retAF2 AF2 + #define retAF3 AF3 + #define retAF4 AF4 + #define retAL2 AL2 + #define retAL3 AL3 + #define retAL4 AL4 + #define retAU2 AU2 + #define retAU3 AU3 + #define retAU4 AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define inAD2 in AD2 + #define inAD3 in AD3 + #define inAD4 in AD4 + #define inAF2 in AF2 + #define inAF3 in AF3 + #define inAF4 in AF4 + #define inAL2 in AL2 + #define inAL3 in AL3 + #define inAL4 in AL4 + #define inAU2 in AU2 + #define inAU3 in AU3 + #define inAU4 in AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define inoutAD2 inout AD2 + #define inoutAD3 inout AD3 + #define inoutAD4 inout AD4 + #define inoutAF2 inout AF2 + #define inoutAF3 inout AF3 + #define inoutAF4 inout AF4 + #define inoutAL2 inout AL2 + #define inoutAL3 inout AL3 + #define inoutAL4 inout AL4 + #define inoutAU2 inout AU2 + #define inoutAU3 inout AU3 + #define inoutAU4 inout AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define outAD2 out AD2 + #define outAD3 out AD3 + #define outAD4 out AD4 + #define outAF2 out AF2 + #define outAF3 out AF3 + #define outAF4 out AF4 + #define outAL2 out AL2 + #define outAL3 out AL3 + #define outAL4 out AL4 + #define outAU2 out AU2 + #define outAU3 out AU3 + #define outAU4 out AU4 +//------------------------------------------------------------------------------------------------------------------------------ + #define varAD2(x) AD2 x + #define varAD3(x) AD3 x + #define varAD4(x) AD4 x + #define varAF2(x) AF2 x + #define varAF3(x) AF3 x + #define varAF4(x) AF4 x + #define varAL2(x) AL2 x + #define varAL3(x) AL3 x + #define varAL4(x) AL4 x + #define varAU2(x) AU2 x + #define varAU3(x) AU3 x + #define varAU4(x) AU4 x +//------------------------------------------------------------------------------------------------------------------------------ + #define initAD2(x,y) AD2(x,y) + #define initAD3(x,y,z) AD3(x,y,z) + #define initAD4(x,y,z,w) AD4(x,y,z,w) + #define initAF2(x,y) AF2(x,y) + #define initAF3(x,y,z) AF3(x,y,z) + #define initAF4(x,y,z,w) AF4(x,y,z,w) + #define initAL2(x,y) AL2(x,y) + #define initAL3(x,y,z) AL3(x,y,z) + #define initAL4(x,y,z,w) AL4(x,y,z,w) + #define initAU2(x,y) AU2(x,y) + #define initAU3(x,y,z) AU3(x,y,z) + #define initAU4(x,y,z,w) AU4(x,y,z,w) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS +//============================================================================================================================== + #define AAbsD1(a) abs(AD1(a)) + #define AAbsF1(a) abs(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ACosD1(a) cos(AD1(a)) + #define ACosF1(a) cos(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ADotD2(a,b) dot(AD2(a),AD2(b)) + #define ADotD3(a,b) dot(AD3(a),AD3(b)) + #define ADotD4(a,b) dot(AD4(a),AD4(b)) + #define ADotF2(a,b) dot(AF2(a),AF2(b)) + #define ADotF3(a,b) dot(AF3(a),AF3(b)) + #define ADotF4(a,b) dot(AF4(a),AF4(b)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AExp2D1(a) exp2(AD1(a)) + #define AExp2F1(a) exp2(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AFloorD1(a) floor(AD1(a)) + #define AFloorF1(a) floor(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ALog2D1(a) log2(AD1(a)) + #define ALog2F1(a) log2(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define AMaxD1(a,b) max(a,b) + #define AMaxF1(a,b) max(a,b) + #define AMaxL1(a,b) max(a,b) + #define AMaxU1(a,b) max(a,b) +//------------------------------------------------------------------------------------------------------------------------------ + #define AMinD1(a,b) min(a,b) + #define AMinF1(a,b) min(a,b) + #define AMinL1(a,b) min(a,b) + #define AMinU1(a,b) min(a,b) +//------------------------------------------------------------------------------------------------------------------------------ + #define ASinD1(a) sin(AD1(a)) + #define ASinF1(a) sin(AF1(a)) +//------------------------------------------------------------------------------------------------------------------------------ + #define ASqrtD1(a) sqrt(AD1(a)) + #define ASqrtF1(a) sqrt(AF1(a)) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// SCALAR RETURN OPS - DEPENDENT +//============================================================================================================================== + #define APowD1(a,b) pow(AD1(a),AF1(b)) + #define APowF1(a,b) pow(AF1(a),AF1(b)) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// VECTOR OPS +//------------------------------------------------------------------------------------------------------------------------------ +// These are added as needed for production or prototyping, so not necessarily a complete set. +// They follow a convention of taking in a destination and also returning the destination value to increase utility. +//============================================================================================================================== + #ifdef A_DUBL + AD2 opAAbsD2(outAD2 d,inAD2 a){d=abs(a);return d;} + AD3 opAAbsD3(outAD3 d,inAD3 a){d=abs(a);return d;} + AD4 opAAbsD4(outAD4 d,inAD4 a){d=abs(a);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAAddD2(outAD2 d,inAD2 a,inAD2 b){d=a+b;return d;} + AD3 opAAddD3(outAD3 d,inAD3 a,inAD3 b){d=a+b;return d;} + AD4 opAAddD4(outAD4 d,inAD4 a,inAD4 b){d=a+b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAAddOneD2(outAD2 d,inAD2 a,AD1 b){d=a+AD2_(b);return d;} + AD3 opAAddOneD3(outAD3 d,inAD3 a,AD1 b){d=a+AD3_(b);return d;} + AD4 opAAddOneD4(outAD4 d,inAD4 a,AD1 b){d=a+AD4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opACpyD2(outAD2 d,inAD2 a){d=a;return d;} + AD3 opACpyD3(outAD3 d,inAD3 a){d=a;return d;} + AD4 opACpyD4(outAD4 d,inAD4 a){d=a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opALerpD2(outAD2 d,inAD2 a,inAD2 b,inAD2 c){d=ALerpD2(a,b,c);return d;} + AD3 opALerpD3(outAD3 d,inAD3 a,inAD3 b,inAD3 c){d=ALerpD3(a,b,c);return d;} + AD4 opALerpD4(outAD4 d,inAD4 a,inAD4 b,inAD4 c){d=ALerpD4(a,b,c);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opALerpOneD2(outAD2 d,inAD2 a,inAD2 b,AD1 c){d=ALerpD2(a,b,AD2_(c));return d;} + AD3 opALerpOneD3(outAD3 d,inAD3 a,inAD3 b,AD1 c){d=ALerpD3(a,b,AD3_(c));return d;} + AD4 opALerpOneD4(outAD4 d,inAD4 a,inAD4 b,AD1 c){d=ALerpD4(a,b,AD4_(c));return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMaxD2(outAD2 d,inAD2 a,inAD2 b){d=max(a,b);return d;} + AD3 opAMaxD3(outAD3 d,inAD3 a,inAD3 b){d=max(a,b);return d;} + AD4 opAMaxD4(outAD4 d,inAD4 a,inAD4 b){d=max(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMinD2(outAD2 d,inAD2 a,inAD2 b){d=min(a,b);return d;} + AD3 opAMinD3(outAD3 d,inAD3 a,inAD3 b){d=min(a,b);return d;} + AD4 opAMinD4(outAD4 d,inAD4 a,inAD4 b){d=min(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMulD2(outAD2 d,inAD2 a,inAD2 b){d=a*b;return d;} + AD3 opAMulD3(outAD3 d,inAD3 a,inAD3 b){d=a*b;return d;} + AD4 opAMulD4(outAD4 d,inAD4 a,inAD4 b){d=a*b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opAMulOneD2(outAD2 d,inAD2 a,AD1 b){d=a*AD2_(b);return d;} + AD3 opAMulOneD3(outAD3 d,inAD3 a,AD1 b){d=a*AD3_(b);return d;} + AD4 opAMulOneD4(outAD4 d,inAD4 a,AD1 b){d=a*AD4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opANegD2(outAD2 d,inAD2 a){d=-a;return d;} + AD3 opANegD3(outAD3 d,inAD3 a){d=-a;return d;} + AD4 opANegD4(outAD4 d,inAD4 a){d=-a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AD2 opARcpD2(outAD2 d,inAD2 a){d=ARcpD2(a);return d;} + AD3 opARcpD3(outAD3 d,inAD3 a){d=ARcpD3(a);return d;} + AD4 opARcpD4(outAD4 d,inAD4 a){d=ARcpD4(a);return d;} + #endif +//============================================================================================================================== + AF2 opAAbsF2(outAF2 d,inAF2 a){d=abs(a);return d;} + AF3 opAAbsF3(outAF3 d,inAF3 a){d=abs(a);return d;} + AF4 opAAbsF4(outAF4 d,inAF4 a){d=abs(a);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAAddF2(outAF2 d,inAF2 a,inAF2 b){d=a+b;return d;} + AF3 opAAddF3(outAF3 d,inAF3 a,inAF3 b){d=a+b;return d;} + AF4 opAAddF4(outAF4 d,inAF4 a,inAF4 b){d=a+b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAAddOneF2(outAF2 d,inAF2 a,AF1 b){d=a+AF2_(b);return d;} + AF3 opAAddOneF3(outAF3 d,inAF3 a,AF1 b){d=a+AF3_(b);return d;} + AF4 opAAddOneF4(outAF4 d,inAF4 a,AF1 b){d=a+AF4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opACpyF2(outAF2 d,inAF2 a){d=a;return d;} + AF3 opACpyF3(outAF3 d,inAF3 a){d=a;return d;} + AF4 opACpyF4(outAF4 d,inAF4 a){d=a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opALerpF2(outAF2 d,inAF2 a,inAF2 b,inAF2 c){d=ALerpF2(a,b,c);return d;} + AF3 opALerpF3(outAF3 d,inAF3 a,inAF3 b,inAF3 c){d=ALerpF3(a,b,c);return d;} + AF4 opALerpF4(outAF4 d,inAF4 a,inAF4 b,inAF4 c){d=ALerpF4(a,b,c);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opALerpOneF2(outAF2 d,inAF2 a,inAF2 b,AF1 c){d=ALerpF2(a,b,AF2_(c));return d;} + AF3 opALerpOneF3(outAF3 d,inAF3 a,inAF3 b,AF1 c){d=ALerpF3(a,b,AF3_(c));return d;} + AF4 opALerpOneF4(outAF4 d,inAF4 a,inAF4 b,AF1 c){d=ALerpF4(a,b,AF4_(c));return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMaxF2(outAF2 d,inAF2 a,inAF2 b){d=max(a,b);return d;} + AF3 opAMaxF3(outAF3 d,inAF3 a,inAF3 b){d=max(a,b);return d;} + AF4 opAMaxF4(outAF4 d,inAF4 a,inAF4 b){d=max(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMinF2(outAF2 d,inAF2 a,inAF2 b){d=min(a,b);return d;} + AF3 opAMinF3(outAF3 d,inAF3 a,inAF3 b){d=min(a,b);return d;} + AF4 opAMinF4(outAF4 d,inAF4 a,inAF4 b){d=min(a,b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMulF2(outAF2 d,inAF2 a,inAF2 b){d=a*b;return d;} + AF3 opAMulF3(outAF3 d,inAF3 a,inAF3 b){d=a*b;return d;} + AF4 opAMulF4(outAF4 d,inAF4 a,inAF4 b){d=a*b;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opAMulOneF2(outAF2 d,inAF2 a,AF1 b){d=a*AF2_(b);return d;} + AF3 opAMulOneF3(outAF3 d,inAF3 a,AF1 b){d=a*AF3_(b);return d;} + AF4 opAMulOneF4(outAF4 d,inAF4 a,AF1 b){d=a*AF4_(b);return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opANegF2(outAF2 d,inAF2 a){d=-a;return d;} + AF3 opANegF3(outAF3 d,inAF3 a){d=-a;return d;} + AF4 opANegF4(outAF4 d,inAF4 a){d=-a;return d;} +//------------------------------------------------------------------------------------------------------------------------------ + AF2 opARcpF2(outAF2 d,inAF2 a){d=ARcpF2(a);return d;} + AF3 opARcpF3(outAF3 d,inAF3 a){d=ARcpF3(a);return d;} + AF4 opARcpF4(outAF4 d,inAF4 a){d=ARcpF4(a);return d;} +#endif + +AF4 FsrRcasLoadF(ASU2 p) +{ + return AF4(texture(texture0, vec2(p) / dstSize)); +} + +void FsrRcasInputF(inout AF1 r,inout AF1 g,inout AF1 b) +{ +} + +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// +// AMD FidelityFX SUPER RESOLUTION [FSR 1] ::: SPATIAL SCALING & EXTRAS - v1.20210629 +// +// +//------------------------------------------------------------------------------------------------------------------------------ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//------------------------------------------------------------------------------------------------------------------------------ +// FidelityFX Super Resolution Sample +// +// Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved. +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +//------------------------------------------------------------------------------------------------------------------------------ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//------------------------------------------------------------------------------------------------------------------------------ +// ABOUT +// ===== +// FSR is a collection of algorithms relating to generating a higher resolution image. +// This specific header focuses on single-image non-temporal image scaling, and related tools. +// +// The core functions are EASU and RCAS: +// [EASU] Edge Adaptive Spatial Upsampling ....... 1x to 4x area range spatial scaling, clamped adaptive elliptical filter. +// [RCAS] Robust Contrast Adaptive Sharpening .... A non-scaling variation on CAS. +// RCAS needs to be applied after EASU as a separate pass. +// +// Optional utility functions are: +// [LFGA] Linear Film Grain Applicator ........... Tool to apply film grain after scaling. +// [SRTM] Simple Reversible Tone-Mapper .......... Linear HDR {0 to FP16_MAX} to {0 to 1} and back. +// [TEPD] Temporal Energy Preserving Dither ...... Temporally energy preserving dithered {0 to 1} linear to gamma 2.0 conversion. +// See each individual sub-section for inline documentation. +//------------------------------------------------------------------------------------------------------------------------------ +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//------------------------------------------------------------------------------------------------------------------------------ +// FUNCTION PERMUTATIONS +// ===================== +// *F() ..... Single item computation with 32-bit. +// *H() ..... Single item computation with 16-bit, with packing (aka two 16-bit ops in parallel) when possible. +// *Hx2() ... Processing two items in parallel with 16-bit, easier packing. +// Not all interfaces in this file have a *Hx2() form. +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// FSR - [EASU] EDGE ADAPTIVE SPATIAL UPSAMPLING +// +//------------------------------------------------------------------------------------------------------------------------------ +// EASU provides a high quality spatial-only scaling at relatively low cost. +// Meaning EASU is appropiate for laptops and other low-end GPUs. +// Quality from 1x to 4x area scaling is good. +//------------------------------------------------------------------------------------------------------------------------------ +// The scalar uses a modified fast approximation to the standard lanczos(size=2) kernel. +// EASU runs in a single pass, so it applies a directionally and anisotropically adaptive radial lanczos. +// This is also kept as simple as possible to have minimum runtime. +//------------------------------------------------------------------------------------------------------------------------------ +// The lanzcos filter has negative lobes, so by itself it will introduce ringing. +// To remove all ringing, the algorithm uses the nearest 2x2 input texels as a neighborhood, +// and limits output to the minimum and maximum of that neighborhood. +//------------------------------------------------------------------------------------------------------------------------------ +// Input image requirements: +// +// Color needs to be encoded as 3 channel[red, green, blue](e.g.XYZ not supported) +// Each channel needs to be in the range[0, 1] +// Any color primaries are supported +// Display / tonemapping curve needs to be as if presenting to sRGB display or similar(e.g.Gamma 2.0) +// There should be no banding in the input +// There should be no high amplitude noise in the input +// There should be no noise in the input that is not at input pixel granularity +// For performance purposes, use 32bpp formats +//------------------------------------------------------------------------------------------------------------------------------ +// Best to apply EASU at the end of the frame after tonemapping +// but before film grain or composite of the UI. +//------------------------------------------------------------------------------------------------------------------------------ +// Example of including this header for D3D HLSL : +// +// #define A_GPU 1 +// #define A_HLSL 1 +// #define A_HALF 1 +// #include "ffx_a.h" +// #define FSR_EASU_H 1 +// #define FSR_RCAS_H 1 +// //declare input callbacks +// #include "ffx_fsr1.h" +// +// Example of including this header for Vulkan GLSL : +// +// #define A_GPU 1 +// #define A_GLSL 1 +// #define A_HALF 1 +// #include "ffx_a.h" +// #define FSR_EASU_H 1 +// #define FSR_RCAS_H 1 +// //declare input callbacks +// #include "ffx_fsr1.h" +// +// Example of including this header for Vulkan HLSL : +// +// #define A_GPU 1 +// #define A_HLSL 1 +// #define A_HLSL_6_2 1 +// #define A_NO_16_BIT_CAST 1 +// #define A_HALF 1 +// #include "ffx_a.h" +// #define FSR_EASU_H 1 +// #define FSR_RCAS_H 1 +// //declare input callbacks +// #include "ffx_fsr1.h" +// +// Example of declaring the required input callbacks for GLSL : +// The callbacks need to gather4 for each color channel using the specified texture coordinate 'p'. +// EASU uses gather4 to reduce position computation logic and for free Arrays of Structures to Structures of Arrays conversion. +// +// AH4 FsrEasuRH(AF2 p){return AH4(textureGather(sampler2D(tex,sam),p,0));} +// AH4 FsrEasuGH(AF2 p){return AH4(textureGather(sampler2D(tex,sam),p,1));} +// AH4 FsrEasuBH(AF2 p){return AH4(textureGather(sampler2D(tex,sam),p,2));} +// ... +// The FsrEasuCon function needs to be called from the CPU or GPU to set up constants. +// The difference in viewport and input image size is there to support Dynamic Resolution Scaling. +// To use FsrEasuCon() on the CPU, define A_CPU before including ffx_a and ffx_fsr1. +// Including a GPU example here, the 'con0' through 'con3' values would be stored out to a constant buffer. +// AU4 con0,con1,con2,con3; +// FsrEasuCon(con0,con1,con2,con3, +// 1920.0,1080.0, // Viewport size (top left aligned) in the input image which is to be scaled. +// 3840.0,2160.0, // The size of the input image. +// 2560.0,1440.0); // The output resolution. +//============================================================================================================================== +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// CONSTANT SETUP +//============================================================================================================================== +// Call to setup required constant values (works on CPU or GPU). +A_STATIC void FsrEasuCon( +outAU4 con0, +outAU4 con1, +outAU4 con2, +outAU4 con3, +// This the rendered image resolution being upscaled +AF1 inputViewportInPixelsX, +AF1 inputViewportInPixelsY, +// This is the resolution of the resource containing the input image (useful for dynamic resolution) +AF1 inputSizeInPixelsX, +AF1 inputSizeInPixelsY, +// This is the display resolution which the input image gets upscaled to +AF1 outputSizeInPixelsX, +AF1 outputSizeInPixelsY){ + // Output integer position to a pixel position in viewport. + con0[0]=AU1_AF1(inputViewportInPixelsX*ARcpF1(outputSizeInPixelsX)); + con0[1]=AU1_AF1(inputViewportInPixelsY*ARcpF1(outputSizeInPixelsY)); + con0[2]=AU1_AF1(AF1_(0.5)*inputViewportInPixelsX*ARcpF1(outputSizeInPixelsX)-AF1_(0.5)); + con0[3]=AU1_AF1(AF1_(0.5)*inputViewportInPixelsY*ARcpF1(outputSizeInPixelsY)-AF1_(0.5)); + // Viewport pixel position to normalized image space. + // This is used to get upper-left of 'F' tap. + con1[0]=AU1_AF1(ARcpF1(inputSizeInPixelsX)); + con1[1]=AU1_AF1(ARcpF1(inputSizeInPixelsY)); + // Centers of gather4, first offset from upper-left of 'F'. + // +---+---+ + // | | | + // +--(0)--+ + // | b | c | + // +---F---+---+---+ + // | e | f | g | h | + // +--(1)--+--(2)--+ + // | i | j | k | l | + // +---+---+---+---+ + // | n | o | + // +--(3)--+ + // | | | + // +---+---+ + con1[2]=AU1_AF1(AF1_( 1.0)*ARcpF1(inputSizeInPixelsX)); + con1[3]=AU1_AF1(AF1_(-1.0)*ARcpF1(inputSizeInPixelsY)); + // These are from (0) instead of 'F'. + con2[0]=AU1_AF1(AF1_(-1.0)*ARcpF1(inputSizeInPixelsX)); + con2[1]=AU1_AF1(AF1_( 2.0)*ARcpF1(inputSizeInPixelsY)); + con2[2]=AU1_AF1(AF1_( 1.0)*ARcpF1(inputSizeInPixelsX)); + con2[3]=AU1_AF1(AF1_( 2.0)*ARcpF1(inputSizeInPixelsY)); + con3[0]=AU1_AF1(AF1_( 0.0)*ARcpF1(inputSizeInPixelsX)); + con3[1]=AU1_AF1(AF1_( 4.0)*ARcpF1(inputSizeInPixelsY)); + con3[2]=con3[3]=0;} + +//If the an offset into the input image resource +A_STATIC void FsrEasuConOffset( + outAU4 con0, + outAU4 con1, + outAU4 con2, + outAU4 con3, + // This the rendered image resolution being upscaled + AF1 inputViewportInPixelsX, + AF1 inputViewportInPixelsY, + // This is the resolution of the resource containing the input image (useful for dynamic resolution) + AF1 inputSizeInPixelsX, + AF1 inputSizeInPixelsY, + // This is the display resolution which the input image gets upscaled to + AF1 outputSizeInPixelsX, + AF1 outputSizeInPixelsY, + // This is the input image offset into the resource containing it (useful for dynamic resolution) + AF1 inputOffsetInPixelsX, + AF1 inputOffsetInPixelsY) { + FsrEasuCon(con0, con1, con2, con3, inputViewportInPixelsX, inputViewportInPixelsY, inputSizeInPixelsX, inputSizeInPixelsY, outputSizeInPixelsX, outputSizeInPixelsY); + con0[2] = AU1_AF1(AF1_(0.5) * inputViewportInPixelsX * ARcpF1(outputSizeInPixelsX) - AF1_(0.5) + inputOffsetInPixelsX); + con0[3] = AU1_AF1(AF1_(0.5) * inputViewportInPixelsY * ARcpF1(outputSizeInPixelsY) - AF1_(0.5) + inputOffsetInPixelsY); +} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// NON-PACKED 32-BIT VERSION +//============================================================================================================================== +#if defined(A_GPU)&&defined(FSR_EASU_F) + // Input callback prototypes, need to be implemented by calling shader + AF4 FsrEasuRF(AF2 p); + AF4 FsrEasuGF(AF2 p); + AF4 FsrEasuBF(AF2 p); +//------------------------------------------------------------------------------------------------------------------------------ + // Filtering for a given tap for the scalar. + void FsrEasuTapF( + inout AF3 aC, // Accumulated color, with negative lobe. + inout AF1 aW, // Accumulated weight. + AF2 off, // Pixel offset from resolve position to tap. + AF2 dir, // Gradient direction. + AF2 len, // Length. + AF1 lob, // Negative lobe strength. + AF1 clp, // Clipping point. + AF3 c){ // Tap color. + // Rotate offset by direction. + AF2 v; + v.x=(off.x*( dir.x))+(off.y*dir.y); + v.y=(off.x*(-dir.y))+(off.y*dir.x); + // Anisotropy. + v*=len; + // Compute distance^2. + AF1 d2=v.x*v.x+v.y*v.y; + // Limit to the window as at corner, 2 taps can easily be outside. + d2=min(d2,clp); + // Approximation of lancos2 without sin() or rcp(), or sqrt() to get x. + // (25/16 * (2/5 * x^2 - 1)^2 - (25/16 - 1)) * (1/4 * x^2 - 1)^2 + // |_______________________________________| |_______________| + // base window + // The general form of the 'base' is, + // (a*(b*x^2-1)^2-(a-1)) + // Where 'a=1/(2*b-b^2)' and 'b' moves around the negative lobe. + AF1 wB=AF1_(2.0/5.0)*d2+AF1_(-1.0); + AF1 wA=lob*d2+AF1_(-1.0); + wB*=wB; + wA*=wA; + wB=AF1_(25.0/16.0)*wB+AF1_(-(25.0/16.0-1.0)); + AF1 w=wB*wA; + // Do weighted average. + aC+=c*w;aW+=w;} +//------------------------------------------------------------------------------------------------------------------------------ + // Accumulate direction and length. + void FsrEasuSetF( + inout AF2 dir, + inout AF1 len, + AF2 pp, + AP1 biS,AP1 biT,AP1 biU,AP1 biV, + AF1 lA,AF1 lB,AF1 lC,AF1 lD,AF1 lE){ + // Compute bilinear weight, branches factor out as predicates are compiler time immediates. + // s t + // u v + AF1 w = AF1_(0.0); + if(biS)w=(AF1_(1.0)-pp.x)*(AF1_(1.0)-pp.y); + if(biT)w= pp.x *(AF1_(1.0)-pp.y); + if(biU)w=(AF1_(1.0)-pp.x)* pp.y ; + if(biV)w= pp.x * pp.y ; + // Direction is the '+' diff. + // a + // b c d + // e + // Then takes magnitude from abs average of both sides of 'c'. + // Length converts gradient reversal to 0, smoothly to non-reversal at 1, shaped, then adding horz and vert terms. + AF1 dc=lD-lC; + AF1 cb=lC-lB; + AF1 lenX=max(abs(dc),abs(cb)); + lenX=APrxLoRcpF1(lenX); + AF1 dirX=lD-lB; + dir.x+=dirX*w; + lenX=ASatF1(abs(dirX)*lenX); + lenX*=lenX; + len+=lenX*w; + // Repeat for the y axis. + AF1 ec=lE-lC; + AF1 ca=lC-lA; + AF1 lenY=max(abs(ec),abs(ca)); + lenY=APrxLoRcpF1(lenY); + AF1 dirY=lE-lA; + dir.y+=dirY*w; + lenY=ASatF1(abs(dirY)*lenY); + lenY*=lenY; + len+=lenY*w;} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrEasuF( + out AF3 pix, + AU2 ip, // Integer pixel position in output. + AU4 con0, // Constants generated by FsrEasuCon(). + AU4 con1, + AU4 con2, + AU4 con3){ +//------------------------------------------------------------------------------------------------------------------------------ + // Get position of 'f'. + AF2 pp=AF2(ip)*AF2_AU2(con0.xy)+AF2_AU2(con0.zw); + AF2 fp=floor(pp); + pp-=fp; +//------------------------------------------------------------------------------------------------------------------------------ + // 12-tap kernel. + // b c + // e f g h + // i j k l + // n o + // Gather 4 ordering. + // a b + // r g + // For packed FP16, need either {rg} or {ab} so using the following setup for gather in all versions, + // a b <- unused (z) + // r g + // a b a b + // r g r g + // a b + // r g <- unused (z) + // Allowing dead-code removal to remove the 'z's. + AF2 p0=fp*AF2_AU2(con1.xy)+AF2_AU2(con1.zw); + // These are from p0 to avoid pulling two constants on pre-Navi hardware. + AF2 p1=p0+AF2_AU2(con2.xy); + AF2 p2=p0+AF2_AU2(con2.zw); + AF2 p3=p0+AF2_AU2(con3.xy); + AF4 bczzR=FsrEasuRF(p0); + AF4 bczzG=FsrEasuGF(p0); + AF4 bczzB=FsrEasuBF(p0); + AF4 ijfeR=FsrEasuRF(p1); + AF4 ijfeG=FsrEasuGF(p1); + AF4 ijfeB=FsrEasuBF(p1); + AF4 klhgR=FsrEasuRF(p2); + AF4 klhgG=FsrEasuGF(p2); + AF4 klhgB=FsrEasuBF(p2); + AF4 zzonR=FsrEasuRF(p3); + AF4 zzonG=FsrEasuGF(p3); + AF4 zzonB=FsrEasuBF(p3); +//------------------------------------------------------------------------------------------------------------------------------ + // Simplest multi-channel approximate luma possible (luma times 2, in 2 FMA/MAD). + AF4 bczzL=bczzB*AF4_(0.5)+(bczzR*AF4_(0.5)+bczzG); + AF4 ijfeL=ijfeB*AF4_(0.5)+(ijfeR*AF4_(0.5)+ijfeG); + AF4 klhgL=klhgB*AF4_(0.5)+(klhgR*AF4_(0.5)+klhgG); + AF4 zzonL=zzonB*AF4_(0.5)+(zzonR*AF4_(0.5)+zzonG); + // Rename. + AF1 bL=bczzL.x; + AF1 cL=bczzL.y; + AF1 iL=ijfeL.x; + AF1 jL=ijfeL.y; + AF1 fL=ijfeL.z; + AF1 eL=ijfeL.w; + AF1 kL=klhgL.x; + AF1 lL=klhgL.y; + AF1 hL=klhgL.z; + AF1 gL=klhgL.w; + AF1 oL=zzonL.z; + AF1 nL=zzonL.w; + // Accumulate for bilinear interpolation. + AF2 dir=AF2_(0.0); + AF1 len=AF1_(0.0); + FsrEasuSetF(dir,len,pp,true, false,false,false,bL,eL,fL,gL,jL); + FsrEasuSetF(dir,len,pp,false,true ,false,false,cL,fL,gL,hL,kL); + FsrEasuSetF(dir,len,pp,false,false,true ,false,fL,iL,jL,kL,nL); + FsrEasuSetF(dir,len,pp,false,false,false,true ,gL,jL,kL,lL,oL); +//------------------------------------------------------------------------------------------------------------------------------ + // Normalize with approximation, and cleanup close to zero. + AF2 dir2=dir*dir; + AF1 dirR=dir2.x+dir2.y; + AP1 zro=dirR w = -m/(n+e+w+s) +// 1 == (w*(n+e+w+s)+m)/(4*w+1) -> w = (1-m)/(n+e+w+s-4*1) +// Then chooses the 'w' which results in no clipping, limits 'w', and multiplies by the 'sharp' amount. +// This solution above has issues with MSAA input as the steps along the gradient cause edge detection issues. +// So RCAS uses 4x the maximum and 4x the minimum (depending on equation)in place of the individual taps. +// As well as switching from 'm' to either the minimum or maximum (depending on side), to help in energy conservation. +// This stabilizes RCAS. +// RCAS does a simple highpass which is normalized against the local contrast then shaped, +// 0.25 +// 0.25 -1 0.25 +// 0.25 +// This is used as a noise detection filter, to reduce the effect of RCAS on grain, and focus on real edges. +// +// GLSL example for the required callbacks : +// +// AH4 FsrRcasLoadH(ASW2 p){return AH4(imageLoad(imgSrc,ASU2(p)));} +// void FsrRcasInputH(inout AH1 r,inout AH1 g,inout AH1 b) +// { +// //do any simple input color conversions here or leave empty if none needed +// } +// +// FsrRcasCon need to be called from the CPU or GPU to set up constants. +// Including a GPU example here, the 'con' value would be stored out to a constant buffer. +// +// AU4 con; +// FsrRcasCon(con, +// 0.0); // The scale is {0.0 := maximum sharpness, to N>0, where N is the number of stops (halving) of the reduction of sharpness}. +// --------------- +// RCAS sharpening supports a CAS-like pass-through alpha via, +// #define FSR_RCAS_PASSTHROUGH_ALPHA 1 +// RCAS also supports a define to enable a more expensive path to avoid some sharpening of noise. +// Would suggest it is better to apply film grain after RCAS sharpening (and after scaling) instead of using this define, +// #define FSR_RCAS_DENOISE 1 +//============================================================================================================================== +// This is set at the limit of providing unnatural results for sharpening. +#define FSR_RCAS_LIMIT (0.25-(1.0/16.0)) +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// CONSTANT SETUP +//============================================================================================================================== +// Call to setup required constant values (works on CPU or GPU). +A_STATIC void FsrRcasCon( +outAU4 con, +// The scale is {0.0 := maximum, to N>0, where N is the number of stops (halving) of the reduction of sharpness}. +AF1 sharpness){ + // Transform from stops to linear value. + sharpness=AExp2F1(-sharpness); + varAF2(hSharp)=initAF2(sharpness,sharpness); + con[0]=AU1_AF1(sharpness); + con[1]=AU1_AH2_AF2(hSharp); + con[2]=0; + con[3]=0;} +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// NON-PACKED 32-BIT VERSION +//============================================================================================================================== +#if defined(A_GPU)&&defined(FSR_RCAS_F) + // Input callback prototypes that need to be implemented by calling shader + AF4 FsrRcasLoadF(ASU2 p); + void FsrRcasInputF(inout AF1 r,inout AF1 g,inout AF1 b); +//------------------------------------------------------------------------------------------------------------------------------ + void FsrRcasF( + out AF1 pixR, // Output values, non-vector so port between RcasFilter() and RcasFilterH() is easy. + out AF1 pixG, + out AF1 pixB, + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + out AF1 pixA, + #endif + AU2 ip, // Integer pixel position in output. + AU4 con){ // Constant generated by RcasSetup(). + // Algorithm uses minimal 3x3 pixel neighborhood. + // b + // d e f + // h + ASU2 sp=ASU2(ip); + AF3 b=FsrRcasLoadF(sp+ASU2( 0,-1)).rgb; + AF3 d=FsrRcasLoadF(sp+ASU2(-1, 0)).rgb; + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + AF4 ee=FsrRcasLoadF(sp); + AF3 e=ee.rgb;pixA=ee.a; + #else + AF3 e=FsrRcasLoadF(sp).rgb; + #endif + AF3 f=FsrRcasLoadF(sp+ASU2( 1, 0)).rgb; + AF3 h=FsrRcasLoadF(sp+ASU2( 0, 1)).rgb; + // Rename (32-bit) or regroup (16-bit). + AF1 bR=b.r; + AF1 bG=b.g; + AF1 bB=b.b; + AF1 dR=d.r; + AF1 dG=d.g; + AF1 dB=d.b; + AF1 eR=e.r; + AF1 eG=e.g; + AF1 eB=e.b; + AF1 fR=f.r; + AF1 fG=f.g; + AF1 fB=f.b; + AF1 hR=h.r; + AF1 hG=h.g; + AF1 hB=h.b; + // Run optional input transform. + FsrRcasInputF(bR,bG,bB); + FsrRcasInputF(dR,dG,dB); + FsrRcasInputF(eR,eG,eB); + FsrRcasInputF(fR,fG,fB); + FsrRcasInputF(hR,hG,hB); + // Luma times 2. + AF1 bL=bB*AF1_(0.5)+(bR*AF1_(0.5)+bG); + AF1 dL=dB*AF1_(0.5)+(dR*AF1_(0.5)+dG); + AF1 eL=eB*AF1_(0.5)+(eR*AF1_(0.5)+eG); + AF1 fL=fB*AF1_(0.5)+(fR*AF1_(0.5)+fG); + AF1 hL=hB*AF1_(0.5)+(hR*AF1_(0.5)+hG); + // Noise detection. + AF1 nz=AF1_(0.25)*bL+AF1_(0.25)*dL+AF1_(0.25)*fL+AF1_(0.25)*hL-eL; + nz=ASatF1(abs(nz)*APrxMedRcpF1(AMax3F1(AMax3F1(bL,dL,eL),fL,hL)-AMin3F1(AMin3F1(bL,dL,eL),fL,hL))); + nz=AF1_(-0.5)*nz+AF1_(1.0); + // Min and max of ring. + AF1 mn4R=min(AMin3F1(bR,dR,fR),hR); + AF1 mn4G=min(AMin3F1(bG,dG,fG),hG); + AF1 mn4B=min(AMin3F1(bB,dB,fB),hB); + AF1 mx4R=max(AMax3F1(bR,dR,fR),hR); + AF1 mx4G=max(AMax3F1(bG,dG,fG),hG); + AF1 mx4B=max(AMax3F1(bB,dB,fB),hB); + // Immediate constants for peak range. + AF2 peakC=AF2(1.0,-1.0*4.0); + // Limiters, these need to be high precision RCPs. + AF1 hitMinR=mn4R*ARcpF1(AF1_(4.0)*mx4R); + AF1 hitMinG=mn4G*ARcpF1(AF1_(4.0)*mx4G); + AF1 hitMinB=mn4B*ARcpF1(AF1_(4.0)*mx4B); + AF1 hitMaxR=(peakC.x-mx4R)*ARcpF1(AF1_(4.0)*mn4R+peakC.y); + AF1 hitMaxG=(peakC.x-mx4G)*ARcpF1(AF1_(4.0)*mn4G+peakC.y); + AF1 hitMaxB=(peakC.x-mx4B)*ARcpF1(AF1_(4.0)*mn4B+peakC.y); + AF1 lobeR=max(-hitMinR,hitMaxR); + AF1 lobeG=max(-hitMinG,hitMaxG); + AF1 lobeB=max(-hitMinB,hitMaxB); + AF1 lobe=max(AF1_(-FSR_RCAS_LIMIT),min(AMax3F1(lobeR,lobeG,lobeB),AF1_(0.0)))*AF1_AU1(con.x); + // Apply noise removal. + #ifdef FSR_RCAS_DENOISE + lobe*=nz; + #endif + // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes. + AF1 rcpL=APrxMedRcpF1(AF1_(4.0)*lobe+AF1_(1.0)); + pixR=(lobe*bR+lobe*dR+lobe*hR+lobe*fR+eR)*rcpL; + pixG=(lobe*bG+lobe*dG+lobe*hG+lobe*fG+eG)*rcpL; + pixB=(lobe*bB+lobe*dB+lobe*hB+lobe*fB+eB)*rcpL; + return;} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// NON-PACKED 16-BIT VERSION +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF)&&defined(FSR_RCAS_H) + // Input callback prototypes that need to be implemented by calling shader + AH4 FsrRcasLoadH(ASW2 p); + void FsrRcasInputH(inout AH1 r,inout AH1 g,inout AH1 b); +//------------------------------------------------------------------------------------------------------------------------------ + void FsrRcasH( + out AH1 pixR, // Output values, non-vector so port between RcasFilter() and RcasFilterH() is easy. + out AH1 pixG, + out AH1 pixB, + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + out AH1 pixA, + #endif + AU2 ip, // Integer pixel position in output. + AU4 con){ // Constant generated by RcasSetup(). + // Sharpening algorithm uses minimal 3x3 pixel neighborhood. + // b + // d e f + // h + ASW2 sp=ASW2(ip); + AH3 b=FsrRcasLoadH(sp+ASW2( 0,-1)).rgb; + AH3 d=FsrRcasLoadH(sp+ASW2(-1, 0)).rgb; + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + AH4 ee=FsrRcasLoadH(sp); + AH3 e=ee.rgb;pixA=ee.a; + #else + AH3 e=FsrRcasLoadH(sp).rgb; + #endif + AH3 f=FsrRcasLoadH(sp+ASW2( 1, 0)).rgb; + AH3 h=FsrRcasLoadH(sp+ASW2( 0, 1)).rgb; + // Rename (32-bit) or regroup (16-bit). + AH1 bR=b.r; + AH1 bG=b.g; + AH1 bB=b.b; + AH1 dR=d.r; + AH1 dG=d.g; + AH1 dB=d.b; + AH1 eR=e.r; + AH1 eG=e.g; + AH1 eB=e.b; + AH1 fR=f.r; + AH1 fG=f.g; + AH1 fB=f.b; + AH1 hR=h.r; + AH1 hG=h.g; + AH1 hB=h.b; + // Run optional input transform. + FsrRcasInputH(bR,bG,bB); + FsrRcasInputH(dR,dG,dB); + FsrRcasInputH(eR,eG,eB); + FsrRcasInputH(fR,fG,fB); + FsrRcasInputH(hR,hG,hB); + // Luma times 2. + AH1 bL=bB*AH1_(0.5)+(bR*AH1_(0.5)+bG); + AH1 dL=dB*AH1_(0.5)+(dR*AH1_(0.5)+dG); + AH1 eL=eB*AH1_(0.5)+(eR*AH1_(0.5)+eG); + AH1 fL=fB*AH1_(0.5)+(fR*AH1_(0.5)+fG); + AH1 hL=hB*AH1_(0.5)+(hR*AH1_(0.5)+hG); + // Noise detection. + AH1 nz=AH1_(0.25)*bL+AH1_(0.25)*dL+AH1_(0.25)*fL+AH1_(0.25)*hL-eL; + nz=ASatH1(abs(nz)*APrxMedRcpH1(AMax3H1(AMax3H1(bL,dL,eL),fL,hL)-AMin3H1(AMin3H1(bL,dL,eL),fL,hL))); + nz=AH1_(-0.5)*nz+AH1_(1.0); + // Min and max of ring. + AH1 mn4R=min(AMin3H1(bR,dR,fR),hR); + AH1 mn4G=min(AMin3H1(bG,dG,fG),hG); + AH1 mn4B=min(AMin3H1(bB,dB,fB),hB); + AH1 mx4R=max(AMax3H1(bR,dR,fR),hR); + AH1 mx4G=max(AMax3H1(bG,dG,fG),hG); + AH1 mx4B=max(AMax3H1(bB,dB,fB),hB); + // Immediate constants for peak range. + AH2 peakC=AH2(1.0,-1.0*4.0); + // Limiters, these need to be high precision RCPs. + AH1 hitMinR=mn4R*ARcpH1(AH1_(4.0)*mx4R); + AH1 hitMinG=mn4G*ARcpH1(AH1_(4.0)*mx4G); + AH1 hitMinB=mn4B*ARcpH1(AH1_(4.0)*mx4B); + AH1 hitMaxR=(peakC.x-mx4R)*ARcpH1(AH1_(4.0)*mn4R+peakC.y); + AH1 hitMaxG=(peakC.x-mx4G)*ARcpH1(AH1_(4.0)*mn4G+peakC.y); + AH1 hitMaxB=(peakC.x-mx4B)*ARcpH1(AH1_(4.0)*mn4B+peakC.y); + AH1 lobeR=max(-hitMinR,hitMaxR); + AH1 lobeG=max(-hitMinG,hitMaxG); + AH1 lobeB=max(-hitMinB,hitMaxB); + AH1 lobe=max(AH1_(-FSR_RCAS_LIMIT),min(AMax3H1(lobeR,lobeG,lobeB),AH1_(0.0)))*AH2_AU1(con.y).x; + // Apply noise removal. + #ifdef FSR_RCAS_DENOISE + lobe*=nz; + #endif + // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes. + AH1 rcpL=APrxMedRcpH1(AH1_(4.0)*lobe+AH1_(1.0)); + pixR=(lobe*bR+lobe*dR+lobe*hR+lobe*fR+eR)*rcpL; + pixG=(lobe*bG+lobe*dG+lobe*hG+lobe*fG+eG)*rcpL; + pixB=(lobe*bB+lobe*dB+lobe*hB+lobe*fB+eB)*rcpL;} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// PACKED 16-BIT VERSION +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF)&&defined(FSR_RCAS_HX2) + // Input callback prototypes that need to be implemented by the calling shader + AH4 FsrRcasLoadHx2(ASW2 p); + void FsrRcasInputHx2(inout AH2 r,inout AH2 g,inout AH2 b); +//------------------------------------------------------------------------------------------------------------------------------ + // Can be used to convert from packed Structures of Arrays to Arrays of Structures for store. + void FsrRcasDepackHx2(out AH4 pix0,out AH4 pix1,AH2 pixR,AH2 pixG,AH2 pixB){ + #ifdef A_HLSL + // Invoke a slower path for DX only, since it won't allow uninitialized values. + pix0.a=pix1.a=0.0; + #endif + pix0.rgb=AH3(pixR.x,pixG.x,pixB.x); + pix1.rgb=AH3(pixR.y,pixG.y,pixB.y);} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrRcasHx2( + // Output values are for 2 8x8 tiles in a 16x8 region. + // pix.x = left 8x8 tile + // pix.y = right 8x8 tile + // This enables later processing to easily be packed as well. + out AH2 pixR, + out AH2 pixG, + out AH2 pixB, + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + out AH2 pixA, + #endif + AU2 ip, // Integer pixel position in output. + AU4 con){ // Constant generated by RcasSetup(). + // No scaling algorithm uses minimal 3x3 pixel neighborhood. + ASW2 sp0=ASW2(ip); + AH3 b0=FsrRcasLoadHx2(sp0+ASW2( 0,-1)).rgb; + AH3 d0=FsrRcasLoadHx2(sp0+ASW2(-1, 0)).rgb; + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + AH4 ee0=FsrRcasLoadHx2(sp0); + AH3 e0=ee0.rgb;pixA.r=ee0.a; + #else + AH3 e0=FsrRcasLoadHx2(sp0).rgb; + #endif + AH3 f0=FsrRcasLoadHx2(sp0+ASW2( 1, 0)).rgb; + AH3 h0=FsrRcasLoadHx2(sp0+ASW2( 0, 1)).rgb; + ASW2 sp1=sp0+ASW2(8,0); + AH3 b1=FsrRcasLoadHx2(sp1+ASW2( 0,-1)).rgb; + AH3 d1=FsrRcasLoadHx2(sp1+ASW2(-1, 0)).rgb; + #ifdef FSR_RCAS_PASSTHROUGH_ALPHA + AH4 ee1=FsrRcasLoadHx2(sp1); + AH3 e1=ee1.rgb;pixA.g=ee1.a; + #else + AH3 e1=FsrRcasLoadHx2(sp1).rgb; + #endif + AH3 f1=FsrRcasLoadHx2(sp1+ASW2( 1, 0)).rgb; + AH3 h1=FsrRcasLoadHx2(sp1+ASW2( 0, 1)).rgb; + // Arrays of Structures to Structures of Arrays conversion. + AH2 bR=AH2(b0.r,b1.r); + AH2 bG=AH2(b0.g,b1.g); + AH2 bB=AH2(b0.b,b1.b); + AH2 dR=AH2(d0.r,d1.r); + AH2 dG=AH2(d0.g,d1.g); + AH2 dB=AH2(d0.b,d1.b); + AH2 eR=AH2(e0.r,e1.r); + AH2 eG=AH2(e0.g,e1.g); + AH2 eB=AH2(e0.b,e1.b); + AH2 fR=AH2(f0.r,f1.r); + AH2 fG=AH2(f0.g,f1.g); + AH2 fB=AH2(f0.b,f1.b); + AH2 hR=AH2(h0.r,h1.r); + AH2 hG=AH2(h0.g,h1.g); + AH2 hB=AH2(h0.b,h1.b); + // Run optional input transform. + FsrRcasInputHx2(bR,bG,bB); + FsrRcasInputHx2(dR,dG,dB); + FsrRcasInputHx2(eR,eG,eB); + FsrRcasInputHx2(fR,fG,fB); + FsrRcasInputHx2(hR,hG,hB); + // Luma times 2. + AH2 bL=bB*AH2_(0.5)+(bR*AH2_(0.5)+bG); + AH2 dL=dB*AH2_(0.5)+(dR*AH2_(0.5)+dG); + AH2 eL=eB*AH2_(0.5)+(eR*AH2_(0.5)+eG); + AH2 fL=fB*AH2_(0.5)+(fR*AH2_(0.5)+fG); + AH2 hL=hB*AH2_(0.5)+(hR*AH2_(0.5)+hG); + // Noise detection. + AH2 nz=AH2_(0.25)*bL+AH2_(0.25)*dL+AH2_(0.25)*fL+AH2_(0.25)*hL-eL; + nz=ASatH2(abs(nz)*APrxMedRcpH2(AMax3H2(AMax3H2(bL,dL,eL),fL,hL)-AMin3H2(AMin3H2(bL,dL,eL),fL,hL))); + nz=AH2_(-0.5)*nz+AH2_(1.0); + // Min and max of ring. + AH2 mn4R=min(AMin3H2(bR,dR,fR),hR); + AH2 mn4G=min(AMin3H2(bG,dG,fG),hG); + AH2 mn4B=min(AMin3H2(bB,dB,fB),hB); + AH2 mx4R=max(AMax3H2(bR,dR,fR),hR); + AH2 mx4G=max(AMax3H2(bG,dG,fG),hG); + AH2 mx4B=max(AMax3H2(bB,dB,fB),hB); + // Immediate constants for peak range. + AH2 peakC=AH2(1.0,-1.0*4.0); + // Limiters, these need to be high precision RCPs. + AH2 hitMinR=mn4R*ARcpH2(AH2_(4.0)*mx4R); + AH2 hitMinG=mn4G*ARcpH2(AH2_(4.0)*mx4G); + AH2 hitMinB=mn4B*ARcpH2(AH2_(4.0)*mx4B); + AH2 hitMaxR=(peakC.x-mx4R)*ARcpH2(AH2_(4.0)*mn4R+peakC.y); + AH2 hitMaxG=(peakC.x-mx4G)*ARcpH2(AH2_(4.0)*mn4G+peakC.y); + AH2 hitMaxB=(peakC.x-mx4B)*ARcpH2(AH2_(4.0)*mn4B+peakC.y); + AH2 lobeR=max(-hitMinR,hitMaxR); + AH2 lobeG=max(-hitMinG,hitMaxG); + AH2 lobeB=max(-hitMinB,hitMaxB); + AH2 lobe=max(AH2_(-FSR_RCAS_LIMIT),min(AMax3H2(lobeR,lobeG,lobeB),AH2_(0.0)))*AH2_(AH2_AU1(con.y).x); + // Apply noise removal. + #ifdef FSR_RCAS_DENOISE + lobe*=nz; + #endif + // Resolve, which needs the medium precision rcp approximation to avoid visible tonality changes. + AH2 rcpL=APrxMedRcpH2(AH2_(4.0)*lobe+AH2_(1.0)); + pixR=(lobe*bR+lobe*dR+lobe*hR+lobe*fR+eR)*rcpL; + pixG=(lobe*bG+lobe*dG+lobe*hG+lobe*fG+eG)*rcpL; + pixB=(lobe*bB+lobe*dB+lobe*hB+lobe*fB+eB)*rcpL;} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// FSR - [LFGA] LINEAR FILM GRAIN APPLICATOR +// +//------------------------------------------------------------------------------------------------------------------------------ +// Adding output-resolution film grain after scaling is a good way to mask both rendering and scaling artifacts. +// Suggest using tiled blue noise as film grain input, with peak noise frequency set for a specific look and feel. +// The 'Lfga*()' functions provide a convenient way to introduce grain. +// These functions limit grain based on distance to signal limits. +// This is done so that the grain is temporally energy preserving, and thus won't modify image tonality. +// Grain application should be done in a linear colorspace. +// The grain should be temporally changing, but have a temporal sum per pixel that adds to zero (non-biased). +//------------------------------------------------------------------------------------------------------------------------------ +// Usage, +// FsrLfga*( +// color, // In/out linear colorspace color {0 to 1} ranged. +// grain, // Per pixel grain texture value {-0.5 to 0.5} ranged, input is 3-channel to support colored grain. +// amount); // Amount of grain (0 to 1} ranged. +//------------------------------------------------------------------------------------------------------------------------------ +// Example if grain texture is monochrome: 'FsrLfgaF(color,AF3_(grain),amount)' +//============================================================================================================================== +#if defined(A_GPU) + // Maximum grain is the minimum distance to the signal limit. + void FsrLfgaF(inout AF3 c,AF3 t,AF1 a){c+=(t*AF3_(a))*min(AF3_(1.0)-c,c);} +#endif +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF) + // Half precision version (slower). + void FsrLfgaH(inout AH3 c,AH3 t,AH1 a){c+=(t*AH3_(a))*min(AH3_(1.0)-c,c);} +//------------------------------------------------------------------------------------------------------------------------------ + // Packed half precision version (faster). + void FsrLfgaHx2(inout AH2 cR,inout AH2 cG,inout AH2 cB,AH2 tR,AH2 tG,AH2 tB,AH1 a){ + cR+=(tR*AH2_(a))*min(AH2_(1.0)-cR,cR);cG+=(tG*AH2_(a))*min(AH2_(1.0)-cG,cG);cB+=(tB*AH2_(a))*min(AH2_(1.0)-cB,cB);} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// FSR - [SRTM] SIMPLE REVERSIBLE TONE-MAPPER +// +//------------------------------------------------------------------------------------------------------------------------------ +// This provides a way to take linear HDR color {0 to FP16_MAX} and convert it into a temporary {0 to 1} ranged post-tonemapped linear. +// The tonemapper preserves RGB ratio, which helps maintain HDR color bleed during filtering. +//------------------------------------------------------------------------------------------------------------------------------ +// Reversible tonemapper usage, +// FsrSrtm*(color); // {0 to FP16_MAX} converted to {0 to 1}. +// FsrSrtmInv*(color); // {0 to 1} converted into {0 to 32768, output peak safe for FP16}. +//============================================================================================================================== +#if defined(A_GPU) + void FsrSrtmF(inout AF3 c){c*=AF3_(ARcpF1(AMax3F1(c.r,c.g,c.b)+AF1_(1.0)));} + // The extra max solves the c=1.0 case (which is a /0). + void FsrSrtmInvF(inout AF3 c){c*=AF3_(ARcpF1(max(AF1_(1.0/32768.0),AF1_(1.0)-AMax3F1(c.r,c.g,c.b))));} +#endif +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF) + void FsrSrtmH(inout AH3 c){c*=AH3_(ARcpH1(AMax3H1(c.r,c.g,c.b)+AH1_(1.0)));} + void FsrSrtmInvH(inout AH3 c){c*=AH3_(ARcpH1(max(AH1_(1.0/32768.0),AH1_(1.0)-AMax3H1(c.r,c.g,c.b))));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrSrtmHx2(inout AH2 cR,inout AH2 cG,inout AH2 cB){ + AH2 rcp=ARcpH2(AMax3H2(cR,cG,cB)+AH2_(1.0));cR*=rcp;cG*=rcp;cB*=rcp;} + void FsrSrtmInvHx2(inout AH2 cR,inout AH2 cG,inout AH2 cB){ + AH2 rcp=ARcpH2(max(AH2_(1.0/32768.0),AH2_(1.0)-AMax3H2(cR,cG,cB)));cR*=rcp;cG*=rcp;cB*=rcp;} +#endif +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +//_____________________________________________________________/\_______________________________________________________________ +//============================================================================================================================== +// +// FSR - [TEPD] TEMPORAL ENERGY PRESERVING DITHER +// +//------------------------------------------------------------------------------------------------------------------------------ +// Temporally energy preserving dithered {0 to 1} linear to gamma 2.0 conversion. +// Gamma 2.0 is used so that the conversion back to linear is just to square the color. +// The conversion comes in 8-bit and 10-bit modes, designed for output to 8-bit UNORM or 10:10:10:2 respectively. +// Given good non-biased temporal blue noise as dither input, +// the output dither will temporally conserve energy. +// This is done by choosing the linear nearest step point instead of perceptual nearest. +// See code below for details. +//------------------------------------------------------------------------------------------------------------------------------ +// DX SPEC RULES FOR FLOAT->UNORM 8-BIT CONVERSION +// =============================================== +// - Output is 'uint(floor(saturate(n)*255.0+0.5))'. +// - Thus rounding is to nearest. +// - NaN gets converted to zero. +// - INF is clamped to {0.0 to 1.0}. +//============================================================================================================================== +#if defined(A_GPU) + // Hand tuned integer position to dither value, with more values than simple checkerboard. + // Only 32-bit has enough precision for this compddation. + // Output is {0 to <1}. + AF1 FsrTepdDitF(AU2 p,AU1 f){ + AF1 x=AF1_(p.x+f); + AF1 y=AF1_(p.y); + // The 1.61803 golden ratio. + AF1 a=AF1_((1.0+sqrt(5.0))/2.0); + // Number designed to provide a good visual pattern. + AF1 b=AF1_(1.0/3.69); + x=x*a+(y*b); + return AFractF1(x);} +//------------------------------------------------------------------------------------------------------------------------------ + // This version is 8-bit gamma 2.0. + // The 'c' input is {0 to 1}. + // Output is {0 to 1} ready for image store. + void FsrTepdC8F(inout AF3 c,AF1 dit){ + AF3 n=sqrt(c); + n=floor(n*AF3_(255.0))*AF3_(1.0/255.0); + AF3 a=n*n; + AF3 b=n+AF3_(1.0/255.0);b=b*b; + // Ratio of 'a' to 'b' required to produce 'c'. + // APrxLoRcpF1() won't work here (at least for very high dynamic ranges). + // APrxMedRcpF1() is an IADD,FMA,MUL. + AF3 r=(c-b)*APrxMedRcpF3(a-b); + // Use the ratio as a cutoff to choose 'a' or 'b'. + // AGtZeroF1() is a MUL. + c=ASatF3(n+AGtZeroF3(AF3_(dit)-r)*AF3_(1.0/255.0));} +//------------------------------------------------------------------------------------------------------------------------------ + // This version is 10-bit gamma 2.0. + // The 'c' input is {0 to 1}. + // Output is {0 to 1} ready for image store. + void FsrTepdC10F(inout AF3 c,AF1 dit){ + AF3 n=sqrt(c); + n=floor(n*AF3_(1023.0))*AF3_(1.0/1023.0); + AF3 a=n*n; + AF3 b=n+AF3_(1.0/1023.0);b=b*b; + AF3 r=(c-b)*APrxMedRcpF3(a-b); + c=ASatF3(n+AGtZeroF3(AF3_(dit)-r)*AF3_(1.0/1023.0));} +#endif +//============================================================================================================================== +#if defined(A_GPU)&&defined(A_HALF) + AH1 FsrTepdDitH(AU2 p,AU1 f){ + AF1 x=AF1_(p.x+f); + AF1 y=AF1_(p.y); + AF1 a=AF1_((1.0+sqrt(5.0))/2.0); + AF1 b=AF1_(1.0/3.69); + x=x*a+(y*b); + return AH1(AFractF1(x));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrTepdC8H(inout AH3 c,AH1 dit){ + AH3 n=sqrt(c); + n=floor(n*AH3_(255.0))*AH3_(1.0/255.0); + AH3 a=n*n; + AH3 b=n+AH3_(1.0/255.0);b=b*b; + AH3 r=(c-b)*APrxMedRcpH3(a-b); + c=ASatH3(n+AGtZeroH3(AH3_(dit)-r)*AH3_(1.0/255.0));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrTepdC10H(inout AH3 c,AH1 dit){ + AH3 n=sqrt(c); + n=floor(n*AH3_(1023.0))*AH3_(1.0/1023.0); + AH3 a=n*n; + AH3 b=n+AH3_(1.0/1023.0);b=b*b; + AH3 r=(c-b)*APrxMedRcpH3(a-b); + c=ASatH3(n+AGtZeroH3(AH3_(dit)-r)*AH3_(1.0/1023.0));} +//============================================================================================================================== + // This computes dither for positions 'p' and 'p+{8,0}'. + AH2 FsrTepdDitHx2(AU2 p,AU1 f){ + AF2 x; + x.x=AF1_(p.x+f); + x.y=x.x+AF1_(8.0); + AF1 y=AF1_(p.y); + AF1 a=AF1_((1.0+sqrt(5.0))/2.0); + AF1 b=AF1_(1.0/3.69); + x=x*AF2_(a)+AF2_(y*b); + return AH2(AFractF2(x));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrTepdC8Hx2(inout AH2 cR,inout AH2 cG,inout AH2 cB,AH2 dit){ + AH2 nR=sqrt(cR); + AH2 nG=sqrt(cG); + AH2 nB=sqrt(cB); + nR=floor(nR*AH2_(255.0))*AH2_(1.0/255.0); + nG=floor(nG*AH2_(255.0))*AH2_(1.0/255.0); + nB=floor(nB*AH2_(255.0))*AH2_(1.0/255.0); + AH2 aR=nR*nR; + AH2 aG=nG*nG; + AH2 aB=nB*nB; + AH2 bR=nR+AH2_(1.0/255.0);bR=bR*bR; + AH2 bG=nG+AH2_(1.0/255.0);bG=bG*bG; + AH2 bB=nB+AH2_(1.0/255.0);bB=bB*bB; + AH2 rR=(cR-bR)*APrxMedRcpH2(aR-bR); + AH2 rG=(cG-bG)*APrxMedRcpH2(aG-bG); + AH2 rB=(cB-bB)*APrxMedRcpH2(aB-bB); + cR=ASatH2(nR+AGtZeroH2(dit-rR)*AH2_(1.0/255.0)); + cG=ASatH2(nG+AGtZeroH2(dit-rG)*AH2_(1.0/255.0)); + cB=ASatH2(nB+AGtZeroH2(dit-rB)*AH2_(1.0/255.0));} +//------------------------------------------------------------------------------------------------------------------------------ + void FsrTepdC10Hx2(inout AH2 cR,inout AH2 cG,inout AH2 cB,AH2 dit){ + AH2 nR=sqrt(cR); + AH2 nG=sqrt(cG); + AH2 nB=sqrt(cB); + nR=floor(nR*AH2_(1023.0))*AH2_(1.0/1023.0); + nG=floor(nG*AH2_(1023.0))*AH2_(1.0/1023.0); + nB=floor(nB*AH2_(1023.0))*AH2_(1.0/1023.0); + AH2 aR=nR*nR; + AH2 aG=nG*nG; + AH2 aB=nB*nB; + AH2 bR=nR+AH2_(1.0/1023.0);bR=bR*bR; + AH2 bG=nG+AH2_(1.0/1023.0);bG=bG*bG; + AH2 bB=nB+AH2_(1.0/1023.0);bB=bB*bB; + AH2 rR=(cR-bR)*APrxMedRcpH2(aR-bR); + AH2 rG=(cG-bG)*APrxMedRcpH2(aG-bG); + AH2 rB=(cB-bB)*APrxMedRcpH2(aB-bB); + cR=ASatH2(nR+AGtZeroH2(dit-rR)*AH2_(1.0/1023.0)); + cG=ASatH2(nG+AGtZeroH2(dit-rG)*AH2_(1.0/1023.0)); + cB=ASatH2(nB+AGtZeroH2(dit-rB)*AH2_(1.0/1023.0));} +#endif + +void main() +{ + AU4 con; + FsrRcasCon(con, sharpness); + + float r = 0, g = 0, b = 0; + + FsrRcasF(r, g, b, uvec2(fragTexCoord * dstSize), con); + + finalColor = vec4(r, g, b, 1.0); +} + diff --git a/examples/shaders_postprocess_fsr.lua b/examples/shaders_postprocess_fsr.lua new file mode 100644 index 0000000..f4ef83e --- /dev/null +++ b/examples/shaders_postprocess_fsr.lua @@ -0,0 +1,232 @@ +local screenWidth = 1920 +local screenHeight = 1080 + +local preset = 3 +local default_sharpness = 1.0 + +local presets = { + { 2.0, "Performance" }, + { 1.7, "Balanced" }, + { 1.5, "Quality" }, + { 1.3, "Ultra Quality" }, + { 1.0, "Custom (Native)" } +} + +local use_fsr = true + +local ratio = presets[preset][1] + +local num_blocks = 15 + +local screenSize = rl.new("Vector2", screenWidth, screenHeight) + +local fbWidth = screenWidth / ratio +local fbHeight = screenHeight / ratio + +local fbSize = rl.new("Vector2", fbWidth, fbHeight) + +rl.SetConfigFlags(rl.FLAG_FULLSCREEN_MODE) +rl.InitWindow(screenWidth, screenHeight, "raylua [shaders] example - AMD FSR") + +local render_texture = rl.LoadRenderTexture(fbWidth, fbHeight) + +local dest_fb = rl.LoadRenderTexture(screenWidth, screenHeight) + +local easu_shader = rl.LoadShader(nil, "resources/fsr/fsrEasu.frag") + +local srcSize_loc = rl.GetShaderLocation(easu_shader, "srcSize") +local dstSize_loc = rl.GetShaderLocation(easu_shader, "dstSize") + +rl.SetShaderValue(easu_shader, srcSize_loc, fbSize, rl.SHADER_UNIFORM_VEC2) +rl.SetShaderValue(easu_shader, dstSize_loc, screenSize, rl.SHADER_UNIFORM_VEC2) + +local rcas_shader = rl.LoadShader(nil, "resources/fsr/fsrRcas.frag") + +local sharpness_loc = rl.GetShaderLocation(rcas_shader, "sharpness") +local size_loc = rl.GetShaderLocation(rcas_shader, "dstSize") + +local sharpness = rl.new("float[1]", default_sharpness) + +rl.SetShaderValue(rcas_shader, sharpness_loc, sharpness, rl.SHADER_UNIFORM_FLOAT) +rl.SetShaderValue(rcas_shader, size_loc, screenSize, rl.SHADER_UNIFORM_VEC2) + +local camera = rl.new("Camera3D", { + position = { 30, 20, 30 }, + target = { 0, 0, 0 }, + up = { 0, 1, 0 }, + fovy = 70, + type = rl.CAMERA_PERSPECTIVE +}) + +local enable_easu = false +local enable_rcas = false + +local stop_animation = false +local t = 0.0 + +local bilinear = false + +while not rl.WindowShouldClose() do + if not stop_animation then + t = rl.GetTime() + end + + local scale = (2.0 + math.sin(t)) * 0.7 + local camera_time = t * 0.3 + + camera.position.x = math.cos(camera_time) * 40.0 + camera.position.z = math.sin(camera_time) * 40.0 + + if use_fsr then + rl.BeginTextureMode(render_texture) + else + rl.BeginDrawing() + end + + rl.BeginMode3D(camera) + rl.ClearBackground(rl.RAYWHITE) + + rl.DrawGrid(10, 5.0) + + for x=0,num_blocks-1 do + for y=0,num_blocks-1 do + for z=0,num_blocks-1 do + local block_scale = (x + y + z) / 30 + local scatter = math.sin(block_scale * 20.0 + t * 4.0) + + local cube_pos = rl.new("Vector3", + (x - num_blocks / 2) * (scale * 3.0) + scatter, + (y - num_blocks / 2) * (scale * 2.0) + scatter, + (z - num_blocks / 2) * (scale * 3.0) + scatter) + + local cube_color = rl.ColorFromHSV( + (((x + y + z) * 18) % 360), 0.75, 0.9 + ) + + local cube_size = (2.4 - scale) * block_scale + + rl.DrawCube(cube_pos, cube_size, cube_size, cube_size, cube_color) + end + end + end + + rl.EndMode3D() + + if use_fsr then + rl.EndTextureMode() + + rl.BeginTextureMode(dest_fb) + rl.ClearBackground(rl.RAYWHITE) + + if enable_easu then + rl.BeginShaderMode(easu_shader) + end + rl.DrawTextureEx(render_texture.texture, screenSize, 180, ratio, rl.WHITE) + if enable_easu then + rl.EndShaderMode() + end + + rl.EndTextureMode() + + rl.BeginDrawing() + rl.ClearBackground(rl.WHITE) + + if enable_rcas then + rl.BeginShaderMode(rcas_shader) + end + rl.DrawTextureEx(dest_fb.texture, screenSize, 180, 1.0, rl.WHITE) + if enable_rcas then + rl.EndShaderMode() + end + end + + rl.DrawFPS(10, 10) + + if use_fsr then + rl.DrawText("EASU", 10, 32, 20, enable_easu and rl.GREEN or rl.RED) + rl.DrawText("RCAS", 10, 54, 20, enable_rcas and rl.GREEN or rl.RED) + rl.DrawText(string.format("sharpness: %.1f", sharpness[0]), 10, 74, 10, rl.BLACK) + rl.DrawText(string.format("Preset: %s (%.1f)", presets[preset][2], ratio), 10, 86, 10, rl.BLACK) + rl.DrawText(string.format("Resolution: %dx%d", fbWidth, fbHeight), 10, 98, 10, rl.BLACK) + rl.DrawText(string.format("Filter: %s", bilinear and "bilinear" or "point"), 10, 110, 10, rl.BLACK) + else + rl.DrawText("NATIVE", 10, 32, 20, rl.BLUE) + end + + rl.EndDrawing() + + if rl.IsKeyPressed(rl.KEY_E) then + enable_easu = not enable_easu + end + + if rl.IsKeyPressed(rl.KEY_R) then + enable_rcas = not enable_rcas + end + + if rl.IsKeyPressed(rl.KEY_N) then + use_fsr = not use_fsr + end + + if rl.IsKeyPressed(rl.KEY_LEFT) then + sharpness[0] = math.max(0.0, sharpness[0] - 0.1) + rl.SetShaderValue(rcas_shader, sharpness_loc, sharpness, rl.SHADER_UNIFORM_FLOAT) + end + + if rl.IsKeyPressed(rl.KEY_RIGHT) then + sharpness[0] = math.min(2.0, sharpness[0] + 0.1) + rl.SetShaderValue(rcas_shader, sharpness_loc, sharpness, rl.SHADER_UNIFORM_FLOAT) + end + + if rl.IsKeyPressed(rl.KEY_F) then + bilinear = not bilinear + + rl.SetTextureFilter(render_texture.texture, + bilinear and rl.TEXTURE_FILTER_BILINEAR or rl.TEXTURE_FILTER_POINT) + end + + local preset_changed = false + if rl.IsKeyPressed(rl.KEY_UP) then + preset_changed = true + preset = preset + 1 + if preset == #presets + 1 then + preset = 1 + end + end + + if rl.IsKeyPressed(rl.KEY_DOWN) then + preset_changed = true + preset = preset - 1 + if preset == 0 then + preset = #presets + end + end + + if rl.IsKeyPressed(rl.KEY_S) then + stop_animation = not stop_animation + end + + if rl.IsKeyPressed(rl.KEY_F11) then + rl.ToggleFullscreen() + end + + + if preset_changed then + ratio = presets[preset][1] + fbWidth = screenWidth / ratio + fbHeight = screenHeight / ratio + + fbSize = rl.new("Vector2", fbWidth, fbHeight) + + rl.UnloadRenderTexture(render_texture) + + render_texture = rl.LoadRenderTexture(fbWidth, fbHeight) + + + rl.SetTextureFilter(render_texture.texture, + bilinear and rl.TEXTURE_FILTER_BILINEAR or rl.TEXTURE_FILTER_POINT) + end +end + +rl.UnloadRenderTexture(render_texture) +rl.UnloadRenderTexture(dest_fb) +rl.CloseWindow() diff --git a/examples/text_custom_font.lua b/examples/text_custom_font.lua new file mode 100644 index 0000000..2063a05 --- /dev/null +++ b/examples/text_custom_font.lua @@ -0,0 +1,18 @@ +rl.SetConfigFlags(rl.FLAG_VSYNC_HINT) + +rl.InitWindow(800, 450, "raylib [core] example - basic window") + +local font = rl.LoadFontEx("resources/NotoSans-Medium.ttf", 32, nil, 255) + +while not rl.WindowShouldClose() do + rl.BeginDrawing() + + rl.DrawFPS(10, 10) + + rl.ClearBackground(rl.RAYWHITE) + rl.DrawTextEx(font, "Congrats! You created your first window!", rl.new("Vector2", 174, 200), 32, 0, rl.BLACK) + + rl.EndDrawing() +end + +rl.CloseWindow()