From 4cf5677903bac3be7a4b18547cc2caa5c23bac67 Mon Sep 17 00:00:00 2001 From: Gary Hsu Date: Fri, 17 May 2024 13:50:44 -0700 Subject: [PATCH] Update to Babylon.js 7.6.2 (#1373) Updating to Babylon.js version 7.0.0 reveals a whole slew of issues when running the validation tests because [#14868](https://github.com/BabylonJS/Babylon.js/pull/14868) broke the render loop stopping behavior when running in native. Native does not implement a way to cancel a requested animation frame. This missing canceling functionality should be added to native someday, but, in the meantime, [#15086](https://github.com/BabylonJS/Babylon.js/pull/15086) will make it behave like it did before. These changes fix potential issues in the code regardless of what happened on the JS side. - Update to latest JsRuntimeHost with lots of fixes. - Call SetRenderResetCallback with null to clear the callback that can cause a crash on shutdown. - Update a few validation tests to not use render count. - Update scripts to use `const`/`let` instead of `var`. - Update validation script to handle some errors better and clean up dead code. - Add unhandled exception handler to Playground app so that validation test will report correct errors and exit correctly. - Change core graphics device implemenation to update bgfx state right before requesting screenshots. This will ensure the screenshot is the right size if the resolution has changed. - Fix TestUtils:WritePNG to return an error when the byte length is an unexpected size instead of silently failing. - Change TestUtils::SetTitle to update the HWND title on a background thread to avoid dead lock with the main thread. - Update dynamic texture clip test to newest version that will fail if the feature is not working. --- .../ReferenceImages/dynamicTextureClip.png | Bin 3381 -> 962463 bytes ...HardwareScaling.png => scissor-test-2.png} | Bin 1185543 -> 1185543 bytes ...HardwareScaling.png => scissor-test-3.png} | Bin 427259 -> 427259 bytes .../ReferenceImages/scissor-test.png | Bin 962463 -> 962463 bytes Apps/Playground/Scripts/config.json | 35 +- Apps/Playground/Scripts/playground_runner.js | 2 +- Apps/Playground/Scripts/validation_native.js | 645 +++++++++--------- Apps/Playground/Win32/App.cpp | 18 +- Apps/Playground/macOS/ViewController.mm | 4 +- Apps/package-lock.json | 86 +-- Apps/package.json | 10 +- CMakeLists.txt | 2 +- Core/Graphics/CMakeLists.txt | 1 - Core/Graphics/Source/DeviceContext.cpp | 2 +- Core/Graphics/Source/DeviceImpl.cpp | 53 +- Dependencies/CMakeLists.txt | 5 - Dependencies/napi-extensions/CMakeLists.txt | 2 - .../include/napi/napi_pointer.h | 87 --- Install/Install.cmake | 3 + Plugins/ExternalTexture/CMakeLists.txt | 1 - .../Source/ExternalTexture_D3D11.cpp | 2 +- .../Source/ExternalTexture_D3D12.cpp | 2 +- .../Source/ExternalTexture_Metal.mm | 2 +- .../Source/ExternalTexture_OpenGL.cpp | 2 +- Plugins/NativeCamera/CMakeLists.txt | 1 - Plugins/NativeCamera/Source/NativeCamera.cpp | 2 +- Plugins/NativeCapture/CMakeLists.txt | 3 +- .../NativeCapture/Source/NativeCapture.cpp | 2 +- Plugins/NativeEngine/CMakeLists.txt | 3 +- Plugins/NativeEngine/Source/IndexBuffer.h | 2 +- Plugins/NativeEngine/Source/NativeEngine.cpp | 24 +- Plugins/NativeEngine/Source/NativeEngine.h | 2 + Plugins/NativeEngine/Source/VertexBuffer.h | 2 +- Plugins/NativeTracing/CMakeLists.txt | 3 +- .../NativeTracing/Source/NativeTracing.cpp | 2 +- Plugins/NativeXr/CMakeLists.txt | 1 - Plugins/NativeXr/Source/NativeXr.cpp | 2 +- Plugins/TestUtils/Source/TestUtils.cpp | 4 +- .../TestUtils/Source/Win32/TestUtilsImpl.cpp | 17 +- Polyfills/Canvas/CMakeLists.txt | 3 +- Polyfills/Canvas/Source/Canvas.cpp | 2 +- Polyfills/Canvas/Source/Image.cpp | 2 +- 42 files changed, 486 insertions(+), 555 deletions(-) rename Apps/Playground/ReferenceImages/{scissorTestWith0.9HardwareScaling.png => scissor-test-2.png} (99%) rename Apps/Playground/ReferenceImages/{scissorTestWith1.5HardwareScaling.png => scissor-test-3.png} (99%) delete mode 100644 Dependencies/napi-extensions/CMakeLists.txt delete mode 100644 Dependencies/napi-extensions/include/napi/napi_pointer.h diff --git a/Apps/Playground/ReferenceImages/dynamicTextureClip.png b/Apps/Playground/ReferenceImages/dynamicTextureClip.png index b8d19817164af5e81fed757f28b73758434cb87c..ffd588badec06ac62b281f00bfc9d996609f2f3c 100644 GIT binary patch literal 962463 zcmeFaXO}KXb+*aOhgmc4TJQXT`7nP1fe;{sBm@l{JVJPog~l{A-h=Rs_rkq+x`h=W zoP-ew4~-Wd8iWK$APIz#FhV)!u9~as?%ffQk&*e-G0(2Ga%FY(d8#rackF%T-goS{ z;Knz+&j0)$|Ih!p!WFLYKd*bvwQhQaEBx=byuua!x2OG||HsLn-0MpJ?{ly4fB(gw zT=!bPdGiOn@Cv{Gf4%5Gu5iUGo|y8;F-RLo8%P^S8%P^S8%P`2&IXdQ(werjrX0&0 z%d~;CfwY0NfwX}m8%WAZYdEro9E%)_w1KpNw1KpNw1MqxASo-YX*+AmvCOed8%P^S z8%P^S8#uCoq^z`tBWuX9$gxNpNE=8SNE=8S*vh8&9=i?o5XfwY0NfwY0`Y#=Est!X=J%CXF`OdCiWNE=8SNEA8%1UcEvW6Us z9E-Gpw1KpNw1KpN?Q9?^E3IieYs#_Au}m9C8%P^S8%P^CvVo+mw1y*V$g#+=NE=8S zNE=8SNE_JB29mPUnzpm19LpTbw1KpNw1KpNw1FcVNXklUII@NuiyVuzfwY0NfwY0N zf$eM{DJ!jMJ8R0Z%&|-xNE=8SNE=8SII@AHth9zBYsj(4u}B+88%P^S8%P`2&IXdQ z(werjrX0&0%d~;CfwY0NfwX}m8%WAZYdEro9E%)_w1KpNw1KpNw1MqxASo-YX*+Am zvCOed8%P^S8%P^S8#uCoq^z`tBWuX9$gxNpNE=8SNE=8S*vh8&9=i?o5XfwY0NfwY0`Y#=Est!X=J%CXF`OdCiWNE=8S zNEA8 z%1UcEvW6Us9E-Gpw1KpNw1KpN?Q9?^E3IieYs#_Au}m9C8%P^S8%P^CvVo+mw1y*V z$g#+=NE=8SNE=8SNE_JB29mPUnzpm19LpTbw1KpNw1KpNw1FcVNXklUII@NuiyVuz zfwY0NfwY0Nf$eM{DJ!jMJ8R0Z%&|-xNE=8SNE=8SII@AHth9zBYsj(4u}B+88%P^S z8%P`2&IXdQ(werjrX0&0%d~;CfwY0NfwX}m8%WAZYdEro9E%)_w1KpNw1KpNw1Mqx zASo-YX*+AmvCOed8%P^S8%P^S8#uCoq^z`tBWuX9$gxNpNE=8SNE=8S*vh8&9=i?o5XfwY0NfwY0`Y#=Est!X=J%CXF` zOdCiWNE=8SNEA8%1UcEvWBs-xb}3#D_;Kim0!8`ba-6nF_w`aSjRIy|m-@?1Wya+T{%hsX6#o-cp=+OJ)IIy_FCJYW8}>QzrnN5>6L zet-GnYFE3#badSCPJoO&exWJx(%d`z+2Q=G&ELbHAI|fV4>F~Jnm9Kn~imrT; ze17%dxXM+oa*}Yaa*|?x?cliTRj+!IR<3%IMy_^nT>a`-KN%ISev&SJ{nFza*SN+> za=6Ax68MdS$F;A0 z?McUVu5+D(A2C2 zZggA2a=Zgz0o{N^{Gbo}n`{_ds6EpBm(NyqQ~-tQe8 zx4h*oCmpxC)vYc)Zhh-pPdaXMo7-G^-1fG&opjvpcDK9qxc%*KKXKgQ4tJP1?s&&L zP8`4g`@cVN{J|gm!Nl>0fB1(J#~=OCA5DJz@gM*3aj9`8&49q(WN?PH|xeGK)zkFmaI{B`EJ z=*;udndhi8&sS%jyUuPC*ooT?_T)B(-C>8=rQ0g@i#=oCZu86o<^}VFdBZ$nUNO&n z?lB*kpUhY0FY}rC&3tG6;}7r;_zU+T_!ayNeg{8+`~gIw?}o4_>l%8EkvS-)KMdp7V)%5sr3KucDK7t zOODd-X#K2pt+o4$;}FLwj$<5We+K?+{F(W)^k?kP9(!;b!EW4^urIec>=3)ePTh8~ zXY3n$$NrfQK1Y~0%p>L%^Ne{1ZEZzb@(uV4{0Fo(N?P(^_%)GMQr2?1I6Gq8`uk4G zN=sN{Mc2O66YY|)cz@-6miJqodFSOFn0I2{k@4hsc04_vpGZJtAW{%Lh$ciAq7Bi9 zXrwbyil{{tBdU3%Bl-~$iI6%IIfg`@-Jb z2C+-*6uZTa-L|oJ?4S9-{9wK?f0$3qFXkKbkNL>_WWF+gna|8`pY!+u`~q)cyCkio ztTl0BzdxIlm6Ww+1{_k>sUR%8FWwmMjJL*n<#3P` zHYqD9Yt0O}_JRJ&D}>TGpm#w3fF1&U1X+Bt`egZe7vLwEnpvRGuRP!<+g_XVUO6S+bnjBU1R6iJ@bHh!8~E!Fprp5%roX4^N@MT zJZ0W8kD1rZbLKsM0Kb5rz;ED3@GJNk{0@Eyzl5K{Z=uuDnK#I!tTl0BzdxIlm6SC; z1GI+lOdu@0I$j>HPZS_35G9BjL=mD2QHH3aGm(hMM6@D$5zUBh9`T5RL`5PckyB?P zDiKy^kGw==kJ28!J*s=OcM^z{5Gf+kMu|#lG}A*)_q^vlr(GUB*HF9u(!Iv>k`l@Ekkk3+ zpFbVP(Q_@Odnny&9FI6&aXfvj{TcYP@n`1GlIQEs96NB^z+T*juq*5gyK`H_KCxHq z7kkFOv3KmB`M~^OzA%57Ps}go8}pC($oyo!GJl!R%x~tq=01J^zkr{>Z{SDpEBG1w z4t@x~grCB1;m6#!C1oXHjmFEQtkIFn&z(5vgM}x;GvTT5TzE1(8=el&hbP1{;wj}h z@uGNDDhGI6JTM*^4~@shgX7Wh@bdV0f1&}=foMVWAes=3(jTg85{XY3n$ z$NrfQ%n#-Z^N0Dw{9?Xo?lBLUm&{Ya&NdzUL>P)01^6E@P_9*Sq+oQTidnW-Wgh&sOFiIp+qn8#z-TU76o=(Nl zx~AHFlOB^>JQy*)82L5dPnfbHi`T8@*4%{}d7wiXnavQ_$utV$;JH>9XW9%9` z$L^U2%nRlT^M-lEyked)@0f?oOXex_R&$v7%=~7)Gym}i_y_z2{sVu4f5G3#_uz-{ zOZX}L7JdxBCLfoSm4r1K_mi?lM=n2in9ITw;hFGMcrJM|yc+M5csx8H9uW_T$Har; zQSq>NTzO!;G4H{6YCJcd9M6uY$MY)^5EY0LL=B<{QH3Z&)FBEHm55SAEut7vjVR}l zk7!7ABw7+diKsdgX^Ff#6Olbid-V3G?$O>UzzHGJLnMqyA~kAh5!8L|bD!x{9<6Ju z-A}ylI4*JAd`x|;{TcYP@n`1GlIQEs96NB^z+SK)>>Rsi z9xyMMC(Ik>5%WrOjQPg=V?HuJnXk-W<}>qKbDepQAHXl*C-58i5&Q~%2ET(J!Y|23 z;ji#t_%r-lQdUydXv|N_8XdX(+!1Brk?>G>tX&Zn@3(khJTe{{kBtYHN5{M4?Rn28 z5)c`P6hsapi6RS8hNwdnA}SH3h+0H3qMAoKq94(a=t#8m2uf5X(h_-fCL(*3_UKJi z_ek#qpzoa!B0WU9h!j#HmKH(X_rCX?mK^uH-~FcEW6AGrU1RB<;(f<)iR0#D8pqq8 z1<%KynLkUOuRn9_0K0Ho!G5qO>e@%XoA=JRYA2polJ?TCIvL!u)QlL)Fa zQI$wb`|J?tuqnbBfS$qqywjiNEVSkYNS%5onBhH|NZYjtvP!CPVG8t_Z9Cy zj*pLB99JK6e-=C+e`fwHdA|P4u> zmBZqF6_1LC#pB|E<&k+0##7_D@#J`Rd3wA)@7qKKA_Ng*%lsA5jc7;oBN`GNiI_xC zor$VMS|YE`L}ZWBL~f7f9_5_?oDB56lS8D6NFOCisS!>OH9g<~516(*dj4kZdQ0~f z?>~-@k6j#BA9H^e{)~8T{w#UE{>-rh?80pY`@x>DFYFEb!ydP!E#?jLh+|kUE`Zzs(TiwC zbR*gk{fLG{M~|39QKBl5mdL9!5t#_BGm+b)xkq^?04IY;4Neu2I!bg>qnbH#df)>e zIPLP7`}?))F5P1s2Opz2em=H7=Kd`F8S&iwS@L}SnPUgo1$N@LgFRti*c$W8c_2_OH3XykMR%ZtkIFn&z(5Ahv9W5QPx&EEIcG06Ay|<#s?iqniTTBRWBzF_GB25@%vc@LO7YC|~Al50kQzutwv4 zQr76m<>!>kI_H40@JRAbcq%*>o(#{1r`v+Cs4VdA%e$~VHt)%Jay+{{J@3~<03w1S z1kpltRU<5_DZKCV{!c!Ds71bjh(?4X;t>IfibP4GCQ+2AN~9(7>P$o?LhDTA_GnI& z_sH)=5NW|lB9ca=krKJI2eTn3S!J_F|U|s z%sb6N<|Ffy`Kr0gJZ4@q&-GTt8y0U|yn*R$Y?q`ZUza{VNo&LglCnldEk8pp>zqs9 z`L-l1Dgk&=?Vo&Smr1Kl6e4A-xuFCd?z|mF5`pjrqrX)ZApAGH;p3 zqOB5XL0k9*`~-eOw6%3kD=8}pYc%dBWsQzpe(o@rMU@cBTARbdQ{p-Cq&33gy%*1n zr0|6;@6Uwi!n9bs448Fq&qVwcz{c8eX$ zrm=7A9s6fK%;mM1U(7enJ?0_vl6k5*%lu_NGrvV!p3^!G(vnZpAs?5Nm6SCa^OLeh zM=n2iNLhFyRSHjc!s2}zkFN;8dv}SjcwZ-?EX`jL#fWM|Iiem>kf=zMBx({xiK;|e zBCpOwVY4QX-oka(cug9x?6W==s~FYcAbeyni1X zA2T0IA7dYTe;SvKPOuy72)n}0usiG!yTndKTP4z}(H8T9d7`%x z-cXoN%rDI~<{k4;v~{MCR#H|{)@aO6${HQH{M->`?TWC_V3ewayyNoDt9M{354;=8 zgY({uXP2kv{hA1%h(L7k{1rJY-rsqj=l#Ane??Ry$`SR5f<#54BvF$nN>nA%5_xqd z8WW+3*g6x*J-QS1odlc^oE#!mMDmDqQX`rcJ^ksQ{^@iaN9!7D*B$RK-hUh)A3GmY zA8W?np9#;&pB>MW=j+cLJHRfm6YK^%!mhA0><&A`E=5~TS~c3j?wJS73(=O7R-`TF z8S_qakom~`)LdoWGLJQ}PggoQ=}kBbM6gvI+Y9$X%s_h>x5JU{Q-PFO?>-qVRBiY!DK(N!cY zsxCw;@&rUOMK+=wQI9A{R3u6gHHo4`RU$2smq@HL5t@kYQJl!`(cUS*>A=assUi|b ziA+i)(;}!xKJt;%aUQK}DP4Q%9^*Lp82Q-wnEF`zGvK-Sv*P*jJpCE-+_3}f0z1KO zup{gWJHzgQU2@L;k2Opz2evGS+ zwLb%%i$5!#AJ5aDG0z=4z%H;8>;^l+uCO!g4m*@hVxQP6_AA&P})X1krOZ>mbJmxXenuFidI@hkRbT9FKeJtWQ z`55}xGT#0S{Mqolcz!%jf5tp_>;SvKPOuy72)n}0ushix_K1CAuh_3>t2eL3JYn82 zkCfM9erc{T@0f?0lgv-%tKN=yQ;IpQQ$bqzx1_8jtkJlilr=hX`8nmXu6saPp2zZj zSX&d8R|u&RqUFFd%S+?2<-w^?;MwKry+WucplCp3P^2Jg@V@SZg*uq`d_^MO|A|nF zSVS+P8F>UEogyDmkf=zMBx({xiK;|dA}^7c$gDHbnh5Suok;J|->JZf!AT-gMkJ9) zEH%2BBd5nc_Oa8J$K2nmU1z+Xc)xKxe5`y7eQbTqc@F+;cwRg|f2KTVo;!AcU0^5J z4R(ZGVP~>A><@dyKCxHXE_RGv*YaA-3+4&)MstYy6iEx(Vm>lIHCLIpk+h&Ka@Y6) z<+XO1)55QbwB+m3=O<~6*g#U&$f)IK$YovkGL^zJjj+&=5D_9_5k=6Yh_IZlAS|L1 z`2Zr8A{fz(=tf>ak&h@yR3u6gHHo4`RiZ3Wmq<)xCQ|E61Sg_2Z&H+_cMM?(dhbIo?;iZyysMD<4B2TOV_O7CaxGmp?!n9bs44nQRXG!yd6u*(!F69b?y{c`fD@^Gt6vyy56==S(84q^u;Y(YT+KH9B(n zxx-wR-uF}~Je7py8Z5mJ#~c=w3VC=c7Wr?~(VInh;T4$m+5#6Ia(cg){Nx=ys(uLDUq>~cOwCL&akAM7hoJZ?gO4lCm z&+k2si;tO)rH?V=?$3hf!}IcI$aCd6^W3om>;gN%Zm=WlN;ZbQVSm^o_9@!hDzD`^ zEzK*UDCoD7`cx=5Z*9eREY$q%#AIf2Q{)!wHIu$A@de`T@UwHtc z6!`%nm?9d{jc7-nL6MNCNR%XM5=DusL|LLPQJBa~q$YCfOhhNb>rCW#8gNQ*f^f2k z)Dg)fQcR7YF1X-=srPuo6P_^j9KF9!|E_jj@%rQa#ryZM@iFtU^f6}K{aNsQ{F(6_ zd9FNXo;!AcU0^5J4R(ZG$;PlZ><@dCZDOa`Ep{xM#=d3i*gf+=d0pN>m@k?;%p=hj z^NaZ=+Ug-KDu~Q)&2{Ge7NjL#6uUg&xA0?nbHu+TWhG^e#{8tL(UHr~9Z{CN5uOQ8 zbt(yqN<<@dCZDOa`?Py-B zMq1ET4{2%c>Me;kCgrA22hvK)O3E6I`AJ!$BbT2$q%5x#o^FJN21M`9R4nlHddGIc zat#*m=QYCO{oV4o$-F+{rXt=82Q-wm@>{j z_B;=NMm#s3BhQuR%yY*MunX)2yTOjIE7=(KhW*JFu}ka}yOj-N&w6{n&awNL*J7S9 zZ$w+nC%wHe&ot+lf6PbCP39@p!_$eh@NY?3Nm-*YKPhW;mKN_SW76% z@}5{^<<%3*tAunNrO!>zuFq5pT2(^NVd1$WVNt=r^D7Dv5fmMW6v|sE+Wpy|{n_-& zSH5z3>s#MCedHq_nZER;FHPV1&UdCC{_uy>&wlo^>6gF!<+LwfKl#Z|rf+@gThnJh z``PLJ?|=XFmbbiRdcg}`aFBO$VkwbLji8?V013>_}FsZS`*h)j7f#Q9 z_Oq)L^^~VPWjfBIMRSqDB7H$yoNLrIm*;}G$ZzRPjw|N3pe>y}zqKT{Rmy8YTb|b< zx3wg%Mdh4Ix@gPuTD1xyZ#v3roodpeEAkv^C1oXHjmG_?tkIFn&ncI6y-O*JcR;-l z;<4~x%46{kshu!RWe!X4_TTu%H+D)H`O7aq0?PeCR8M{CQ>WhJX-|9F)N}OyKK(nc zBc3CLwa5$F;#{Jx5^Z8jbr&S)(JDpF7NDL0Nbvc_}1$Ffj zS4lY^iK~|1^4wNSC0|m=m#T=aj-ShG)s*pGL2Qwh^jdSI1#PWW#N%JsU7CFleRpYe zTKKIuZSH|~}7PJ*r@wN4bv3hu%IW7E_a$5K^y+O*yC1oXLjmG?>tkIFn&mB^hybvBq z-U&~2DhNv+I={~B5fDv1=Q+<=b+m{~|F1+}`dK8f7J1dEtCZ(z5f>UR@>^TemgluZ zT*_^E6>&7L)#|Mkb6PdhqOaEJR6WFhMbb*jO3E6I`AJ!$BbT2$F<*E^^din7o& z$V;IrMTbSDP}&F`DBaOw_3oMzmF}*?QlG1R>RokavsC@+fANc79P}ttpR%>b9IcgT zEE1TLR}Xc0o~uP%v7!iV>FmU{q>@O7Q_pRAUQ5IUX+c|_*K)m9PX)0>TI|jfwOTQ! zbzG#yPF?an_#re}@=x+r_$~P`{F!{4-X!Jg(&r~>jo3g^*2t*kXAWnZJkJ({1!XPi zIO>F@9M+3p{Nj4(&i`{pjli4V{O0Mo&wcJ@M~l`yx)@+x92ujT6yM=FTkFaA`LmV68Q93(C^oW$a_7JKKycU`*uOhlSek8BetJR96MF&&mpkwz^_GO?)yyiGv9(r|J z@+CU7XT%nyC1005KS^uE29mNyMlC->F6)r8&@#+vv3QqMPb@s2ydWM?yTXdFpsW&M zY3CeyT6tZ0Umf56_P5V2#K^muKKHrL9qitt9ekD?PGPn0OH>vKtVLcQG| z&g8hLD8~HOC~cLxo6c1cNAg|S*0!y_z(FK{K_0@vF@45 zA=N|tnyQCMSxH!wr@;R}b4 z7M+y{EYeqtygc7kqOMY9F%s7r+G?rfowmG+xHPZj%J^0VaS3Tj8Lzw+iumIuE&Pw( z_)J!+&@qOeF`PF_x3p6lu*F4Xgpwrcq;v|Fv*R*AN}*Opfi zd-7Vn6~vZSE9SJ+T}62xr-fe=X(eSPWsS!C zq^!}A%g-HB7M`d?SR?BR@s!eH%@LN?E9KYOJpwO!(TgTOw8}w;|E%}>8m&bNi}dB> zHAh_~;wn`YYs6K`Z?$O4HCrMstx0r+d{17>*CDnlh^;k<-Y-6CwPLUM*y$m5FI}S3 z;th=IA^ro>(itkIFn&mB>g2n))R#(|a6R0+vrMJ*N{ z&~;eyk`NY@MGgxsmU389$m%^kzs~Lvc*#p%GW8x1SnFIPvPfV~UOm)R%5$}di;Ch} z+M)x5)0TIC=*esOI>eSTK2{K0-5z#LTJi-b;ze5W6}_Z|A5u=MMOyeZy-6lzC1s7q z{G_bWk;~7Wn11co4(ZAX3(8tbSbFzdLRg|KMaKL(yGP)qFMa7Wakdn5c=@{Y z`AJ$MHjtDxGHUsm!`TL9NymU^qDly5p@Be)71i!|!li_ze$nrL|NGBw#K^mwKJbAL zOfP%c%Z85@p+ypl^tF__TEvBptCru|lD4+YYsCs;?DlY`krw`^N24X5hDIwXD+y~f z?k8o9j$D3DxvWE_D3lc|g^{r2+2rN)UWq!@b68XgRRK_ykV>JtHKrgDvG1DHK&mGDq?S5 ztJd*B%J`_&Ixf=7=YKw3c;SVMjuw@bNG#Hqlb2JM=el}{3-$a6 zZIvpCbU1a|a?RFAUMqHd@ZMT+4Pw-4Nfp1VoK_EM$wzoj3qP`j8Xo_al$De<8uOE~ zMn^6`cSKp_u-0g?@Qf|Og0i&ITkq@nwI3t!rZ>H5y6B>dhK?GYl_)IImy_2Vb(M&V zs^VOJt0%WLM_XP+^v(~xd9B#-!Fy}5GY@$!X|+nE1#L;IrS*rS)x)?OPl>eT3v}r1 zOVz_3(n14@8h(jJD=8}}Yc%F3WsQzpe(sR6=CoLNGS6X=$3llS*RcVQsvMRGOM9Ow z>gLyejKDkJ`OZoCtTji8(n-<9m#98IzGe-V$^Dt zRPm)w4_lI!<~{o!$`{}loV4aTJS-(G{9961Qr2k9Ps$n{x%}J_Ws$>DwUA079RR5m zqCR!Pg0fJ%;~~9D=#@eUOP=;)AN$yTAV%Kh^q~)ZXnOUlU)?-j^{Q7*b4Q8N=IEd=U!Gq3+Sd*pH9CtV7V}_EUQS)T z#8uL9wTKJtRw=jTv_-$I7H#$BwPMGISV1glwW2D%wEj>%wR)?ErOu^GNK3xKNellm zr-olzIZVn*!WxbHNm-*Km!DHE>jszZP~n6%*R?_Kl~Id@dR1O>j9rZ5n3d$7J1E47dbB1a*^YLxTq*Dr7e1H)o9B#TdE@V=Cz_SzEnXxm87NJ zrP*Oxig@x_n*W~DDv=icgq#-nEYE2zsT|7JrO!{&8nJ<-tdUX6&ydSHq$~()u2P6L zA`+H7p=+^FwZ6im=W-_uTzKjJ(t7$3On@^oBRQq3al-u}EJ|UQS(}>w>s+hPtRM zQdNxktu1M5G_Mtv@g>rNwp2lsR;#3n--5JC`ySRvOLJa_=d_?KbXs0HjHHFXA*Y2B zK9Uwn2l+B6Oa3h>D=BL<<|k#1j$D53h_c9GL0PS?4ZT_{DO+9bt}1~DOI|iRlI|aR zz4482oR%CdDl3s#%!4_3m8h#kT+o)zJ;c?jB$ib2uG#V`;*z{p?D!CSYn8NGB~|=Z zIjtILDW4T{T2NMrwD1>BT5B{~@?}n1Nm)r*qcJ}zYjouDbBB~gW<3&?R!Wn{(tG1t zEtUui%F_F8e(l!?s59xvQ6sZRVUfI?y4DcaC~cM2B)UT0tB9?ytG=U zg0#**|NKdZ_C1s$-YbWCtD<@cY2gocttkIFn&mB<~gtewph^8WH zvE)TPk0mcFPn=)-H3EP6mw!3wc=Mazd~opJz2_34MFMM)SBbh>IW8)SgPE1$3+6@k}>QL55rBGf_hdiPaR@7ptN+80L8dlyozxHbc-tv~W zOmhbWRy&u7EYg>gms6MLx?03l(s4O)wP>rP+bXR`6m9k9wdR!Zsvs^QEoiG(5nt=4 zCDPJPJiZ%G4{5d3@Mt)lwD2#khNp*C)M%BuJIJ@`kgrRhpQJTn14&sUqn4jJoNZ8+ z=djjP3ei^72n)*6t{wTcUnB6=x4v~+a@2^dL|~D=oV@0!tB1JIaE;KGcYml=5nFjJ z@2{o2mMVyG4Pr^FRZ_)k{b8x6*4)~|InvUehwOXkq*d#sMRy0U9>&UHBrPb5b%&4^ zN(R*MweAi{SxH!9wScFOe3sMHkbODqiakx6Wyav>y1t2Ob>s zh>xTt%Bqo;=6_8MPd=-p(Zb)5({jq{sT|7JrQc7=8nJ<-tdUX6&m2*f2+JwUb6Bza zDE*k_>E!hyVbv;yXfdEH?cR}J`!xc8{nvjz)sDaVtG}A&juN3o`ikULqAt&KMdB*y zxSY6Jxvi2)-urE}@><^YA?mf%@gc53ENQjKX_42GR!i#-OPwCPpH@q!Roab5)k8>2 zl+_}w*1ALa0{lX)awvbYwsMGnOUg>Z8jbr&S)(JDpF6A=M#6H+(mSH`54B36)b6f$ z*E^`^uu6F>RSo2s^J~9G;2rOH$JBGwNK8L3kyxZJCoiY2QR1SaI6_gPE6Oj){Q5r-Aju1kxHQm%PGsXSgI1xsZbS%{MxS(c;`FcIW0MAL{_4( zNMBA~CF*JsS1rHQOItOCd@HXtS3!)uwNyc@sp8`vh`l+jIntuT!$TkX&`Dh$Vopn> zMdh%z?$9Z#rP0EVY*9JHzsc97&ri}Cv4Nzlkx|Rf9L_c<%e7c^AoV=fno6PGSE&@r zvq}jo51n88H3IK?*Sn^XqeNyU0(1Iu>gpvfbX-&vM`_Cy@?J%3<+Y+RK2{Lp8pN7b z%N6mXIj!1mJWG0MIc2rf@aVH*PD^TdkrtG-v~nmP7S|jmWhG&a#{HzM(UHr~DVKGV zgZ}PLS+2#RS}4kjTC7&3P&uqp9!rF!_gTFU=huFXz`Ni5?rHQW(OIOgNM0rCDiIe| z#ae!=hqg*8`AA#dXDjBl<|>FatyW1D4{5n7K2{G~ig@o@>YYoq?_ne@@>#Ca@|+gy z4yhc{wbXN3kXBE3hgRiKzRJ5hIAzJd$=9XNPtqE(fuyXFQOnPe%Q~bi*J3$k#jXt{ z#XDN8xk@2~B@e6WLw@bo2)yS#@0m)+-~7$rOg%@5&?0>~c{z2B5?3j|#o9!a@~R}d zW=q5+mArR z1!c+0>b*F>_G<**``-6Xb4Q87{AVQ^i}dB>RidsIaiQUg`7NidQf_Nbvo)93ipuy> z1+k`zFRedZLRziec$V~sujRA6dPq(S(vnX=pA~ajPFhqB<#Qrwd1nXj?%_OslR*7VW~m@Wl8UmU;8xz?|a|-rlm)X$|8k1dDW<^L|jxA$!{&8t&&Q< zRz>taTgq$oR1iyAt>YjqdTFs6kL$GNDu?K_s2;X7TI92OD~Iwmp3{P~ylN?)ET^ocigzhk^*&0qkTr!)SbCS$`*D8l*9g4- z{qLVfjuM$g`s$@F@>~#CtD@+{)yi!-ZMkO4tB5hL)l)$%X|-ZsEors1{*as&9Zc!; zpq^S@J!IWssdK64v>+`ymM$T!SUKz=Ew3EnKTyNB@>wO)g0jeIk9zt2AwTDy>BWcCTp{o9^b%%5Ltlr8Y zloh)>M17X$v>+|78p_wD&ri}Cv4Nzlkx|Rf9L_d*9?xURJ4MQhTCAELi#%4VQYg== z{yC~f~fUlTCBM| zmglf)l!X=x!jckJ@5%YKUnB704}W;-IhK%EB(EBEk>@JqxX^Kt-&#srz86PwZ8CkdmWyiHrQ!656V1 zwtDkg-d~H}S|zPksjrq)@k>an)wvX%R!`+{L=7J`TI|3xvgVM=A^o%FlA0@2MOd*~=#-^Op}ed5=ScgJU;8xzAN}Y@rzJ;? z$|8Y9@+whRiMVR{tsdGctx0svmT0S$*NVz`NK1Mx)*!0egZjm9MOt(%ttsMrNDDQ5 zt#U|*hgwc+sS>`ohZZV$a#~(FOv*~SS{v(=veu5^K7al?*SXnc))Yp{su5OC=Y|?# z>3voeiTv8H5%}20J~k~qYE%{p%*o5Ct3_O{kjAo!}qG;U8B{atfi#oJ+#PY%_-rbEbr_fU$@V`KJR^;Hj}c_dd96MQWon9 zQM}hE%k@~bt_>~9deW1gv|oskcRO8t@x?<&iOeE>Id#nuSE;H9arM#`9Uz>xVihs! zwW2aUu0dpHo*HS9*FqJqP7fp1LvmV9T3$KaBB!-R2`|5~R0)p?o}5-mpOus~9w+ua z#wKO$y9KPd|La`m=9jK1?9F3|uzDy<>xW#6rAndlSbCRz@{^yO+_7gP@bQm-d>TDU zgcj+`$*V+Nr5qQ;MMbehTP@v|)0S5emDif9AjaNW(rVRI@l+3cNNcWh>0B?ZHI%is zyMwPeEGgmX2Tx85jaF;DA^9x%x;4HsKfAw_mXwv&KW_a_SzG3@AgrXUJwvI+?SAr; zpPXvPCqD6ssdbd-ERvT~SBbbvIW8)SE!tvTVu`kVJz~sj%~cS!AL1@aOZy(yc9`BJ zY4uhPw^YJAW!3UoNm=7@V&7wIQr5m(z?%C%=bW2Qde>W$$I|;`ZwFFWypzY$s&1_u z&#(O&flqzvQ&a1x5g0!&QJB-0Q&%r>k>6TMTP4j_%xm>j5Pj!qv|6?GhwAh|POC=| zubdX7<^AE8bS;e?9=6tKl}M{6pG8hytE-H#kXv;NQUPbi0)?5X#q}5tWTBzaGC%&YJ zZ{@U(gS6Ht;cF`RC7nuHZzx~4##iQN_m|R=veNp;tzVw!xbs-*RH%K_@@v0F;Ip6o z?9_YIXe<&~i@I9GH9}jikgw&nynnp%T0L5=*jGzht)i@shFMjch)6%0vWs$s`y5@+BisBO5Dy>Is<+Y+RKCVGT6~9GJ z%Sp?7X>CneyQmy0r!576e1m%Ejp(yjYdEKbmyb)zO2QhA`$<`&BbT2$%w_dZmb}>( zl|tpQL|M68^!`!Sm%j9+Y3Wg-vPfP|U30`mMR5sjl@#(ZuN5nZTai|)%LDr!o(`mS z!37sgkAM8*r^h|+anoZT``GC*k9o}W=tn<#deoyHH9hi?kDPW{IfS(4^jS$+Nm-*Y zKPhW;d=U!Im8B`S;L<; z@^$I+le9)`ASr8P)bcZjv#my1+POkrYs*TZQ;5T zqN2Ejw!Hg;=e4|}X{;c|HHgw`MOD1kANE!cowVqsMUVKkIjvo+IXoTnSxH$*Sfg=2 zDQk4(@^i{%{ocX4!k)E-TjjB|>xL=``L$mo@YSz=by|9qs4S9~Q`a1EQBmxnt&&2% zmDf5Iq@|sB)>78e9$Lz2X@{&WmGHaBXQ|&GyGYBw$=9XNPtqE(fuyXFQOnPe%Q~d2 zxh|w@2umtg&tr+Q9`T4r>=$C}`Q85f=Rcpm_O-7qK1x&;$;+v0j<~2O_Rv;IAs_Qv ztqLO5Ln?^UYW3u_TAfQvIjvoi)^ncooax!me)jaNXFY2gsT`h8`mCg^B&^Z6pOiH^ za{0NVT-L5BONw{tu^=p|Ve@N$M&JiO_`!6^C6_EdN@Nzv%c-kGT&4V0i?&LdEzfI3 z(oz*sw51AS)M}Mf@x7$g+F=@<7S+SGq&1Sy^6n06d%|zI-q7_~p3h3kO3E6I`AJ!$ zBbT2$FNqgD$=Jb5jt;ytI;QpDH#!;{bIC9R7tx@g*>Tj`me&yuf8pP!^PVgpH8 zBcqm|Ih<|BMOiVArPbZ}wLc^9jc*E*G?756*b zN~87s=RbdX-t(R}J@>iKotEsrGrGs26a~A^XC-ANVU5Q9q^!}A%g=3-%ewDp7@ zD=g`;l*dZS+8@Hwo&Npb|NY{lL}ih@YQ)t;TT~Kz^IG0xt5rdydPoIPs`#FqR;@?; zTuy5ZX^rl4sC`J6bavQ6pLOqh-+Q{(z3w%gfByN?J@0wX=^ppE$8_F#=S_FN``sty z+vj%NNya*}po7yYqZ_3nBs5mv4-+%L-d<~P4NEjemb7Rk%0 z%ZaO$-=dlrb6X|an#*gcf;dWAwJr~;9*VTQ!^1n?@s5cdrqOBno`+Nq=SXW!U-;w5 zXGyoY>wK1cUHbeatq~hY${HE9{0tSt?I=sTWcW@xtvr?}>qkHO(SD)Ep5ES)TC6)P>9itgZ9!SR`y9ULMK79q zmGDdQS!?=dDNn9^`F-wlpJ`1#D=8}pYc%dBWsQzpe(uC{;>0a?p2u=c7I`e~z9AKH zex3az@Q?rakJFN)Mr4t^YQ$BdE#OYhhDb@q?Ix4!kQsrP8nSfnqfE+?*1ev4{ii?mcpblUQ~mRAsWMOsTc zJoHu$NA@|~I-fPNzu^|UNPAa@q^u;Y(YT+KH9B(nx$#_S65gP|9af zJ$&2S-Zu48*4#darz)SNytdxFM)FxBD)^+Vq^!}HpOiH^a``z?)~%*p?OE8$Vg{4rQLXrOQW?! z3E#8FVUIrR@sEG})SJ)Rl?que^`E_=W zfD_fo_a*X*)HO$3R1={sbX%<|Vrd;>DW}y#TBUwk%4vCrhb8L{$!E>2Ib6D%^p^Up zCqMbgQ%j#EA0XvGZ$9h(_rL!^x0p35_@t~PtkJlilr=hX`MG7eEM@7n#%F1@FzT_$ zV{6+l2$37RqF7tMGvjs zp73+|te*W1FSy`>X)d2Ns)FzBot2c8lrY%djpy*5th8JB5{75og?tQ?|pCj=YRg^p`%7;k-FxHi)v!6lGw^?c?Hq6 zT5Cv)JrC*fP|9hQNUNmLT1r}T`Yf$E?8#@%_0M|hQ=d9LCKl+I*ILUHbeatq~hY${HE9{LJBOTSHm$dP{m_L0D7^MOoVIHowl!5%}(RzdMZ_ zH8P9j<;1m=wpvxh(mF(RTB0o!@l+7C{;-zQik(Z9(}J|<4ZkI6z4^^=o_af#YK?DC zK5K5h;oSa)bGz>JRt+Jo(W+rmRua}|+)v6H9l88me=cijwXoFdy+m10c)}CTPQ=*r zyK=fxPOIne&&2N|dG!!ishSvbTcWL&UdtcVBG`I6k zPd;m|AN;eQ{p_ih&uUc-y#& zT3hS0=K5zn|M}0KYWb{IR|maOwN&s+dS{JP4U@8xutwv4Qr76m<>&fySxeRy_EZZY zENM2h;yb_2{1I@f`qzK`*J;h+zstXO+S2zW;wt5~psk*~mRAs4q!ktMHPULWJB%8w ztt*FHI;Cp&!EvXuwfByN?T-7isD=BL<<|k#1j$D53vXnKtws1|g z5X#b;r2INFN1#Ph|MqYHHZ3_?zsqS$-#c-wp{-H{aSdt3-FWC)x@(QrT>mVsG@*x9 zYrWyz{)R7j!3(BRKC4zW)ZE^pch+dtP-~~PhB_%LDQh(5CuNO}Tz>Avbi*6odZ>d# z@7lsvwGhIRc0-<6Ynk%v%o%|n{pd%R$yd$M)c3#t{i$@!eXc}aPF%gTg=VXj*P`PC zIxW$bv|3AZT660T(P)+QS*10HJ-hGJDu+G!tX4nxm%QX9Qz@TSs~W0*R!arHMb+?r z_q*T04qxisr#ySljyv*o>GPAcMruFDW+Vsj- zzVgh$j6J>5|NPJYJfN!*9hE3*&Cg1I!>MbIw$N?GD&j6kEAGYvX)V!c(cNK9<*;Xu z!`Hp;byF>$r8!v3XVtnoJm)#jnQAKdR_`n+;9I@3daH&>SxHa3jOj^PyByuK{dbor z>mK*G$AQK{T8Ewv4n3VnOKS_EEO}vRI7C@`2bO|X`kDMXJx1U^{^LKIIjb5){on^b zm`0A;?~BB>hPFz1Emy_&=CoqxQaU`$X|zznw{luF(t6K(-ZPc@!MAqanagKstzjvj z)#_4Osv3&4YQ5msRt=YSD82jL?>jv~zc{GuDNp1iv&Nc;P`#Ip(uURYQ?h ztrz?*tA-+WeZi z55G6=*J*1FX?c%$<+Muu;UTTwU8R?-Iqc16&GmzS&1+sWz53O!o?<>LQkLtpYI_?l zts1u08cKceu!lWtnyVW2YO|8ElCVbOep1%x$mQqeb6LF|9OMmanyjVOLQ$5ywX_~i zS$c1lcb7IqE6eq6Ezkebm%emTj~_(^X-f1iuAM_fWr`k(BGRDf-Tn2ifBhJqG(Po# z4}4(y;0HfAslp@Wva~hofux8jb+fuqDrcc8k=B`MWxr@qdL2by z)K>JSSCgW;RM=8u>yR>ADs8G;S}RA@OBGkNN05ZHNe~9a0fj&%PAX0?PBxKzO7!%@ zAO3J!bCiB({JVZleyx5F+!y!BeKQUk6UK+}(%3PM8dJs>MLgrLXQ5}p^KsImau|2v zL8C=?2P%hKb}Fr@;MsY{*BZurR;g;JxurSgl!ZR)xT=OyC1@@4T-C7E(IF`-DQh(5 zCuNO}Tz>Adly%;D=S@pAS#up6N;~S1%aTTeJXWb%7%5BMTr0>UWy$+XJK~fDVd>pG zQkJSETYs$~tVmgru%N8tBrGQuCmE-lNI`Q%rTk;-nEQPEdwwl`jehOW)^U-Ro>hso zJf9VFT6A|9sT{VdhE7^@q6UHRY9Dq9x|R9TgF*q4rO^SEuNFAhfZ2l4)u(;B(2{5S*@-P zEfu`-!BGXjq-t2}P^vep(ptl{9UXeKSxH$*Sfg=2DQk4(@^kH6R*xo2%G41}mglit z`7ZCgi)x|tSc)prq-^=M1!YOQ6A4R^S`pg`3(E2w7I`d)V1%%s5$Hr`sKrSpQck3y zUZVQRPku7#=>7Nc@5JlyYvj89UbsJKYfI89X|(k0YNQo=Xl<>}dgnXeIlbc@?>JbS z>s7RSoBMk6xqAO3F&g8jbl$S)(JDpSwI|X)myz zK3SuhtWvd5l;wFWD63R0lyFd;otYZ_f2#8tVmg|&wAq<-+1uG5%XC~s)i`w#dZhYh09(bW$YqC(iN6PX%7S%#g zR$N;sPcE+>HCe8DpIcj~h@$AS)tA;1Zb4ZwkHwlbD2p6ctQ4{iPI)ZW$Ei|CpYzzQ zfgBDw9dbP6eaHjp47E7PIN?MJYSEOsn)V#6-x2?wUyENO*X{Sh{c*qCKjRUrhm4=b zl5y1-Gv1=xR^^cAcbxgGxvmb%WjSThr8H6&T^*LJHH?&{xvM!`sv6e%z}GrDY^lwX zuS=hwq%~p#Nm(PKmY+GCZM~E=*C$Kfu+}H5q{%Avd#`nHD6K8js&evJ-oZi9A!@R` zTDT=;ZADlm%5uVjvTBt=NT5YnA_-9igaL6tAW#XEqBHd3L=$PJL`FSC)$?ykzdQbY zzc#Md?}7W`KDlqkL1R+t^3Wo!l18gmIaIzA6+AhuHTo=7sMR^-{qKMO)XHbos)iyh zIy%IBR;g+jwON{TrH&4oqosWfHK$8e!=>7+ngU+?smeFq_rCX?=CoN!SxH!ZtPXT{2) zo}G@C5`Ig4)@U#INLh4sC{+zh9ZHo~j#a}Yec(s5S#y1}T02K4WhG^e#{8tL(UHr~ z)hTOE`QF;Qu+}FlRts5Q*sIBk)k0C0=dtLM6?`^>!nbcWWu%m-NZn zg(izUmUnPqZQ+)b<%Bib=eaVy|JKz@9hKMtIb;4H>tfZ{bn4gq2 zI&%5BHf3$Cd|$G6q27sI`MySzh3Z|DrC!);zofxhyDN09T1XzNw62gGmg}*oXo&`D zc`PbpP=zQ1;($OP5~mdC#fc`;PKk<^P*v;SmVS5q`+jX)uipdr#eH($8Ux0I@mW$i z)H8^r&>OtznNgOZn*7H%oczk~V8; zchXS>d{S0Y)@aO6${HQH{9K)~v?{tqS+$<;OO)?kEu`nWR|}ym))sm<(o&zS9!-|s z^Vfcn$C4sB=CVHd$xlw&0a!h~Kl7Q-Oxm-qwIg)gO$W;IN}*Q^AuOteCB-|HFHr*o z0a1|05p6&okciF@3dG_R;$Y)VC^v(`FT}nUx@sCdzUwrW-vX`yZRPe11rL}zxOC22|Wob@rtX<)GGl#T|7<*A|w#H$YgmJQleuCoCvy%Ss`n;`HKF6N#rpM?F;4 z`ZuND8ULA4$t8t43O`(TbHr@`h9n$z|!eP&wRnKC4Dq-aD(-q4e!6CFdhwJof=YF_9s)wG_Vq6#}jTz&ov1DAGvYfQ$@>xz7TPRT>!UWSr#op$0l%aN{MvPfceukJ=MFnpOxhZW z`AJzLqn4jJoNe+vbG@?ku2`b1E!G#7dcIRFq>IC-@;!ENSVLK&nOH59PDYv;ML`H_ zi6#rmiiAZjt0#};lvUGXK@uV?C=23%Kp+yQ6v)Mi#z{xtmxyQyQMLYU>37G!@7Kol z`aN)8+$Z-f(qcRqACcA;`Ydu;rS*nOC`+~!NvoyJ5^2>sI;_!V?OFl9)jGquo>@s* zNm!$CKPhW;tkIFn&($ewWPPD@S?YE3*vCG0@?Ke`opnan7g8a;I6lp$7J>XmG3`_eNCS@gMjmG?>tkIFn&y^`_ z%PtP;h0{Y>uF0Zeh$f4QVNcKZ(hkw86e_BZe5nc}%JMu`se^-ZS!=6>wY7y1mai>@ zuvlBTG>=7Yhg=UtqBEodxj4;4(kT&9i=dYLztZoHf8Vc->*f0Wp14o$TRE+z`7Bl$ zXzX2^Rmx|1E^BQ*t4Eu)v~O0VEakI$6!1Mg;Ct2?mMBZUqO{I%gtFwrVmF7RtfZ{b zn4gq2I&%5B%TrdXi-XkfwOyi1>kH*EBW3M!ePK=cuE@IdOOaGPh&+!~Qoe7k$s&(c zs}|0w-pRwL`i0(ze2u7riW#&4eLy4-3dEu_BohfIQqUYh{qmQ;oJz;s=i=Xq*TMDq zHFF=_7x&41GY%RP#)t7L>9cxO@T@i5Rn;)&vSJ_jTHmZK74V+R(wmP~rsz$`74W0$ z3`>+H-%`qD$rs5dwYoX5#!%YRq^zW@(U_lD&@+py0iKu#hR{CA>@A`mv)YhJ>YxR8Ai&|9DeR|pF4RE_+52#Xi=7Y8a=a;vXZh! zV}4TB=*Z>g4k>G{i-Xn}*19-6;t`KH&`rc#mflHYudJ=QIJhQ@j-=Xmja-)KN)c|! zmncL!7O94{5?(oh5@pq@g{_|NB~2E47RL33;d1i&d_sNCCXCAmNnfRtkIFn&z+d=c*j3Ec-K>Ay_U-=b#Zvu!yb0v@k+h2T=`DLu(V6`R$UzE`L6X- zOTQHT)V)y$l%-riiL!c??{mBA)Ku@KTo#%v*0iZ&=y@!1St5a2wb1ieAVmzI)AT2!uv{`yK zEp66lM~Afv_+76vj6JiI&ytqX74V+Rq6d6wKf~T`4mHY>kJ7I8t!@tTb?Nhyv_@+B)y8> z^Yt_GSS8BxYGJL5!_sb~wLI1mO;%i6NFGMCfZoU{i#!gr0ewIt5DLViGb9rUClXML zqW=58|9fg3tkMNqOI5?z%|Y`mt}~P_%hwr3%F>*U-5l0XmVAzUkM>b5?HZkw zl@zs$*q)TNi&5Q|-#JQIdZ#Q=R_x-ST-MgTvQ#DP`BD@VIf9;A1XJbD*c2!^p1Dk+L+mwU;3k!(CFAe3p9sl{%Bk z*PU(tJMZUsXfr7*Ep6P=MkotSR?KB>)sb{=M`!(isohb&qsuDw%8HbwJXWME*JL?m zwYoU$VrQM2@_j2!7Wo?}1L}Z6pb|(0a?#m|#%ZTSLrZ9?^>0hR+yAy-lV2;>&;4+J z+%Nafc!;!?P}YbxYi-Z0ST$_zw6mt0!${98uNb0$k3F*@WvOad>Y1e#GqIaPtutw< zVwjXQ9t$##*2gX>E3Kn$9dlz-qpXqLbw(*Gb|fWlr96QY=b|F5EA+na7hZVbEM=(z zGD=yc^@XV4mvnJxtuK`FUeQ;_obnwFkSpJNHCg0qpbAk2!~uaoBoK>Ra9OQp%DZV(VO1q%4&0 zR1DSiF!p_qltmW@-(4qC7Md*2WubhJ6~h{3Q7xRyWwmzJp^F2QCBix`$|8>gZ9pH; z2!sN$=5NRC;Wi4H2*xSuPUZrFtM^!GGw9^73(E3cqS0mbQdX&pgCbdPuPpUET1r_| z3tQ_8mB)&?td{bfDi{Jw3CQP?l^yb|#IvtRBkJ-V5rg z;ytrs|M$6ER#Miu-_DqKyknP?l@>Q{agnl=X+O?fmQ=BGxvV+L^1km>3?pT+zR=b0 zCCc*kg|2=_lhvviD)+DjWhqMQ@Q$R)mypL&bXTNT#D}ofP!=Qtndl6yIKepCMCz## zQY5Ll|E~R>`1kx;{2IA#zZdRLq%}%ewqqAbmg*#AA|vNXqH|M#VorMVt=+1X_-OTNr^jLuxvc%0bx7@L%}?-sD;{+Ccz zZKr5u^Lr|WJv&89H5+wVODRjblH;bVtvZsD$2tzmBEJKLIGI2zPB2b3k$OsWG)Gjm z|6lrD@$dPy_%(9felOe~_bbv`n#($slqK6bZpzXen%iY(j_p0vPgqkioZB%v zDJ#ipO?>3DNm-0Uo|n(4EhsDQVaN_UQJ0mJ_2sFyyN*{3A*@<1OL?pIZ6atw* zDmp_jPBr@8si#Cm`q}7L?f3cLjMu|;`n7XE+#mP51!Yk+RDLn)vUbrkYu_nLK1TaY zsC$fh^?Cny?Xg6_wl{4 zApc!`jFYm`I_lOjH#SLGhkF>dC`;<%IbBv#*5&spOv?I)X$xIePsLC+TH4F7q{~Xm z8uyoT{xtvp**UgJS!vnhmYtN9ltr$G+>hsj$PszoD05kd72Dbx!&6Z)Ov-xT!ERvc z{q8$PXT@+lHe_5q8oQ*dw1y*VSUMI-S@V>o^;?PKZz= z6qyyncOLAVp1G`Xf4S7Z=D$BX#yTl0EqUCMcSTw1EST!|&rdx&?9}?c*K}E1b#j0@ zpb)6UNdvT$7g(8 zKXyr3X+`5!l$3RP=CW94*wd9%%4{7=yOKg3PzY3_GqmCaQ==>q*H-zg+V63y)AxQY zevMqW-wXH0{qCBw^z3wO(Up|`@43b>*BEA;8;^5q$1W*r?FjDk=eMA&-hNr?Kp1z} z$*!d3ouZde7M)3#QkG}~@_pm9PmPY2D&T9sC;mOZ7QaTWoBQCtxKCeS zxMYpt(%$b|QdV{)t?#gt{jyFSdF)f%Ov>7)r@i&PM#|FOU&q<|T|3CG?fw3$SG{VA zyG6&Yq}oL!?qgW${r;Z!yk|O{ci5r-yZ6iLp)Bnnqpk&X8BfmWPgoNOZX z=)Y@()Js~m&zJs|UyENO*Uf!!U)(46%{XXG*ynNS4m)%E7;cfv()z+V%E}#f%Kfr( zAHyWDt>R@;)>b3AkH6yG55elg5hS9A%Nu(q4QzL=I5I2xW;{ zoMN1ABK_1TiJ#YKi~nCc$G_v(;MWFW={!nVjN4YbMeCXBkoNBDU;p}a$t9OeU;EnE zrmueWs|Ouwzx?GdPha}dm!>a%@rwrl_kPzLjk>Jd$1o}9Y>BB!S!c_WJQeq`OUhDr!6!cPiPMvw^rVAbG*mHEXVRxX z{pr&)p7D%>JP-5+_q}iOeGKWD)!N4}_RLbm>`}if`qmWiPFcQ}A-OEy z%TRxR0THhE~qR zuB1{WlzP8E_`wgJYQ5hxmz9*aRZLCF+G-^C@%NM{OS`P9=Zt#KJm3KjIM}^Ho=hH1 zItcAT&J6MJwW{YN(xb=}M}|)S|49eB>jOcIv(O;)|z`fBfUqCqD6sgFWVyZ-|uj zxzBxWg0z&cP~IZ$u%p~Y4`qGlJKs6j$;ES7kQNm~SHMFHRKchiLRq2=5eM`Ejpz)e zKrK2$Gfp`AzC=NzL=`_*`rYyGb6tL|T)*EF_o;jKY9SRv#))wwm&M8gjWL8(>ie$e zBO;#bl~v1SiN-yT)k9gb#U+#_TaT2bIiWnS?-m_%S(;l*b6J|hbGfWu%93v>QI>pB zjk4s!lCqMrwu;)rwcX@|BmV z7?vnY+AOaaE}<-Gvr4%vMKP?pASDP`#y^-xxCwQ!EIWP>%zlI@mqSv{1c zIWtFDntNU`)ZEmYkXH;NWoeEpZ|ytmBxNN9ohdOlDeFvmQm5l})+kE~_*yROp$~m% zF_)$N#$ql@J+orPQ0ol6VpyUq?SrO0(a2@>P?i+%uY29=4m2f-6eY@%0$zK#Nt;40 zOWLfM%R&LKT}#x9h>9VUrF_1RvHihp1GeNI{NdbmyqOXnWS(wOLgMn$HPvNX1y%R25}Sy7iI+pAHQD%~Z@l8r~o z(p-qStVmg!Q!$q{M_KG)=((&&S@Hpqvg9jb#gIJ=V=hZRsFur;@5)?O65N*YHYsb% z(cHJ+b-GZN)){&(t3+8+z@vVbr@!!|0$z$-RWlR`N|Yr9ys9CpiYTJ6&M;Dzs)o|A zP%(tEl+UVB7WpjoGMcLxE~PB3Ppfrv@O&1!ET=3;i|UsM0-8{TELII$qy@d`>}2D_ zqwi~Ew1l==za#!VzZSnnD65CCs1`z5j2C$&{5N5ZLz*Es^3NRvbh>% z$yQ60CEJdarOJ7vEX|cU%F>*3%F>*SxvWT8n%hZPNj+zBY)#5Klb_zHzV;SnNt;#D zW$8Uq+N`L{(tD@0S+2{X|NE1l{Nw|rtKMJfnWgH1stEGHCCZXE%X3*hlqGEzx-3Nu z)*1FtmhxF8%2Gb7l*@v$&}OxCS-q4sx6W`*0q@$ZQa)=bWqCep4QV++&E>58-|G9N zgw>nJg0fn9EGTPK`98Y7&?|;>9Z6-IrJbT>%d+Vn%2H)LQkLdY%w^3{mgejnWoh+G z%w=hnOw47;my~i@@;$X&mhWMhx~wF-t>SA^)>b3AkH2S*vQ#yE-~%6cu#N}yJCr2_ zysO{WQkJw?=(6OgOOz#T7P>5Xe06j1Tvjh-scPsILs!5{)8e@-s)loW8G1fzX*Y*C zZI;y4OBL`X%2M9ONh?+jrC}luL|({A%X3KjJ`&JeUMmt*>%U9CJN|w0SbnWsKYA>k zxzF0#LXlRfTIiG|)%&ROUC+0sd>4U7^?S@?iR5d!tR=ha$mV7HF_+atS(IC)3vyY~X3@!kT$Z$1p35pxmb6(?)~YHYk6xlIRSl^avd$1) z7P%}{4ZUKBHcMKSB|Wo5T1&b)_&USheAYL=`OSmfS*RF}XtPRHLn?<(S*$fIRSiWe z& zTvqIrr8(j23#k}N-{`q4@0CTxFjAK0>KtWh&PU3UZ-}`p`H)gBt43MMxzACSeBD0! z_`K)!w3(EZRy1x!@;vvr$32EAOI1TUIgraj0Us$#RYR{BqJXDj$T~xCv0^TW0 z+AMTgt0%Dv204Qu(V5@|s%Ixi(HNU4>t z^8d%bFP&>SEGH~oBa{^hOZOHD3q4j(9*Zsx5Y};PvOJenQ@*3iDy=V+jmy>}WyLNI znlp19Nj3MpBdO-5uP>xxD4nI}vV46Z6+`)iNLjI07M&dAYep$cJ}xP1JWlL;j7`eg zcMDi^|4^3lS*=`_s)nVCA=)f?!mTJvRl`VG%4dNtW_d0PZB}p3 zEUJc73~SmfbXi_8l&)rJ)sW7lEp66Z)o^PSyyvs#NUN6DDp6DK&mvvvzn%7+0wJv4 zJQkGIt9oBrE!;wr<+&`+W0kr%l$7t}vf}ze&4IYSP;(`AanPJv+AB+Q*Vh+PFI1o~5@qR~+H+aGJ+qY0@>~{GLq#N2SxO4{xpjt+)*1!;oHnb} zH%qw{MSs-UCCcj2XZ2PNBWa0vN+i@vQMG?p`khW!wH($`!YbvlyjsY(LRjRod~M-e zpRA>ttfk6#*(P}`y&1$@R$N~w+mGuDrOTpP=(()9E)IJ0(cJW0R_v9fxgC3D$p^%~ z@A4H(`o7B-)%w2E$w59XDJuzUZJbZaT04UK{P{>(`P6!2^ES+31OpXHPl*BU}vP!?6g z5q(yva@fjg)ksUERHLZc&r5WrpN$e0mBLspoYP~?sotHk==WaRS!apr-3iO{SWA`f zOV$@^ZfK4~%9>kWsJW;)IY(Ksi-UAou~(LKS+Q4^bXimjOS!CC-*@@0sC>^{R+845 zIG>cYX7u*`vo*@nyWm#4?C2e}r!%SEc}IF?tyyO%(ju4T>kJj8s2D0w0A*1%+_eIJ zN!8HvS@d>_lr@^qlD^3+abBT|VZy!1Ex8vNSOV(Bcpsu32I!nr(_#?tjz zF^|>SRcGsJVWh0Bl<%sdkE}1$n@(I`sJt_pEGmXn3q6;GCd+eK@)1iYi)vvhmqi{+ zZ;YACO1j!2#wTTMF^c>8J6e>bcfzguzw2Fn9HhlBh?##Y_wD@q*qq2@?E;Dk@ba|qjT#EHP_?%LivQaz7X}h6bPkU zmU8Je%977Y%1W}@I-VzGZ9Ss<^!w&0Ypye?*8KGL%9{9h_FhOwH0YWN^|)t{y+b|boTt!62gj= zLh@LycvqfpY4?Uw9;-xIrD~y`yY{p!^~sXWNZS_)tEGHjs>xcTd@rpp)ZC0+9OC*y z<+6IaIEb>Aba6oa-s(u26~iR2HE}&DYt88G`)6-|`@>ESYkFqYx;d13X6fD4b6K@+ z4$5bd%W5g$<2u8&3V6Ny*A(zn4Ua>c#d<^6XOYWteHQr@C~Hoi)mu62A+4y@s^zsx z)YSX47G3G@x2l9S!fI6tJ&&caMD@-%yB@2hde`&YGLKcFtXelxRl8-wxseAgUP z9?L1KNBO>XeW5CbCFMI6L-JU29ZBWmlCs9*#Jok$?{Qb1k!sm>Ol3l<%&~a^-tcRua~lIGL2SX7u*`vn9&v>E@t! zN$n>lU6%LEs&#W%v(8Ye+EE3(djFJqz$?m>x|7anvlP`>XGktfs^l7FEzM_<%i2{w zi_Q+CIju-qyP_?pD!zBR()YR+4v`fHI|!jWQ@)NWVa#DcSX<|@^!!Sitd<_DmB%V6 z-yy78H&V}IdC&J!H_}qicU9kecGf8=-=)hs73&LYT^y3KlCsvs`lPHiqqpy$ZBf=- zHwUftDNz>PNu^?q+N{z#L*=u`Wyv#>&+1XYtAepcnyuKs2bM#XO$|4OGrz^0=+;qoFnx>L#=bnZMFU_|1bU>{yt}}54zHMEn#^cOJnPL zERB6>UEy3FYfZIKHdgBQ9+mI1*;2JoHXf^m>|YrBWNH6|*p0NL$)aL7s(i1lFDxnF z(Pg#P7fStJTVI%zm6Ww6)+c4H8NGe~>>Oq3-EefBVQtswtrhU49`H-GS*^ZVkk(vB zhvUd+&8;`|&JH34D2vLWayFu@QPL98KsTHt1?j&bq|tNfck=f*LsvSlC9IxGVeh)a zj9IJ(o$7TT;@-z-I?*qs#0Qe_6x@?6$d z9ZFpZPvsC5{L*|DYYs(OHPVV2t(GF*b6U_1w8ObXM*g!JarwU~eb4Xb%-`qSLs+p= z=xTQeOXEZi%N6fy^H^(D?<3Vh*>GH2NG?mZPaeyAzKgJw$8zO+uO>@X{WUx5NV`d& zteDFpk5!_q*8YW;*Jb@tt}#5h;%VPw-pKtYWsQtle&%qtiL&l~_q$(8TJlUS%3`M- z>9X`*S<+@vF;p(gtA-^7yhtl5;8it@J>b`Bvub^_^lo3;)6hw)L|LtU4O?psm+l~4 z(r3kdmMh`Ca@ZoRn9pkIw7jQQ4{eEtO0=}(XAqU2p(@TLx}qOeNr$C--}>iND_`A*+= zy*(*xS!k;`gP)|##kwR{#iE$^Sz>g-Ue9LAg$#L=oAmO4E^H_#4eC!*H( z&{pf;`2XYgbLQ`JjvA~IVfnq*2#Y*c?2Q#Gg{967wf$x6yfvRWJOld{&1;68so=CYi!=;$EL z0~JH3Eb>`P74Wql@FFeWX@`7PsXM8vhCMq+D{AOHz0}cRZPn08YiZT+bD#U%^!d+! zewyp*5G#kGEGI3L*yOWN!$S(>v(RWkT2#!OK4Jw?w88<&#JrYB2l|O;C#5Cd$KS($ zLsgvpnz+s$!h*6i7E}p)2rDYy^&Fz&y?0%qo_{NkwPfEyy#d5NSxc*hnj^7Vs9aX; z;85EkdPz4@sRiT%N;~VsYN2;=sA;n3mDN(dXD%yAYfYR_%33pe`~KM;%0inZ4Md5u zs2a*6?n;}rq;Hm@gVso?@}bBfy$PgM+HI$&YB;xh^j2LRYU>TRs2oPp>QThIR!aoq zWCQK^86w*1?7y4u{TfPi6*X8QNvKly9IJ#7R!xhwrc$_eUE$hJq$uBA_3oOiCDpg{*q^zW@HL*S^Yt88G`)6-=yFZ-ncDK7-s>>?nvY;&T zSuF*;yrQ!GyV7RWs)kx;*xS)zZPjoq6?{)VOMSy*J}WBWy>duSi|zwd4r@8B7HO5V zT2PIXj=qP0;&~};#n1EK@$YdRoJCenS5bpS{%Q?j#Y$n!W6{5VuFw0@JeGHGnCp?{ zyXrt#sNTI#mgYcj2Z!Fh3so&#(kH8>$(rl=E+4c+`7R$PU$^i6KJR~wHj}c_a>gu2 z;|6890$!xmqk!L9o7LMliw>n)O&+U;S`(#QmU>ouK8svdsY9vW^A(BEX2l(Mq$*Y} z3vHHDmRAk8$Y-f*WhtK(Nh?+kw-?AT{&TS6?Cf{ zmJ`;JO5v7WNL`QRs&}e|PFS+xNLjVDg|%v-=2F~Mr`Ewi>kCC#QIn-PO&&}8mwGO% zq{&jnaE&HQDhK(lsL4vo8jlefUr$U)S!o61GpI&czSc12vPxCMU2C)C#kJ0`cekC9 zs-Z|rkttG^x;&OhOL>Jglr^{Bu%v{KmBSv5R%zW~Pxa7A%kx?#+Nu#3q%=oX{JYW_ zqVjWzt~`IWrb_5)_c<+AX-(mr;(du8%kx-JR*A5db|O_SYl~`Ot%HN~mC{+3C~Fs* zEcqx1D|T?u8)Q<}c#O#Snv|8+vt}0bP!?+qsTh_hYmGKb@3?y3jU6560k2Ag_kkyu zMc*v)Sxc*iwcVqgw7jFkapbdV`y0l}VO(?Q-5o>*R1Q(YFC{JIfIKH8g5hw&86mC` zNyVQzQSp8JyIfC=u12baHNuKr8{(QmJ)csgP|vfpUty_pLsYz%2rKq_CyxbX)l~0P z3u~Q7YikQ@)xz4YI-0Y+dl!<+TBFI5PZD9xDc_T_GET0ElbOp}GkW{}+1uXs4-Ve* zV#UyNSx}a*HJsCCNd>=Dn!B4vrN*7kdERSRo- z7s^Mh?UNNNhV;o&9!owhDQi4V?0bw&%G!4eSabg+$|~))vsRnc+S9PM+m5{T(yC#t zLuqSYLr7~*1t0ZUBb24Qh4L43`7FiV>$Q%_VhjP(@yOEOv)p zS}oN64z7Cl4i4H)UcO;XpR68DR#H|H)|xn(l(lB`_WiTHl;u^!xxQK2cdXVoOI}qw zYRJ<{1sn5OQJbZ{JCN3rs-e0%C}PC5hKeezH>~x}^7V#uz2HYkD^gaga#&h(SR*Z8 zcUY<(qSYdYBiitsR;k;A)0XG9O0-oYt`afz{!G6!9#EB&mFKQXbVY?Q5|+k92i;v+ zONb6Dt|#;yR!NK1T2oltNvF3`C|jGW7RGgjrD|bK^$ul~I+2c43pJNTSaEG(t6IqV z!q(n}%A?B%_3RK$wJ<3wDQiuvPs&;|di(y_+uY_4rn}tbE|;komU3B6S+0VYPJ$lr zDB$HGy+f(qQ;(x+sCRC?e~+l(mCsT{TAI&N)Km0ptvA$OblQ_n(X_S4p{h!?%3;)K zIcZ6W?P_?|9kxhI6|x#_Y5gIGh=oHW!{MY;BQB?-IjZu1Tlzl!J+7lhSFzJ*q$?_f zy@WNVcCTr%mQ)H`dlvQ(R;^lC(qnnGP#sSotXM59<*}rT99dh~sus?5aFCCYuYs`S zgO*U1e4KpUzWe*U|1sK3%1X-_vmA{ZlohpEkXCQsEP25iWw{ETTo&3a&u8hqx3t!< zwtIA~Ln)+H>z$>1R&B?fNLq?qyUJ(9oR)WXsO`Q(^-z?xOVaYZmUn%Kxh+vnBq08~ zL`ywCpyyX3tP#a~FJ-CcTd50aTw56RSWa1`9$8ciAuR31 zpxkn+6Di8~HPu4-6bMTzZssVf)F(^6Eh%d}Mr3@=P7Y~3Yi7Y5WyQ6IzRu7U@TGiK zt7=%HEL9dDt)*)XYx^2PTHc}5tA^gCl(mMV`K-}3hg#LIqf|L8?Q`g)r8REev6P%v zNvBonTq@#d<+WP0RU)nybwz5L`|n6qk*xIh$+>y%szq0kurwyIN~p2ZL5`3rVO&pG zBCJ~1hFx`TXz8)27DmGIJQkWP*JDM>g0Q5=ij-CAL^`*&P;*~zTeS`jJ=H>TSv?&b zlCm;Ju8EQQZ054oj^I9jeko-|6?{)e2Wc%vtA^SO&9zzHJ4+tF);p`Ef}h(#I_9&a zb=fMP)l)h2oEDT7)$ldanp4C>TJ+RHt5qVcnq~{Kp_0hCL`7TtjNi|hzt34z#i48F zpdt!MmNZx`!qQmw5>}}%R!{d)*+Z*R*t4!sw%Mb{^0kGfouR1~wp8z$b1{!4-DGXw zLf2$P)w@#`dMqgoT9h@?!67LtDQj(vPs&<5g8TgW+uY{&r*qFe_aLM0RYNL)JfB%g&wtG9pFlHGS|8m&lLTEph+4n3#k zIxSH`tR6yIsN!3BEzfOvRb57sv@>%OOylg_!%l%d-mVS z_fA&&zL&0aZ=S!3gykA6s)S1jtEP5u^>^Q*Qdpy`8ex@G?^Ft-9!qlxO%{Yzsuq^= zSl+<_!jc}VL|MmCEzDfjc#O#Sn!2pCo;9=J*0;Xnbmu$Y`7%|*rMvBnR1KxckjHJQ z;3H+le3nyINuL!dtJFV>-dT~dj!Ow&>+Voe!*4-asN&bsR?KffKhV%tXMV4rovh|^ zS95elg;12`Rl>D|Ra3if*&C}ySgkIkb9pSynO64(%{jg0)Ku@S9$8S9>#-j}Q` z^lBkJ-_c`(={7aQ zJ`2+FE~Qirdw1Sx_0e)Ae65dGTz9yXv{-*QS3xXQ5k0r%x~-Vsf_j{UO5fMWtMqUE zXZ(IYm#TuUyDHID%wIuRPFJiW6lL{P31bec)_JsdpF-J7ui`yY7HbNRGmoV?sj9x# z6}GB{5Z0-x7A9ql$B2xtnafJ+Su+bpC~HmCQ17m_4yBQ_&}NNvb)a(Clh0bxKTB)5 zmXen1vp)Rc4^JQY$VaA+e)OXUYs;LpP)A3NR*SS;5$~N#eK(#+T2v5Ad99_i1?@Ni z#qXV%THnXd`G50w{EX&ml&)g_3c}LZL0B47?>Opf33~`@PVFvx_|S(wH1%q+dOA0h zD63Q{EOl>~%VX)yrj^Iiob`2ut!iN_k0pJl=d#wUEzDe2#>h1>QlHIS*4h!==g;5j zR(G84bf-IArfN8s%PLV;tQyKoKK$ViKUkIRec-)AsVWD0w{2AoowRBycq)gbs-cv; zk+hcRv&d)dqKDRTk(Tl@B8XAia+Q3{Z^ay!Q;<{AlJEU@`1ej#HL}vRiWE7}qKPQU zUHyOUo!8DRS+%9lx$M_zorAZ9tAdRyy!Za73vY!Hl!OriB!nf3G)Jd z-!F1*v-ikfUomRdTqagT{(BcWw-j2D`R_;*IoDcWj5*ef7G0rVU7E1;yNk4Mi?Fi# zSn2~aek}DVc^@-BmUJxj$&rpl1&5xF+ zeAe|2WzF1CXA5O%tlKkI*0I++*Or!5(Z3%3?|a|-PCe4qO5ZtV2cawUE3buhDZ;w67ps*ijFieYJR znk{VUSQ_Ir*413GK381xxhu-L=3jsBcOOw!Z>?eUXJy{3Q7w49Su=W8Mp<5^bVbi{ zkJgct^|!zM?ezD*|NZojfBfUr${fn|oVnp6X~{wDnpUjxfTrc^4{^n3b+sTZ*R?X* zlKz7p6!ooH>T)_--S1IVOSj6jE9k04Sgv28Vd9A~JCwz}3Ki>-u#`=2QPzx(r9LU|q}RvlWeX!=&14I?XQ8g@RYt{wtq@|Ji5_*9dQf*z_G+bk)SR6)#5;RGZ%@Q;9vu=a5(6d^a)(UBPjd-v0fUYIVg0!Np zwU@R;Gfp{~&K0SsM`79jjnBpRbFw;$uF$V$GK5~!eMZCT5!MlIcV)L%9PgQql@V5} zdczhSOMTrlg)=%m9<4EW|TGS$0A$U zTUXe-qmDeCmvFsHvoFe8pC_(;jxEZ%_6XSX{Vz>fqxFU@%2LLmMOs_*tXSDW*X}LK zI&#h7s5bm6X?czKmRE}m;;gQf(N@d5<^C-w$H^z#kHpk^oE_`W@qV;q18HMcP~SnW zc6YW%y6Wjytqh^BBlMc?nGdU_VT}lDuNSMADfIgl`n?L(S2DsnO2<-P?-7<)A>HbF zm)<^dz87T`Wv$MYMOmw7u8zBNde3{_b1^-uwbrm#)j=K({8=r^YUx=x;c>xh#)y=q z+Sa#4T5>LHlZ#nio6Ms{m4}|D74I%RBCRZg70-w4y4ut#pO|D$}prgViD|{YJ4KRz_H(yXPF?cAq7zQJr_`!!5$^R$`ziYvrufF*m*Kl9VOoe?(fbc9v6CPtSt1yhBA^gBQAC9J*c2^F8LDRj!(vZioG$C8fRBdn3@eWeQN z5o-&jV-;l;VXe;lMOmw7u8w=WmUYy6!xa~NFLStMybN_ncfb4H7tI>p zhMq-DcsX=il(j|A+Ed*@?&OghJ|nHnr`01Zuc<`_v8QWop)JoQy1o^OE9zWMKp7!z zdCp(U_noR-vucsmtacUYD$}o89;^{z>9@^%STlsR;&zuFyM?gSH+qyMy?8`eGgV0M zwT`8+uEzW#t(6!k%33*Vb<9m~+e29&{pd%}_Yu>|YOOciS_^*GpGD?ywtm*VCM_td zx9)I;wA`zu8u4By-o08qU8|Kv>}3+Y0z{9vGU|eYoQziXd7aqmJ$=hEf=*UcWymsv zz3d?VtCoJ{8Nzty=UCHyD`6>aR@W0AO<3F!x<^?vgtb-2@=B!fo`o8B9(m-ElkQ6~ z>w1@tRg_hPwL0$?Wv!mMI_}QtUGI9=eW7P*6z*jWSDogK}=~|;KVn$o;-Rcon)VZ8~A{}jg?62kfPF30$QL*W>vgtdp$^R$`ziYvruf zF*m*Kexj^ZC;Xnu4tv%dstV+c8-5FE#mc4e4%1Fr?$v^{(6z`SLJrcjqOK*%$TEpk zfPhjUmq=Vu=W+saGFsX9*YbUciq=0HpI0qp<#fgOjdVqJ5W>>AgRbyjd6fpw5ISM$ zw~BXuj`gs5S;AQj>!|9ZqfBAcv9?wq)m76GRY;?jwW?zkWfftq&ih4Kt7oo`d%Tu) zuh$#unitaAa(_eC8{*H>>~Py2tv~$X52w*xrT_frKcD{cm%p6vOGkx=Z0%u>wA`ze zWe}k)NI@Ad{92-jNL#E&blP%#t4CZJbwNKp0&{B0_Wd>DzEhR^!pNK zgr%715ticVXu?w5s}IcH4SFkKsqfxTlts2sV_l8;MOrH{P?WWD*6Ns>-qxb5qpCW{ z#d>6AhdutR)oP_%?{&D9w9vJ1#K-ET&=wgfNGsE|X0nJ*TO)m|r*V;$i{!)ONJz7f zIXlCX}hjn#9! z@4b@_6-Y02Hyy9uP?S|SYl# zZOe%Z8gYHgGm4S8pc|*2xF1Pr^f(^xk9j|Dp(?ddm0JHU(3QR`8yQudlSo(SS2~x_ zmHV$+`jvaIZiBEChxcMlA%xX)yx(trEL}}%th?s1zP|f9Z5Cx!^sGnEh_ZA)`wx8J z1E&vu@Pnrhedt4{4}bW>r;mK(Bj zS-M49RC;iqR=n>ax|Xs|tqh{Ht*C4DvWR%LoVG+8kOwrP)+xp5B~n+5ydWW`q?vt= z%UUO@NL4Me%5*DyS1r0iztXvct}_2sFGCnz?%90_Th$sySwgSvzPg@Jy6&ac6y8h1 z(sL(o_Vq;YtPxc_G91v_P3uPtzK1!Ytyr~R(RM-T3LH{~B!;WmV*?MUKv!Q`S*>*2g~fu@m*+ z{}nQazx?GdPrv%ruTH=I^{-FA`OR-mEjRpL((()#EUptfH*-dE(mV*rKd!kAOYj|7~x3+v7;&Lc5RkLIj zwJYdK=Z(xD+7)y)qhG}gVO&S3-#y;>xmB%UM#EBE9!*$JrV6C9I+n(|J!54Zd%cua zlvUBc9{m|*^+;>R1^;An!YgyKwZg-yrWHv`Gp&u$L}*JI zOw1yRA~NEVro%=?u}54Pb+yRL$!K*SlJeH6YKE+Ox>ckr^edf9v@7V!D>S(O%JnPH z5RM2-F_G1DA8A;#gmqhP_ujn<)vrgc_dWM3obh8Z&g~g1>)7k1w4$tv{`Kg8>s#NN zQr3!|buTMB990({%A!8}&wlo^)6akY^Rw4Qo|ov+YWcLXDi7Ix5C8bbKc2HkXj<-y z_YC4l*CLA;X$x&DBQ9mgJe%nHmZ+pfT|wlaMy5?7?IsC7BkThasq|8r!>xCfUL~!pM*Q!7_q&s8T0O7UNY~1;h%2<^{w*i2 zp3Vi?MB?GGQ_;#kuM=C|*IN-G8~U7DeHS)-YIv@;YF8Oux&NxAUyVFiiih9+_P6Ki zw&+)_n(jvv*32DrRx~VXV;z|()c7*1W5soa5SGTe8uN>^R$`ziYvrufF*m)Pb8$-$Nm~|ADW_ds@g(UTeT~%&H!Dt{8z4D#SCHe zVBI!h9aVYsQg?%1alB_$NZs*XlvQN4cfK#m+It4C`S>@#`OUlatUY($>AB&l3%}I~ zzv|H%k(NAKEuU7a%7gAR7d0&>E!VY_P4Wz)JL5B5E6XCX7IB8QaLPMvxqmAXSJb&6 z9EhjYMp9}$ZXM6_yq~vdRkWFAmFZS3S3ABdv@7T;bGOgvSG^2jBrL^B>%N4owSImJoW-1qnJeTa${s!|J8sfDbZu5=FA zL{=+w}_^@2RQoO`R3ft>+{65&#^^W*B$|TzQ0DENLsky(X?9Y4YxSq zA9>`F^F7J7+~e>IU--hgn&H;W;WxhVjng;3`OVX}zV)qB&!cq-(weEMHJd@qbge9l zh-WL(7QQW9@)>PO!$H$=;_|Fw)Va`!BI)oL658AHda>pGz0LG0&j#wdp;?V|D_rd{ zGYDN}*})n8DrN{>!;*d#H7vzac3;B18rCdfsULm)>t8>wGTA~{>bIZ&{O3=f``qVF zpZ)A-&y|QrnL>>ZvzfwC<%X7yRg_hfwK`W8Wv!mMI_}Qtjc|jg3iW$P_!OAK%WOsgURU92< z2^Hrvgr$D7g|J4B_l&S+Glesb_m&?^V;O!d5thcf8uN>^R$`ziYvrufF*m(UBTpnP z&l>jhtQKjht_@j3)oh-v39r$3HgkCNy2DG7R&>Qjua;*JGiSW7LnMosdA4R~E7P}H z#1(a}sCBi-%L&K{X=dM#&Gwz9wC_ZveW$8Sv&zV74_#&1!Ir~)lp%~R_uC|_t?LQ5 zc(F3dYI(6{YP@f$+#tt0gjJMPgta>F7iF!UxjOF7=?!mq!{aE+*Bp{Ll$(?l(k(q} z_8y0`nL}k7lyOkzVU#)i>Q}#dnsvi}``h0>*R*b}JN*9lzkg~~EzLA7?#QDo4pkoF z9eJWFUYQ|CYev_~-0@p!tCvmmjG_~luTz9xBGu?|B%j{nNKU=~8~>KQH=m1EpP5bH zg-zd)O;nYUmClj$z?d1#v@7T;bGN(ys;6JY3}JM+D~@8NhU~66Jq=6o+|#hsA7%+l z{cP6lesrd=cdtTR?^&kMDNAErjrm1dD=|=%wQ|<#n48|FktdQCSwq*fAT20smOt#h%9zj^@PrW-q za~J3>UaXdmX@6}_WIYq{&AGma>9$UB56@S z>!{4(CqMbgbG5=z=5WRhKO!yNqfJ|;Y4x&)8EO6Wr$0U4nHQgy>slf$XbVltGl(r+ z3))f!v6n@}vlVFzmweB=<@%Nr*ND0@@@mnSQ_<=^6y+@>w*+Nm4#=08!i?UW?peSqQtkp4(r>soRLetukIaFnbth$5yvu3K5 zjz~+6+Zi|f=q@~4Nh`Y>&*+Xky$qr|<6YN6+mfb*u5}b`W%^c+xFU5yGfp_Z?=-Y$ z-(S=Bd4F%ws%S-2Z2E3&F(Vjtt37n(*+E}FsNWU3a{a28A&i8j7>k6ZSZvjFUnMN{ zml0vjxZP*GSfe$CquN-lOks4qqhl3i6=AK;`$bu+XReOBb9&wDUU$C6gR)vq_()nu zWe#WVb2##7&D>R5E7DsP9=4EHcHcv33z4+YwUqVgWe~UMTF{n!KdeQ>vxP&xr)_2W zmJ^q+QH<0TwXPOIHOAo&;nAW}>1fSii?rn5(AM*4jn*CZ?!vQ`w6e;jKls59&UfDR>|tDg*z;<6 z2GO1InXZ*(5hHEIOyZ2bMI{JnIIeNIk1Oh28FfK9PCl2|=Y3+U&*YmvGn>8(8{gA4 zs~OK#=DQ*@h<24_2gwlXH+BD&>sP*p&XhPoc(}EK`{2Se_}=)uhI{Yaj3H`>)YvQC3CHTIA@wL0M>8PFaz( z$Qoufw0dg}b$!}eb2##7ed<%6I<-7ny$TO2q}5t`7)dLu^blR~vl&Ep#%H=#=8ng+ z)uOFlHZjw;dc+l}DaRVwd(VrN=~xg}wx)2_@vgD0C~JL=sQLBIsVJ+W zV7&*u<~6T*jI>bJowUdrW={B(%%N8-je1tkqtzlUtt8jhT6fs0rPb23GSd3q_r72aD|i%L&FEL73?V)&^s7i%icxLx&d*y3t5*|?stuWj)pNV|yjU6! zT3)OznZnHR&Ugl4X{@WcVtuZ-=5tq+bS*<9!(WQDG;>ALg0{#WMpwLN5NC9)jJC)&xo2yuwnaA4^{pA=iqthjUJws$B%|5K z9N%htMCDmPeTNmY+Cx{P?BJ|^6*Gj<_En)yfoVjG4_8 zM#sCxHf=>&MOdrzWl`4ZnXBXOoF0Do;q&MNWzA*|$sR^MtCcxyRd?_T53w$M&kf&N zcX$M8Wp|j4q~*GnNDJCR)A9`BjIQPD5XmBDo-G{m&=$TeT=Fy7#1?V+I>o4SWz;32 z$>=LmkRG>)iRZIp;&Z%TY@sPyeRei|CpOU~o6Z57sA`t1aJA#RA~OhGd3A;?JLvwa zo_-ZGglJgMRa{3H2}?1Y)pT!V30oRgi?Dj@343n$R$Z)C)rKrnsIezoQy6Pwd8SZ= zrLnHY{35ND7%0kGIcs&yO>cv;(6pSg(6rp2HJdq%Zg?oG<hfb*@NV8F@iAk$jGR z%=^R^nxciM)IwCWP?g$9RwK{V3SDLHcK2WP9PXnGVRX4GKHcShF9>VJ?cVZY;m2~f zd$y)9%M@zdVof0$R#8^b)t))OC~MEzyY^#W{pwdgPR~NqIx2J6stdo>4c{Uyts-w# zc*v@jWjp{^!1)?NE}U*CU?HjA<G)54$S znil@7EOVH7w5afarUhlS-0(4b2xYY@JlqCp#pvO0pU^gE(md3Mm(54!)#^{ZZn5FZx$ zm1|hHO<1kn(vqq^ zlR0GFA*7`daJITbtBzL74WF$$+)G+nm51!UhtdLE({f!)KA4_Y%hw>fGk&CNZJ{lE zTQk|j5pj7{h^TW#>Vjq>ImikEMbkDMct(sUF zVQq1{uX?e1YYNe@in7+{h?-wZ%c|(v(+ght%2%GR@t~}g%%N*qtU1i8J7gZM%nhHB zmfY~}h)2&llC&Ou^wIM*zx%X$q&1sC9O+u_jwg$_m$rKPmak8A;u3*CBeYH|8F@iA zk$jGR%=^SPem*`oEmYOgta@t&bq+;VN7Gfyf7Q~jdKtnIVJW7yeeG*sJK@2K8rELI zYE^8=2rF~D$4udD)rOfhg;18RCNz!*$%c|&KkA5f%O$*9G)54$Snil@7 zm^s|z(ZZ*NrlmE9*}B6cNh`bWVI-|sy>vE%DE$GimOJA!T`S8XuF@9k5;J|v*C)Eh z)uXN%@^aFN6twrTzjoY*q-dR}W~j}gk-yS?SVYUx+8N`q$zqs!ej ztcY zYbKkxmAE2xMZF7}fo`%k64BPj*=zC~@9(WXqi^~SzKN*VL{x0jt6Z~kx{}{2^IYM( znxU)A-R|oLXY?yv?xHIvEaK5KgmE3AYgkV6P?oMHHP-DJE9=Gbv?q*mKI z*8j%)dduhY7NTnDRU@*>=t}1rvSQ6(^j)D{&C=D3e&uTjM;StQxktiMtZQ?Zd%SDT ztPd+{ShMR1v)b;_?LJc%D|)e9$I{pZWuajeWv$N8 zP!^gNlod0FWDlXN=+VNb<$6}ESbD#aR;;Pzq&1sC9O+tF7BO3kh-a&%ZDq8zN?bka zf?Awp*mvT&iiN^PxevaJt_wao_kGD=#+Rv!UeO9w%HLG3q=xUT5bpKULzlv2FJVU5h zb^Xd+?jyodABlI($<`9CXjtmkQNxOa6)TU<)Wzx%R?HL@Wv$NmX z=X{L^Wua+llycHi=8)_mdKTG3^enQ6=vgDu(%Pc9?l6*;-0<#*S7u@*dl*UU3t#xc z$w@2LiFeZS45DT=CoRt)ww&?kTF@4&^QB|pkk<@M7BSLR%eOV7Z+S+srEz&?F;Z7X zUM>29cASLv?DKkA8)+&Mm1|XzszzirlNFrF45D3Sbmi3;S;t=Q`714}qJKU5 zp)52lC<{#skAv%3WDlXNkw+_5EcNW+rAUiP51;<@r%#{x%xBKm={?fYIx8nFWmzDt z*$kpP<1<|g+LES)uGRBwwP=eqiM?zhl^|T-Y7tlTaz*OO$g4$Pk$y5l>OB{a&mOxz z#lAkHZ~6|t;aO8F+GHaen32`UbA@ijnn7Ick*>%NLf8EMk*;R+D_=v{%Mg+!gsyxY z;fS!*cVflSk%qNGSh2Qybi2oU6mIci;m1P5D#}`)BWivvEvuquPcL}c%U*WA#)GoZ zv@}XNX~oRprAbS5bz&{8t(sOOEoh6%rMi}PP0LA38I~4lwbmexbgf<%@hIBL^sO1< z5_LF<%#asU1L<74<^5voG^PFcJs>LDtz;E-t0U+N!ct5?SH6DG{Z}ph$}@yL50(>_ zV%{|@^$Bgd8X-ftm#}7PVjW3X8k^kh?t}$p>1tAA-JY?sj=kQwwzRB@{`KgGve2|N zDmiJz%%N*q${dnCM9(67IOB$onwIM4#QPmylC+>LNXs*bqAVvZbS-5MJ%fl}OB#ba z<1<}LvnZs6u0<9RZOgNWIOIo}M9un{zBMAQp3Vi4L}H0VpSBiY2qk%U$s$JdRI z`8?h_QE6Y_iA~>?P3M43=Y~z^j178KMlj0?ZpjQrxzVOdgt2GvMT!5 zqaVsb)6%Hqq!lxVRCtJ_C65+9E%YpR!{dm@qZKPW97$U7zK1>1n)Pa}>RMSAvFF)} z+7`5hOMXV*!Y%Lm)+}*F>dMHgMPE)k+K*H;`#8Q%_Bhg1Br1Jwh>8}fQVUh7g{ss> zvdZX+l?wVT@LZu=>GyKq)eK!_*}*;fm1hW@u%gSoM_B4ZeqX{^4=ehxjwGxtZueHE zkZ}wRt0-%Ij;Q&yw5*DrJ-y(ehaNg#<3U+yS}}9z9xW#=`Lo=k)gvvf9qO$++(TON zZah8GLf68l<)r0aEzcmX>RMSAF@D4DD_v#Yt1LU%s?gy6tCoI+h7~RwOL$cEw$D_7K*nVndIxG|sq&)gmnSVnJEDn$%dgXRNGauXnC3Evuq`J^G<6 zG%bxvGo-b}4ey%Py&^4TL0r>v(t@^P22rzBMp|2Rt*q(;S;V7gE7P~O5?9o^r13;j zfnZ__>0G*v@5|%(IbElUT9xZnD`d5*UG?ZH%MSYb!IA&UGlWi9#4~i&A}sX{vV`{w zVfEG%&br;5u!^$Q=ZKnLOUtV0+0zRieDJ~ZHJ(NxCoL3robWA=R?iKOBOc1a5q~6U z#mc3wX+84DBj>D#{3XmJke07Mob_tq*AitxTBCJ{nLC~=BD94!M>8=F`JT3gQ$EwT zM#M$MQ#2bgWKrkJs4F8cCmg8f=+@sO?nk0pp{kK))gvqY7Rm~?GK0`n&wG_+2V3h0 z(XSML&{Zo#*z;idIzlHb^%-qs2;-ffy&l$>Q!uz=hHPTCoFfnYkX>HSP+)R zx;f+uk4lkw`Pc|M_n0tiN0C{=EM_eXzOEtt!$sq5nHE5?L(7l^<90F-p3ZbR!&wk zSwYqe;%eVQS6Oy&Rlo8o4QN==kP&&~Itg{ag*Rcb|4Y>;Y3R$Fx|TrWG@XGajwIq@{I- ztUc6quQqCF^&Ig|S|9q*ht4wrl}nwp{Ej^Ciq}j7X|*zldvz`9#zR}mAd*GIv(?hJ zW@szZx3Y|4kGLXr&5)N9O{ASmJ@)sE`;Zi^6IG*FMJ zs^_rsbJKvWL1p+^T6Qb4d2^ejqJq3wJM?)|L$7 zNZ0CR5uq*3s_0rPwAItMGRJ(6xJJ~Kk(bCNBO9KJ6twp-ubZ_{l(&$Sw=JsLLRKxm zm41&^x`MD28)#S1RhAv}^@EvywI@U9F86y)SnhVmm4MTI3t^#SX>8M0l(jxjT>Bhb zly&V9u;=@~=tVC&k3Nh<=vf+}psbNc%WG-%Dm-{Ct(K-WTTjb1t@pqG{bwhxG8)=i znpQ?y(3Y<8w`34AU8`61L9-{bDZ17c+KNtjr>)Gt<;3M##VynYoj9?u?_|^3&&X)z zxz_9X^L!3(`MlnWqghhWML#Td+%U%6%q+zj^n40dsme8|=u3KGU^&Swz+% zZl$dm@0Jr6RG=18=n+?>u9#tjP@G(R-zn!d_WiwmpU>$nG(`(h(Lz;f{apDu6jeD{ zWppK9ojclbweO*;EIYWOU(I9)TP}BeSfZ;S;t;4r4?mW^shz#Q=j_M6O!4@h(UMxfrE2LOO=~M@ zwbmbwNGr=A&gxpdEaH*0mFZhqMsb$7qShrEaXRsRCz-hK?=8pUwB3sG?S- z@0;mWD`Yj&tybx(mmP$#&{-p0W%|`jhOp&wzh4P!w4RVFM~!XTG}dX%tNCJWzPRS| zR+M$kzy6-@t`X-2FL=QdNXtE1__SK2CCa+rNz1)j=vu5+N7KsIAa2#QdRas~TYG4$ z=a%x#09uvX^GqO6s(R>$1AJK{X_7-dCGYn8Ncsa`sJ*s7^T1`#g_npQ?yN9kJJp{JKc z#Iv=BwlcT8W_sxhGd&o5F5UCpvZAsHY*CLCEXKRJFr~;uGoEcr(2^+e~3~}{zu8g`e@`7X{;oR0^-aodG zl((}~rSsC$temWRx>c(pL(6wXW)STPx>Ed*8AQ8+t{^PMBH2OduF|eX`jxLC%=D`* zghh?JBi9n*!`ebv=vZ7C;&dlV2w`cg)0kKD#oByv&F8Hs>zaT4J>UH)Px&WhkvT-u z!qdRWMdt7}NsGHo%SEe=wTC0pYPsSw(#kT3WD&i#R?n}cSr3m4q=l}vhqii7`7OlN z)43vb#S9~q;^Y$dBkf%3aeQBY%;)r$>>n)|K-!F|+-Ef+tDbH(a3c@0;6l2g8 z?sf>v>52@YVi<>ewuUextd`3ir@QoHobFCo^iL`^sNbqm!`5mIM-dj3rLhdc()cFI z(wMhr%&cRtlhBH?D)QGNUn9=*pa1;F^sKF<qBhA|_r7JWI@IQ1O4@9&ZALs8yB zQnXG~+SfVo&GkSu!c4Prvg+wptJ)QG74<8{SfnfTE3O96VV$s~U7=r%GK3?S`xe4d zAJ#@iVrsg#>S4_i7G5m0_2|Ru5thcfqO7&K;@aofqO5C=fYtBsltt#SM_L-uqNa5u zY55(dN2G`drew?@579=My_~WucK+r=vtXy3tcN}Tj&jYX{+bma(!zLaYgD9 zfkpCa(N~MWoOIaF$f)-m$Bo*Iq%xY)cW}DYzH3#kSFMs&Pq*4jSLjzFdbBIWU8Y~L zZg!+!Dej>w^ebJ7DATU1lPp6xa=DWwbPelX5Ehgrx{8FQv97eNBCVBqzbI?vtkp4h z&La+#Mdt8ck(TZZOFgYj)6z9E*~5&qGOyNb1`*nFXM9iB+LA>Sam{L5J*Rx8Z_N-_ zPv?qSS4Lhf`sxu_i-3-LKBK{mrba~7qbi-Zo@SMiRZq8yuJ%@D5bX-O!g~c_WpuUG ze-#OALQ@&wcJ=de)PXv@%!xYzA>v*V3#ONh|7F(X%x}TRo?Irf+%etrl_l9epBkxt~kK zkx>`APt=2W+#;S!JDEAxI)*2-C{W29w0_!woiG%eQY9F;xPy~wmm zTi3DL9)9@Y^TvG-GfgYI%QU){Mtw+YHiMY0L5x|%OxMyZMg|emI)b)lyjxi%2-mk} zi7Qgq40%B)kzo9Bq@JT6`+LQGK3{AhDOxA0NL5?OD$}i6)fk+tTE456b`|ML@#ch; zWe2nMgFXFfHbV$uxd#g$mb={XVY!AyKd0YQsewB`XB9^?r+ZJsVm+bsczjrBSQ_7M zDC@ymV|cfUPtAYZ&OuRDMa;H{;e0_^8htbh-EX8t29XgQ&j;DVt)!J@5OK!$bS=$V z${=pZB5u*Plu^{|o6%OLZ*3*6p3ap~S4LitN+g;1I1zaT4J>UI5|MS7qbDr~@^EKWIX?ZOzU1PGknyRI`7Hw57oz=8v zNvr48>SYjTbgf?1haau>a*i!CFS9)UUOL>k)q z*k7x)-}-!fesA?1ebYJcOK+gucW|jG^T)V=P2U)2-g05z=gERUSgrzMv)_@OdM#Iwhrm;?A-kveDj=fG&E6S?KUyFPw z>t2%<8APsml|3YbD9VDgW?k_!8N?Y~i!37g0D1xX0ofXn)*jl@tjjFStcVim|RD8chIIZVfq{Q>B<66)AXYqYLcWj-gv=33yLRD(FkX5Ez={Mtd}B;HrKFVU1kwXjt@L`g3%-uVxAN5>`>x+B{M7>zz|kRz<;D z?-6A^``OPvU*nBPiL=zAOZ6uK%eTib6Zfq^U7n=7t;jxEZ%_6S)0{!rEtq@@v1 zqahBGXrrbDX+c{~T9LMxNyv<7hS8=OXN#_-*^JqZ*^b$d*>IM& zdfqK3E@(^bh`44miy3ugXPEb8^K0*5O-uKZd~_efYpSw&c@b7fK1>Y2Ot z<3w4{de*bf*LasEEoh5tS4LXyFda25NJ}HOMsG%P&meM5jSJUxEzJ^{kLt1#X(6yKuGF?lQ6-jHBwzLM3b%-;h zC4FmF+sbOmXW2y8w^oR2hPpEH5`Fau48qCi=ZNQc{Z`B8i>74i`qGEI1 zP(;OsX60m+`K_F;s3xjb>jhhD1{E`CqiWGh(NFhg2Nmz=y7Y&Zel?RJoFOdrZ7May zEa7buR#8?F*6O@pl(l;1uKhS&*FE!@&pcDsZIc%34_(uOw9vK4I^d!uBjLK1C@YfI zOa^g8Thajb=vrHRTbgm1g{5_{p@ZxpE|CJ%KL-7X6HVM?V~Su(lAEyWEd(y2}M2x`ME7DC;Fv$)T!}ZjOPXtRkkh>l0DdGoJB` z`+~HfEse@i(~6|k^J-l}*TNltDcaJkOFeJ0Nv?0Th|4vuJ=B$vmq;Wd6`sq;X7)K~ zY4-nT|Bug+wNR9|k*M^T3}EzE%}|xjJ({CyR!&x#Zq-{exL3QHrK=VFYQ=*EVd2H( z$|}ne-UeZ5tkalR^TpbHan0wgDC?Sk{XO6PKmOx}vi6b|cbI1F4{L#Nt}=r9`a?!q zMqWnZOxF@+wMa|X)LdU{Cg^1lb*;{IJJ<1=akORH7P^+&z1o&CKWG(b7ibx3(Knzi z*SGc(*GTIUWn>i6qA%zrBb?iKj`z>n*7tCt8c~&J0$s1#N>-U}<-RN8fvlj~p6{xa z8Fac*thThP7G2@Ll7{6D_bnMh^-pE`xk{oR)3530^m}RBj0cPlj2Da_j3-oT*s_)| zYFHZMv}w%SGiKJY*GXtaSrz$fk*^Ww=}&+9dGxs#q@|HIW)IP{G)gmKYxKse1#RI4 zA#>rRHKMI8q@@`P+KQyLMceAFNfdQKT+o*5TYHHs>RcIh#VjLK(xMig&q(Kz&+)#k zme1|2zFXXM4t&!&@{J7b9F`|_fqpG5cJ8i@x@u^tdN?2KT5W0%`mHHa} zP97|-e(0a{*B)Wv9Jm*RrLnFkYi+K$_BpmF>)In=_4`9vN0XMWJ#`(*H7VDqT(dI5 zGU9T5%k}OoX~i{&=vtZ`Mx?b>*SaKaNz>@*TPwunUM`V>69oHC87=Z^(U((9i+GNH z-rqCZk0iz8_&xdl-nOV}CL8FQm6KJbTV+|ndqGzt{R+b3D#qz*mayp0^l$n*{h#rG z@qzI|V~5wmIzq$J7*~{4gtaKg(*Sc-m%BnzQ*+kd3Jfk>ET+z!FscV+JAQXtD*Jk8% z#B;s(-lkE< zH@QP^wg=Cvp0)DXx2HVi!PCw5(B1Zl+f$$V(CKD->D~5;+tZ%*(z|4*?PX8cp8oWg z-6cM4chc<{&$x3pyQ%Hv>GsTLzWlC}U)w95us!QpueiI`NZTvZ?b*+M)!pi>{p~r= zdDY#!5ovpMx;^)~uRi^Ad-(4DKey*S@8Q!Ex7XZ#uDw0~`LDTq#}KyH-retS4?OVN z)8n_--F>`md%+7{cUMPfZLh!kxBGExeco-~{h#OR@#k@L-f#ZS@6`T1|M^=#F%~>l zZub8fORab#9!GJjzqjI^ev$PV`ctdF(H~p=mHwRdb;f~gjJSF3W*o`Jn49Nr#vvb@ z9>0Gxj``SDgjIyKI`0={t)96$u6kX?K*d1CK*d1CK*c~W28yzJXQ%#Fy{BTJVxVH6 zVxVH6Vqi4}in3PETpd@vu415Kpkkn6pkkn6pcey0S-rDUf2-b8F;Fp3F;Fp3F;FqE z8UsaHt7oo`t6oUai3{(tM3{(tM3{(uP#z0Zl>Y1zKs@GKv zR18!MR18!MR1EZDpeU<%cIt1{dnyJh1}X+B1}X+B23BLBC~Ni1)p6D9Dh4VBDh4VB zDh4VBdNELx)jK=&x9U9=0~G@m0~G@m0~G_SF;JAXdgki5>U9+Z6$2Fm6$2Fm6$8B( zD9Y-co%&n#o{E8rfr^2Ofr^2Ofz=o&%33{hbzJqjih+uOih+uOih+uOUJMjv_0CTH zt$I(zK*d1CK*d1CK*hjn3>0Opp1C@%dR@gp#X!YC#X!YC#Xv6xin4lVr~X#Gr(&RD zpkkn6pkkn6U^NDcvR2Pr9ap`sVxVH6VxVH6VxVH67Xw9Ey|Yt)tKL&FP%%(3P%%(3 zP%*F?14UV@XReN`URNTlJ1Dh4VBDh4VBDh4VBR%4(jYxT_4anOB<$6$2Fm6$2Fm6$7g=P?WWL=IXfWbrk~@0~G@m0~G@m z1HBk1%Ick+`djs$ih+uOih+uOih+uO)fgzsT0L`hT=lw&fr^2Ofr^2Ofr^1%3>0Pc z&QATUdQZhb#X!YC#X!YC#lUI|6lJZRxjL?TUBy7fK*d1CK*d1CKraT0vU+Ey{#L!G zVxVH6VxVH6VxVGRH3o{ZR?l1=SG}%cpkkn6pkkn6pkkmG14UWAvr~Vo-cvD9F;Fp3 zF;Fp3F|Zl~MOmw7u8yl-S20jAP%%(3P%%(3(2IehtlrtFzg6$47^oPi7^oPi7^oOn zje(-9)iYPeRj;cUs2Hdis2Hdis2J$QKv7oj?9|_?_f!m23{(tM3{(tM46Me$zrX8$ z{O|w%x7DNTxQe`rfr^2Ofr^2Ofr^2OfjkC2{{Q~hfB*ac{h$Bm-|qf<=tZx1;cp*! H=U4wfv8J2R literal 3381 zcmeH~_g9l?7RO%#GK8WqD1r=aa0hV+ngtdO29#nzrHM)?0>TIg1cnljl29TF6WOrK z5v5xwOAkT?9)4-)TT zHD4|9`s`g*_LRYX_P;G>e$CO9`1&QIF71MSu&3Q5bw^*m$Wd7g5dH!4ru4y&*lJSx zKkm1;4m5FjaQ&$2hrubm#A4GTof4Dvyy#A!0;5+0lwf1dM8s#yVNIP0`hEa_7mRBI zfZwQ`5s-2`bq}EY3mE{qWYmDu4`>iTP8C#p4Wa<(UhtCwZb83a`rd;7rzsex*;42#fo;u_QLAkLQt?j%f^*ED*_>Z! z6?BX@%?V5fW@cuvzR|C=;zrz@k(5tNMEvq^SKDqbJx{hYF^T3glefPHiI?9V3&q0# z88w*3?VQ>6b&gj+K!C7MpET0bW1DsN?#Rr{Z*2S)UpQp5kAn1>NZR5zW0$6S5s_22 zS{AW2+Z)p)MAVGE7(6i~S!P}la;DPS^Zl-qn~*Z>MiodXk5MZWtqd|3*sZ*VSK8I@ z-@m7##$E86a~1go1=VpAxaPpXK(6TP3Wj8hf*OtMlF@Mlb$5%ng&L+$Vy#cYTp1Om zkw7F8&5+2eg0>YWF_;fSvO#4SF$!q@SC!6r^VzM>@O2y>>FTm($FeXOk_zk?5pQ#0*x5JtHm8Wk#z2If?pWUTQ9t8=p=~GQ#EzmUD z3~8S}x)g9-{ihP#ng>h&c&Vt~dehMleyL+?p_Pi#4W8)0VMqY^D1_K?WBS$!IJ{w@ z-hneU<&WI>NJz`;h$7lqL5*&|dH3!DOP?&OprBv|hr4<;hu21b9-tui(yE|Fn~!UA zCIy{4J`rt8XMe5l=i-)M8(;^K>m$^#^fC;Dot~_L8%mK^jOxgZu?K3ko)lkK?t^-QSNAr$pJrISy>q(aV@OBzn`8e z{X+;I-!V{(WUU30{eLDNwjyRfVwyVg#~Lfv=EvMFU#@RkK8IZDQ5Av;n0VOSCJ(Su`GZvoc#VZrDSLu^S3%GhDH6#td^KR>vlXPTI?2X zZwZ+H)Uvp;`q`=QgsV={;u)N1=@o@c9;%oF)GtAZW*RSZIN%K~-QBkqbb=S2Uv~a} zNNg*I z6U6IESHN;dDyyn?7l)Z+G=EwtFer$naY)@hya5d}M=NIa7nn1P;y4~)KkKU9>lu%1 zc9dwH+wuSpbVbb@U5*b72BW7d9OQE8)ff^)G4sp^Y?va1c%bq36yt^!w2}lH&?*Ke z9}?#+ubh)?&D?(I-YKyst1$6iTbi=ly83U4gy z!ygjj5dCUknvxuZiZYRaN2HBYAkZA>z8oQ=#%wqa_Oi0ub+@3J;U# zN5fUoTCOEx|&erZf8?79~U#nsHK3O3~ONp#@r_H>-Kj zZ*h#?eO{7OZ^l-J9637H?eN$0^kEtD(kQz-sFW|&cL?0}YjVI)eE4@z`_8KW1^{g zr~98~W841t0Nb?)Zduvw|Mm4x{daYJxc!JE`+Jt@=Q-K=wjY#Z&tetQ(>no@_@9vQ z_kV5e|NqmcZ?{!p7wco4`=5bf{&s;o?6OSTOSiCZ6JngcUFSGEE7x@Y8XlSH?>Dk@ zOwZ>666XUsc&77faBxgNy_#KSd+$YdCb{XXUpWM}&wtMzrZAnikV6Edl4rZ90Eb^G zySMkt|FN;Zw`a9-lyfnam9;F+@XY|Y^cTyK_0$ zcIR?#w(#loOguc>L(g%4n#iQD-|m^f1H`;Q%m>8$KrFD`GeL0GHWBaD)_nP!D{B~l Nz|+;wWt~$(69BffdHMhV delta 360 zcmWlRPbdU&9L0CW&iux2hF#kd#ke@E$n23sv9@+g%E6yP<}BI+nu{%IG!9rUzFW!` zNl{yBcfUpAKt%o>M6D7|av(?1&(EvR>-Bm)CmnIpk!f47MZ3Uu+fqh$9AOD`kgIXa zkpq&ji8u4|7!}sB735Sc42;uad_R(}`c+*=W*Fxz9Dc#$3i9C=CNQHDOY#}U8E>DN zY5uZ!oUvpQTWUzzqO`_bt`vG7NtK9`8Itn}NvuC55{0@w#S6nzng$=cM1dIPcHOOL z;0Y)ST!I4?TqM~IvuL%WT6NR})P%T=C*-_d{;V?@ZosdN_WT|o= x>_4a)_FSo7%VIF-L`{uT#3|-@I9`mJCEGcF|3-7w<;q;l#T5;Ab)?(PnLpu%ir@eM diff --git a/Apps/Playground/ReferenceImages/scissorTestWith1.5HardwareScaling.png b/Apps/Playground/ReferenceImages/scissor-test-3.png similarity index 99% rename from Apps/Playground/ReferenceImages/scissorTestWith1.5HardwareScaling.png rename to Apps/Playground/ReferenceImages/scissor-test-3.png index 083884186fb5706b534b21cfdf06b64628a3fbf8..3c9e2b0774b317d150ff3902d3bdca59999bf77f 100644 GIT binary patch delta 461 zcmWNLK}b|#5P;u5|G&SwZx^5L=H{zq!AKBdsZ&8(ty!)VS{a&DpoicEOY#;wSb-6- zA+K7ja44vR!cCIBn9QZzf-dPH=@K1`s8iP@T99S>2R;~P=9`(VX?JVdy?I-_fp0$c zmcXIkK6pADi!HO(i!1~+<>880`n=l}^y?aQI^j}@xm;#06sRMBthR@T|8QH|2d=AI zpc^xAFCs0gNRel-U)!$xnj0Hmu#^FQuR?+@y@bV-%;o-3{39III+OXtgM)Z+6|xvz zfo2-3$m4=)YcQwm*4A0J?Mb#Q4_~dr;ca(KdfzZ(cx1jq!xI{<$X`AWe8Ey*Q>F(e|jOh@7>S5Wuos`e^0r4^wxiQimN^V delta 468 zcmXAj+e;I06vy{Fzq6lnTkXVc3(bd7|?YrtNe9x&q z{*i+o;|>OA#h6#36bJHfRn2_a998_Q4;uXLVo{`266x>ftp&&!Pg~n>sjK@aRaGze z<_O&PYNZ~s_X+GVzF6#~_$GvVw*HasZjqrOJ`m6uWAb^Ct}Y%9>!eZBJQvrqH>!8q@DrAk zlxn~P?QcOZEuBFx&x~s@)!Xc-#d4hdn=njE8!cl2xS|a=7j=GAUFY|E@f*;UpE^X>wquAAz38HK%i6^Y`*3j=-P(cmT)vD==p40& o&eDeo2;Yn6(BkBH&dhHJ^}Kmrb7cMQcMe<4hmNI7P3?F70VQRvTL1t6 diff --git a/Apps/Playground/ReferenceImages/scissor-test.png b/Apps/Playground/ReferenceImages/scissor-test.png index 9d0d2ad938e696253cc43f54048ffc9312636943..04bf5c11b0966209ebcfed1e476b9bcf87caffc9 100644 GIT binary patch delta 439 zcmXAhPe_w-7{~W}_4ns_)7LSlm(E6EQYLtqp0W-?9sKoTU-;a;`tsnM^A_+ec9VO7^JEUzXvQyBPTp&u~+o_j3>Ru z;2=G}DrNz{jEfvhTs9S^I)xLXeo=-WB0^#CJ6AOB7sY^&R>c+kR3j|<(I^a%EiGSl zLBRwrepDr>L*n+i!RBf4AsjPIH`$^05j)hDL>svS>8bDk#+|J3H9$ z|376d$t@?pr5%nKI-O8XnKju6IJ~R8baP8u^(;CQg=$YxlS?SqR}JZhuYSYD>sGc|b;W0htprU{D3_g2k@+3NZ>RP5wF)p>bS z+U1(jg@yC@Jm7ns+Sr5_@aS1e?ZhOR@redqUAQzqz5<=h35pX>-9Q$0nDK b0@jN@xA#xq^FYVDO;`#qPrmRsj~ z7x~*UOG;WA<8whMYQ`RD%bK8}Nq#iS$}~!RKIUE1$t1Py@teTr=>9jJ_hTgmihlm( zFV0wn0zE%1%7D}B;KPF|IJmi`d^ivn5#$Hlq4Bsl0hk*!`&3pm(oRx%L8j9ix}jkl zs~=Po>a^&EDedu;jb~K7178`|I)sDSPT|Af9U_F;d9E;f)}+{b9>T<-Y%H#H30mor z{e8^+U!uBMx$5v<^Cpju11+?-D3gFQ1QEKrDkE1gb errorRatio; + function compare(test, renderData, referenceImage, threshold, errorRatio) { + const referenceData = TestUtils.getImageData(referenceImage); + if (referenceData.length != renderData.length) { + throw new Error(`Reference data length (${referenceData.length}) must match render data length (${renderData.length})`); + } - const width = testWidth / engine.getHardwareScalingLevel(); - const height = testHeight / engine.getHardwareScalingLevel(); + const size = renderData.length; + let differencesCount = 0; - if (error) { - TestUtils.writePNG(referenceData, width, height, TestUtils.getOutputDirectory() + "/Errors/" + test.referenceImage); - } - if (saveResult || error) { - TestUtils.writePNG(renderData, width, height, TestUtils.getOutputDirectory() + "/Results/" + test.referenceImage); - } - return error; -} - -function saveRenderedResult(test, renderData) { - const width = testWidth / engine.getHardwareScalingLevel(); - const height = testHeight / engine.getHardwareScalingLevel(); - TestUtils.writePNG(renderData, width, height, TestUtils.getOutputDirectory() + "/Results/" + test.referenceImage); - return false; // no error -} - -function evaluate(test, resultCanvas, result, referenceImage, index, waitRing, done, compareFunction) { - engine._engine.getFrameBufferData(function (screenshot) { - var testRes = true; - - if (!test.onlyVisual) { - - var defaultErrorRatio = 2.5; - - if (compareFunction(test, screenshot, referenceImage, test.threshold || 25, test.errorRatio || defaultErrorRatio)) { - testRes = false; - console.log('failed'); - } else { - testRes = true; - console.log('validated'); + for (let index = 0; index < size; index += 4) { + if (Math.abs(renderData[index] - referenceData[index]) < threshold && + Math.abs(renderData[index + 1] - referenceData[index + 1]) < threshold && + Math.abs(renderData[index + 2] - referenceData[index + 2]) < threshold) { + continue; + } + + if (differencesCount === 0) { + console.log(`First pixel off at ${index}: Value: (${renderData[index]}, ${renderData[index + 1]}, ${renderData[index] + 2}) - Expected: (${referenceData[index]}, ${referenceData[index + 1]}, ${referenceData[index + 2]}) `); } + + referenceData[index] = 255; + referenceData[index + 1] *= 0.5; + referenceData[index + 2] *= 0.5; + differencesCount++; } - currentScene.dispose(); - currentScene = null; - engine.setHardwareScalingLevel(1); + if (differencesCount) { + console.log("Pixel difference: " + differencesCount + " pixels."); + } else { + console.log("No pixel difference!"); + } - done(testRes); - }); -} + const error = (differencesCount * 100) / (size / 4) > errorRatio; -function processCurrentScene(test, resultCanvas, result, renderImage, index, waitRing, done, compareFunction) { - currentScene.useConstantAnimationDeltaTime = true; - var renderCount = test.renderCount || 1; + const width = testWidth / engine.getHardwareScalingLevel(); + const height = testHeight / engine.getHardwareScalingLevel(); - currentScene.executeWhenReady(function () { - if (currentScene.activeCamera && currentScene.activeCamera.useAutoRotationBehavior) { - currentScene.activeCamera.useAutoRotationBehavior = false; + if (error) { + TestUtils.writePNG(referenceData, width, height, TestUtils.getOutputDirectory() + "/Errors/" + test.referenceImage); + } + if (saveResult || error) { + TestUtils.writePNG(renderData, width, height, TestUtils.getOutputDirectory() + "/Results/" + test.referenceImage); } - engine.runRenderLoop(function () { - try { - currentScene.render(); - renderCount--; - - if (renderCount === 0) { - engine.stopRenderLoop(); - evaluate(test, resultCanvas, result, renderImage, index, waitRing, done, compareFunction); + return error; + } + + function saveRenderedResult(test, renderData) { + const width = testWidth / engine.getHardwareScalingLevel(); + const height = testHeight / engine.getHardwareScalingLevel(); + TestUtils.writePNG(renderData, width, height, TestUtils.getOutputDirectory() + "/Results/" + test.referenceImage); + return false; // no error + } + + function evaluate(test, referenceImage, done, compareFunction) { + engine._engine.getFrameBufferData(function (screenshot) { + let testRes = true; + + if (!test.onlyVisual) { + + const defaultErrorRatio = 2.5; + + if (compareFunction(test, screenshot, referenceImage, test.threshold || 25, test.errorRatio || defaultErrorRatio)) { + testRes = false; + console.log('failed'); + } else { + testRes = true; + console.log('validated'); } } - catch (e) { - console.error(e); - done(false); - } + + currentScene.dispose(); + currentScene = null; + engine.setHardwareScalingLevel(1); + + done(testRes); }); - }); -} - -function loadPlayground(test, done, index, referenceImage, compareFunction) { - if (test.sceneFolder) { - BABYLON.SceneLoader.Load(config.root + test.sceneFolder, test.sceneFilename, engine, function (newScene) { - currentScene = newScene; - processCurrentScene(test, resultCanvas, result, referenceImage, index, waitRing, done, compareFunction); - }, - null, - function (loadedScene, msg) { - console.error(msg); - done(false); + } + + function processCurrentScene(test, renderImage, done, compareFunction) { + currentScene.useConstantAnimationDeltaTime = true; + let renderCount = test.renderCount || 1; + + currentScene.executeWhenReady(function () { + if (currentScene.activeCamera && currentScene.activeCamera.useAutoRotationBehavior) { + currentScene.activeCamera.useAutoRotationBehavior = false; + } + engine.runRenderLoop(function () { + try { + currentScene.render(); + renderCount--; + + if (renderCount === 0) { + engine.stopRenderLoop(); + evaluate(test, renderImage, done, compareFunction); + } + } + catch (e) { + console.error(e); + done(false); + } }); + }); } - else if (test.playgroundId) { - if (test.playgroundId[0] !== "#" || test.playgroundId.indexOf("#", 1) === -1) { - test.playgroundId += "#0"; + + function loadPlayground(test, done, referenceImage, compareFunction) { + if (test.sceneFolder) { + BABYLON.SceneLoader.Load(config.root + test.sceneFolder, test.sceneFilename, engine, function (newScene) { + currentScene = newScene; + processCurrentScene(test, referenceImage, done, compareFunction); + }, + null, + function (loadedScene, msg) { + console.error(msg); + done(false); + }); } + else if (test.playgroundId) { + if (test.playgroundId[0] !== "#" || test.playgroundId.indexOf("#", 1) === -1) { + test.playgroundId += "#0"; + } - var snippetUrl = "https://snippet.babylonjs.com"; - var pgRoot = "https://playground.babylonjs.com"; + const snippetUrl = "https://snippet.babylonjs.com"; + const pgRoot = "https://playground.babylonjs.com"; - var retryTime = 500; - var maxRetry = 5; - var retry = 0; + const retryTime = 500; + const maxRetry = 5; + let retry = 0; - var onError = function () { - retry++; - if (retry < maxRetry) { - setTimeout(function () { - loadPG(); - }, retryTime); + const onError = function () { + retry++; + if (retry < maxRetry) { + setTimeout(function () { + loadPG(); + }, retryTime); + } + else { + // Fail the test, something wrong happen + console.log("Running the playground failed."); + done(false); + } } - else { - // Fail the test, something wrong happen - console.log("Running the playground failed."); - done(false); + + const loadPG = function () { + const xmlHttp = new XMLHttpRequest(); + xmlHttp.addEventListener("readystatechange", function () { + if (xmlHttp.readyState === 4) { + try { + xmlHttp.onreadystatechange = null; + const snippet = JSON.parse(xmlHttp.responseText); + let code = JSON.parse(snippet.jsonPayload).code.toString() + .replace(/"\/textures\//g, '"' + pgRoot + "/textures/") + .replace(/"textures\//g, '"' + pgRoot + "/textures/") + .replace(/\/scenes\//g, pgRoot + "/scenes/") + .replace(/"scenes\//g, '"' + pgRoot + "/scenes/") + .replace(/"\.\.\/\.\.https/g, '"' + "https") + .replace("http://", "https://"); + + if (test.replace) { + const split = test.replace.split(","); + for (let i = 0; i < split.length; i += 2) { + const source = split[i].trim(); + const destination = split[i + 1].trim(); + code = code.replace(source, destination); + } + } + + currentScene = eval(code + "\r\ncreateScene(engine)"); + + if (currentScene.then) { + // Handle if createScene returns a promise + currentScene.then(function (scene) { + currentScene = scene; + processCurrentScene(test, referenceImage, done, compareFunction); + }).catch(function (e) { + console.error(e); + onError(); + }) + } else { + // Handle if createScene returns a scene + processCurrentScene(test, referenceImage, done, compareFunction); + } + + } + catch (e) { + console.error(e); + onError(); + } + } + }, false); + xmlHttp.onerror = function () { + console.error("Network error during test load."); + onError(); + } + xmlHttp.open("GET", snippetUrl + test.playgroundId.replace(/#/g, "/")); + xmlHttp.send(); } - } + loadPG(); + } else { + // Fix references + if (test.specificRoot) { + BABYLON.Tools.BaseUrl = config.root + test.specificRoot; + } + + const request = new XMLHttpRequest(); + request.open('GET', config.root + test.scriptToRun, true); - var loadPG = function () { - var xmlHttp = new XMLHttpRequest(); - xmlHttp.addEventListener("readystatechange", function () { - if (xmlHttp.readyState === 4) { + request.onreadystatechange = function () { + if (request.readyState === 4) { try { - xmlHttp.onreadystatechange = null; - var snippet = JSON.parse(xmlHttp.responseText); - var code = JSON.parse(snippet.jsonPayload).code.toString(); - code = code - .replace(/"\/textures\//g, '"' + pgRoot + "/textures/") - .replace(/"textures\//g, '"' + pgRoot + "/textures/") - .replace(/\/scenes\//g, pgRoot + "/scenes/") - .replace(/"scenes\//g, '"' + pgRoot + "/scenes/") - .replace(/"\.\.\/\.\.https/g, '"' + "https") - .replace("http://", "https://"); + request.onreadystatechange = null; + + const scriptToRun = request.responseText.replace(/..\/..\/assets\//g, config.root + "/Assets/"); + scriptToRun = scriptToRun.replace(/..\/..\/Assets\//g, config.root + "/Assets/"); + scriptToRun = scriptToRun.replace(/\/assets\//g, config.root + "/Assets/"); if (test.replace) { const split = test.replace.split(","); for (let i = 0; i < split.length; i += 2) { const source = split[i].trim(); const destination = split[i + 1].trim(); - code = code.replace(source, destination); + scriptToRun = scriptToRun.replace(source, destination); } } - currentScene = eval(code + "\r\ncreateScene(engine)"); - var resultCanvas = 0; - var result; - var waitRing; - - if (currentScene.then) { - // Handle if createScene returns a promise - currentScene.then(function (scene) { - currentScene = scene; - processCurrentScene(test, resultCanvas, result, referenceImage, index, waitRing, done, compareFunction); - }).catch(function (e) { - console.error(e); - onError(); - }) - } else { - // Handle if createScene returns a scene - processCurrentScene(test, resultCanvas, result, referenceImage, index, waitRing, done, compareFunction); + if (test.replaceUrl) { + const split = test.replaceUrl.split(","); + for (let i = 0; i < split.length; i++) { + const source = split[i].trim(); + const regex = new RegExp(source, "g"); + scriptToRun = scriptToRun.replace(regex, config.root + test.rootPath + source); + } } + currentScene = eval(scriptToRun + test.functionToCall + "(engine)"); + processCurrentScene(test, renderImage, done, compareFunction); } catch (e) { console.error(e); - onError(); + done(false); } } - }, false); - xmlHttp.onerror = function () { + }; + request.onerror = function () { console.error("Network error during test load."); - onError(); + done(false); } - xmlHttp.open("GET", snippetUrl + test.playgroundId.replace(/#/g, "/")); - xmlHttp.send(); + + request.send(null); } - loadPG(); - } else { - // Fix references - if (test.specificRoot) { - BABYLON.Tools.BaseUrl = config.root + test.specificRoot; + } + function runTest(index, done) { + if (index >= config.tests.length) { + done(false); } - var request = new XMLHttpRequest(); - request.open('GET', config.root + test.scriptToRun, true); + const test = config.tests[index]; + if (test.onlyVisual || test.excludeFromAutomaticTesting) { + done(true); + return; + } + if (test.excludedGraphicsApis && test.excludedGraphicsApis.includes(TestUtils.getGraphicsApiName())) { + done(true); + return; + } + const testInfo = "Running " + test.title; + console.log(testInfo); + TestUtils.setTitle(testInfo); - request.onreadystatechange = function () { - if (request.readyState === 4) { - try { - request.onreadystatechange = null; - - var scriptToRun = request.responseText.replace(/..\/..\/assets\//g, config.root + "/Assets/"); - scriptToRun = scriptToRun.replace(/..\/..\/Assets\//g, config.root + "/Assets/"); - scriptToRun = scriptToRun.replace(/\/assets\//g, config.root + "/Assets/"); - - if (test.replace) { - var split = test.replace.split(","); - for (var i = 0; i < split.length; i += 2) { - var source = split[i].trim(); - var destination = split[i + 1].trim(); - scriptToRun = scriptToRun.replace(source, destination); - } - } + seed = 1; - if (test.replaceUrl) { - var split = test.replaceUrl.split(","); - for (var i = 0; i < split.length; i++) { - var source = split[i].trim(); - var regex = new RegExp(source, "g"); - scriptToRun = scriptToRun.replace(regex, config.root + test.rootPath + source); - } - } + if (generateReferences) { + loadPlayground(test, done, undefined, saveRenderedResult); + } else { + const onLoadFileError = function (request, exception) { + console.error("Failed to retrieve " + url + ".", exception); + done(false); + }; - currentScene = eval(scriptToRun + test.functionToCall + "(engine)"); - processCurrentScene(test, resultCanvas, result, renderImage, index, waitRing, done, compareFunction); - } - catch (e) { - console.error(e); - done(false); + const onload = function (data, responseURL) { + if (typeof (data) === "string") { + throw new Error("Decode Image from string data not yet implemented."); } - } - }; - request.onerror = function () { - console.error("Network error during test load."); - done(false); - } - request.send(null); - } -} -function runTest(index, done) { - if (index >= config.tests.length) { - done(false); + const referenceImage = TestUtils.decodeImage(data); + loadPlayground(test, done, referenceImage, compare); + }; + + // run test and image comparison + const url = "app:///ReferenceImages/" + test.referenceImage; + BABYLON.Tools.LoadFile(url, onload, undefined, undefined, /*useArrayBuffer*/true, onLoadFileError); + } } - var test = config.tests[index]; - if (test.onlyVisual || test.excludeFromAutomaticTesting) { - done(true); - return; + OffscreenCanvas = function (width, height) { + return { + width: width + , height: height + , getContext: function (type) { + return { + fillRect: function (x, y, w, h) { } + , measureText: function (text) { return 8; } + , fillText: function (text, x, y) { } + }; + } + }; } - if (test.excludedGraphicsApis && test.excludedGraphicsApis.includes(TestUtils.getGraphicsApiName())) { - done(true); - return; + + document = { + createElement: function (type) { + if (type === "canvas") { + return new OffscreenCanvas(64, 64); + } + return {}; + }, + removeEventListener: function () { } } - let testInfo = "Running " + test.title; - console.log(testInfo); - TestUtils.setTitle(testInfo); - - seed = 1; - - let onLoadFileError = function (request, exception) { - console.error("Failed to retrieve " + url + ".", exception); - done(false); - }; - var onload = function (data, responseURL) { - if (typeof (data) === "string") { - throw new Error("Decode Image from string data not yet implemented."); - } - var referenceImage = TestUtils.decodeImage(data); - loadPlayground(test, done, index, referenceImage, compare); + const xhr = new XMLHttpRequest(); + xhr.open("GET", "app:///Scripts/config.json", true); - }; + xhr.addEventListener("readystatechange", function () { + if (xhr.status === 200) { + config = JSON.parse(xhr.responseText); - if (generateReferences) { - loadPlayground(test, done, index, undefined, saveRenderedResult); - } else { - // run test and image comparison - const url = "app:///ReferenceImages/" + test.referenceImage; - BABYLON.Tools.LoadFile(url, onload, undefined, undefined, /*useArrayBuffer*/true, onLoadFileError); - } -} - -engine = new BABYLON.NativeEngine(); -engine.getCaps().parallelShaderCompile = undefined; - -engine.getRenderingCanvas = function () { - return window; -} - -engine.getInputElement = function () { - return 0; -} - -canvas = window; - -OffscreenCanvas = function (width, height) { - return { - width: width - , height: height - , getContext: function (type) { - return { - fillRect: function (x, y, w, h) { } - , measureText: function (text) { return 8; } - , fillText: function (text, x, y) { } - }; - } - }; -} + // Run tests + const recursiveRunTest = function (i) { + runTest(i, function (status) { + if (!status) { + TestUtils.exit(-1); + return; + } + i++; + if (justOnce || i >= config.tests.length) { + engine.dispose(); + TestUtils.exit(0); + return; + } + recursiveRunTest(i); + }); + } -document = { - createElement: function (type) { - if (type === "canvas") { - return new OffscreenCanvas(64, 64); - } - return {}; - }, - removeEventListener: function () { } -} - -var xhr = new XMLHttpRequest(); -xhr.open("GET", "app:///Scripts/config.json", true); - -xhr.addEventListener("readystatechange", function () { - if (xhr.status === 200) { - config = JSON.parse(xhr.responseText); - - // Run tests - var index = 0; - var recursiveRunTest = function (i) { - runTest(i, function (status) { - if (!status) { - TestUtils.exit(-1); - return; - } - i++; - if (justOnce || i >= config.tests.length) { - engine.dispose(); - TestUtils.exit(0); - return; - } - recursiveRunTest(i); - }); + recursiveRunTest(0); } + }, false); - recursiveRunTest(index); - } -}, false); - - -BABYLON.Tools.LoadFile("https://raw.githubusercontent.com/CedricGuillemet/dump/master/droidsans.ttf", (data) => { - _native.Canvas.loadTTFAsync("droidsans", data).then(function () { - _native.RootUrl = "https://playground.babylonjs.com"; - console.log("Starting"); - TestUtils.setTitle("Starting Native Validation Tests"); - TestUtils.updateSize(testWidth, testHeight); - xhr.send(); - }); -}, undefined, undefined, true); + + BABYLON.Tools.LoadFile("https://raw.githubusercontent.com/CedricGuillemet/dump/master/droidsans.ttf", (data) => { + _native.Canvas.loadTTFAsync("droidsans", data).then(function () { + _native.RootUrl = "https://playground.babylonjs.com"; + console.log("Starting"); + TestUtils.setTitle("Starting Native Validation Tests"); + TestUtils.updateSize(testWidth, testHeight); + xhr.send(); + }); + }, undefined, undefined, true); +})(); \ No newline at end of file diff --git a/Apps/Playground/Win32/App.cpp b/Apps/Playground/Win32/App.cpp index 029a30936..d9920d856 100644 --- a/Apps/Playground/Win32/App.cpp +++ b/Apps/Playground/Win32/App.cpp @@ -137,7 +137,23 @@ namespace device->StartRenderingCurrentFrame(); update->Start(); - runtime.emplace(); + Babylon::AppRuntime::Options options{}; + + options.EnableDebugger = true; + + options.UnhandledExceptionHandler = [hWnd](const Napi::Error& error) { + std::ostringstream ss{}; + ss << "[Uncaught Error] " << Napi::GetErrorString(error) << std::endl; + OutputDebugStringA(ss.str().data()); + + std::cerr << ss.str(); + std::cerr.flush(); + + Babylon::Plugins::TestUtils::errorCode = -1; + PostMessage(hWnd, WM_CLOSE, 0, 0); + }; + + runtime.emplace(options); runtime->Dispatch([hWnd](Napi::Env env) { device->AddToJavaScript(env); diff --git a/Apps/Playground/macOS/ViewController.mm b/Apps/Playground/macOS/ViewController.mm index 18f20bd2f..81190ccdf 100644 --- a/Apps/Playground/macOS/ViewController.mm +++ b/Apps/Playground/macOS/ViewController.mm @@ -103,8 +103,8 @@ - (void)refreshBabylon { Babylon::Graphics::Configuration graphicsConfig{}; graphicsConfig.Window = engineView; - graphicsConfig.Width = static_cast(600); - graphicsConfig.Height = static_cast(400); + graphicsConfig.Width = static_cast(engineView.drawableSize.width); + graphicsConfig.Height = static_cast(engineView.drawableSize.height); graphicsConfig.MSAASamples = 4; device.emplace(graphicsConfig); diff --git a/Apps/package-lock.json b/Apps/package-lock.json index 0251a5094..485399448 100644 --- a/Apps/package-lock.json +++ b/Apps/package-lock.json @@ -8,11 +8,11 @@ "name": "BabylonNative", "version": "0.0.1", "dependencies": { - "babylonjs": "^6.46.1", - "babylonjs-gltf2interface": "^6.46.1", - "babylonjs-gui": "^6.46.1", - "babylonjs-loaders": "^6.46.1", - "babylonjs-materials": "^6.46.1", + "babylonjs": "^7.6.2", + "babylonjs-gltf2interface": "^7.6.2", + "babylonjs-gui": "^7.6.2", + "babylonjs-loaders": "^7.6.2", + "babylonjs-materials": "^7.6.2", "chai": "^4.3.4", "jsc-android": "^241213.1.0", "mocha": "^9.2.2", @@ -80,39 +80,39 @@ } }, "node_modules/babylonjs": { - "version": "6.46.1", - "resolved": "https://registry.npmjs.org/babylonjs/-/babylonjs-6.46.1.tgz", - "integrity": "sha512-UiXeoIP+xGdq0oBgQorcY2eOIGlCru8peTKJ3Z8+tLR6dp+icDxg7W+XEJqvT9eSd0f+IW1fTVJwvHQIr2IUQA==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/babylonjs/-/babylonjs-7.6.2.tgz", + "integrity": "sha512-uolOo3wqRRZdVUtY4X8qocBXFP/VHNeSK7jWyFIpinxMP0UGKvk28Hvjncgh9mF0Bi52uVUtzTCifQr98nz4Mg==", "hasInstallScript": true }, "node_modules/babylonjs-gltf2interface": { - "version": "6.46.1", - "resolved": "https://registry.npmjs.org/babylonjs-gltf2interface/-/babylonjs-gltf2interface-6.46.1.tgz", - "integrity": "sha512-BOeuBMmbfZh33xvrrDmogvxOIg7VybFuLPGfSm4GQP0p1cNQ85WKaKbAk6zaN628LboqWYFORuMnp5qa5ObjpQ==" + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/babylonjs-gltf2interface/-/babylonjs-gltf2interface-7.6.2.tgz", + "integrity": "sha512-Nr84TFwfrCOuoEhnEvGRgyvePX+HIcr1I2p+OQjSPEjiwwKCCusY29EC0q61LsVW2hU3Ahl5OKxrNSuAfTr/LQ==" }, "node_modules/babylonjs-gui": { - "version": "6.46.1", - "resolved": "https://registry.npmjs.org/babylonjs-gui/-/babylonjs-gui-6.46.1.tgz", - "integrity": "sha512-v0sBI/xGcr6FK0lmxj6eOq+1jqDjwJQ3MsEvr5VTufP5RSXn6b5hPTbmFMkpT8WtqLlM9FzpW1GPha543QyJpA==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/babylonjs-gui/-/babylonjs-gui-7.6.2.tgz", + "integrity": "sha512-BFq7n3Sf3kezWif3r0NcREnTPq8WoBOAukTpdIWUUIkNU8kNywZaVfGVXBHL/mnJ11CN7vWs8aWTp5cINpHXow==", "dependencies": { - "babylonjs": "^6.46.1" + "babylonjs": "^7.6.2" } }, "node_modules/babylonjs-loaders": { - "version": "6.46.1", - "resolved": "https://registry.npmjs.org/babylonjs-loaders/-/babylonjs-loaders-6.46.1.tgz", - "integrity": "sha512-nUTGCZjSS2yT0IX3STDVebS6M5nOc/vnhy4bRh/d4FMt/9cB8+GcXPf2kKPZfsy5AdPKT/hyPvo1+ULWCd5APg==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/babylonjs-loaders/-/babylonjs-loaders-7.6.2.tgz", + "integrity": "sha512-/c3JNz1SOasK4Rh/fGVjpIoi/umn3pvVaHcAumUXhsxz61+J/nFyeTJ5y51qyiZ9Bo7bWF8+FVZGtnRDq8RRiw==", "dependencies": { - "babylonjs": "^6.46.1", - "babylonjs-gltf2interface": "^6.46.1" + "babylonjs": "^7.6.2", + "babylonjs-gltf2interface": "^7.6.2" } }, "node_modules/babylonjs-materials": { - "version": "6.46.1", - "resolved": "https://registry.npmjs.org/babylonjs-materials/-/babylonjs-materials-6.46.1.tgz", - "integrity": "sha512-uuIqnvjZYTkBxD5eWO6m8bPqWeq+H/UIHYqv5+p/eEu1XyJvferyF30sEsjeCziCJBmthV9Q48/N/zYTIC6ErQ==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/babylonjs-materials/-/babylonjs-materials-7.6.2.tgz", + "integrity": "sha512-kF1PMSEOyTfhCheGJ8uOsActRJ9rybtzJTisVsoZ4K830rkUkCgbnmSYhIXUnBAp+S0eTJx/pVLP2XeO4g6iUA==", "dependencies": { - "babylonjs": "^6.46.1" + "babylonjs": "^7.6.2" } }, "node_modules/balanced-match": { @@ -1038,38 +1038,38 @@ "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==" }, "babylonjs": { - "version": "6.46.1", - "resolved": "https://registry.npmjs.org/babylonjs/-/babylonjs-6.46.1.tgz", - "integrity": "sha512-UiXeoIP+xGdq0oBgQorcY2eOIGlCru8peTKJ3Z8+tLR6dp+icDxg7W+XEJqvT9eSd0f+IW1fTVJwvHQIr2IUQA==" + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/babylonjs/-/babylonjs-7.6.2.tgz", + "integrity": "sha512-uolOo3wqRRZdVUtY4X8qocBXFP/VHNeSK7jWyFIpinxMP0UGKvk28Hvjncgh9mF0Bi52uVUtzTCifQr98nz4Mg==" }, "babylonjs-gltf2interface": { - "version": "6.46.1", - "resolved": "https://registry.npmjs.org/babylonjs-gltf2interface/-/babylonjs-gltf2interface-6.46.1.tgz", - "integrity": "sha512-BOeuBMmbfZh33xvrrDmogvxOIg7VybFuLPGfSm4GQP0p1cNQ85WKaKbAk6zaN628LboqWYFORuMnp5qa5ObjpQ==" + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/babylonjs-gltf2interface/-/babylonjs-gltf2interface-7.6.2.tgz", + "integrity": "sha512-Nr84TFwfrCOuoEhnEvGRgyvePX+HIcr1I2p+OQjSPEjiwwKCCusY29EC0q61LsVW2hU3Ahl5OKxrNSuAfTr/LQ==" }, "babylonjs-gui": { - "version": "6.46.1", - "resolved": "https://registry.npmjs.org/babylonjs-gui/-/babylonjs-gui-6.46.1.tgz", - "integrity": "sha512-v0sBI/xGcr6FK0lmxj6eOq+1jqDjwJQ3MsEvr5VTufP5RSXn6b5hPTbmFMkpT8WtqLlM9FzpW1GPha543QyJpA==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/babylonjs-gui/-/babylonjs-gui-7.6.2.tgz", + "integrity": "sha512-BFq7n3Sf3kezWif3r0NcREnTPq8WoBOAukTpdIWUUIkNU8kNywZaVfGVXBHL/mnJ11CN7vWs8aWTp5cINpHXow==", "requires": { - "babylonjs": "^6.46.1" + "babylonjs": "^7.6.2" } }, "babylonjs-loaders": { - "version": "6.46.1", - "resolved": "https://registry.npmjs.org/babylonjs-loaders/-/babylonjs-loaders-6.46.1.tgz", - "integrity": "sha512-nUTGCZjSS2yT0IX3STDVebS6M5nOc/vnhy4bRh/d4FMt/9cB8+GcXPf2kKPZfsy5AdPKT/hyPvo1+ULWCd5APg==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/babylonjs-loaders/-/babylonjs-loaders-7.6.2.tgz", + "integrity": "sha512-/c3JNz1SOasK4Rh/fGVjpIoi/umn3pvVaHcAumUXhsxz61+J/nFyeTJ5y51qyiZ9Bo7bWF8+FVZGtnRDq8RRiw==", "requires": { - "babylonjs": "^6.46.1", - "babylonjs-gltf2interface": "^6.46.1" + "babylonjs": "^7.6.2", + "babylonjs-gltf2interface": "^7.6.2" } }, "babylonjs-materials": { - "version": "6.46.1", - "resolved": "https://registry.npmjs.org/babylonjs-materials/-/babylonjs-materials-6.46.1.tgz", - "integrity": "sha512-uuIqnvjZYTkBxD5eWO6m8bPqWeq+H/UIHYqv5+p/eEu1XyJvferyF30sEsjeCziCJBmthV9Q48/N/zYTIC6ErQ==", + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/babylonjs-materials/-/babylonjs-materials-7.6.2.tgz", + "integrity": "sha512-kF1PMSEOyTfhCheGJ8uOsActRJ9rybtzJTisVsoZ4K830rkUkCgbnmSYhIXUnBAp+S0eTJx/pVLP2XeO4g6iUA==", "requires": { - "babylonjs": "^6.46.1" + "babylonjs": "^7.6.2" } }, "balanced-match": { diff --git a/Apps/package.json b/Apps/package.json index 31a5a4e26..0114d71f0 100644 --- a/Apps/package.json +++ b/Apps/package.json @@ -6,11 +6,11 @@ "getNightly": "node scripts/getNightly.js" }, "dependencies": { - "babylonjs": "^6.46.1", - "babylonjs-gltf2interface": "^6.46.1", - "babylonjs-gui": "^6.46.1", - "babylonjs-loaders": "^6.46.1", - "babylonjs-materials": "^6.46.1", + "babylonjs": "^7.6.2", + "babylonjs-gltf2interface": "^7.6.2", + "babylonjs-gui": "^7.6.2", + "babylonjs-loaders": "^7.6.2", + "babylonjs-materials": "^7.6.2", "chai": "^4.3.4", "jsc-android": "^241213.1.0", "mocha": "^9.2.2", diff --git a/CMakeLists.txt b/CMakeLists.txt index 3780df8c7..f79c6d7c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,7 +37,7 @@ FetchContent_Declare(ios-cmake GIT_TAG 4.4.1) FetchContent_Declare(JsRuntimeHost GIT_REPOSITORY https://github.com/BabylonJS/JsRuntimeHost.git - GIT_TAG 66c863be8cedf2869a4e59ce99144417c78af73c) + GIT_TAG 5c06ce7c096fbce0523594979e0083410e1b4c15) FetchContent_Declare(OpenXR-MixedReality GIT_REPOSITORY https://github.com/microsoft/OpenXR-MixedReality.git GIT_TAG 67424511525b96a36847f2a96d689d99e5879503) diff --git a/Core/Graphics/CMakeLists.txt b/Core/Graphics/CMakeLists.txt index c17bd896f..44458c63e 100644 --- a/Core/Graphics/CMakeLists.txt +++ b/Core/Graphics/CMakeLists.txt @@ -41,7 +41,6 @@ elseif(ANDROID) endif() target_link_libraries(Graphics - PRIVATE napi_extensions PRIVATE JsRuntimeInternal PRIVATE bgfx PRIVATE bimg diff --git a/Core/Graphics/Source/DeviceContext.cpp b/Core/Graphics/Source/DeviceContext.cpp index 54b516c02..93512e0fd 100644 --- a/Core/Graphics/Source/DeviceContext.cpp +++ b/Core/Graphics/Source/DeviceContext.cpp @@ -2,7 +2,7 @@ #include "DeviceImpl.h" -#include +#include namespace Babylon::Graphics { diff --git a/Core/Graphics/Source/DeviceImpl.cpp b/Core/Graphics/Source/DeviceImpl.cpp index ee33877cb..1a21f3903 100644 --- a/Core/Graphics/Source/DeviceImpl.cpp +++ b/Core/Graphics/Source/DeviceImpl.cpp @@ -2,9 +2,9 @@ #include #include - #include #include +#include #if defined(__APPLE__) #include @@ -19,6 +19,11 @@ namespace { constexpr auto JS_GRAPHICS_NAME = "_Graphics"; + + bool FuzzyEqual(float a, float b, float epsilon = std::numeric_limits::epsilon()) + { + return std::abs(a - b) < epsilon; + } } namespace Babylon::Graphics @@ -52,22 +57,23 @@ namespace Babylon::Graphics uintptr_t DeviceImpl::GetId() const { - return m_bgfxId; + return m_bgfxId; } void DeviceImpl::UpdateWindow(WindowT window) { std::scoped_lock lock{m_state.Mutex}; - m_state.Bgfx.Dirty = true; ConfigureBgfxPlatformData(m_state.Bgfx.InitState.platformData, window); ConfigureBgfxRenderType(m_state.Bgfx.InitState.platformData, m_state.Bgfx.InitState.type); m_state.Resolution.DevicePixelRatio = GetDevicePixelRatio(window); + m_state.Bgfx.Dirty = true; } void DeviceImpl::UpdateDevice(DeviceT device) { - std::scoped_lock lock{ m_state.Mutex }; - m_state.Bgfx.InitState.platformData.context = device; + std::scoped_lock lock{m_state.Mutex}; + m_state.Bgfx.InitState.platformData.context = device; + m_state.Bgfx.Dirty = true; } void DeviceImpl::UpdateSize(size_t width, size_t height) @@ -81,7 +87,6 @@ namespace Babylon::Graphics void DeviceImpl::UpdateMSAA(uint8_t value) { std::scoped_lock lock{m_state.Mutex}; - m_state.Bgfx.Dirty = true; auto& init = m_state.Bgfx.InitState; init.resolution.reset &= ~BGFX_RESET_MSAA_MASK; switch (value) @@ -103,25 +108,26 @@ namespace Babylon::Graphics init.resolution.reset |= BGFX_RESET_MSAA_X16; break; default: - m_bgfxCallback.trace(__FILE__, __LINE__, "WARNING: Setting an incorrect value for SetMSAA (%d). Correct values are 0, 1 (disable MSAA) or 2, 4, 8, 16.", int(value)); + m_bgfxCallback.trace(__FILE__, __LINE__, "WARNING: Setting an incorrect value for SetMSAA (%d). Correct values are 0, 1 (disable MSAA) or 2, 4, 8, 16.", static_cast(value)); break; } + m_state.Bgfx.Dirty = true; } void DeviceImpl::UpdateAlphaPremultiplied(bool enabled) { std::scoped_lock lock{m_state.Mutex}; - m_state.Bgfx.Dirty = true; auto& init = m_state.Bgfx.InitState; init.resolution.reset &= ~BGFX_RESET_TRANSPARENT_BACKBUFFER; init.resolution.reset |= enabled ? BGFX_RESET_TRANSPARENT_BACKBUFFER : 0; + m_state.Bgfx.Dirty = true; } void DeviceImpl::UpdateDevicePixelRatio(float value) { std::scoped_lock lock{m_state.Mutex}; - m_state.Bgfx.Dirty = true; m_state.Resolution.DevicePixelRatio = value; + m_state.Bgfx.Dirty = true; } void DeviceImpl::SetRenderResetCallback(std::function callback) @@ -245,9 +251,6 @@ namespace Babylon::Graphics // Ensure rendering is enabled. EnableRendering(); - // Update bgfx state if necessary. - UpdateBgfxState(); - // Unlock the update safe timespans. { std::scoped_lock lock{m_updateSafeTimespansMutex}; @@ -300,12 +303,12 @@ namespace Babylon::Graphics throw std::runtime_error{"HardwareScalingValue cannot be less than or equal to 0."}; } + std::scoped_lock lock{m_state.Mutex}; + if (!FuzzyEqual(m_state.Resolution.HardwareScalingLevel, level)) { - std::scoped_lock lock{m_state.Mutex}; m_state.Resolution.HardwareScalingLevel = level; + UpdateBgfxResolution(); } - - UpdateBgfxResolution(); } float DeviceImpl::GetDevicePixelRatio() const @@ -343,8 +346,8 @@ namespace Babylon::Graphics std::scoped_lock lock{m_state.Mutex}; if ((m_state.Bgfx.InitState.resolution.reset & BGFX_RESET_CAPTURE) == 0) { - m_state.Bgfx.Dirty = true; m_state.Bgfx.InitState.resolution.reset |= BGFX_RESET_CAPTURE; + m_state.Bgfx.Dirty = true; } } @@ -369,12 +372,11 @@ namespace Babylon::Graphics bgfx::setPlatformData(m_state.Bgfx.InitState.platformData); // Ensure bgfx rebinds all texture information. - bgfx::discard(BGFX_DISCARD_ALL); + bgfx::discard(); auto& res = m_state.Bgfx.InitState.resolution; bgfx::reset(res.width, res.height, res.reset); bgfx::setViewRect(0, 0, 0, static_cast(res.width), static_cast(res.height)); - bgfx::frame(); m_state.Bgfx.Dirty = false; } @@ -383,20 +385,11 @@ namespace Babylon::Graphics void DeviceImpl::UpdateBgfxResolution() { std::scoped_lock lock{m_state.Mutex}; - m_state.Bgfx.Dirty = true; auto& res = m_state.Bgfx.InitState.resolution; auto level = m_state.Resolution.HardwareScalingLevel; res.width = static_cast(m_state.Resolution.Width / level); res.height = static_cast(m_state.Resolution.Height / level); - } - - void DeviceImpl::DiscardIfDirty() - { - std::scoped_lock lock{m_state.Mutex}; - if (m_state.Bgfx.Dirty) - { - bgfx::discard(); - } + m_state.Bgfx.Dirty = true; } void DeviceImpl::RequestScreenShots() @@ -421,8 +414,8 @@ namespace Babylon::Graphics // Automatically end bgfx encoders. EndEncoders(); - // Discard everything if the bgfx state is dirty. - DiscardIfDirty(); + // Update bgfx state if necessary. + UpdateBgfxState(); // Request screen shots before bgfx::frame. RequestScreenShots(); diff --git a/Dependencies/CMakeLists.txt b/Dependencies/CMakeLists.txt index 1657fb184..47cee4242 100644 --- a/Dependencies/CMakeLists.txt +++ b/Dependencies/CMakeLists.txt @@ -199,8 +199,3 @@ endif() if(WINDOWS_STORE) add_subdirectory(WindowsAppSDK) endif() - -# -------------------------------------------------- -# NAPI Extensions -# -------------------------------------------------- -add_subdirectory(napi-extensions) diff --git a/Dependencies/napi-extensions/CMakeLists.txt b/Dependencies/napi-extensions/CMakeLists.txt deleted file mode 100644 index a0d7204b5..000000000 --- a/Dependencies/napi-extensions/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -add_library(napi_extensions INTERFACE) -target_include_directories(napi_extensions INTERFACE "include") diff --git a/Dependencies/napi-extensions/include/napi/napi_pointer.h b/Dependencies/napi-extensions/include/napi/napi_pointer.h deleted file mode 100644 index bc16614a1..000000000 --- a/Dependencies/napi-extensions/include/napi/napi_pointer.h +++ /dev/null @@ -1,87 +0,0 @@ -#pragma once - -#include - -#include -#include - -namespace Napi -{ - template - struct PointerTraits - { - using RepresentationT = uint32_t; - static_assert(sizeof(PointerT) % sizeof(RepresentationT) == 0); - static inline constexpr size_t ByteSize{sizeof(PointerT)}; - static inline constexpr size_t ArraySize{sizeof(PointerT) / sizeof(RepresentationT)}; - }; - - template - auto NapiPointerDeleter(PointerT pointer) - { - return [pointer]() - { - delete pointer; - }; - } - - class FunctionPointer - { - public: - template - static Napi::Value Create(Napi::Env env, ReturnT (ClassT::*functionPtr)(ArgsT...)) - { - using PointerT = decltype(functionPtr); - auto arrayBuffer = Napi::ArrayBuffer::New(env, PointerTraits::ByteSize); - std::memcpy(arrayBuffer.Data(), &functionPtr, sizeof(PointerT)); - return Napi::Uint32Array::New(env, PointerTraits::ArraySize, arrayBuffer, 0).template As(); - } - }; - - template - class Pointer - { - public: - static Napi::Value Create(Napi::Env env, const T* pointer) - { - using PointerT = T*; - auto arrayBuffer = Napi::ArrayBuffer::New(env, PointerTraits::ByteSize); - std::memcpy(arrayBuffer.Data(), &pointer, sizeof(PointerT)); - return Napi::Uint32Array::New(env, PointerTraits::ArraySize, arrayBuffer, 0); - } - - template - static Napi::Value Create(Napi::Env env, const T* pointer, CallableT&& finalizationCallback) - { - using PointerT = T*; - using DataT = std::array::ArraySize>; - auto finalizer = [callback = std::forward(finalizationCallback)](Napi::Env, void* ptr) - { - callback(); - delete reinterpret_cast(ptr); - }; - auto arrayBuffer = Napi::ArrayBuffer::New(env, new DataT, PointerTraits::ByteSize, std::move(finalizer)); - std::memcpy(arrayBuffer.Data(), &pointer, sizeof(PointerT)); - return Napi::Uint32Array::New(env, PointerTraits::ArraySize, arrayBuffer, 0); - } - - template - Pointer(EnvT&& env, ValueT&& value) - : m_pointer{*reinterpret_cast(Napi::Value(std::forward(env), std::forward(value)).As().Data())} - { - } - - T* Get() const - { - return m_pointer; - } - - T& operator*() const - { - return *m_pointer; - } - - private: - T* m_pointer; - }; -} diff --git a/Install/Install.cmake b/Install/Install.cmake index 6239096ac..ef2ebee8c 100644 --- a/Install/Install.cmake +++ b/Install/Install.cmake @@ -101,6 +101,9 @@ if(NAPI_JAVASCRIPT_ENGINE STREQUAL "JSI") install(DIRECTORY ${V8JSI_PACKAGE_PATH}/build/native/jsi/jsi TYPE INCLUDE) endif() +install_targets(napi-extensions) +install_include_for_targets(napi-extensions) + # ---------------- # Plugins # ---------------- diff --git a/Plugins/ExternalTexture/CMakeLists.txt b/Plugins/ExternalTexture/CMakeLists.txt index f2f8253f1..737bfab72 100644 --- a/Plugins/ExternalTexture/CMakeLists.txt +++ b/Plugins/ExternalTexture/CMakeLists.txt @@ -18,7 +18,6 @@ target_include_directories(ExternalTexture target_link_libraries(ExternalTexture PUBLIC napi PUBLIC GraphicsDevice - PRIVATE napi_extensions PRIVATE JsRuntimeInternal PRIVATE GraphicsDeviceContext) diff --git a/Plugins/ExternalTexture/Source/ExternalTexture_D3D11.cpp b/Plugins/ExternalTexture/Source/ExternalTexture_D3D11.cpp index c5bcacf36..37b08a76e 100644 --- a/Plugins/ExternalTexture/Source/ExternalTexture_D3D11.cpp +++ b/Plugins/ExternalTexture/Source/ExternalTexture_D3D11.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include diff --git a/Plugins/ExternalTexture/Source/ExternalTexture_D3D12.cpp b/Plugins/ExternalTexture/Source/ExternalTexture_D3D12.cpp index 9a693ca3c..6d49c017c 100644 --- a/Plugins/ExternalTexture/Source/ExternalTexture_D3D12.cpp +++ b/Plugins/ExternalTexture/Source/ExternalTexture_D3D12.cpp @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include diff --git a/Plugins/ExternalTexture/Source/ExternalTexture_Metal.mm b/Plugins/ExternalTexture/Source/ExternalTexture_Metal.mm index 3cf175c6a..38ff679f5 100644 --- a/Plugins/ExternalTexture/Source/ExternalTexture_Metal.mm +++ b/Plugins/ExternalTexture/Source/ExternalTexture_Metal.mm @@ -1,7 +1,7 @@ #include #include #include -#include +#include #include #include "ExternalTexture_Base.h" diff --git a/Plugins/ExternalTexture/Source/ExternalTexture_OpenGL.cpp b/Plugins/ExternalTexture/Source/ExternalTexture_OpenGL.cpp index a3d7e00c1..37b7d4e1a 100644 --- a/Plugins/ExternalTexture/Source/ExternalTexture_OpenGL.cpp +++ b/Plugins/ExternalTexture/Source/ExternalTexture_OpenGL.cpp @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include #include diff --git a/Plugins/NativeCamera/CMakeLists.txt b/Plugins/NativeCamera/CMakeLists.txt index 29f590ec8..a3b73b4fc 100644 --- a/Plugins/NativeCamera/CMakeLists.txt +++ b/Plugins/NativeCamera/CMakeLists.txt @@ -43,7 +43,6 @@ target_link_libraries(NativeCamera PUBLIC napi PRIVATE JsRuntimeInternal PRIVATE GraphicsDeviceContext - PRIVATE napi_extensions ${ADDITIONAL_LIBRARIES}) set_property(TARGET NativeCamera PROPERTY FOLDER Plugins) diff --git a/Plugins/NativeCamera/Source/NativeCamera.cpp b/Plugins/NativeCamera/Source/NativeCamera.cpp index da9da32ba..906da78a8 100644 --- a/Plugins/NativeCamera/Source/NativeCamera.cpp +++ b/Plugins/NativeCamera/Source/NativeCamera.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include "NativeVideo.h" #include "MediaDevices.h" #include "ImageCapture.h" diff --git a/Plugins/NativeCapture/CMakeLists.txt b/Plugins/NativeCapture/CMakeLists.txt index 0ed5d3895..ec57cf6e4 100644 --- a/Plugins/NativeCapture/CMakeLists.txt +++ b/Plugins/NativeCapture/CMakeLists.txt @@ -10,8 +10,7 @@ target_include_directories(NativeCapture PUBLIC target_link_libraries(NativeCapture PUBLIC JsRuntimeInternal - PRIVATE GraphicsDeviceContext - PRIVATE napi_extensions) + PRIVATE GraphicsDeviceContext) set_property(TARGET NativeCapture PROPERTY FOLDER Plugins) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCES}) diff --git a/Plugins/NativeCapture/Source/NativeCapture.cpp b/Plugins/NativeCapture/Source/NativeCapture.cpp index 0fee30fff..06888c384 100644 --- a/Plugins/NativeCapture/Source/NativeCapture.cpp +++ b/Plugins/NativeCapture/Source/NativeCapture.cpp @@ -3,7 +3,7 @@ #include #include -#include +#include #include diff --git a/Plugins/NativeEngine/CMakeLists.txt b/Plugins/NativeEngine/CMakeLists.txt index 454e8b8a8..454687ddc 100644 --- a/Plugins/NativeEngine/CMakeLists.txt +++ b/Plugins/NativeEngine/CMakeLists.txt @@ -41,8 +41,7 @@ target_link_libraries(NativeEngine PRIVATE glslang PRIVATE glslang-default-resource-limits PRIVATE SPIRV - PRIVATE GraphicsDeviceContext - PRIVATE napi_extensions) + PRIVATE GraphicsDeviceContext) warnings_as_errors(NativeEngine) if(TARGET spirv-cross-hlsl) diff --git a/Plugins/NativeEngine/Source/IndexBuffer.h b/Plugins/NativeEngine/Source/IndexBuffer.h index eae42aa68..d680a5217 100644 --- a/Plugins/NativeEngine/Source/IndexBuffer.h +++ b/Plugins/NativeEngine/Source/IndexBuffer.h @@ -16,7 +16,7 @@ namespace Babylon class IndexBuffer final { public: - IndexBuffer(Graphics::DeviceContext& deviceContext, gsl::span bytes, uint16_t flags, bool dynamic); + IndexBuffer(Graphics::DeviceContext& deviceContext, const gsl::span bytes, uint16_t flags, bool dynamic); ~IndexBuffer(); // No copy or move semantics diff --git a/Plugins/NativeEngine/Source/NativeEngine.cpp b/Plugins/NativeEngine/Source/NativeEngine.cpp index 754bca5a5..4f30f4ede 100644 --- a/Plugins/NativeEngine/Source/NativeEngine.cpp +++ b/Plugins/NativeEngine/Source/NativeEngine.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include #include @@ -624,6 +624,8 @@ namespace Babylon StaticValue("COMMAND_SETTEXTUREWRAPMODE", Napi::FunctionPointer::Create(env, &NativeEngine::SetTextureWrapMode)), StaticValue("COMMAND_SETTEXTUREANISOTROPICLEVEL", Napi::FunctionPointer::Create(env, &NativeEngine::SetTextureAnisotropicLevel)), StaticValue("COMMAND_SETTEXTURE", Napi::FunctionPointer::Create(env, &NativeEngine::SetTexture)), + StaticValue("COMMAND_UNSETTEXTURE", Napi::FunctionPointer::Create(env, &NativeEngine::UnsetTexture)), + StaticValue("COMMAND_DISCARDALLTEXTURES", Napi::FunctionPointer::Create(env, &NativeEngine::DiscardAllTextures)), StaticValue("COMMAND_BINDVERTEXARRAY", Napi::FunctionPointer::Create(env, &NativeEngine::BindVertexArray)), StaticValue("COMMAND_SETSTATE", Napi::FunctionPointer::Create(env, &NativeEngine::SetState)), StaticValue("COMMAND_SETZOFFSET", Napi::FunctionPointer::Create(env, &NativeEngine::SetZOffset)), @@ -726,6 +728,8 @@ namespace Babylon void NativeEngine::Dispose() { + m_deviceContext.SetRenderResetCallback(nullptr); + m_cancellationSource->cancel(); } @@ -1619,6 +1623,21 @@ namespace Babylon encoder->setTexture(uniformInfo->Stage, uniformInfo->Handle, texture->Handle(), texture->SamplerFlags()); } + void NativeEngine::UnsetTexture(NativeDataStream::Reader& data) + { + bgfx::Encoder* encoder = GetUpdateToken().GetEncoder(); + + const UniformInfo* uniformInfo = data.ReadPointer(); + + encoder->setTexture(uniformInfo->Stage, uniformInfo->Handle, BGFX_INVALID_HANDLE); + } + + void NativeEngine::DiscardAllTextures(NativeDataStream::Reader&) + { + bgfx::Encoder* encoder = GetUpdateToken().GetEncoder(); + encoder->discard(BGFX_DISCARD_BINDINGS); + } + void NativeEngine::DeleteTexture(const Napi::CallbackInfo& info) { Graphics::Texture* texture = info[0].As>().Get(); @@ -2247,10 +2266,9 @@ namespace Babylon encoder->setState((m_engineState & ~BGFX_STATE_WRITE_Z) | fillModeState); } - // stencil boundFrameBuffer.SetStencil(*encoder, m_stencilState); - // Discard everything except bindings since we keep the state of everything else. + // Discard everything except textures since we keep the state of everything else. boundFrameBuffer.Submit(*encoder, m_currentProgram->Handle, BGFX_DISCARD_ALL & ~BGFX_DISCARD_BINDINGS); } diff --git a/Plugins/NativeEngine/Source/NativeEngine.h b/Plugins/NativeEngine/Source/NativeEngine.h index d9c5b01c0..f29245de6 100644 --- a/Plugins/NativeEngine/Source/NativeEngine.h +++ b/Plugins/NativeEngine/Source/NativeEngine.h @@ -195,6 +195,8 @@ namespace Babylon void SetTextureWrapMode(NativeDataStream::Reader& data); void SetTextureAnisotropicLevel(NativeDataStream::Reader& data); void SetTexture(NativeDataStream::Reader& data); + void UnsetTexture(NativeDataStream::Reader& data); + void DiscardAllTextures(NativeDataStream::Reader& data); void DeleteTexture(const Napi::CallbackInfo& info); Napi::Value ReadTexture(const Napi::CallbackInfo& info); Napi::Value CreateFrameBuffer(const Napi::CallbackInfo& info); diff --git a/Plugins/NativeEngine/Source/VertexBuffer.h b/Plugins/NativeEngine/Source/VertexBuffer.h index 33df36ba3..185379e7e 100644 --- a/Plugins/NativeEngine/Source/VertexBuffer.h +++ b/Plugins/NativeEngine/Source/VertexBuffer.h @@ -59,7 +59,7 @@ namespace Babylon Handle(Handle&&) noexcept; Handle& operator=(Handle&&) noexcept; - void Update(gsl::span bytes, uint32_t startVertex); + void Update(const gsl::span bytes, uint32_t startVertex); void Set(bgfx::Encoder* encoder, uint8_t stream, uint32_t startVertex, uint32_t numVertices, bgfx::VertexLayoutHandle layoutHandle); diff --git a/Plugins/NativeTracing/CMakeLists.txt b/Plugins/NativeTracing/CMakeLists.txt index cfa8bb047..8cbd591e0 100644 --- a/Plugins/NativeTracing/CMakeLists.txt +++ b/Plugins/NativeTracing/CMakeLists.txt @@ -15,8 +15,7 @@ target_include_directories(NativeTracing PUBLIC "Include") target_link_libraries(NativeTracing PUBLIC napi PRIVATE JsRuntimeInternal - PRIVATE arcana - PRIVATE napi_extensions) + PRIVATE arcana) set_property(TARGET NativeTracing PROPERTY FOLDER Plugins) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCES}) diff --git a/Plugins/NativeTracing/Source/NativeTracing.cpp b/Plugins/NativeTracing/Source/NativeTracing.cpp index c10668b06..e9b0703c0 100644 --- a/Plugins/NativeTracing/Source/NativeTracing.cpp +++ b/Plugins/NativeTracing/Source/NativeTracing.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include diff --git a/Plugins/NativeXr/CMakeLists.txt b/Plugins/NativeXr/CMakeLists.txt index b15a7792c..97fa938a3 100644 --- a/Plugins/NativeXr/CMakeLists.txt +++ b/Plugins/NativeXr/CMakeLists.txt @@ -14,7 +14,6 @@ target_link_libraries(NativeXr PRIVATE bgfx PRIVATE GraphicsDeviceContext PRIVATE JsRuntimeInternal - PRIVATE napi_extensions PRIVATE xr) set_property(TARGET NativeXr PROPERTY FOLDER Plugins) diff --git a/Plugins/NativeXr/Source/NativeXr.cpp b/Plugins/NativeXr/Source/NativeXr.cpp index 5e6592fc0..1d2001fb9 100644 --- a/Plugins/NativeXr/Source/NativeXr.cpp +++ b/Plugins/NativeXr/Source/NativeXr.cpp @@ -15,7 +15,7 @@ #include #include #include -#include +#include #include #include diff --git a/Plugins/TestUtils/Source/TestUtils.cpp b/Plugins/TestUtils/Source/TestUtils.cpp index 0dd6e4150..7ec3ad500 100644 --- a/Plugins/TestUtils/Source/TestUtils.cpp +++ b/Plugins/TestUtils/Source/TestUtils.cpp @@ -27,9 +27,9 @@ namespace Babylon::Plugins::Internal const auto height = info[2].As().Uint32Value(); const auto filename = info[3].As().Utf8Value(); - if (buffer.ByteLength() < (width * height * 4)) + if (buffer.ByteLength() < width * height * 4) { - return; + throw Napi::Error::New(info.Env(), "Buffer byte length is invalid for width and height"); } bx::MemoryBlock mb(&Graphics::DeviceContext::GetDefaultAllocator()); diff --git a/Plugins/TestUtils/Source/Win32/TestUtilsImpl.cpp b/Plugins/TestUtils/Source/Win32/TestUtilsImpl.cpp index 70ed42a76..dc208350d 100644 --- a/Plugins/TestUtils/Source/Win32/TestUtilsImpl.cpp +++ b/Plugins/TestUtils/Source/Win32/TestUtilsImpl.cpp @@ -1,6 +1,8 @@ #include "../TestUtilsImplData.h" #include #include +#include +#include namespace { @@ -31,16 +33,21 @@ namespace Babylon::Plugins::Internal const int32_t width = info[0].As().Int32Value(); const int32_t height = info[1].As().Int32Value(); - auto hwnd = m_implData->m_window; + auto hWnd = m_implData->m_window; RECT rc{0, 0, width, height}; - AdjustWindowRectEx(&rc, GetWindowStyle(hwnd), GetMenu(hwnd) != NULL, GetWindowExStyle(hwnd)); - SetWindowPos(hwnd, NULL, 0, 0, rc.right - rc.left, rc.bottom - rc.top, SWP_NOMOVE | SWP_NOZORDER); + AdjustWindowRectEx(&rc, GetWindowStyle(hWnd), GetMenu(hWnd) != NULL, GetWindowExStyle(hWnd)); + SetWindowPos(hWnd, NULL, 0, 0, rc.right - rc.left, rc.bottom - rc.top, SWP_NOMOVE | SWP_NOZORDER); } void TestUtils::SetTitle(const Napi::CallbackInfo& info) { - const auto title = info[0].As().Utf8Value(); - SetWindowTextA(m_implData->m_window, title.c_str()); + // SetWindowText sends a window message synchronously and cannot be called the JS thread with the way the code + // is currently set up. If the main thread is calling FinishRenderingCurrentFrame, this will hang forever as + // the main message loop is not pumping. Once we fix the rendering code to never block the main thread, this + // threadpool scheduling will no longer be necessary. + arcana::threadpool_scheduler([hWnd = m_implData->m_window, title = info[0].As().Utf8Value()] { + SetWindowTextA(hWnd, title.c_str()); + }); } Napi::Value TestUtils::GetOutputDirectory(const Napi::CallbackInfo& info) diff --git a/Polyfills/Canvas/CMakeLists.txt b/Polyfills/Canvas/CMakeLists.txt index ab9704cbd..9403588ba 100644 --- a/Polyfills/Canvas/CMakeLists.txt +++ b/Polyfills/Canvas/CMakeLists.txt @@ -35,8 +35,7 @@ target_link_libraries(Canvas PRIVATE JsRuntimeInternal PRIVATE GraphicsDeviceContext PRIVATE UrlLib - PRIVATE base-n - PRIVATE napi_extensions) + PRIVATE base-n) set_property(TARGET Canvas PROPERTY FOLDER Polyfills) source_group(TREE ${CMAKE_CURRENT_SOURCE_DIR} FILES ${SOURCES}) diff --git a/Polyfills/Canvas/Source/Canvas.cpp b/Polyfills/Canvas/Source/Canvas.cpp index b84d8bf27..03d9eb02f 100644 --- a/Polyfills/Canvas/Source/Canvas.cpp +++ b/Polyfills/Canvas/Source/Canvas.cpp @@ -2,7 +2,7 @@ #include "Image.h" #include "Context.h" #include -#include +#include #include #include "Colors.h" diff --git a/Polyfills/Canvas/Source/Image.cpp b/Polyfills/Canvas/Source/Image.cpp index 9ec91a8c3..89dd3da86 100644 --- a/Polyfills/Canvas/Source/Image.cpp +++ b/Polyfills/Canvas/Source/Image.cpp @@ -10,7 +10,7 @@ #include #include "nanovg.h" #include -#include +#include #include namespace Babylon::Polyfills::Internal