From c91a482b4e9fd0596ac9166a9b623fe16a288da7 Mon Sep 17 00:00:00 2001 From: enricobuehler Date: Thu, 6 Aug 2026 13:32:31 +0200 Subject: [PATCH] test(vkdecode): the ten-bit path finally has pixels MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every golden set in this program was eight-bit. So the strongest thing anyone could say about ten-bit decode was that a Main10 session BUILDS and streams clean — which is not the same claim, and is exactly the shape of claim this program has been burned by. A Main10 stream decoding to garbage logs just as cleanly: HEVC Main10 on D3D11VA has no per-picture status query at all, and on the Vulkan side the devices that matter report queryResultStatusSupport=false. The HDR legs were measuring that the pipe ran, not that the pixels were right. So: a Main10 vector and its goldens, and a ten-bit leg that runs them. The vector is 50 frames of 320x240 HEVC Main 10 4:2:0 from libx265 — 48 KB, generated by a command recorded in the golden file's header along with everything else needed to regenerate it. The goldens come from libavcodec's software decoder and were cross-checked between two independent builds on two architectures (ffmpeg 8.1.1 Homebrew/macOS-arm64 and 8.0.1 Ubuntu/x86_64), which agreed on all 50. The goldens are P010, NOT yuv420p10le, and that distinction is the whole reason this could have quietly gone wrong: P010 puts the ten bits in the HIGH bits of each little-endian 16-bit word with the low six zeroed, which is what a D3D11 P010 surface and Vulkan's G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16 both contain. Hashing LSB-aligned samples against MSB-aligned ones would fail on every frame on every driver, for a reason that has nothing to do with decoding. One golden file therefore serves both native rungs. The readback is now depth-aware. Its only eight-bit assumption was the second region's buffer_offset, which is a BYTE offset where the extents around it are TEXELS — that plus the buffer size are the whole change, because bufferRowLength = 0 already packs rows at the copy extent. The expected pool format moved onto the readback so the sizing and the per-frame assertion come from one source; a readback sized for eight bits that then accepted a ten-bit frame would hash half a picture and blame the decoder. A CPU guard asserts the vector really is ten-bit — 4:2:0, both depths minus8 == 2, 320x240, 50 access units, 50 planned outputs. Without it a regenerated eight-bit vector would turn the ten-bit leg into a second run of the eight-bit path wearing a ten-bit name, and it would PASS, because its goldens would have been regenerated alongside it. That guard is not ignored, so it runs on macOS and in the container rather than only on the fleet. Hardware: HEVC Main 10 50/50 bit-identical on NVIDIA 610.43.03 (Linux) and on the Steam Deck's RADV/VanGogh — first run on both, which also confirms the P010/3PACK16 layout match rather than assuming it. The four eight-bit legs are unchanged and still green on both boxes. --- .../pf-vkdecode/tests/data/test-main10.h265 | Bin 0 -> 49181 bytes .../tests/data/test-main10.p010.sha256 | 83 +++++++++ crates/pf-vkdecode/tests/gpu_parity.rs | 166 ++++++++++++++++-- 3 files changed, 236 insertions(+), 13 deletions(-) create mode 100644 crates/pf-vkdecode/tests/data/test-main10.h265 create mode 100644 crates/pf-vkdecode/tests/data/test-main10.p010.sha256 diff --git a/crates/pf-vkdecode/tests/data/test-main10.h265 b/crates/pf-vkdecode/tests/data/test-main10.h265 new file mode 100644 index 0000000000000000000000000000000000000000..a065be5b9afd21297895c3b8bb1c4fd719c9fc41 GIT binary patch literal 49181 zcmZ^~1yq#(6E{kCcgg~S)Y2v0jdUx$# zLL4kYQczm9Dt`xh;h!}RAE=BEvlWng+?%as5YjVbbojrYYp&uY?01yP3+)Ca_d&h4 z&wTiWg#c{Uo{nH!06(8FI{*j}CHef4Pd&Dem zUT)qOX2OC1Ye$b406t#C0kKSl8?hCSilU)Bzy@LqvO$>SAy8j;M|%ek06#CE01)vZ z3ShHg2WWbNJsfQy?jUa~cMumq)%9VrgNFxHjECpJA#RAfJ;DNkdN@K{VPXIqsHdZ? zwjij7gCrj>z|j@z=>fEXK_&SBc2JlkzYq`KKchPcCMm$r>%%YjFwFzx4z{uZ zN%8_bARbm=;2$qw2=&jlKu22}gzSKb6X@Xxa!2gu>0k@AwnZ4-L3WY?51v?o!4Pkt zE5sFO197#3xVu=n+JF$lP>_`e&;bz*$Q=f-a&-mySpS*sY5Q;hLH=kU2<$;~aJNM` zV+R2^Siu|~oB_E&A?^r&z*fG9

J|3UUS7L!bZ`M^~UT$QR){A}YZDBwhew5D11q zoUIe^{~6U&HuHO9})e3`v75JM;k=O zcmXgQkSoZ>(?b%Gw+D|MVIIK$YyfvFTQFh{!VZHzEO?L*9=clr5otnX+yP+Y;VFrr z)r0pooVP*mxMrD&p(QT3kcv1dZ@HN>R<6M zlU%L9zJ4Ge41pfh(FO*v1zCe3HqMf~VhFgbAP-w14u7cmZwQelgpV)~7?E{HFT_$S z2%^+LR_n*d8Z~B!eS@MFUrjeaI=B9KoL6$BSa|70|Z3`1M{%*fC0VT9X%eZ`mgrTQxJ+H)ZGfv zb|m?@5gdkE|ECD>0<7GC4-g?>K}ZOcL9X@>jpV^$1m)d9Ab=ml6@(~oPej@MAqQfT zqYa|PSi1w)%y>C;NB17H7$e7yV!#zO4*fe$7q1knXx z)`+_F@^nP>JA~H{Tt{@J2OZI({wn`E!XI`c+Bnb+3WP%cbg>5wfhB_T2=0LYZajuqK|1-m^5Vi3CGX{oOJuvkD zUWY&o3_?_cjR)kRRs5wdA~^_v{$J{!M)Gge#R~!VKN-;rfe4%ss~@65EI~x@w@*A& z?ZfCp*8kZd-hZ-Yi)fSomjBOf4`1kCjzE1M8UW%14=`GJK6Kar+(U5qPlEpIi2o}! z7y?~H6c6cn$R^0k29f9o5$5$LrFM=W#C-z-+&|Zjhq`eE{$=H#viKi*Ru30?1X7Z` z|JUX3AN_z2B@FU_0sh=6AOf<6cz8fu5CY;R0#R@7e|U$e!-p^sr$!V8A`u8I{tsOq z%q})C54%6ES-aaj9RJ=cxZ4CH?ts{k>*xU=!5xKD{T6%mjthZJeH60^_T5!rH~;Cp zm2N@v#n7mFm?QuAKwTo+Id?(Q+PyWI{Yc^&=}WK52&{A`tM*@|$bk+{O-!qVNp%;P z%jse4yJT<5r3~I*+y`f8A9ESsEhp~4eYUMp`q$IY(_s1EyPHfMrlEjAJbt~&??i6wRk^k+Da z9dGw7Hc<-V%GW0Hcfg*XYw`P1yFvFxLk>%?sqhm&e@zIp{l(htJKy6i+!5o@;$SL| zOfR1cifOl9W8lb^h^6kWd{yli?t6@1#p2{?nTo_rw8IjupugCS?@R!EzB;7(LrKUi zeRbEojNXQZL{RRA44l`!9&&WWU(NWj!gU6^DcC#jI=`v6eNt$?T+TT}9@My_xYzYo ziV*o#_4);wB`!lo!B;(g>Y2z|O!ce!%n@YW-e_2I)%%=A2c9`}s-yV&1HDgII|QFv znhE&yea+Ov>_25AF0XpEbf|gg%3|n6Bom_7!_DiVIWe`|8|)*2elSRI$%Z zA~I#f4VPu%taN4W9}LXJw5J^ORMM-1iQ->Tz9<$8#YnuwYNaHt6qr+l^cmta$rny8 zlpSsF%V2Y2#gV*|3&-_(hkCD)RPj;C{B8R5!90@5#pe~O5y8opSq%PmG;`z zWfYe2jjU2pQXqf+3RL_$pPELx zby=5BFIvv_-kA%$jMEeTl7#Y8SYRHL^9X z%KNuF!<^C@U+XRBg@RRHoz)v0fHbjMIiAZeyGJT}`Kd=_)H(Y&F~3>@bWa8DPZqH7 zSvVNhOHyeyCFEVusdwJ6JCabrSwE-?iJB<-oLnwF%DXw+&&Aa2Imh9bPdVTbZDGd+ z_0H5_oEd#>=8J|J9^Jemnzr5F>Uid8IWeZi7xP{G(F@{z#}-+s*)Q6>n{*IyJmJ@2 zLo1eS#J7NK^e8n)az_-J^lJX{=L?DN>w}=3t99Eoi_jaYK?4vd)z`#o35w09#T85BcvUWCnpUZ|lwK7#y$< z=JPK;FZz?cbe(#1>FD7~?F{>3dZ96)W1A~irlSA{^Oi0|3Hj+hKS}LASlttRKdwS~ z{t??HuEsE8$oQQj4f)Hj7UM-TF$zRsd~2^hhFYuL8T?!Ym`=WwNlA`y=P_0aF~)jN zATKf%U2?m_bOztu11e^YT2j0MIK!`*rDKn&r0-_GAV-!Qm#RP3+88^7tS=h?!O5>nL#*URH<+Ih( z@hlN-z@wM6;P=t$huo_d9v{7Ih%P4GGDd!1ojYDs%Flf?_Vy>yNj}zCH#;?%Q+F%U zmp;m$pb+SMZLSnu*O)39DZ)neXnAYm&a;&f!kA{8Ht}=6q;}ZIu^prR5VX1_XIQ{iLeLPiM7scf4Nk&F3nk8FkG~Q`HR*3l9WTYx&eiVyS^lsbZ zMKEJl4Lp8RoGTvR&5m?xCwkRUqcqt$IC~B~yo~|oa1+0LPEE6z`=jsm6+kYm+iZ^ zLET&Vxydwp6?p-(oq-uJX* zX<&rI@{Va8B%*Gx?XKme6^H}!D~EmCZ8=+h-qv+xGA(r40Y34Qpjm9xBaK&X<7 z4PUyHhNhs*Pla72`nOuN3<&}ExZ2>Rm>)c(ua08=bz@Z$Y3BT92kqQls8B(OuQP z{bBj?k&SgQGaYTk*V10vB+Yvcj5l|>l=baxHfE-y9}{D8(Kf0(#>J7sf^@7pMQ6hV zPngcJU3oy0TfNV-$M)L#rc7_i);!E<|fUY#x?g`Bwb&QKU3DeFcS-3 z5Z7A{@RslnYA2PG(o1_hh0H{hmNnZcbsFs5@N#KmW_smVyzDo2)XVYK=&tf+ZChOY z?@eiKJ+JpC@ZZ?dPX1YF-n@Hr%E1w5@d>Z<#F7uscZe7V}3Jrw@ zDbB7c_-^3E?AtWOZt$^Rn4KYaB!AW0sij*?LA<(maT^J*@t-iu8mvyZ^1;8(uo z8$9qPZqV-!3E2ahoZYK_D}8I6oVv973Yhtd6*9tC<5Xn2$`=aVD$e&)jE`!sHs8F% zT}_L;|AuTQjBn|WPXEa`NPIO*ZJ`Hzo{axQdf_KQh0eY{Vv92#8<{9W;1l9gi{Vy?M{>*rhhhU)g)`D$896=XnFAZA~O+_EL3I+}c|bIg7*$ z8I(~I#%qjq=_;h&r4WIm4=4gfPBCB5BYb$WwHhc7-A_5j($Lt_ zR7TImV`bZX2hh{e5ot!tNA?}F<)yIb=w=A3cgF>e(=yXD(tuhYHVi>{)>C}l{ZAMSDkTvb z^Te26a6g+1Ij{wlmfRbv5`@-^sOMR5?eSu0T)3AYiD{ErL*vGH`J|_&4dC$meQ$9# zU}wPX@K-{|w3qm!O}*Z11Fys0zq>3ly6x6{YLL9rn~s_XG?o$jo%tsu(e6VQIfHj+xPPB2<7;ZF zJ7NpSsAGn~In28a55SE_iNAOP+7ioRJy)%P8OWe&uuze(6Gh+rmZt(!sSF)iP=!pS1 zFl2PrnyoQ2)liEYV)o{aEDBy!5x0IP{p&W{)rL3u>o`frK$$1zs>9g~Z}xr#M-xX4 zvv7D0%=Ut#q4_8JJ+gsOg7@jA0mr<|{y_k4J+41+Y?I7Z9S;4(rlX8O1yR6Q4$r}N zt}~`KTDlCEfht22rV}`QxN$NY?w8A7?cs)`Qu%NCa!lp~ec*C_)Nr)??rf-l6#b`r z!u+gfz#Kd?=cnz)e&pOs7d%e)BFX%;g^-42{4 z9yEDED9ZfQ_C+%>AR1fM>de1o^l|ovOth)MD#dV+8&WEGh~z8J+!l^!Xvyna3GhGPykGozC6xpG|#bd83N4bj8&EOGs>wF6zTuB-RnH%QF+g}(1@+-edV|c^P z^N%ccQY?55H=H!Mx6Bv0;5PZv~UR!N4Ik3-m{p%o5 zF}l86`(34QXTYL3=A*nV^R4B$6ma*LRL4MUFgEjdVe})M4lSzM@>qeKJ3;1S89tFt zme}lXb!{{WwF5G%O6D(R+}OxB>V|E*+Z^7ozmCYxJ1T6FL-NinIr|;J=B>+d;!s&9 zn=(-B4ZA8-jd;EpWLq^Pxk^UphQ=%C4T1Pbc^w#es4O5?;01L-j|d-bhHtjsAwKVa z=yOlW%5^EugzkNKnXX0tzUqXl{F(EI45pI!ObORlQ@vs`2E!*$<6a-h53F0fnh8%N z^J(gx^7Q1CuD6{jbj`5fVCdm2<|hGY$;iDzRyaMPBzOIctZ^WEo5%a!ee2-hFznYB z@~dS=9mr3H4uY87cTL*%Fgk(#x*?TEOI~E2eM&=S)OWo#)8-wOXA?ZjuFlp>lL5Fx zvT9YlI29f%di=Uonu2Wz`ZcDJ;@-0Hr z3N;LbKCg%ABdep*XR|{UH**`iknro1WBS>wuHS)CQEpCyx`u#Fl_&D1g0C}&RTPQ z`P1m~D3lYvoTUFzw!4!auX-mHD@{JsA3TN=6Kwl>_oM2`ZU(mK4n(83iU-Qwx@zLix~yAcX$)L@U2{X z-h@hxr}a*oxj)ienYs-Zx2P0=7W};}>ANVZ4{h%~wQ5a=)d^R87&ot#kK@CaC&2_$?LPPsJQZ;?5xzob`E`z8js z0;I|suhW=HVu^M@$h&9!*6OM*t;9M<$jbzEL7{9|b?f={7F zig*T^BfHee<3!FEISmnA(wYjiU zD`caXDMa3IT6$O3xQSUsR^gaKeuw#6dcujv$I6`q`Pb0K4{=IJ-G57}YL=jM)Eh?> z@CkoLQ&3&MC;~64zw>bv&4>F<^j53&o{9LaOb+9f*xt+T%-a()@5qF46()jy@oc(qXqOk> ze?@Js#(YaWm5hs>lrY53xFL)e#zMkFIZgS^tB4hq00UiAqJRD}!LW#BW230JeX&00 zV|ccDCf*2y^3##T23He@u>Ot{w>1+@jyEY%dcOnfZmXD|vLN1VlmbHv@YvSQ-=H+3 zIV37Fmp$&;O2ZB>s(13#+uUM&irU`LPw z4+iU_x)8a>;1}n(UUDzP!=rnnv%(W9HG>BQNH*)G6cswty7_XEqi-t1X+nbCL5(<% zsr$}Y7WdYff@56YCe1Ij^Q(esnq2;$-8k4w_G%FE>@g|+D_ZUWDkG?^WH$ryBB zMxt_j(g&F9Mf2ND=J9+Mw1AneY`%lj>bLoA{6HmaU79+T*M6IqG(@dRotk^nsJ0z7 z!ovQgtT~k=Q~8$wC31RNMoz3H*&6Ev7m7@C&$=|n2#D%t;sBF6Novs#`+tMZ3Y^&$+zo^ROeE0&R?Gq2>xa_A^jc>AHBRi><730`dDsmVJen6_f$oApvv*deV#KBU0af~{PyBgU;6(J$7JU+81?oGyqB3tsFUW)Bl zEdDwa@JG=rV!(g;96X-^PUCM<-Owz`lt*D9^Othn@l%>&&+&Nu``-IE-hx8xw_ExJ z6gH`SZez7RCDwL=88-B6BgkA{Qgp@7lk}S9o!u|$w{;$#WDq30Xjb~KeMRO;-*`-b znU2LO)B6$vQ`AWCm4YD0OR@yt(LOX&jgM>Z$&A793VewxG}fl33F0zS<~6kC>6Zhi zOq?A1U>bI@w(-Vv69N1MDm%NT#2^+KYh|g|$sa;CLY~=c(^WhIp{Tvjw}>DVv!ZLA z!5xs_;peut#|wAfz3M2 zI^PZQAJY^li;p+3c5NH^N%aMu771j$s8{k>5S=vY7592t*ychazPlEx@BywB02;Pr ze|$>oA%}l!;U3{1n~sETPXtB!M!S-$`Z_WA9|sg>5gZ7`2B9#IFQx^VeNfIeEO!!! z8Sd#&xpLDAF7@$bj#>sWtx$3|$R#Q)%o)xBFWz!|c47>=AT~G#VVDrEh zp68p}5oQHl+Ma_iwTY1qnxk`DWg>%gWN*maMS31Pdu;}6JNv}S+gv@0|5cw3eM-}N z`8@ZIJ4TFz^fDS*Xi^SyO~}E(i7g*++f1xu=XUG%Sgf>P?6tE;rBj+D9^>-9_Y7C( z;6mU~4NXx(w95XhD9zN~z2zfn)tCsJ4^K6(iA#)L1u#Ea*8Tldxsz4IDZLt-GSTn5bW3BF&Ix6q6?~0n-w)(ZM zBHMPP(8Vn?(x10adf+thp!DN2bI`=%B{RJFOu#ObGG}7he>Xdv#}nj|^$HUi-qI!Q zKAKUNiYhwIUM4)IQHOO^s}UTMcI}+>X1VK;t>r7iAXBhF*)xMS*{!jY`6euJus@L@ zIo79l-34`}(7AkQP)0XAu?Jem2%bl*peNAm)l&)znZr8czA z`nZtpDL$XZ>qsHRm{-5G`}Ro+X&Bo2zkzR2r$5%cVHF-M(v6TM^!vV2XM@HB{Y3=E zam6xM(=Vl~I!4d63DO9Df$dj#@B?FrZI>J~GDkl8nPF1Ok65+N4kt^XZtE?PWw|M9 z0aODmKe{oAY2CNYEfm?eXS;AQp{F+l%Os_UakSN@VwHsExbI1vERt_zvt9dQ4{}!z zZxk}f8BLZCX?pK~f)5EZTcE__9B^r}U>lzVkaQiut7xNwx#IRL)q>{J3N|7XwOcX=-6r->lhq zH7rr$X;icFUZ%hmBMQ@KHrttV@!&4lE-cMMX89DP6_8@d1dYH=pIey3LSv@4?3{(0#9;p;_~UfTQbr}Fb?3Kd$dZZF%? zxXF-R+;pMU+g&>hY??m?V)0qNZ7XM$*-}%IWNjxzPr2Rf_OUV>=j$X|7Cg~_mM<8a z;eNA`=l+a%N}B^Cp43Rj9jN9EdKi+kVTO+ax=rjor98^OlkiD0VLQCocjUvYzkAgI zTNWrVq76B^$u15Uiz{}yV20iG*UZQHj_~G)mbYlFW)+{bO};#B!bx)BYFNdO5Tn0`W>UyGhr)CaCZKl0@FICv&Z@Z^x2W=<35Z>r-@{Pdnc zo)nkc!pK+YTBh$$w6ptNE~M@#pd_Po@Uk{%J^NG>IFyArEn#oQBvo=8C!77?DRPsx zP=)yk-~C;__;;_(-s8!;&UbhYE15(jKI{0=)B$(hREbJko+okR z`g^L9S}LUmF}Bj0^J6LEkuQD?{KwzVwat}UpZW87XP8Cd4lF|O$EV6yIINpz|Fa zPGIh1^tX-RgWU|jJ`s`GFQ+fNiPs`%D?DXYA}FGtN9oxfukKSRD__CGVZl6~vPK#e~KW9?qtXf%*zX%pB z?OIlj5D#?nkDtaaxqv|20flCFb%KM5dnKDnK(N!o)k*V&)BNRGQm(VYo)p&1JC6loZNyZB!P%K5bGy_9y)!t)-Rfu`Ym6^&f&?t@BmDxiy z(Ns9hP##%!)o)bqIZmD3W<4*h8~8{hcsd>muy9Q)YBlg;I~@^KGOaXLaRXAgatywD zapueq;{VoB?p= zQLbLv5sT|_XNOYJmul&M8H*?1Z+?8+G`dZDY;c9!^(CM3zKrN3L|)F0*v+tVdNWyC z2v0n)%*PV|Qk1kYL9emcRr)?|sLS-Oi-A^hZ z`6<1}hvr&zztFsLZUzYzt0&`FR^roD8_wt zrUD0@o+w#0X&QZgCi0%bmq&c!cnVd)BjLs3_DpE(=6#;6(dO=T{A2~0LYJPpU%!ZM z#(~49NBwWhe2uGyuF#YV=YA#A?zmt~wGYpu>BmdKf=*bU#e{xZ1-&YBvLkd=W9Mrm z-4~E}vix?e&8yvTr$MT;==IpD5RRug5ok%&rTKR)FX2=F*Mj0|`X58RL>NLi5`+pF z!miKWtQ!9=Eh(ZSGlb z`)a@V%~TDGQgv_?YA@D&qtn^>JLLX`Q*?X_9`s(j$kZj~XQyj*N+ei9Tqa+BRLZ?H zRvk^pz0?v49NcKq@#)oW6DED=vZrW!ac-0 z4;XQ_QN7PGL&Wo0J~QF)r{)V#{tuF)=u9yk(rgGG+cCdHgv;V_Tq~E=6eDIqe3UTh zOLA`a;0E$_ags@Fh)m@}1tH!s44lnQaIphY+3?|8dl9`T+#^Jv+iF>+$=;INAKmVC z>2F6{emm>W=Xl$r^BJWe{;)3D0fi!U2$FR<^XBpOp~8Hn?f~#n&@58?jzy@gb)|#< z;AEl0XSuUQ+dSe7)%-A~es!?i;Y`jHNe8UF zXXnma#*iYCv!E5la| zH@TPzRts6}bwRObd~%YQ63A93c=qdl=@O5;Z%qUKpf-QCrZUAw?rm2Klo8`is#j3ef0+N?R7Uuzh@=R+??zX;*z1MWRMl`s-WE zr#Pmv2{MdltFE}ueppBwV)fg1GrHir(YYP&05b?OonVB|J|(&{W?f1)zaZ{nrrdDG zRq}`)u49mY(Rw^2c|OA~JtqC;M1Z0OUBw?x)lwY%`rGaIH)^|-a3lD3_1BLZwD&~g zB+*!kZ8ND_Lb64Nt+uSIies~TgMo}2L%?JIgz;O259sONUADZ9T&)vKC3d8FZ>pRtt>Bqq$g%~nX7^|wziN~&lnO`%cjCQ$@dv`$8@z_ zH+&rXsT1|B7ZWd&FiusDTW2*gtN6n2Xd7qzeZHi6&dT_Lo0qWbX4L95qvhEq)J4 zI`_1V;u)1{yHpd7gf5#tgI2}`{>)Z?r|SP+%2y#sHTCNeOyY&MM*SOhJ+2n`kG(ft zrEvHnLlFod>-(z^i~61uo{Mj~L4RcC<~*FRxI|Ok%kl<4(seesWAObiy{2rDzMPNr z*`?PRh$n?J4aA3Ef+OD0qDcz@RYZAr6G~XBDzgcUDqC`cpvEGp0|m(1KX!u3_>5>b zgj$(7fy~A+(;e+ff=_i5K<_l%ypk~cPVwUtzd#PXQMn>%syk9j+k(LKj_b%n`Y7H0 zSa_`AE0x;>(HvZ267uLTYxBb%MHnfRL8(~N{)SEzVS2JhYZp8BRZRfxrkj3|%kBB1 z89OOA9F*4$VPlq2&OQ=a{S!+X1^N5S)s0dmaf!|pvgv@uK4I^l`B*Mrdte+tyXMtc z#YQp}_5Hr`2E^TAtrB&~WyZ3XJ5S0lG3w-FR;0Q0Pq8a)4&T1M$)YywBw~Ia=%D{r zjHVan#Qyq_`p)w@G3ia+)3w~+AuQxo);Nor0ky$SLsW)3>j5YlVnVjXI(XjLAlsK} z(J{CylY2^`HJ&MnddCBPCww)$!niiL5e7EAF&+RA1A}< zE9akQv^Mt}?wu{ZaS>}iL z73p&C$r%tQj)#LTA5IL|EV0x#u>zeYjmHPi#KjZ%6DcPe*5oF#emhe2fzGSOOAEzJ zL-C1Z&MQ%BPl`b;dO;(tgfqcnUz}eJdE|!jAX$oOSPUYin76xX4RsijfwrsJ&t;a> zm5{8RP&9%*b~KjRu%$96Tow)MqvkZaMADiyt19v{dol&zf9*bb%l;{_r+&4D{ZrBs zi;1DJf58EBM(@FiBMG;C0_XD9s4M|a%)3iagnUj8R{|#Ent|2sX+p=(aOn);{Xp@R zh!!Sp1LNQ$ak?AB*8xCPOR941%-9J4U$dLw66ms66qBh$q~(#c*RE4CNrFSBo>sJ?^Yd9vBfNA_^1Qy(*C`@Go>4)p;gfa(+b_rMZ1Y}E zRlTV^^{mYBJRB%}a|#??VyC~NKRc&%tuAQT8yhYr{Q+nmfaaC^o^o4=_*$!1?dH^bY8fY=VL`5mDDtbuEfpRK88>exU&kq5%5^=f zAEz+rSlqtdOPX&xU~nY-Eq+vQ_#Q7T#YC$Ch4ecVHK0QNiz=GpwU*>X=i5iLZOJyw z*=?wC>4l%xK95y@jd~jIa!1lPbj`4lvlE~=WafbL7Oc9joZCUU>5QrZR)|+`dFih< z^~UcP%BqT=Loj93|@L$ z{@-(Pj}Dce?-g*)O9RIO<(rTOX@ouLUy=`plrHv_>WNE~bWT_-hVXbWc!tpExCpeD zYucxHCdsyI;!f^Z;F>#L{&em3&5+VO4gg1u%uJx8aC6O_w_6@wd&|Ql3MOaE*s7mT z>})2n2?j5<$bAgu-=*F6k96IuF0YVybx3~E?nK@)<+SuwyKC=gn_x90>VEKMVtrC( zlbGzQzaz zm6(#*7wo(j8L1~&wE@&GUVhH~7zA6H=5IEca~k+K zn{Nc*L;P{u!)wP?YZhIjMJsl+9<1+~XK55(`vnZaXqD#37e((diQ5T22-^B4NViUE zrrwy@C;L#TTz&objw@YQa70Q!SCq(0E!0ov7l5Btt?*Yf#ZkFnLdV4uqaYR8B@6jw zf}6u+2U%W8iF7wFI>Albp2z!wCzQ5GiJJl?(5Yv%RbX`7@j@WNROKklik396l z>*Ohu5py%G-o?kFT;@1w4$M@_5`>3ML`XSF%&YOaEb*Nh<~`&6J5$cbt6j7$gnE-i zGkaYmdbWHb7qQ(sca$e5`A-+L>^NZoY_aRj>LK!_n`;h1W`ZA(K_{-FNC6>fllk& z4`}&j6v+4kILdWiiV^!WIjpA;U`k*WJTV#;pMwBhz|JXesnObgH|j;Ai(M0cyEjo=Z;kh!3w zoo&iiw7ZFGShO60{w(o0^pA~@8KQX~^U<6!>E9-Qc z*+Kp70PnAtB{+~E3D8JEN>4`vhD2FeOG)|R{@#4MEFQe`4lt^5OPP3IDQ#m{;D#5F zG>$u#W9e|LYCbuR)+bG-8|P!{=a;U0AC|uk?}lH|9C`kXYNYVZ8tk2Ap6jM4>*fc_ zHTPPU+^0x8WDFB;;J&_1XVB7!K--o~&ItaBguFo>LX@q8s$2_N{=T>Pi_2w>gXBBX z4gKInoiJTg+H%p5STyo$(JBqyR~tlCeDCZk*@647(`6qQfDczGUoPz` zCT5T%ZK(H>HCME^ZfH>+9DYM#oG2zL$ye{-K>TBN>e6AehOrfFS*hVb?E-WMC*w#G z5artNfhZflf4&-eg5lkxp&wgsRV$F^GFl>6fgn4Wgo$BdB5{^;rGAtoUmfV!^t zN!QiL!XH7*4rJh;20r&iqSO&wu9A{oK`#JbGvZ2qYWj|URmX`_% zK!JL~M;}jZhu-V|uFt*xjt8CTNe8VcnSi_5HUl&ic2g=s7@xvI{8A zNksOd%1Cwow17H+E;ogH^2MwEeS*Rwi&^2a*Mt62;W3U5aDSJE=!N4;@1*nxa^HxX ztfF!X)>{%T1P$>A;77^o6wvTCA!7w>!Sn~`@CjLf+Cc)S?`aWnBxon|etLXe5`F;@ zT~iRiyPG-7Zy0Q$tr=g(4hG>Y3^1EEEZb!)<_B7z!*rBimOIB8fA0=SIQv`A%v)B_ z<18dG3EL|Wv(3SwsNEY_!w=~b3b+-@NTKcbpE@mi^Yd@PX|Dj8D<85Jp40Y1tyIB8 z`Ze7UF=r7Kxr#VF0n0u^7ec^0>WUlrw#PztH(1Lmd@g9}IWeXg6Y z$%Z;{z&vs)Dd3~gxt`$LybOlEeTr|uRQmoY;#onnn7#)X2mdNXa!Q2Rq~<*Pt?7qq zHr7;Uw#8rK`hK9tM1-3POvyC|s~vBS;^%X21ngeD=bQh#=RaR|-SqQnXZ!Q>@*m#u z=OeGLi zJKJiZM_`26^!0O!*&bj&eMYSuiBG$|4O1oGs&(h+S?0B(KUTqd#yQ1|k--}4RsRr2 z8lPN7Ut(0U{yfGY*E^DFRx@jj%5mgSEKK|%=Dv}f==-bx4*`rQ>As0FY2#4EY#lM>^B8g*vHQ&FTBKlp4sL1@B+6Sl`xOHR-ddUZT*}qcS=63 z8wa&=Ou9*4^{I`CfvE{<6nxR{sB42 zX3)fX+;00#)E2doqq8t3h6m&O<3W*=+yVp)AB2c12UW33Hi6;}bc8GcsrMLlIfr_e z5bMOmna zc*4){{=q*I1C8s0ar$YGwgFq%%@edvyIl^SZ^XTyWJsTjC8Za(&V50qVrhh1L2f9a z&HaEX>gcEho?06lUzhBpOnICZI^VFfdMQ?&#L0ty5dsq-(2X_b+z_N+enrNzDH9#1 zlP&!|mPaIghlq*x`7_S?;s*zAC?`2&_FkOc>4@ePUTUZO`t9|{D#%3lW$-B4DG?qK66%_vcE`l2pOWwbvUoq|kT ziClu9kIw4)x0qe+C6{P&`XWUxgz-U_6g0~Bxu?!XXm?>kRdGC5zEfYS`N8U>fQCED z7cQOgNb5M*No0%tqv{P4sGcfn!^e%b@Q6Tqv5p&Bi`0nWC)*^hZt1ka4I7a=BZYs0 z1aqXD7X9Vw&c7^kuRI?06((Vvu9J?aBzvHK`w6Q^i~FCuB`Q)E5%pL8uuDwh^L!Y8 z@JnD=6Z=)(q)olqiG_H_^v$-B$RjQWXq zaqUrF3=%v4z}!kbi!Xw9xg>B)q1M)=$aF|#k7f&il5vhj~n4QxuR zaf<)2fNVRfBB2|kqy97G2~S;;q`gL!l0h(Eb*-zlyb#|JRvOrZR0tX%J?o#Fd2^lT$sxa4tB(?kvT(EI*BPvX&zs;Ln{jUS9h*WgPD(2p|yM(rDFp@ zFcc1pex;i6?G4iWVTH`rCOIj#yaz^db2NH!Kg!UV@N{cJ21#keEPEFoJqJnQkQ+OS z%5=;hYMO2D4}CcS0&!?CdQhD=x4_|+pGn*-775`@9g1P3t~{)dGLT_(rF&@C2#nX$ zL5T=?KQeG!k^dI7y?qizzyDBa^(bcgXUVjGJ!pJzQhx5+7K}RFZE>(ohh7lg&Xx7P zKA{)8%`~iwBAmU$UrODT{i6gZ&xgG@`TZ0Uh?>%JOKxc+<-2f09xaF@&bo9|Ctl+J zQS}ckX9jnI$;L0BB%>62BqLu1!*BdoZfcHA6aL) zsM1@~oVWY*`l(&z%~D52qeI5 zFrQC5$MxC`?${Ml3p!VPzbBB4GK?+!lk`d}6bonvCVT~|W}e3{n{Q*!$rPLzwnEkd zZJ-A{^MJi{G5&IbF9m2px4_{qlfZ1t7{2skyFd_52dPtEB=$U108Aj~m)Nrg?-msU z7bYf8xw^b4 z41W?%O@hh8ry7>(7f_~szHrQM0QN{OG%8GY%J=8sg6MB*EOu$UaGmqP4pSl2vDJAQ zx^sVfomWs!tXY-5!5a@82a(#-4VT~!61JO)kIf=s-HW5{&iMUMVLrRlfeR2aafOr7 zn(BD>|4!(`Pe0=I#euTB7T(tQ-$-e*^CUIk3Z&hbA^vsCkuKczU$H6I3AyT<=fvG^Mz`4F{4CPxJpjsgg*%e~^A z!4E=tfTX{Phzma$(Xj_@X~mNH_+yHsg}U!j(TaC*&H?PlX~3d%X4QxJU1)z|%4*ufUVQsi-PrmatOYz4J@XF4)iGLu{_m8m7AwT568Ow?O--$cF(dEA< zZn=H1i{9^tZ@G=AZb=c#gJ2)BNf*s_Guc3rSo{Jf77kuKrp{|9##MSA*XamX&8yUw z@>&AQh4xQM6Tt9HVB*6}L#SFjN1?WX*0_g_gM~f=RSN0v`K@YLfvmEq2c~ia;VWaa zMe!oiy7nk43=x?X)K?bv2a0|%YeZae#~Hp zLJ=mv2(*E+7LY26IkAgWD!7f;wpNNB=sNgSx2xX79Sb_X?DOhhUSlSgP)`(f@Ien? z<@ETfl4+(^!&nV!KL7(U@oGX&GbY4E7JCD|aXh(TKf8}49esyvC9>j6y^`5J6}csF zgNP0W8mO3koT3&NVrnD&3#x2Jb2kN>$or|HD)O~AriQ>hGp#9(8m`07*b9)$>Ktl+zZ^gh@3nMjzbc7#2sq{F?dQh(Tpa34(T)d0g^c? z^Irs8@SD$j_q+BB&Pj+JF#p&%C?efT4r+y&_U6&r)!tb@F`F-$h`N*3l-O2Pl@?0V zv!r~d2IE{pERvQ#1EESW7gvEEQeZXk0)o*pl_}@k$>@8=Ah@Ey|Cf@aK2qKLxnH<~ zY1>3X8qK{iom%=Nd>|dy$JUW$XnS;rWUhJ)S<&I;4pnHR4;BKt9&M!f(DETXu*L2uaas=W?AeHzHaE)&04e-RySj9c+q5 zv~%O|wrivS%rrYN8%Jp4g9#HYPL!X=CZN+1T*YKf^)7*ZsP2@C$2VH>u4u3!yLDVH zNrn2^HNmCE=sI@;olhPz4~od}F%zAnd^aTH#ey1)nNsCdI}JpG(IikCBgNaUD<~XU zr759cb2F6JwqZmV&aKhL68t^mX=iWwI#vW)`20^#!WU^y9~v?TxQGW4B9`287d8fg z&E8TFF23Q?c4zOQ-5BU$)OP>t!ppNgqUz)sbhj6?)OMHsS*`POG0F&;(%c$M(gE=b z8>#JDwk|K;pXGZY`jY4YTZD={8XF8P%FU560#z~IQoWx8ylb$$fHCPG8XMh8Vp{?S zpY81n*#=+nIe=IAHbD;sZ1$U#g&)hzly(iJtzK&WZ@ejmO`rliAc@1Z;O-1f8cs;e zV09=$!WlrX`?m$5u7Z6 zxRds@=2K#XdM0uB%nfxw$Bxp+se6ZXEswDYI9Id#KyipPZB_dRtvsqd*SBicG;*o+ z$EeDsX|5MA*p89_-A%j2NZNpukG%<4icf9r+-R!~V0r<0y-?&#bI)41VOLGKwlXW# z;1b0!q1^&cdzwyDJm~GodqP5?!5m25P)_=rV+_O4n-F=p!uL`*eg-+GNQdH&78ck( zh6aodb>RUvi2vB~J}8OgAa`W#9RL}^Nck55cPC5IHh@tvfmCkaD(@xOnqgf-WHM-q ztoj$hcA(>>88+0jyMa!JpLk-9%Wb#K)%cl4uf>;|i}Z99GZJ4=1%424hSq3*#n@ZjvsU?`6;4X0*T zQyMTSqznA7-jQf?x{lRTt>5%TOneqp1{^?b}+bG{z9WcAK^Y7IwM_s@Vz0 zX0?;Rm`InE3pzJO$Y-O8k%!q)xD)vLR%}Ao_BfW(+JMpOn1|v7KAIjBjy2hAw2j>& z`(z;3%JUD8uN{A#*~F45FzzRIW&#HiTbbPLW_EyA zejL*f!di&Kp}Tu*bmlRon8~i;m%D{e>0LEuta>-D}M$=L5~UNN#1cmwyz>NO+Y z{S(;k>-L$#3pLn~#mF$4iHZ8Kr-#?f1?|@B>m5TwRMw0s4VOW!z+HTnHIfWc?B0mM zIfyX4y+9?2lYigR)u9!kkV?)XOazp%KCsYAZ5)KG zws6K5b|qrUHTN>6e1hVlGh^Rua9k||Od~M17?nA)Nd!oQX?n*V)?mpeCV?V)zd?r< zu=N9}j9HC&D~TxNUE5d0!iyX4RYbvw@Mu=nxk^pu=OIwJ&^;B_I_6=dzTi+4lA_=aXyjeW$D@d%0`LjeS@9@+V~4$5ZjFx+P?&1+>L>>S4dl-rpBGgAC{%upqi=; zVx|QLZzfZ0gnyLE!0?*OJ#6HB_VaSTzgF*pVdtl3KI(luV}d3)A?qK_b$d}pl!vxM z6;x^CL&_DzpV(oS3w4~a=H>Z+MsaOMUt0dv$7{HTqcB&38EGj{tU`v~%~D4PH}_`@ z;JD;M9ZQ05gdfiMEqPyf3^+W|{@itqm zt3ZwgbVdGB^i63;Z&;(xVt`oN|IXNCFOWJ9h;Qa8jSOhx>LP&Ivvn5r>~QT;ui-Cb zQ|X1c`M!mQ5LrE8_UelUGI-TWI)OhY`moZ1gKz$QO4(&*0a>lCG=u&ToEdqgu6d5a z&p0@_iQITa@lkVTqOgWJ5e=?k(A`p4l^Daa+-F(aZpzk4wN-ONp1I0{s#8;@itv#@ z&YK*{C|oQUHtaJ|l4p13X{C$(e0K4K7~7UCj;O?h>{B0>ne7=#8S8uD3(51J57LwA z%S~FcK00PXb7bY4_p=~8E6C5ud^K5oc4J2DCFW7vYbBFWjaQvu#eNNZA6Jf(^pErt<7)_EK4^mj8#2?zt4AUhQ z>EE0e%_W>Rw}1)na{%ioUzcf%njjnU>N}*&$Xy(laMb?4u~-1WebOJj|DYa)0`bk0 z*=$Se;AT^D2rmrI9|8pA8@GtBxk-pD z$oo%=?JHN0ZLSC95HdXA{`@p&Zow{;-t^hx{9|FHfR?wbPQL*R@ z={Z&~Vt&^ZuM0*>h#=A7!Ee474<4TdpC{|c>LWhP`M*DV4DyCGI|uy3G*-mx)&KL)P`li|KSY5Y*Yx;`ybO>q5osGKfbt6U1sk$3ak+=N!=K zu)5Usv4i%!`EsUvy9?q8i2=;}ffr@2m=N!w(Hq>qfh0asL5~79*dJ%YXbBI0&h-zv zd94+iDom%%;V4Mf?A_-;qtm);+YwLlGXa1<6a^b((1Vx>-jF#%OzQhf_z2-Q zl5n^Gi*NT0Y5X<~B>toaCY?5DIQ-f0l3d=&IfQiEbZ>=G;9orW`?1vn!!%QDRc|l` zVI((&FB=0(pX3>8WU|S}7%-p4eOldRXOx7f3haS|{@8M|WInvC@{IaV`{lh;7ssVZ*HFd@ z2GWV^Y)AHaN1h+}U}Ga(=ufnlvS*amP^t=s^xkzKz74V)NEbMb+Dbb@CF97%xvp}f z1QN`jp(q#)PxtE>ePpk%C9JZ$YWN=Z_)IJUb?-~R*dO<%QX9I;=O6Pet_}$&m=JLI zyNlY&=S}hl#Xs6Pv#o{rVAM+2n`_b#vftk5n(Bk0uBGKPyY2h#Dj(xu0wV}0vUd|7 zJ0X2h%QJE~bD>hUT-4^1#%HeubW@D5>i-t|R{}QLxa9ZCIdKXoq<7Wzem{cHT2M!| z{m{o~g7N9IrCeCmX(2kP4p$9yYKzQ;Q{`d8RW!wn&8!?}AnO)_gV`EA1bgXtQJbXJ z;B#pZ{mg&B66|sOynJ5V6nOesG5YL();Jt{5PJi1g7M8kmIhR1-sH(u^|} zC&|uEd0z7mE?B+GE~-#-$!jxT9eNQruGT8Uz(l2$9qVTZs-zmd)$>$g+7-xw-A&w7 zdZ0anFE@p=zI+M z`A)S3t$H&B&nB>1MzB<9iljBVyo3K2);8O=9$B1^S$8<2B0m8wMhV(**|3@wrLKHt zr6P2Zze?zS182$HBiO85AEq(ZUh0Aqfttjv{8PL6m>@1kl|cST4+vmF?zG_;V~6zd zIeLg?#>LYgfY9M$qV>JnmQzMjj~?_FKtBY#8CvhIWH{h33>=5toO^DAx!sz*Sh1p= z`~E5Kht2T|1}yIkuy|9s21^E>aT<2V=A+yWl44U*sArh6PEHNS-Ekke^pOnPV7x-; zy_0D!0e0PcNV`I3P#sWHh{ycf^pdKgB&+qc}& zI*}NjY#ss~*D4^yxhx@&ynY-bN)-#(c%ew>IFGV3ezNY0+Z1)kSbmH`=1QX}dlN4; zGKMT(Hs_HH%M#{|GJCZMW8SiLdl$JVrZ17YaK?Qw<2&lZ=pyIIQmgY}Leb*JDX z0We6mKhCfHzUY?V8`7j*#Os54*Ufxht;C=D#{S>T)Z=p>`L)fT+bm=V;lMvX_zXu# z8Vhd)5h^#KOc*Sdq1K%aAf=2B{b3s?hFaeTYqS=r8!+8pwRWsFqG_E;oK6evegLSl zh_Yj+;d=aev-F}c2->x_oNe*kyOvsh8dBZ{uV``y#0?$lLZA3C-XetH^O%I zm&4DOYR}J4E#K$wDD9`i0GV&Paf4X=&a8Xz<6j0YoWfYLFt#_qss{UP3YiSb9)VHA z;OAV2+cTkt+QH5YZ%CRK6pdF$ypRj#dm|YBPT51T&s-J%;<>jl7N16IlgMjz0{3Ub zx&EldVM2Rnaz?_Ko1pIPw{QZUYZTDG&3wbva(zxB7m7p~n0wV(9xDev-c(_oky4(2 zJ5cRxROkAy5I@zUIFjbeeE8(J&60ouN>^4$N@p#p^AEas?Oz^HHfvWkr_H)Fiu>}N z>QHu(jJ!gD>&{F{gf$@cH7jnMW%uK62!70RCTsc;5%!0Ab8hgxK6X-Ty^5#e`!-=| zIty%F+fTi*qz!If_Pz$&1C3_hq8ZLZ8IU=|KYVN&SxnsuZ@_#0l%&j6i1O(+njlWWUY;c(@9F z2sUSOJ(`r|*!zQe=ELsZf51YZ%2{r0sY}gkfZ%P%2+lZ=2s5)c7L6l#xOA}xmNwoV zc{+xm+EjaGDxRn;E4!OJsd2dwkzXcYf;xreelg#{fLHCpNmVG3_MSdo!VrmdMclSB z%XLVJ2j=$Y`B!NMBXy4nEVG?SSz-ellvrVY3s;I@9{WDI(n^YD}zh$%*NP6H=*EBJ{HJ0tG zQqtOd1hpP$(D^?zlNKhoQ`q@xT@^mZ`yzMVIy8+aq*pp9bs*rW9bIS-fEOIN`}h0m zUQ>>f2_n7EavE3tpMIfa+M*gUOHF(pcepMZ!NNEpw=i(nO81-Bgi2E({XqI~j z5M>S(I7E*+m_k{9e*m$_#1RAVVO9L0#mLy6V(Xfm+7OU_{^)qea@2U7IW$sur<-Uy zRim3)9Y5CShY-)G_|T0Rb2+;btGS<~WxXRrC$lWzyD4at>MjV447&dKNIu1mpYO1r za_5J|2e<^v<>W4?ld=YzX)Cys!2G6wD zRU~L#$qju~jP^U?7Eh)Y7*`19pE&2nf^ z!3@S37PH!*#d^Z`FqOP3%u$UVH9Rg}nv3J>v?CZtxr3JNUw@{|$^n7`V=ka;3Hil) zTF#snC-ToDC-IQZiIkVI535Aket|M8)v=)aVV)HYm47!xJus}54Ywco|AVjlWdD_x zg+bo#o}fB-(e_W9p3afnsK&Sl!VH28xtx^En{Lu|6Gw^40-qik@>f2Tb=Kv;Tz5el zdEO-ZY3=cH)FG2IFLjPjX!##9sAd2GCiy91!oZ1_$yn9<*{tj&{3P*h!jGU$dp~kw z58coF<8ExoCmFjI%b!@leTa|Z&Wc(kcS{xWW_{k4bbH3D#x zf5-&`kHl}F%zg3A(A0sI9Z5-nnt-foRjjX_)XW$JYfMA;W<)VTmUE<0nHPCFov~1(TLw1 z-5f!w3SI%koqdoVUNykNmpl=BI~?j#oADRXb{eCJy(8XV$|}9|j475=m$T5C_SRak znb{&was~{;Q_*|{R;-0)J@nXo+S%P<#TQR8xCl^x=lNRwWgtHR5q{hL(WWz@8CH*5 zKSe@s(xpZhNi#==vI|#TGIR8FaXNZyU%`;@R`!j-F;D3+MQ|6%SiM(}2w-)AU-O15 z&;7eB0sVRlymkR_E!iG{Fj-&=$MRCRc<77AL@mN$jItG-_apmtrKKUKKDLE&K!X;bvZXQ|ED!>goHTcYY9h%0${Q6@@95tn zgH;qa;aUSYwNcEmBp2vBnF#G)y(sYC+dvYKIWI^uqNV#yk68#KE$n89A=T=Hs3_ml5?(9 z;$IVlvTl(A=VLPYiTnp;eWo6`gvT=XKfUfMNm&1s#xh!Sw&88Y)CQWV4jCmG1%qeQ z23H1iJ@{kvE{{3~G`IBEt3!W?M<})6g6L!(0HizoOi{tqa_-BpTNy@hsc+q%2ytU& z>bR&~KmHFzls>6EMnQrTDNBVm@jdjYl-7r4s_LvAOiJum8Vp2vY$jcW7zyLqa3IM8> zwZKRW65>QRU{lO!)np$#q5F2h2>?=ZJk=e4Sc=92P3Y!wyZucb@Q)0!Wk_&qbbCb`HB^Pn^E_&ZwCK&QQ!ob} z5q96N0~qlSFC@Qwb4|0Reeot?Q_8*QN~P{fO&)&nYkb-wf0|M{H9)0od>sehhq~7) zkLXU(oU9Jp7n7%H`iHs)YgT140C>AKcO&fXs)+ogDG!--Z%dEe`7$(o*w+*Ng zIq8_^BnR6L(b35Y@WADA)Z6qdq8Nsax$3E2Ff?m;0=}@hqz^h#cYx6m#FV?Ol@?n@ zEGYAp)+jvKaKQg-u%!hJz$hGC6c0IFv`gh+&daY3&1F@aJ+nNb7d5oWh+)y{#& z#?T7H1NEk4PpHC!gJ6PZaVj=dmjCvW%Hpm;O9wtM40x8!d-uOw8D4Vv%@uLtZp66W z`nSKeCqISZaecvE3~rfAG<8eJ=#7S58yQUxmWr_&bB3C!_&+`>ejadJ6I=jsEwEO>yY(&GwvunDH#IG zZ97X_QkKWmPiYrh|09{nA0&CJJq)*DtZ^B&#cTgle;f~A%$HBth%tsC$sQQ!RG5XCUhr|q4dOrtkOPk-v<)*fY5mVM0 z*Q`W)w=|xDiWICqMJj1=)3~sLHqOVCVpV|u1kS%eny$uz z*g)J(=>?!7u&OvEx?lk4=_Ym)btR7@if4FU#SrVv&Qu^>>D@9r-^5Bs77``B#G+1=XFlxZ!F_KdmwNK^}k{mtUFX{ zmqr<+jg__7QgLL_wO|2i{Qq6|10b4}7j#7d3V&r^x|bidrAXs{g~u>P1oY73Cvhi` zw%pPAt1paXHVz(ZP?Oa(c~RcF7wrjq>^G7W%&09W@UCTFh}L@95dg^HNdyr$PiMjd zbfmZNg5IjH=OM`-NBxwWe>nL;^sY4?zpy_0PQLmcUCZ0(Mu;`#jfY@&>^vqw#%3 zTLa{M`7MMwDlJrx8a#qNg{rZBhcz=koj-Vk3!&uxy3B;q&R{M0+7!tT(F-|2j6a;* z7}{AFiey(~CJCUP;Mp)o?+t}gwtl)>p0@Ps_;4#t+#c5%d&$6dwpGkRYD3xfSeZUB z=}LjSFMaR%2_OyeK2R^Hju&5yfiOCM71|;rSG?`LK<$PLeNDjtSs0#CNs*?$S65)O zQIZV2_sjVph%8ywv;=FGA=J>2M`aVcZ@b!uCdV>)flIZ9mOYy`bGwsfAB6c-WdK8s z^N>_b)Yr((HmL<7kHjeHfgnX)#lv?6vnhWRrqD!A88v>6$$M1rlEdsB*E}D_eNzrS zN2r7)_7K>CU`gDoTD(?N1FGU4!jS7&S++QbX!a3krr%c$>)0Tdu>x4Ft4d>9Q>?8o zp0q2a<%~>XSQvy#bodtT_>sf5XK3IF};de9&E^TQ_JSd)h8vv}Lwcpl{5ch`N2 zBP8+Q0ldSZ${9A#_ik1r18Cq6t492arkPz){A@Kai7hdvsmkTI`@V6wCXqZ49|R*i z3x+pldn1r{e=wCp|948Fg37m8E(UP3Q)+fV$S9EcJcIA21MFP|L3*iG$*{8HN_$Ht zT>E{45e_I>pI4>`?GNu{#+Y5Fp&5(w9{hW=I)RSIOQpY2-QMxTCloaBkuP)In?FjK zMzKB+NOg52nWQ{4a@R|T7eitfHd;>JuJb+s1cDDQS^-o z3rec#UI0+OpV9Kv9UHx@`yHUrXg{Tq!1^@SoyeQtZEvp%wi>yc<~M>l^a5JhGl(e% z+;`B@8^ee?)zz`$&amL8g3)=oDS)@wK&elTg9#6UCrzTlSJ4bVjEm#ib2A)`P&UM*d8rxFnecId^gchYvbNVepRx|i1^kX{@j*@y>U9+% zD&kB2hhfx7kg3cLF&ZBO2aU+b{gb_vBk<1M|tS(3TZ& zVFjk-2+M(kwQBs#Wnp)j#`!3(B=iq8R*kc!pnez6&7UK z>#3A95!?RTbFcQ$=pa-O^c=3#kF+J(GNpIredbcc5n1e4ts>^H${vl~uU{SYb!41o z<^j=>ss~y)J_B9ZjX9qc>)D;ifyWUxoZS_ue;+-Se}NVN~}id<{J?e_}iyNF;oL;y@RM|3URu z@Mf;O;o}^Q|<3)^!#M zn-Jld1-HQ%%8~GMug;PZ{_Spzye2?xR7*<=-~?9+DACBt<_2tnu?uUGr9|41Y$p)bVuN~*9mEY$TfUT-d8+1L$) z%`&%Da|56=!Fiq~d@E2GDDxM?Uzp1mO5{%%-qpOD;0@V27QHWz>GA?9Ak5a2*_K{C#$O{1*-;#3`PeBsm z^nwCs+cmENH%NL3q@;s?I9HG>X=s(?Gb}gEHj8B)u=A5#Hi>!4VEkF)WIFR~tKXL# zm*WRF1=VCvW27@V1JY$mI8HF}2ae}X=RkI`(^Tdzlo*PY8J;U1SmLFWkPGL^r2@hH zwos@4+~N@B6cA@oeQ643v+DKg_@z)vfL*1%ub7>kY&nIAPSLLMJyFs<>+c1ho3- zt(D_IP4x>P_K4A2>R(Y7p((g}*b#vxH92zmM3S}Fpn+wDz)(fA?nsZH$d`%}1>Pdp zu$^GejkDnHKA%2eYuq))N9#~k#_P8E9idAck~p3NvNi4>7$B#!caF_W-76Pj#-*wG zJ@hH)+B*!6ymJRGq-_o?u@d2TOX(5*3STgBKHOzgd)R-RxkOmfNd)bLNLJu+Tb0` z8qhN87x3@N+=4{g6mzc#AhYypb3it0=f(eO@wHhwDVgB;(nXyH}K^N1T8WKjA9 z9n%#pEJ8(9Z4U_);v!mNhHmR!hy+>z=UE!T z=VcBq?p=IlT|x-{JHo@s@6*K3G33)R35!P^^hJUs36Q$jZZm!5Li-&3@w$|yJL6NYUH?c zd)uro;&hDu<4v-Ii6m(QV`kS0^-14%0bF4*@w|uAO3+X)0yWJuyQsuEPjFCf-LZ}B z0Y|}+SmQ}!vO;i1W?Ly;y#kV|*j{r^c(FLi?%8LQH=bToXcjtr&fK;Z&G&ZcAOrU~e9kt`UTz35Q)K7+o5Pclqf#sf z+4`<88*TM;&dpRMw%Qhzv10BW$$>`rdNT;e3K6g6(77gdeK~9WTvbUBKpEM87$z{& zMG(eMF{7Z&9(JcDv+c+U=bB!T_9+Yf0-L&Ji_Uz;^;<5S>o-VDi23bXH7|39Mo;QwTh z6!>BIxvvd>_P^KYPYwfaDfREZPsTfMPf(i#6CcRq3G_y!;>33aU*5t!XXQQ)e6tar zR4Tbmrpwc~`yLrUVL%jZ|TrTw3 zZ75vbMIZ9&2Q8%}7-aK|pbq)H)d#P!QdHYtpM{zpsYdw`gUZ%izodQ|*V4hpx0;=f z_F{w4Df?s@-I_y9BQsxVw0PPa0#GI+mfG-OneIZ#qFoFUXCwc$M2(%xm5sjHwZrSK z*RdQ+l+ZICe|os(qCoFK zW+My@6H-HCd&-+F2;b?sr@qo_p!krt$meL86VXpEt8{t#jIX4!HNOYA`I6)D0vX>V z41%F9@-z?AQ8jnxhLdANE)G}k|S+RIt3KwhQtMO@G60-$gfn)o9={=R8MY5%QTdyQ) zkq6veLx6jHHNLmi(6+lAl=Kv1UK|+L1$ZzHr9jl0$(Tp)J=H2!x9OZ2qGa&mMI6m-+lDr4gJ@J+1f+AIw`g=ik=J zkJgFI*2P+`GfSYMSVUm%wGjaygv;4 zVQ=Wy%tGZXGyCXE$aAh~pmjE+XVpIHQs@w3pB%c~LELtltHX2k?F5AH4V85^8tOt( zNq1_={`dNZa{$6~Oz=&xOAkbF?tgdvIq?5)Yolb0adP_$!aR~ms4Gr`6dt0N;mu8K zsRgsHiPCqLbhY^4t_nqAaglb%*BQX(D~Lj9@uw>~=|jKsbpDW-LL)$W)TTtyaY;}>)B-?npFm)W}& z_5U=s+amk&59hq-`E=%?3kD0y%Rf?a*gWFNZS8sX3$EMi-i|k#T#$?%_7KYQq>+$> z(*N9(-?Pfj5ADH$hC3KxngqWRVXLN+4I|XtEUT)eFd+BN191R~o+4>?!_tjOSGFb3 zDQqP5B2F}9NS)tfeP=jPxzx#P604zHrIrHpzB zOlqWh3@cgcA(8ty7W4ZBNjJZA>vLRF%n#jf@PqYf_shOYo*Z?0KaKR2GO+A_&X*_|v$Qz9WMEXANr5-k#nzSFDXGoacYK1q zeWc2uwSw@u>c-L%?FtF(>D3z!^>x=WsE9gh8vN{7j>F8jciE~KMD79Xf7p-8(gxwU zZ{e`39CHN+kZv{4Ec0c|<05b4p1gfh{FG?|6}lQ;Lc+O_M6rcqL~2;Jya4bKFfQM{ z3OI-;dw)Chat<-e`~lLabXQ>%q!%7zb|ht|PBid=xzwmlJ9U05m)8~o+wmlAq<+%B zz|2`@9_Urc$IEWEMRp~)93c1iywO3mSa|L6BVI;IHM;*U;rB%z?pY!DbG7o;J}v{4 zFNSpUIyha|lzL_>Px|sX0||RE`7#vXE%?Pl2jF26Im3LJ+2^ec1gT?EZZ%u}E$ASE zY>SV?XaAW^cXSSl0MIN$z~*~8SStYs@x`MJ^g~!$Y7XQLpj$AAiS(!X;e1Vs;P$3lPA>1W@F~~d?3gUi z?!GfSRS`gq&z;6JJI4k;e$$Dg5l2YaP%zhbg*o2 zLaexS%fK8EdYYAl+x;Jg_|*yipCL>Ka1VRE-;-YzynxUVwFmH7fYUf%!7QxEXk0`8 zgh4%X{xj%ZX2Ic5(Y^Vbv{GjL56EDvKLBDTK&MXtys|hVwa=cw(AWd+B*43xIq|jc z1@?Yn!bWrmd>8ugG3HwpR`NjiZBZq2(oNDKj@PuS&fFJ${+kUrFyU%Lnz?*y9ZtV_ z^ut%0Fh^9B)t^Nu^NjQPMkloCWjB*00|#t5eOkT0XjZ%F?GS65fz zi2_+{@}*6_GgHx@d3{_}rD9E1b*Y61|i@w3p@GjIigk-0|evfwh)XB&t5B3N)vX}TWN-Gawbyo?t*)WF8 zPUICPBK-b*FaU_JtDi%2#FcY8xV)**_ahn;U*VrJ@9T|$kugJM&POONF9#7z<6R$S zW1x$PIjEs(OkoDB9Y$02!{$*^8Did&r&u$+qTbj)RFU*xs48}p*y0o0!9K5tjG}d` z=U`4j=$fb4OzYuJ&89T0vZ)9B1@qDrOYXdpO#u#~=?>*SrjaoRYkebfJRnyvOpmc=bHk&j#t)pOCmY6Vr;49XlsW!=*&PbCE^GePBstywHNhNco ziEg-Arh~zzXd%c6K&yY4loS#%{X?ru7kwv<0G2RF_c)xeOyi=dw_dhD0c{;NT`H7; z#p0yZcID{XO9I6b#M=8NXVk~S zfrN@680yH~Y$7Q+X%3tG73yZnQ{hxj+i!{7XkAZJ`U)KldfoJqAtqn%7QGJ1jOH0 zwAODe?M=0K$*vaMNFF|GJ5vr*XjY2W!by75L>4js%?~eb?KPo+$Nn0XtNQrs#>5MP z7?ysK_xBetXi*P80^vG=>sgDrV1#CTZF$<6_Urc$KgTy^z85B_2``(t0jI8zKvXLr z{@dl!sW7wjL1!zmFkO3U6wO`F;QE4jfY;L)ugpmB*Tozww$8VbDI6p=F^vxd(s~D5 zXbmqf+UzluZDLSVGpg^#X4UyK7!~#&NFq}ere)Fz2uHwi7vijEiUBdhP)j85)pcma+VqhmOqW*IRJ3XlMu~)umFvQnX)*3T}eBc>PQ1q zKxK7C1A(LNoO8RBrBW=-TH5dKv9d-VxRtuU_mjLUr*{90man16MQzAuDK5!qKJ3H` z6-gGtcARrj*X8FAU@Jw~%G}{x6Qn0}Sa^=Zl}=Tmel)5B5h5m`VFi1KeDaC6LL=tA z9K!NQh2e`r#_7``##tOFv<_T+~x39D505 zCHSHi<%>+i7C2S6pwhckZ+h zs^2<63=1BZuyjd1TN)S#T3lT>sV(R@s$#wO5<~Klt)g8zUh^`aEM$m(rjE6NhOn}* z*r-->{viw<6Kq?8LVb}YJsckK{nf~zne?YA&HO1bd#8D;^Uk?k6i8!CU$b5l1>fC- z4E>LS2|x#g79-3Q30mZNgQa4J2b14@QO#5>{~Cdmu(9Hh{~DK>8|3u~+(LnN*D;Z0 zB~53MmL2Ixpapl3z8DY{J?LX~U@iF8#{IX3&NDVH|5s>K>562h{--1J89zuhoW*2F zPEWDat)MSnBCML$@JFgtWGpmExsOFzL1tz3YFo$8Z!Qs+J^qA9OQN|TY?_$xDL%5{ z6hZpw6HQdEBWOb^L6;lSLQJZxxs9AmW5i=^)DS$XLt_XoprhNx&oUP4NGTK^jUwFN zQ+)gheY0hpemLM3Obg?YQch;wu)nW7Ox@yaod-TFMGj4MAE+>BYdQn=gL;a~rX>$# zeT$4H(;0U|kFg?S5kwQ{)@R0qIF>E2b7MV6G9I6(Q58Ahnk~8ni>j$b!ls%8P7e^B z^F}&?=#%)pm^$M0?1TrNNo42d{|aWvVFD?Bsy9R06Zrz^wnlW{S2RAXc1lGeGjvn`4L#6-+-QPl0d#B z*1rU#D2y10hah!AUq)&%OWSRZ_A4TVcJ^Ve_5?1oFn8d7yDo@H%PlK})eb#LXltJK zWGs8RzrEB}uYPy6aw$!GyohAJdq_;~V-pH&@tTEDaO@p6KZJ#)Y5RQkYQw^}WHuj$jm;x&>$?YDZs^;cNTc^}OqQW)TXpT=si z2c?HRAsc6?>)qNz!x&K^AWuUEZWV{kT%4Mc=qN?0vBY49+3u1SDff{W0>Pv=DnR%9 z`s-KzS^7heK>HYMDS1)@1Tt*vz;4J>&p#EY0j*+Lz_}W&suhu#b<9hsI=xL%l%eL@(!5N&hh3wx^#OylijUTI+og)`Puluw)}mMLs2~&3-_4$^;nHV2$X$Z(BQXPm-X|f*GWf+ zjSCWbNBDjzClOq4$qYRTLAqi3>iM7U{@~eLHBsQzMOqQc+hmrrd{tkwmwy~YL?y?B z;qOXVI{J1VETuf&zIw@%trH%m(fskrr}@pGkZga=BNYa;#a6$oWf(#WavJEj2zOs> zov;!gLM{Z$o-w4yR~dwjIVHe5;j|CR@NkBCf-ST8P)mHP+8t@*eBcTD-{V*R!F_BN z%apUTM{%TESF`@7ZhEhC>Y%o(bB#rTWfy~Hg9vM!$aa+~+EPy=;u)a<==*clFSNT8 z9D-bfd=X+y>AIFV?1_Yq3@+?iLAuM||D09OuzkaI=nevhKhvxSESB4O(|1WJGl0P2 z+uiwV2Xw``VHip*5i)-nna(! z&};^|(UV6YUk2SriIA~U{mfAjv{(3|8UxYc34ZN|9(9uHAJ)fw?;SEXT3DQ;{v9Oc zTM*e}k_LIQ$eP>H+LQ>NQxOx#W)k^bSm^p#Y{>1(wcR>9$B@GiaIeN^UPP$4%xFAd z@fA3xO*=yWtprCw6#wa_h{5s(*30XEoQ~897izB|ObUoC`khph?@famG@g$WU*ptx zt|sXbAN`1a;axC*kWv;wUEO$)TYGZ*i?K0Z@zV~`Sn)W(|E0U0A1M$t9%9Z+&P_&&-dh>&N@>uvUZ_l0KSsdQ$tFlpQVA1 zj%YB3;K!jp4#?Jc0V35*4aKUiYK+8pF7K;1K=yd9#&k9%Uz-8*Aq@f7%v(sxGQ;2( zxX&$Nb9cc}wE-1#)(gmU$F_X*Sq_ps?!yIm_9D{*9lwV%Hs{=y@Hv9pRBgJ3l!~^% z1CJ2X8Af~FH*1dJ=U*lS$l3R@LMVjX9fR^Ay-?e$PWQ;aYczGNO#v*R1H-FpB@~Fe zq|@ly@r$UHmF?gr|FWycb{EWM4E71;>Th>4-H3~-dEpoJg-uDqTM?POhIm63mdh%a z0nM-ELAev7=OTvkQjUtBqtkionhVQ7!eA$oMuD`qCpkEt65@OeG$T#l^uK@BKISJL z<2E;1Du@~^O!QGqzksSpYH{ao^ve)d>md7z0Az@3O_6%7)Vq%6Is`-$-!9c>3%m~s zyU})rOB&zrxavt8@P|Vbc$PnJzmft9P}m_#!t<;i@4p3cPo2EHov@c9qVkR!*a)?y z+*mL5!Ok!{iA)#b$hM>S85ypAFf=EYuf9e8x25K1emuEu=J!(4im40zI*T4?Y zXZh^meK-il%_FIiQ<>t3&cnoqXj&SJihfc#T2;G}M<(MkCgL*PmP)E=nl5ZZ1#Hem z?7R zppiNl@^m@Uapgusjuy&dN%L)=9`88Ic=lGn(H+bLOHE)J1&@2J4ic@dNWEN@j9bEQ zmIAP12&=c8F0qgiYh67NMC@|=?g^rGu2$6u%XCv6Cs=5hWVi1}AUB9_n5_Rr?3t8o z+)eqrJlgxVMBvS(jlD6S{Sdhp{ti;ZkA`;D`<%=>a(TT0di((%H;Md_(n|9fR7ZBc z`qZJtimEOa*HC7|6=sZ0q|OBz)GKgIS0CroNAW7%5GdjjiCN+J1SxWQlUQ#h`e`?Y z-sDw2q_(LD#Whr}G2HMMnX&G>yJlVMpd5U4i3a9#^D$2luVJ)^;EjVQ#ITra#o*K=ufv=U&(E+-c^p;@OtcN;FuuZjdA4mmBM{0r!etQy@O|yH zw=Wg6S}@4hDuPzVncAHj^GCKO(82<>S;^}E7Fc%sZJxmOO3|f?lQDP{fPd9-yWs5# zKiTarYmGQTP6qrhB4+>PXA;}YPBbuEE$DnG*`Ss4u1u3=5QE2ETqR9CveDxykFyqm zGKa_k?uh#QUcw9wI@Hg~KF`|-4JLrAoB<-yE&h_Jo2P#0yJR6p%lij&hxdHDo?JK$ zF!+l%+wHV8`z7OQVm)4^(@3}7*4xd{8tI@ULbGv*m@ixSRG(|m|z6CBofseK? z)a;#Mi)J$7sZppZ8p*KKs1>~>F9W>6=yW}tBB)ZoZP9|6O4yUCWNkm_0^6mSi!@FF z8eb~Y<^D0<^zDJbr~A}oq=o;bUc&A3A^p&^yH6wW^^wMCO(Z3TcvriF*s1!w5O?`BwSFnB=#)AG-Y->l z=l6dlWVfY3_5Iauy*+1r2|JnGqt$Ju@9D84#=KhX^-RRqH_z;AykpA6^G3#$MElI3 z6+B;pQbx}tY0n>9piScr-f)FkoTWs`jy9Ny6eeAfS4Il8>%DsT{d%fbF0{_Q#yDw?b- za6)6^naX<})^sb+lM6vTlKcKgvd}qDJkNU&Wy5GWr7lW05%N!`7dpD6IXXWTHPL_x zGG3PFeHg@CVuRafAih-Iwsgc;|9=7p0z5C-_OnCsACXY$cA-kapFWSE5A(;dTnv^v zs8wRZU~cA}C0R;kvuMMIjuzv$&f21nBz}OL+^TX;p9iVXE?7AXzFiOR@aSK~MTIr+ zxz)aZbkmr@snruO#e;q{f_j`sHKA&l!$Q7`^K(8Tc{9xn6Lc>vRcA)9f7&ao68;*I z^(Kf!x_h{{(M^SlwB3dsznaGh_^htI0KaaO&l8=w8$oGgjR<{vP5*1mR(byYbu%MB zG-kFccM!g)Wy;UrDu|O>Ievjo2tR~7H&B<0U#UsFBZNS6Y=92DKYHulV^Q+ies-d_ zT>|mQ7!<3Gc+XNPe)O9EAl|jHN+suT+&j0XERP^LsbdU8BJkRd1h~nA4Nrs`!lWc#eRQ-=Ap@3V?{0PJF2P7=+AaO@UrO)5geQw{%QHa zs2mS&B~YLV=6bN#H1v`_C9S4?z^5Fn%GT9sqOQ#|1cDd1<%dQ8U@3fh_T+>vt7*R{ zA=Jl8+XmQirHOr1!qdS+Xzeyu2PR-Y{Q6bv@sD&>l^yGr$Xy*ck~eicr#hlQ%V6>o z78?E`{6i=W1)(J5w4Xe7^=tt_wwqn8jYO}l)>2SmgLlFj_ifBH6X<(LhQb*3{?P@~ zT1@j!C1RjDpl&#j-pFlReaEQA3G&l&!!12UxPaiVSr1Y6vysd(>s-t|we6=ALK~}~ z+!8CleJUmgwq7cHxXR6O80Pt3b5+udil!0M^II1KY`84V4iyV9 z!{=dzhYYZLlEnETSoAL7&wBi8eFlSn`9}upk%Hcn1A{=&HBlUXV0?3+k>wH#l$(e$ z?7Icz@GS8Z(9SSoY&_f02!Iu7+kftoA@7&*gWmgad}M_&eAt2#B6w;_nLi|gyQHbS zB(@L_@!wF@RKTWlahh?`hFd2PWcR``gAXum-(v{xP=q4KQLQB%QD^9Rn>>BKQT&sq zX^3!nSa3M}K1Ul@9p_v)4)kxIlB^Xu@*(!W3h}Ha7!QA~jq@@7#UX|K%4eAWc*Gz- zTDJG$2fldjH{X4!6RFja6h*fcrKoTLp7Npre2o=RDTLo%p!1dLjRvt5dm!Q@*L>o`;_4h901swG@Ce+*>zvF7ezU zr~jM94xzhjQN%Qs(NUPHVBj^HL;ZmEUk$dnNLFh;Hk3Pe)ZmcwnBRl?%rMrWgT}uc zSuiAJ%J^-K3~CNqXY%GkAoK3hB_K{gV=aQs4-^c(#wagr9#B@cqMS&|8=2(omS;ys zA8j-%R(&3eYf1>!gTnxXa(xxE^(rJ`EYCeVb^H7wrO{HFuXe3uCb>d=2sYOAH>KY7 zJC(R?2bjJt`kO1 z2f8#50BYBBs=9?IFRA&JezJ&`G8bvaoj3bbIR~i_2C5{-_6{3E#3c_p{8Kb_07SA2 zV;}%*`eCo=YK0vq9VF?a^-2U@nG+`QJ-A{7ooXiabKkJ^Rtvo_&L*>8L}6g5SvPjD(V{Pr2<_0_6n5}P>pB#@A=H#VD_`k{s+zUTbpK&a1HOz_r)D3iiMZgF zvliB$@7AJmwVj7Yg`QADd=Op({jkzr$gkA~KP5h4cIE-gIt$Onl^J_1nj(Bt2YM1? z6Dnq`F@Q(45S{2Fd9d80Uo4mT5oAt`kzR@*?Ut*`ie@EUuya-R+Dxoq5|{T-1L$F& z>AcL8%YylcIBpdC=58qA%+CS(U8%;rVBtwbaDJsO&9>V{${mkFBpf$&P^x=pk59ks z4P&sYe4uL^p^_wuufw{cx%wy@sPurJ=RvstI1X4-EU;?*$%9X{Vqnh7JDUc2RB+T| zJ%q{rLjzH*niRc~Ax_~_(ET#gsscvae?3-VHwCrFb#V`-CWi1%`!Y3GJz?eFNejw2 z)sLts8L}M#S$*ycyzZXyW3H5;@6V8ozXV@(nE2f`Uh1Y!~VQ zu3V<^Fk{6)miqGTf6Lo32$?eAN8tx01>%;d?iTR>V}tqMUlw$@Bk`LBtDgIUzjeB_ z_@hIZ+gXv}fema>XN7he2ejt%&XLhGSn{PlSWAQ2T56-aRARKX zI7AuzKY5XmS}82sMEtQp%P{A;rmf>2-MDRh`TW$isb*XV^}v8Xv&f^nS0Rgr6kmHB zB!DT5rwXEj6=Q*E)=0o7o+232Mp;Zy+5ya&`NzsLg2fJSfZ!rJ;(M-FB7zSJZ%EJk zw$g(VL)?p+c^_+4J3VxACf;G^goEf-2gq@JQ{z#5n8KjC-@v$bVV0!X!fr1tX_E(p z>WcY!dN;vKm{{3&tn*)cNG0>o(9WyRDC#T=@B?NX7^4Z~=CJy8X4t!`Fy)VBmi%PP zJ-%(|-J`|(vfJb^DzxsQ#mR?NJ2Qy|FjHK%;Dc>@QOO;Pb*{n7%r`kK%Y*whMicGt z!pNtOCxl3n_klyycYX6*#B^=+xuLQJ`hLCn z>)YYtwSw1gZ3)?Rh> zrWQP>W=*Njm1iOrU&Z2z8zFU&$@=P_^&JM1!86yU=X{$?+$BK>BzI^yHxvOMuTic# zHPh+do4zG39hjM{p_lh7KVu$131U z>J#nYFv30UwpyYS5tHk6FVi{Q+@-W!!b%gYk8Dt?I~o-x%vp!cp#4_ryby3JO|TKP z?+INFt(xo)Fs+xqNo(V`>haLzE*zz|vIs7K%P+irN-6#xc-@-f@?xRsOo4S=`5!Pp z`H#A2evqVLhD`%QavbA6TGJ-VFz;GqYlI4BdpYZ;3Df|&M|pEQ;WU}~J*^RsV(f-{ z@4r2upd&pHX3Ez&>l8uj03XimmQ%#n+<=l=@>GeEu7H_<|KD;udxpotO$82sf@N3+ zqydiR*yerbh)EcLFjthNzV)iz)>HWvY^JL>GR8tX(#mRFV5sN<9+-w=q%H~Mh6nD$ z@fvBvC-Gwnio{+6a*29VmiSal#M29Bp;s>7HdNQS#WbdyV-t_$nw3WH6OdG`fEK_NX8D5#4F5Cj<0AI*U-QibFOufYx@0;)S82wE*2plng-f&JM!o0)(+Lj&ke>YH*l zp{fi}Y)!9QfL!TohCBK#W5%W9OL4dno^*I&D58F)(0t`{B25SvyrOn3)?{yz5;T3c z&bsp???XS%b19xuwlhBxT@To;_p&-BT(Q5)NxoFAtw4Pe_lV6Aq@aO^Hyxz^&Z8yZ zA;a+PuK$QMhQ!EgB2GA&1{Wm+>5I1m{g3Ntz6X0w-J!^+67JDO&Y$eHY2;lO4x;mhd-?sil-oi~n7Mu{*W!>v-8lOhZRh34}y7^8V{(P_vMDq#Eo6)jHwIyNUWSKq5-j+b$9I(uEN5m5N zY4MSbY`yAy$bKCQ`RGo6q?I;jdQBUaBD!2?Eq=)3lgLlT*J&1*^w32Nvo>xf8jk(@Fe!-d zp5w-J%)qZ_y;_jN=)2`0u%;U*dtT=HQGJo1GI9Qn+6b(4M5%rGDY}v_imC0>iwJjP z3gh_k2{p-3q`f91BjlXc(gk{131E$85=pD*X&?T=-({m&#(=vml9clSxRY!@dtUFQ z{cLIQWN^_rTBd;gXG}KMVPgb!O%yfv>wiQeb&73>u`{@yT1k|e0W#l(t)`i(X80@CH^bw zZN=Kjm1Az*=|bO_DC8#>eZV%ISXdvph7N$qUUq8Y&1@IB4}#PPDP&b;eHLWLo(I)T z^@Q%&+XNcScewm%2Qi5Qzu<(fe+M6;Vt) z8!+G%z%0SKzMkxoSex@BEGzqoWV(<`GeA^t13Zf3k~p%U*mgwGpKPpqu z`_VlfktV|@<{8(xQ7ZdDxKwgs#NRrQR}@lnxWi-A*}sGa#^jztQI3xYTPkrhtTlMi zyLGwxSLe4QbcR_|#m1GoCMetD&y$c27$f_I0dQxOaZT^u021BTdzx2!Hq~+D($TOTGn~s{vve|%-AHZX}y%QVDb?*cdMF@ztCW50{Oa_ zuGzPZa)43+iZ^fVwhz0C{2uyEw_S`vz?44VHt0P%&MnbCbFbPu0Bm@4IGR?X%tqND zVSxb(jOu0_1OcB!J$%<wod^!!h76G1A}IEWd?eE|Y7n#bQD7Oz{x<8;7=MG{JxNPY6xBIpkhshKE+vSS7L~8gUqN0_#5Sgxt(j_n1Rc z{I@c_*wq!O`xmaY>(*33jQm6+%x(q0qT zlTqAc%RxH-t zJCn-2kQL|mF++5v2$s^Tix^%1yMHK^NO<6b14(UbIhx%@Q)ULG^%q5;W}0dyNCnB~ zE1g-C34wF2(S7od1Am-RVq}QF&UzbR2i8isT2!2%W2jCP_ zU!{D8Mg?ncYVk)s#*&gdB{5RU16V8y9)8z%DYKodcnGgKWQfy)w|X!O3;2=1zTm9F zq_j^A)ENQ>{eY_N@rwR;u+JDxym*nZoC$V8@upPt-lU;v+1W^bscjqLv)`@`2iGKu}iG&dQ z0#9^8Gsomkv;UbFH#D&+*S*Bf&tY@dHQJ25@^ECdqE1G4M9>nw6=X*EO=4wVw0dCr zDh-giihm;;ACR)LjSAv4#FAM}LTNN7HX6Ixxj9V=@%fCy&1R+On5bq}v*+#XBuv2l zptJjgZN(U>_7z0(rXmum#Aw(FVrK~a;Yt@G8-ig$9^?IH8WgG~C8s z;Uq>}k%A!y7lNdW2oR;B&L@j{Cm;}Nv6shE>Gio0d_qsdN0Fpdm}=KOSt0$|qJyIs zmJd$3+v^j8mQu`)K&XeFo(e%hT1KJXykRR^ciEzXgR#HKoWQ<2?VXz$^@Jr_M4HA8 ziazDCI3v=m3eEG9WuGDerGA9OOKH|Kf@VpZT$tiA)q#3Q#Y^99Lf+R9pt+7MQFM?b z_1TB-QvWFXtm+kLAGG)CNb+(jwAbDJ#tIYQfQyqjq95^d2>XP+vKu;`<` zYu*==TC}zTw`|U)FFC;cILz{d^ta0X+P%^Buxp8QXu8FMS9JF&Fkz!m}T&9+4{`y6RbOKJ~Rt~V;N^$$~CO$ejyKIIQ zi_KZ;7x&oJ)ajw)pG!)s)8{)LU8Ia6*s<^Q0q&HI3c7x%tqHA!ZVswiXH+!%crpU= zz44`|R~$X^$_uck7$no~Cj`WwCU&@fTYj%7X^i;qhK49rigAi&72H$Tj<#z3vTQ!u zEh_XIsX{n?hI74&wUBkv*LSGsOyB^$qc^QeNG#Q3EwRlpHA=#B~FNClM+ZnDac-F2yMa*X%{hkA_tLHX?AG+}`gVXI*%j&j*h%S5yXo}><7lZ@c z8J}XM!Q^e_0P;#b%Rvq!Ii5&NZI>D?U`HkWeP&s|9lm^ z`V4~|{S3_}BE%L>yw@S-iiU%^Zro>CQ8r5zE$h(0El;;lRis9PHAp>4j0Iu|SMp3n}w4-D*H=Dn8xh0loT$oKP6inf{B4kkokEU@l}s#pG3?t#>7e$~%Gs-OuU@o;$2dD~KL^1+BF$IhO5uG9 zD%!*k69BXN85|IOmP11PyWCNvC%Z?nbe;SIM(W`l?aX%H=MbcDOlMB-`yqf z*I}|{-kwzOQzsktNqVRpNppDCaa`bl|M74#Rrfl_p9NiboKhu)r~WEjI(O0+C% zqVValQAmI9Sj*fR*vO6ms79IZ3zc83=2dWO@LMZ$r!TOw*3kcM%-|ZKjs&Ta%*s@z z;Q~F#ZJ3TW=LC|+Me8@r&Zn>UqZt$j*c29H9>O1>oR^MzGW)+~Flst~6C=4mCu zJ0WmN{K3tMS{icQ4>)W=hMK(G99C*Tv=RqfRBxJDLxg95M>>RDZU)ntPqsh)bc9{n z)BG9rhhT0NuC-_TLZ-{|H?*>Qt}lZzppZ#vp#P>?N$N>n%dQ2i0RQaC_<$Si z%Zf{D+lEwCesGAw^Nw7vXJO?xHZPF+I*{JUj?s5x z7n#px&Nc_aV+Vn4q<Q8VNVn>m~;zb=i*M5!~fx-W8_JRN(ACN8k zj-ntb24PRs*G6ehC8FlxjKt99PEQE*&qX=7(Y~f@#&t*~B98B@sUfSybK<*r$?~>K zn!?mJA~Hb>s+tx{897jLuu{D}b&Qr&j*y^6+HfYwhe(8%&&zw`paOb#m(yRSZ6`66 zB0IRawek+{o)PQ;jjq2rc=+!tLKN$N0Y7P4BtEc04JB5zs0F7TJidRTBJ)1%H4B4@ zJC#$GhO!$Z0043rPPfwa;o}IV z99BFd$CQqpSgc9K?oU}ydOA7uS$AvFeE{$2*gtA=65Zp5N z(mJNps0obCBccRs=%(e)l_R3%F!1*Vg1n(?okO~JGz*KPbh_e8>u5zjlEyHo2Q)tF zcyaa)6Zi){1cb1a)C3> z_!v>QGXf`&3t0D@mB`hoj3@J`j!^|yMM_050Wu_@BU%ct67#61ZugP$CIzk zZ`KDL-M1v>GvhZSG6-nl`fTGxR5y;mAmC%_CLUCTBneXBeLqbK-Cg(!uwpG;tp*CE z?&*bF23Wm%~iU+*B9tBCwyaMLsRvk&ahF(jC5?4+#NXYshXk71T8y_ zHvKbsm~45*wBnsPk^x^r2y514M7LVYOZ)4_hnUuF2UCtArv11$kzt5eOL!Jh0_S9{ z(TkEo`YTx4qWkXqO#q1LTPK`f+3|V> zW=)l#$o{xRDf5!MBjNI&#>cT>r5N1|ic?AK|yZE%I$tgKb9$3JRdH zD{+eWzbz8W2jD-^`_SGky~%&1%^GR`8`cvLy8qYiPX<%VXaJLF33i}>IuKjnM-U7iQexj^3ylih(_ zYrVu#S))EGEv$9;q1RDMh~t;}f5&5bdd7sq!3$;kuqF`-B*z!E4xchYGB%aL?j7`$ zEb%JH@d?dl#yX^aR78{3>P$!|u_q97gePRjhY6pZpvp~>6_Kzk4ZkJjq$#4 z=c>NrPy)B)oX|+Ajwv}oxo{dR&g#(pv%gEy5?b3EuXki2*t=MlRH;Tb1C=A}V=gz@ zvY?!~*&WP&IA59dFH9t5y13}JD;#M;_`NSM&$jjpqchAM`(U6j?PDRgPU;77;OR8l G=KljJl}l>? literal 0 HcmV?d00001 diff --git a/crates/pf-vkdecode/tests/data/test-main10.p010.sha256 b/crates/pf-vkdecode/tests/data/test-main10.p010.sha256 new file mode 100644 index 00000000..98e0c075 --- /dev/null +++ b/crates/pf-vkdecode/tests/data/test-main10.p010.sha256 @@ -0,0 +1,83 @@ +# SHA-256 per decoded frame of test-main10.h265, DISPLAY order - 50 frames. +# +# Each frame is the 320x240 picture as tightly packed P010: +# Y plane 320*240 16-bit words = 153600 bytes +# UV plane 160*120 interleaved (U,V) 16-bit pairs = 76800 bytes +# total 230400 bytes/frame +# +# P010, NOT yuv420p10le: the ten bits sit in the HIGH bits of each little-endian +# 16-bit word (15..6) with the low six zeroed, which is what a D3D11 P010 surface +# and a Vulkan G10X6_B10X6R10X6 image both contain. Hashing yuv420p10le instead +# would compare LSB-aligned samples against MSB-aligned ones and fail everywhere +# for a reason that has nothing to do with the decode. +# +# This vector carries no conformance window - coded size IS display size. +# +# Why it exists: every other golden set in this program is 8-bit, so no rung had +# pixel evidence for its ten-bit path. The HDR legs proved a Main10 session BUILDS +# and runs clean, which is not the same claim - D3D11VA has no per-picture status +# query, so a Main10 stream decoding to garbage would log exactly as cleanly. +# +# Generated 2026-08-06 from libavcodec's SOFTWARE decoder (HEVC decoding is exactly +# specified - every conformant decoder is bit-identical), and CROSS-CHECKED between +# two independent builds on two architectures that agreed on all 50 frames: +# ffmpeg 8.1.1 (Homebrew, macOS arm64) +# ffmpeg 8.0.1-3ubuntu2 (Ubuntu, x86_64) +# +# Vector generation (libx265, Main 10, 4:2:0, 2 s at 25 fps): +# ffmpeg -f lavfi -i testsrc2=size=320x240:rate=25:duration=2 \ +# -c:v libx265 -pix_fmt yuv420p10le -x265-params "log-level=none:profile=main10" \ +# -f hevc test-main10.h265 +# +# Goldens: +# ffmpeg -i test-main10.h265 -f rawvideo -pix_fmt p010le - | +fe40d5f2aac672155dd200cc64ae3c6c47dc90e6dfcc59f634db0603ea553068 +ba62cb199082881c76eca0bccdd41201fd9c1142e97ce89747ef64a836c31f4f +2494e3695cb7be56aabedbfae3dddd10ba0c2e0ad08b51bbe03d7e2d26baccb8 +8195990eeb8475d01abd2f0c27cbf20a3abe8d2fc273996aa66528277070aec1 +ff45876b191c83688a37898e0fa9757b03215f2d2cf1a1a50f9899c837873a04 +a684b50bf67c59052c138cda318c83f85d55132c8f9e8e28a430ea6731fa59c5 +65e4dea6aaab1e73a942b3c3a9334cd2a215c36709a4338f3dfc71bf8dca5819 +9d8deda227c79a931be366370093c42c5e8ce417044069c883d144197eb089be +48dbfe1702045aa13e3d6f7fa3f95aae2e863c0b792f77a444504c373bc28313 +f700d0bb43c2df17e49b036125f313ae41f31ee75f1d6ad895e36e7145a402ca +ec351982a1e14ef5edaba99d578e9d77fa3644ffdd7101d5d3d95b484313b9f7 +0526ca0453f5eb05b9cfb694d5a0d30cdd3fdd9e7914b4a527b33fd0c2ad3cef +07fdeeaeb8de894030c60a4fcfccf116488e9f9dbf19de59963f279c9b4bf3de +ef15d30d0bbd9f2bb454a10d30451151ae4300b0b5844f8dda2c06f4feb41d40 +f8e7bf24d3d8c3938c4a3b0bf7f2d81af457ecedee22cbf0cfeed8e4e67baa95 +fb48d15f9462cf359acab5ae48c91176b7b2931a57755815d4539bc4f3e8ad4b +a8bec00e24b39b55fcca51fde360fce130400202c1ac9370657c60578536acc4 +f3fe62e6ed17d4e59dc16b7c8627d494047163e14e5a0521792d3d9680a06fe1 +b811ebb99d9339e3fce4a2db070a315385ea7570f01a3d18275bc5a97a359850 +b38cd7adda00d5f8e33b7cb8e0d18fdc94686ab16e72bbc67ecb6521d19ab412 +cf7946c7be96da66e256e2235ee0f7b2a35e1e344fea2214e2045e8bd5b548c7 +635916c2a10c1086efccb71c505a71f4a77e2007f5df165f0be15ea1359eec5b +e70cc81406480acfd686091d023ddb0b5315230fa2449a5d73ff403ea0f6d532 +825301a5c79ed4b28f503af92ae569fdb66c53de60c661b93d96467cc2ed6b8d +9739d731f87ad1dd21d26331bb4f2d28857815ed4c2b1d45dde2744da1efa60a +313d001c0c2203eef18a2386c73e63fdf9ac0417dba4339fce95b11c73b68b84 +74597aa7d898c4ba79f88fff2c37cfb54dda2f9e9834cbc92b1b090ae730efa3 +ef22cd7812608173266162af239e845c66ef71d025ce17a4f815af33a8617760 +c6f5f5ce376f4b5dd82f9d01cc16081a352736051eb00b352cf3f4d15ca5de17 +a854b63bd8d288734548ddc792c3d67c5e3ec744264038b5f0a319038b129112 +f3a14d11678dbd8e4dcfbc8ea0b1cd3503cb2e695858f03cac1e9613744308b2 +8d6abe1632b2586ba830a69ea509c72645c1e7cb939adffba05ec846716d2ba9 +f809f1e5cff7402bbc281ef4651760858dc5e8feed6e815bdbe780972f03fe0d +43c64be95cfd7512a96f8d82e48198766f7eab292023c2b84780075cae4d0544 +4e9e8f2336bafb17267c2deb18b9fd4fc561b2df3cc0ab63a6654d8946a0362a +1003635166c1d34360bca984e31a06dea1583859d0cb515eac6c65f13629ffd2 +31ce8f6f38026317ed31bafc11c8ecdc5fa6c14cf72835126a6def695c4934d2 +eaeb25f2e4f6a881fc3d707a3076eab45c5605880c953f552591d199ba48ab0b +ec6aa73e67516f2e6053cfb3ab6df304ee4093190ef6b52442b2ad2cf598f287 +f07f7d2d5e5d48d37804e33234a0fa029cc7abf60ca19d21663ef8435d9c3ad8 +a0f5d8dab08fe6764eb46c46eb4320a105329b805ab6dd0f4ce0758b4fc10409 +3f98a8dc1bd2c7eca8fc39945195b16de868db1ff0445d9ac2aa74dbcc370b8a +0ca2d9f7251aa020c14a6548922e55cbf010d14c2c3a2a27db3eb8a9a4c185b8 +79b6e0898dfdddca8a878ace9742aa3607b3a968e56b95d3aef03dea3b11cf71 +59487538cdcd63a48a28e2682fd56cc6dbe67ca00c696ddf06b21f605dac91dd +dc98f561f0ecc58824123fd8cfa5f0c2ce96bf4fafa56ca1d062b00df12f2d7f +394804f6a890eeecf6ffbd7a54015cc248ced7e0a951e921a329d615522d3102 +7f8d56e2e57099e1cd456c9cb67748de521a398180b86f52e06bb6ef6ca8d816 +a860ede88b1f66e2d08a1755f6d49e70b553270c61e709ca0b1ca5adac3e1594 +c6b7df04a6292863041fe141f6a4cb4eaf0d27f267e733e1ba75a6fe3f8f05cc diff --git a/crates/pf-vkdecode/tests/gpu_parity.rs b/crates/pf-vkdecode/tests/gpu_parity.rs index 0a2027e5..6934d0eb 100644 --- a/crates/pf-vkdecode/tests/gpu_parity.rs +++ b/crates/pf-vkdecode/tests/gpu_parity.rs @@ -67,6 +67,16 @@ const GOLDENS_H264: &str = include_str!("data/test-25fps.nv12.sha256"); /// The H.265 twin, cross-checked between two independent FFmpeg builds (header). const GOLDENS_H265: &str = include_str!("data/test-25fps-h265.nv12.sha256"); +/// The ten-bit vector and its goldens. No hardware leg in this file consumes them +/// yet — the D3D11VA rung is where the ten-bit parity leg currently runs — but the +/// files live here, beside the other goldens, so the guard that keeps them honest +/// belongs here too and runs on every platform rather than only on Windows. +const TEST_MAIN10_H265: &[u8] = include_bytes!("data/test-main10.h265"); +const GOLDENS_MAIN10: &str = include_str!("data/test-main10.p010.sha256"); + +/// The Main 10 vector is 50 display frames. +const MAIN10_FRAME_COUNT: usize = 50; + /// The H.264 vector's display (conformance-window) region; the goldens hash /// exactly this as tightly packed NV12. const DISPLAY_H264: (u32, u32) = (320, 240); @@ -126,7 +136,17 @@ struct Readback { mapped: *const u8, /// The display region every read copies, and the crop it requires. display: (u32, u32), - /// `w * h * 3 / 2` — the tightly packed NV12 frame this buffer holds. + /// The picture format the pool must carry. Held here rather than read from a + /// module constant so the sizing below and the per-frame assertion come from + /// ONE source — a readback sized for eight bits that then accepted a ten-bit + /// frame would hash half a picture and blame the decoder. + format: vk::Format, + /// 1 for NV12, 2 for the `3PACK16` ten-bit family (its samples are 16-bit + /// words with the ten bits in the high end — the same layout P010 has, which + /// is why one golden file serves both this rung and the D3D11VA one). + bytes_per_sample: u32, + /// `w * h * 3 / 2 * bytes_per_sample` — the tightly packed frame this buffer + /// holds. frame_bytes: usize, } @@ -142,16 +162,22 @@ impl Readback { device: &ash::Device, graphics_qf: u32, display: (u32, u32), + format: vk::Format, ) -> Self { let (width, height) = display; - // The two-plane copy below halves both dimensions for the R8G8 plane, so + // The two-plane copy below halves both dimensions for the chroma plane, so // an odd display region would silently drop a chroma row/column. assert_eq!( (width % 2, height % 2), (0, 0), "the display region must be chroma-aligned" ); - let frame_bytes = (width * height * 3 / 2) as usize; + let bytes_per_sample = match format { + f if f == pf_vkdecode::NV12 => 1, + f if f == pf_vkdecode::P010 => 2, + other => panic!("readback has no sample size for {other:?}"), + }; + let frame_bytes = (width * height * 3 / 2 * bytes_per_sample) as usize; // SAFETY: fn contract — live device, queue 0 of this family exists. let queue = unsafe { device.get_device_queue(graphics_qf, 0) }; @@ -219,6 +245,8 @@ impl Readback { memory, mapped, display, + format, + bytes_per_sample, frame_bytes, } } @@ -309,7 +337,9 @@ impl Readback { }, }, vk::BufferImageCopy { - buffer_offset: u64::from(width * height), + // A BYTE offset, unlike the extents above, which are texels: the + // luma plane occupies `w * h * bytes_per_sample` bytes. + buffer_offset: u64::from(width * height * self.bytes_per_sample), buffer_row_length: 0, buffer_image_height: 0, image_subresource: layers(vk::ImageAspectFlags::PLANE_1), @@ -440,8 +470,9 @@ fn consume_frame( // hash differently for a reason no mismatch report could explain // (`DecodedVkFrame::format` docs) — refuse it here instead. assert_eq!( - frame.format, EXPECTED_FORMAT, - "frame {index}: 8-bit 4:2:0 vector must decode into an NV12 pool" + frame.format, readback.format, + "frame {index}: the vector must decode into the pool format the readback \ + was built for" ); // SAFETY: the frame is delivered and unreleased on the readback's device; // the pool carries TRANSFER_SRC (PF_VKD_TEST_READBACK was set before the @@ -573,6 +604,7 @@ fn h264_parity_run(aus: &[&[u8]], label: &str) { &setup.device, setup.graphics_qf, DISPLAY_H264, + EXPECTED_FORMAT, ) }; let hashes = collect_hashes(&mut decoder, &readback, aus); @@ -614,16 +646,24 @@ fn h264_four_byte_start_codes_decode_bit_identically() { /// The H.265 twin of [`h264_parity_run`]; see its docs for why the AUs are a /// parameter. -fn h265_parity_run(aus: &[&[u8]], label: &str) { +fn h265_parity_run( + aus: &[&[u8]], + goldens_file: &'static str, + expected_frames: usize, + bit_depth_luma_minus8: u8, + format: vk::Format, + display: (u32, u32), + label: &str, +) { // As the H.264 leg: one codec at a time, `set_var` under the lock. let _gpu = common::gpu_lock(); std::env::set_var("PF_VKD_TEST_READBACK", "1"); - let goldens = golden_hashes(GOLDENS_H265); + let goldens = golden_hashes(goldens_file); assert_eq!( goldens.len(), - FRAME_COUNT, + expected_frames, "the golden file carries one hash per libavcodec frame" ); @@ -644,8 +684,10 @@ fn h265_parity_run(aus: &[&[u8]], label: &str) { // host the combination refuses here with a caps reason instead of failing // mid-stream. decoder - .probe_stream_support(1, 0) - .expect("the box must host H.265 Main 8-bit 4:2:0 (the vector's shape)"); + .probe_stream_support(1, bit_depth_luma_minus8) + .unwrap_or_else(|e| { + panic!("{label}: the box must host this H.265 shape — {e:?}"); + }); // SAFETY: as the H.264 leg — live instance/device, queue 0 of // `graphics_qf` exists; destroyed at the end of this block. let readback = unsafe { @@ -654,7 +696,8 @@ fn h265_parity_run(aus: &[&[u8]], label: &str) { setup.pd, &setup.device, setup.graphics_qf, - DISPLAY_H265, + display, + format, ) }; let hashes = collect_hashes(&mut decoder, &readback, aus); @@ -673,7 +716,37 @@ fn h265_parity_run(aus: &[&[u8]], label: &str) { #[test] #[ignore = "needs a Vulkan Video H.265 decode device (fleet boxes; see module docs)"] fn h265_every_frame_hashes_bit_identical_to_libavcodec() { - h265_parity_run(&common::split_h265_aus(common::TEST_25FPS_H265), "H.265"); + h265_parity_run( + &common::split_h265_aus(common::TEST_25FPS_H265), + GOLDENS_H265, + FRAME_COUNT, + 0, + EXPECTED_FORMAT, + DISPLAY_H265, + "H.265", + ); +} + +/// The ten-bit path — the only leg in this file that is not eight-bit. +/// +/// Every other golden set in this program is NV12, so no rung had pixel evidence +/// for its ten-bit path: the HDR legs proved a Main10 session BUILDS and streams +/// clean, which a stream decoding to garbage would also do. The goldens are P010 +/// and the Vulkan pool is `G10X6_B10X6R10X6_2PLANE_420_UNORM_3PACK16`, whose +/// samples are 16-bit words with the ten bits in the high end — the same layout, +/// which is why one golden file serves this rung and the D3D11VA one. +#[test] +#[ignore = "needs a Vulkan Video H.265 Main 10 decode device (fleet boxes; see module docs)"] +fn main10_every_frame_hashes_bit_identical_to_libavcodec() { + h265_parity_run( + &common::split_h265_aus(TEST_MAIN10_H265), + GOLDENS_MAIN10, + MAIN10_FRAME_COUNT, + 2, + pf_vkdecode::P010, + (320, 240), + "HEVC Main 10", + ); } /// The HEVC leg of the production prefix form — the one that would have caught @@ -684,6 +757,11 @@ fn h265_four_byte_start_codes_decode_bit_identically() { let stream = common::h265_four_byte_start_codes(common::TEST_25FPS_H265); h265_parity_run( &common::split_h265_aus(&stream), + GOLDENS_H265, + FRAME_COUNT, + 0, + EXPECTED_FORMAT, + DISPLAY_H265, "H.265 (4-byte start codes)", ); } @@ -844,6 +922,68 @@ fn h264_goldens_and_au_split_agree_with_the_planner() { ); } +#[test] +fn the_main10_vector_is_ten_bit_and_agrees_with_its_goldens() { + use pf_bitstream::h265::H265Planner; + + let goldens = golden_hashes(GOLDENS_MAIN10); + assert_eq!( + goldens.len(), + MAIN10_FRAME_COUNT, + "data/test-main10.p010.sha256 must carry one hash per display frame" + ); + assert!( + goldens + .iter() + .all(|line| line.len() == 64 && line.bytes().all(|b| b.is_ascii_hexdigit())), + "every golden line is a bare lowercase SHA-256 hex digest" + ); + + let aus = common::split_h265_aus(TEST_MAIN10_H265); + assert_eq!( + aus.len(), + MAIN10_FRAME_COUNT, + "the Main 10 vector is {MAIN10_FRAME_COUNT} access units" + ); + + let mut planner = H265Planner::new(); + let mut outputs = 0usize; + for (index, au) in aus.iter().enumerate() { + let plan = planner.plan_au(au).unwrap_or_else(|e| { + panic!("AU {index}: the Main 10 vector must plan without errors, got {e:?}") + }); + // The whole reason this vector exists. Every other golden set in this + // program is eight-bit; a regenerated vector that came out eight-bit would + // turn the ten-bit parity leg into a second run of the eight-bit path, and + // it would PASS, because its goldens would have been regenerated with it. + assert_eq!( + ( + plan.picture.chroma_format_idc, + plan.picture.bit_depth_luma_minus8, + plan.picture.bit_depth_chroma_minus8, + ), + (1, 2, 2), + "AU {index}: the Main 10 vector must stay 4:2:0 at ten bits" + ); + if index == 0 { + assert!(plan.picture.is_idr, "the vector opens with an IDR"); + assert_eq!( + (plan.picture.coded_width, plan.picture.coded_height), + (320, 240), + "the goldens hash a 320x240 picture" + ); + } + outputs += plan.dpb.outputs.len(); + } + outputs += planner.flush().outputs.len(); + assert_eq!( + outputs, + goldens.len(), + "the planner outputs {outputs} pictures but the goldens carry {}", + goldens.len() + ); +} + /// Count Annex-B start codes in `stream` as `(total, three_byte)`. /// /// Emulation prevention guarantees `00 00 01` cannot occur inside a NAL payload,