From 71f69d1d483efecdc224a70a1c74c5ca56d80c67 Mon Sep 17 00:00:00 2001 From: kd-11 Date: Wed, 17 Jan 2018 19:14:00 +0300 Subject: [PATCH] rsx/overlays: Introduce 'native' HUD UI and implement some common dialogs (#4011) --- bin/Icons/ui/L1.png | Bin 0 -> 2829 bytes bin/Icons/ui/L2.png | Bin 0 -> 2224 bytes bin/Icons/ui/R1.png | Bin 0 -> 2704 bytes bin/Icons/ui/R2.png | Bin 0 -> 2569 bytes bin/Icons/ui/circle.png | Bin 0 -> 4307 bytes bin/Icons/ui/cross.png | Bin 0 -> 3560 bytes bin/Icons/ui/dpad.png | Bin 0 -> 3474 bytes bin/Icons/ui/dpad_down.png | Bin 0 -> 3618 bytes bin/Icons/ui/dpad_left.png | Bin 0 -> 3641 bytes bin/Icons/ui/dpad_right.png | Bin 0 -> 3706 bytes bin/Icons/ui/dpad_up.png | Bin 0 -> 3655 bytes bin/Icons/ui/fade_bottom.png | Bin 0 -> 43214 bytes bin/Icons/ui/fade_top.png | Bin 0 -> 43606 bytes bin/Icons/ui/left_stick.png | Bin 0 -> 3888 bytes bin/Icons/ui/new.png | Bin 0 -> 19960 bytes bin/Icons/ui/right_stick.png | Bin 0 -> 4178 bytes bin/Icons/ui/save.png | Bin 0 -> 26456 bytes bin/Icons/ui/select.png | Bin 0 -> 1813 bytes bin/Icons/ui/square.png | Bin 0 -> 3385 bytes bin/Icons/ui/start.png | Bin 0 -> 2606 bytes bin/Icons/ui/triangle.png | Bin 0 -> 3784 bytes rpcs3/Emu/Cell/Modules/cellMsgDialog.cpp | 101 +- rpcs3/Emu/Cell/Modules/cellSaveData.cpp | 13 +- rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp | 5 +- rpcs3/Emu/RSX/D3D12/D3D12GSRender.cpp | 2 +- rpcs3/Emu/RSX/D3D12/D3D12GSRender.h | 2 +- rpcs3/Emu/RSX/GL/GLGSRender.cpp | 103 +- rpcs3/Emu/RSX/GL/GLGSRender.h | 9 +- rpcs3/Emu/RSX/GL/GLHelpers.cpp | 5 + rpcs3/Emu/RSX/GL/GLHelpers.h | 93 +- rpcs3/Emu/RSX/GL/GLOverlays.h | 378 ++++- rpcs3/Emu/RSX/GL/GLRenderTargets.cpp | 4 +- rpcs3/Emu/RSX/GL/GLTextureCache.h | 1 - rpcs3/Emu/RSX/RSXThread.cpp | 77 +- rpcs3/Emu/RSX/RSXThread.h | 22 +- rpcs3/Emu/RSX/VK/VKGSRender.cpp | 204 ++- rpcs3/Emu/RSX/VK/VKGSRender.h | 7 +- rpcs3/Emu/RSX/VK/VKHelpers.h | 38 +- rpcs3/Emu/RSX/VK/VKOverlays.h | 398 ++++- rpcs3/Emu/RSX/VK/VKTexture.cpp | 8 +- rpcs3/Emu/RSX/VK/VKTextureCache.h | 6 +- rpcs3/Emu/RSX/overlay_controls.h | 1456 ++++++++++++++++++ rpcs3/Emu/RSX/overlays.cpp | 31 + rpcs3/Emu/RSX/overlays.h | 762 +++++++++ rpcs3/Emu/RSX/rsx_cache.h | 78 +- rpcs3/Emu/RSX/rsx_utils.cpp | 1 + rpcs3/Emu/RSX/rsx_utils.h | 2 +- rpcs3/Emu/System.h | 1 + rpcs3/Json/tooltips.json | 3 +- rpcs3/emucore.vcxproj | 3 + rpcs3/emucore.vcxproj.filters | 11 +- rpcs3/rpcs3qt/emu_settings.h | 2 + rpcs3/rpcs3qt/save_data_dialog.cpp | 35 +- rpcs3/rpcs3qt/settings_dialog.cpp | 3 + rpcs3/rpcs3qt/settings_dialog.ui | 9 +- rpcs3/rpcs3qt/trophy_notification_helper.cpp | 22 +- 56 files changed, 3682 insertions(+), 213 deletions(-) create mode 100644 bin/Icons/ui/L1.png create mode 100644 bin/Icons/ui/L2.png create mode 100644 bin/Icons/ui/R1.png create mode 100644 bin/Icons/ui/R2.png create mode 100644 bin/Icons/ui/circle.png create mode 100644 bin/Icons/ui/cross.png create mode 100644 bin/Icons/ui/dpad.png create mode 100644 bin/Icons/ui/dpad_down.png create mode 100644 bin/Icons/ui/dpad_left.png create mode 100644 bin/Icons/ui/dpad_right.png create mode 100644 bin/Icons/ui/dpad_up.png create mode 100644 bin/Icons/ui/fade_bottom.png create mode 100644 bin/Icons/ui/fade_top.png create mode 100644 bin/Icons/ui/left_stick.png create mode 100644 bin/Icons/ui/new.png create mode 100644 bin/Icons/ui/right_stick.png create mode 100644 bin/Icons/ui/save.png create mode 100644 bin/Icons/ui/select.png create mode 100644 bin/Icons/ui/square.png create mode 100644 bin/Icons/ui/start.png create mode 100644 bin/Icons/ui/triangle.png create mode 100644 rpcs3/Emu/RSX/overlay_controls.h create mode 100644 rpcs3/Emu/RSX/overlays.cpp create mode 100644 rpcs3/Emu/RSX/overlays.h diff --git a/bin/Icons/ui/L1.png b/bin/Icons/ui/L1.png new file mode 100644 index 0000000000000000000000000000000000000000..8a1d87c5401994ee31aecba235b7b539e007e414 GIT binary patch literal 2829 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 literal 0 HcmV?d00001 diff --git a/bin/Icons/ui/R1.png b/bin/Icons/ui/R1.png new file mode 100644 index 0000000000000000000000000000000000000000..722e405c60db46cad3d4c00ea98c6b2df98900a6 GIT binary patch literal 2704 zcmeAS@N?(olHy`uVBq!ia0y~yU`PRB4mJh`hJr^^Ll_vi!aZFaLn`LHofDgP+e+;C z|0yAwMKL${gcRZ(VixdM?r_-KJX_YM%wVQ$3FlndJCO%d$}Ze&e7=^y_5*|KMJC2q z(I$P3EPX-J+f;*}*c`d?qIptkbXnAN`voi3cr2;?_r$VYMAKnmfQATHs}m+=X=!Cu zwe{4elUm&OS-yS$UVq)>Ezbc<#U+COt)K64<;Y~p;B7y={rmUt{HL!hXX#XAQD~XN zXfgYF+_}ox#s7|_H58neB2j^?dVu`o1_13Ll|KIxk-u*oa zijU1X6w~K?wo+WO?~s=4{qN^@-+fpAY-i?UmX%ktL{D#$+O_Wba<};QkCzJ==A6A8 zTo>iY$H(Vq@%-z`{p*=BeEx@auk*fa5;2YSut7qkn29elgTb)}yY!AdHdLGJdF$=D z0*f`zdB53jQTYA)_kQMvjJFjg6?-+}=ex~fh}^NRyKX=Kv4}_cb3UK;TmE^wO{x7# zg+mW6ZxO06DU;RCdTC_h+q~{|Bg3*xy_jk5f4}%z^-YTq=6CTP6BaLC9Nx3h*s0^g{q*iB zo4w}QR#1wkuCXdUF>R=l+rKNTWw)WPaEpLMo8Xo|sfND7+j18Ntvs+TcZ%22 zBc|%=>Jq(f61{E{KQ9gP)Z$Ql!sL*qfAXp`A3y(prU(6{yQLCS7$#ox4dYMppK(=z-LA`R3|}9wah%pFhoV-KM2Ce^y!^W6GD` zwK1`=uIDP}u^%r?nI0b#AOBx_?HZryrf(&uf3C57=*HmkXPbdZ!&GVO9)lgZx4ot3 z2XP&fkVw99>lW8J`}%(}*4EY&E0SiZ@f>DYY7)fu{Ev*oDutD{OTt!%dMyo-)_l4A z#8ua$r&-P{(wKhQz*l%#Cfo68sk$-q-v9nkD?jgfV-VN<@6G!|S6@AmUluDaZ(rAQ z$6l)U!u#*SPx;ypXU)3p^;U6P?tZr}iQBhtZ^+#};ejRpu668dW4>%s;?X}Lq;M|5 zz+q_+r(N{4sV`KgrFybzPW5{J{rmSzag&~I(o0d-p6aD&ALn-Gf)m4oi(fk!;zg!h zI`E@sqYq=s_h0+uU#eDDR|`6t_%>(G>TEb~Y46;1d(yu{FM~H${rU6l+c&4A7G7zy zUAea%=R~TlIOilWCurA%kC!F-kKed=kI!zp*U}aSBgYAbd6T&gS=x&;MmTVA_RIpM zhWq#DYv=0BI+?@6VXIF(>Ij+e@`d%G zHJ)a-w|)Hd^z@;-UNRSVX)b**r@2^e2meW#ONXYMPJQ$4-J+61B1UbN^hXz9PZ=2Q_(Wga{y>80a z-*<8c=}i~De&W*omp!Ft<-y&rZ5X6|*6EsSc&9;%ocZRhTU#$*4z}oP z{#YS1$KKYK_lQLQ@g2K&AO85UaNV!RYwKr5cHeP+yGBg@{O0hvnMYTKtuDU4bN6m( znfdePvCY?9`s2gH!$F)!);x3+i?mfaWYc70+V?oYKqA3Ff@Nivjadt8i46Pi-@h3e z!dAB)Ob`&vnd`xt*kfsbDCOazEm57{TJpAcSMAl?YbWD>iRs_2i1qoEPLE~=*Gv%S z|FpWTSg$r>)5j)>w#KTxaywKW7-lTDv5D&VA))*3Q1G4FP14EEtb&>AXMF2m|KmU3 zPBbdsX7w4(jw!t&OD;}Wzj*PKN3UM3x^RzCW%DWdP3;0sTyx(d0N%cMUh~Aa+HzG#hrWxzGohA#IrDvtK?%cKO z(7SheD#EL-zuvKX_i34_<-70Byncjz_LNH}bl#^JO;j_RwaoPLsXH&^m~5n0-px}# zcfKh0@kfhb9^cDCO?U3yQ#&=`LCV_|w|>YmG2Gu*d-&r=L!QGNYBI9NC)!1w^{y#o zUTUJ)@p@P1Z;R*on@{~!WnpY6e}7N6biYTw?`0mb+V-7$_x7gDN;=xz67MW<@m04} zYj=aL%ZaLcO0Q)<>T*5GF0-0jpMG_>V3W!0vrA74wDEs$ve~h&TR(oEN>#f~-I=2B z18p2e`m>g~Zri52OX_~1OwHe4rb=wdKD*|LSKPT~w)#x3xXX`!AKtBUl{e&XcdnS% z-rmms@|@;_LYYZMR{Q5{oy&Ve!I4ApLaA-imk)7MT?HO33^*ztJp=-M%99J74mfvqt%onK!X!DsB7P6*NoBWBv8!b+2#SzCAh5 zmxF)Wp*)p)m%5L9-g6>vRp^=xPv&grRLt+HxUV!p@x+!zCoIo@Hu~NON(|3R!g?#7 z36z;W+^gYgJojG)W5(H8)1z8K9GB(a+QTtlc(%;-y|Pws(kz}&oVMRcUSB1wFnK=v= z*G=BaNIq)m;W@&?%dmgZMp3LSI<8iykHH`9|MGu$ueaftGXnzygQu&X%Q~loCIC;F B8Gir( literal 0 HcmV?d00001 diff --git a/bin/Icons/ui/R2.png b/bin/Icons/ui/R2.png new file mode 100644 index 0000000000000000000000000000000000000000..f05529fea95f591a429d0468e832ec80dd388f6d GIT binary patch literal 2569 zcmeAS@N?(olHy`uVBq!ia0y~yU`PRB4mJh`hJr^^Ll_u1FL=5*hE&XXJ2yJ$wwK(o z{nK)|fA;N^xXsz764JOjXmX_Eje{xd#+#crH(Pq2c=O;4*RwNxGrrpttY}es!lZJ- zB7k+%&J*UIpA)<%rfjV-pR~MQ|H8G{`TwTpm2PKXYd)A@Ai;w|_55tJd3LaPXH4Mr zue`7GTPB3JOfYHSbvn^@yKL9G;OV{`k_YxZt*}jvy?)l{{O3(7p7lPD4%D)0ig4AJ z-rF*f-|a-+_xs@tmnPp>etrYz^{3Mlk1XQav~AnBS zckBIKGP#WBKkskqG!*1eT+iZIVR~X$h$F)rzh@;94(?MNuW|eScKJ2WL4(DquFB7e z|wcC)f~Ug5Tw(KXP4qjBBqV~2pkHcO>WW z!rHY*#G8Ix)#dfpnrifa`Hr>w3Xh2Ec?XIxIPAXbrXgZvEb4nX=jGEaAH9~&eQB9+ zUW#!BqwI?vy5eTP<~HlI80PINES$A$rqWrV?7PVV9ExVXj%t+mqLv7cc?sEuT@i{$nLd5y%DZ4uISvZT9Jx!UjOyNFi+Y-#O6;axt@@xbL7xscZFKzj?zmNkvhHPg#U(W|D-3?t!(!GQ6j(1+#cQ z_9|Rfo4f5k^J-VC_`XO%*Na>D7F^CP2{3d^)MJf|iZZ&K(DzuN`{;&Eo06iBXBy_| z=;$m6(Ykr(+|{dBQ~a+TjN$t9=BDxar{BMS*O-2K-s>YthEu#$qt670ELC41y<$~X zcl9ETzAM{v^jJSGidy^V!-osSb3?RRgI2OQ3Y@tgwN|XJukS*RS?uFN3w78VAtB^}iNo_Sg{?{@0u$UQYbFR8WZoVKvAknrS@id$d!JV=1qb5f3G&cc8V zUjw!WX^3ptygB*z+fSc_*xA`vTz?&G>#tp`%h51TAx1~6`kJO#x2>=G^XJbkY;4|G z&nUihCY5Jnklyr(=bxWd`{<+A9HZCX-ObG~_rdO+J2P)TdwqTVj@`RAf6dvxNaNI% zs^{nTDmXmP+-^2oq}w$nHa4(b{)_kQ`+KX+FMi%^^j(jeL2GK)TC;r{HavK>>rX?z zZc4;`-_`OGbLP)~{PXkkG|Sm%AAR~H#Lv%PyM3utk=R;n_dhlzNIqRGg|oHa{u{ftj<4untJ}%B$b`_ey`l6<9qo+*4BWvVd?YM zvP_+MWJ1|)-pFY)HJBYlU9Nw<>?zmLX(SQi$f20O=JE~Srn234x9e)Iut#GV^VZSNaoNv% zx6YqYc{^3*W&DB;CuN~dmMo7$OJ+nyhlPu1KAED>=5B4QlWx8!@x$4xVXLps zt~uDPdHuClfuW$NsL5^d#n06?a_&}h-AtrpSGzwp;b=c> zE4z1XslfIpF4w=>-ufrEckA|>IkOn|-pqOQ-Fj_H!eMhTHEHMmK--@k;ac7 zAC<3Nzg}G>EhTYz$E7y;=;O%}NxfZ)4Cg;HA2p7gmaBK#(rih!pKYn}=L+R{&pk3` zeVg|Dr+MFfh0~kfmabg2%EHpp(MIn0sZ(5`5>n?Fe{Y`UwBcfg!z7i>YgVm#efU7( z9l2b2Ia3Ud9;&wtK5lT%e)oy|9K zb=c}F=lIr!txlgJ#_(d-zv!JY|EzQW_XmAmWNIVjq9k}EY2(Ca<>kl30!5CWI@2>d zf3C$&`Cd2Xtx;!94_+|Ief;Rrq1p{fav!;^|GF?*nTHFoa5O#m&9%tzef8eBl!ewN;N+w*1m^UB*YyA}pyED}q#`Rf*Y{D-xRtTWpN>l4fSYZN&y{B)2|e6lv0 z%}mej{q#i1?R=N-9^bd_b^e}5ZKC%%k|vyf+9h-RZrDIeNEl-FjW-_*X pinIQ%$&=#Wry#Yjk`w;13*EXDV0yXl2m=EHgQu&X%Q~loCIHtr=U@N; literal 0 HcmV?d00001 diff --git a/bin/Icons/ui/circle.png b/bin/Icons/ui/circle.png new file mode 100644 index 0000000000000000000000000000000000000000..6ed88bb0a6c56106b9417aee086b6fea70677412 GIT binary patch literal 4307 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 Am;e9( literal 0 HcmV?d00001 diff --git a/bin/Icons/ui/cross.png b/bin/Icons/ui/cross.png new file mode 100644 index 0000000000000000000000000000000000000000..f74e38379c48fd6edf1d179dcae4d5d22113b65c GIT binary patch literal 3560 zcmeAS@N?(olHy`uVBq!ia0y~yU;twdHU4%=nH>Ur0csJ?Yv8lUbc1`h` zII|+=iOo!rbwyfp=e(Vqc4BdfV82a6h2Vi@28{3Z80;9kKBY*%{Bh*phrQo!=i4jY zSQNPa`s$xG|NbehfBXG+e9q;qi?4q@`nK$M?EUZj0#4rrovIlMHav2lE99idp_nf5 z_;TL%-(T4Ii_+7dPiOeR!N9ldLy&yS1a1b4ueJYECI`*7JQ22fYqy~tQ-d?Zirk2R zww4LWPA5)2efo5>xlqdLtG8S^WR+XwbU)h#iZCjg_`f<(dN)=7<&E!uAFpXx)Wcxs z#vyyIdaa7*gwszQCaDOx3K|^quATn=_k&$}K5D`RGVE%TPb#hYZ^6y!s_FcPBl%Co zG%j&2))UV^PdNYF_#(^gZ3nmIZqUh|cS`ZPSxT))4Cs&=8q6 zZC7af``-zXViIkP``)X2J{NX65h!(Or`d_J-G8$gEczxp^%zd^Qk{@m`7Hg!CZ3%! zdVkEPpFUf&_p!wT3%(i4IG1HMJ+?58&!45%bNsQ&0_XFe<<`D_c%5nI=FOjvTSRvV zGzG0>Ihthnt#)S&pNMOt+hWG2MU}ZPi(|!0V%slfuq?jFVROE_c5b4Cfn0y1fYA3Z ziQ8}gz3k?A?e*8JjS)3M;x68kSgvN7hP6ufx@CPx>kVHYcQC<#A@+K5Xq}0as^zlB zdE0;Q?rm6e{dH*MH2=!#4uPhmjT}pZG{qLITGh#S?6b`W8~&KYE!!j& z!}XyetX6aVbR4di9xRmEkZXNWQpd{R*o5=X6{nwW)aaV{+|;-E`T6U~lzzhrlTw8jCWeEczy=yty2eeK^I4 z^Rb7cz@e!@J7d^f10Ak^{ouxS>5T>7ddYsRsi#_(W%>tCK9Si`d}wv?;)@9it5$KX z4bxs^w`<+{wfkPL*tBypp}U#jV(J)v8|s0xg#- z%{E?sd0=hycBP<2S=mt!8UJm@x0G%&&d*^>G*ao>yi#3e5Y^|5+e3 zwAW$J&Yd@G>8-x8qdF|<^d&Kih zqzay&lXc5=TYND)_-W<0%iD4%sAx_(leLv=*0R=fY8-1b`1=Kdw5NLcUfyzk8r#jR zt#(cvwFlH?URbZt;M()|+wGpYJ9q9pd1dAjLz&}^F?#G0$p<8y*M=o0cuf*Hw)ILy zfZp`;CceM360fei{`zXBSo_BdCN7cF9=^M~yT>Yf*0!$#%qOpy=w6Wu6j^xrrOTrJ z88g0AXm;k9NX=Jjk-N|zx;pe(^ZUEIkFWT##`N8`Lvwu868C6&3O)H;DZ$si&{@=V zqUUkNmaL~mk|w^tlk`r!yv88>YKoVt+JVqeQK??HAKTZx=2i{eF1YOd@5F5@12h_* zS0+ffW#}{K`yJe)-}PwuwA7o%q0)+9X7HE3Fqxrq+GkD1EVUckc=KOv$erD(7ZRF! zsbZ7P=V*D;$Z7p)w@zd>eVY)ke|cm0^2>>HybHeHdy**OmNAW~`Xu|8IR&y?qt@Pa znXtzssYfz1$3)7f&rr|TG}~h-+myWb%^_JSTxnC^FMKYfqnf$$$)zUKxCa8$llL%a zU5?e8E;-@-p3iCeXQ8?q4k|@jr=P6UoXoaw*TYW_v^L%f%!|MHza$u?Ph=9 zT=e_6b}j!~ZWG_;&!3BrwPemZwx(j&-8@^RiJNrfF9--a^@OhrTYcvK=`~Sa+rK+^ zFfdp?6??P)yJCW;pXj51)$(lu&F?es{Jt5qdcMlaGtWN@Mz37;>afMtv~Tl2|Fls) zcQ$RZXRGe!qJ`?>_jVkcdnJKQ;OVis88y?R|9o!#<8WtAvyNc;FFs|3pS?A|e0Rq5 z-IR+c=GFh>^f}hN)XrGLs$=$Vov+7&1#J`d$Zbz`sJ}6%_xq8=*z(}kog6N& zckRBlHK=&WbGhScH+CPoY|l68N|XSL+uG}q2bZuKdw2Yi`MD*kr%Jv>fm5-%rm3lK z;R}YEyI*eXGT7WEvBG?=`GURWK1$6KPwZ9sxYS-{?bk1>49pn~^VV%KIJ)AS90S9$ zvKL<#___1gnv9AGThEGUuj4Gf zED#=*m>V2dAUNYTN8xLSy6L~98W!`1)&2Xg@Vvz$GNrd}f`WMCj z`Kzt_W{}-6FF9pe(`?fhJ3svnvQcii!1~SRy^?0#DWfNmX-6lya^+nO&}h8KI`6xh z)y0$Rjm;S^?s~UuUQ=|+h2p2eH*J(11=?n;|ND5eb%|l%tn`#=vEOo~)NI4|rOZmd z^0sU~+j=>*n+(75mZU0&$6DtLrA>1A^sTT;%KVJ`JhjFp->btd^=Fi6

)X9y=4q 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 R$iTqB;OXk;vd$@?2>>J+q!$1H literal 0 HcmV?d00001 diff --git a/bin/Icons/ui/dpad_down.png b/bin/Icons/ui/dpad_down.png new file mode 100644 index 0000000000000000000000000000000000000000..dddcc0d479f410730f8b7c96cbf69278eb997c54 GIT binary patch literal 3618 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 literal 0 HcmV?d00001 diff --git a/bin/Icons/ui/dpad_left.png b/bin/Icons/ui/dpad_left.png new file mode 100644 index 0000000000000000000000000000000000000000..78beb6350ce0550f0d28dfd1c59ab6b6d6db1e64 GIT binary patch literal 3641 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 D`W+e2 literal 0 HcmV?d00001 diff --git a/bin/Icons/ui/dpad_up.png b/bin/Icons/ui/dpad_up.png new file mode 100644 index 0000000000000000000000000000000000000000..5bc87051e6205568f2b3e7a19275c9c6e2e9707b GIT binary patch literal 3655 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 HP6v+kqZpS@-0-+tV9ul|3{{^QH; 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 literal 0 HcmV?d00001 diff --git a/bin/Icons/ui/left_stick.png b/bin/Icons/ui/left_stick.png new file mode 100644 index 0000000000000000000000000000000000000000..101b99cb170ad267ae83d33153fbdfdf647af792 GIT binary patch literal 3888 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 Tf3tysfq}u()z4*}Q$iB}-!g!V literal 0 HcmV?d00001 diff --git a/bin/Icons/ui/new.png b/bin/Icons/ui/new.png new file mode 100644 index 0000000000000000000000000000000000000000..ac21f0e9e6ea9fe3343901c399147037aa6cbc95 GIT binary patch literal 19960 zcmeAS@N?(olHy`uVBq!ia0y~yV7~yu9Bd2>46`hbR5CCyuqAoByDEaktaqG=ZLqR47 zfddC*k~TCkiY(}zCa3trQsu%wgWW=xmNATi(GVCxAy6m5$-uy1aq*qqs?|dEuZ@lU zB|-eoMm`3H0~5=>$KIXtBYOSJou?TX7!G*q2|0mS+fI?t29e_e%v^*G12czi#6au5^02B(N<-uq=7|H1%zUi*YQ_5pw&zNP8`(bVPN?1FtKUto&t7IXfiM~u#`X;3@b)iurO#aw=NH8b`W52 z++gm=l2(z-Fumo!r3Isk#)0FX>{xU3LWQ2)KFPq~pt^t=5_+So(a->;gwb?Br*sf1 zF0At~*7hs|14BW<>UC)rYxB3xW@KP^@F2PAzmysS14Dvq@wv6Tq91LEOYUh0ttgA= zN9*nef@MB{-yD3A11H<8O2llh9nZtJ{p?N~sp?%B@4X5D!=TX*Z zXn=Z9qv>EY9gL;}P(?YK4o1@ffpl;olU;;?fj1X)E&xdBXno2snrlXL4Ja#&ri0OR zFq#fv^OU2l>e2FGv^*Fs56CYMR!w7JU|_Jx3Rgb7y${3$2j)m5?tFXD015zy3;(ab i

#xK`?^-9LK~aq@yp!H{mTP5IkM|T-G@yGywp=r$Q|N literal 0 HcmV?d00001 diff --git a/bin/Icons/ui/right_stick.png b/bin/Icons/ui/right_stick.png new file mode 100644 index 0000000000000000000000000000000000000000..374f589bf9c23971b215a74b08609ab0d69799c2 GIT binary patch literal 4178 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 Z^i}5kZzNToGB7YOc)I$ztaD0e0s!Q80m%RW literal 0 HcmV?d00001 diff --git a/bin/Icons/ui/save.png b/bin/Icons/ui/save.png new file mode 100644 index 0000000000000000000000000000000000000000..c1683f7223c5ece9bffc99de2b5051362e73a941 GIT binary patch literal 26456 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 T(o;J_&=KXHu6{1-oD!M<1E9%4 literal 0 HcmV?d00001 diff --git a/bin/Icons/ui/select.png b/bin/Icons/ui/select.png new file mode 100644 index 0000000000000000000000000000000000000000..b493aff682d18d34c7a09f898d5315fbe62b45a0 GIT binary patch literal 1813 zcmeAS@N?(olHy`uVBq!ia0y~yU`PRB4mJh`hJr^^Ll_v??s&R5hE&XXd%M0vCS8*K z!~4(g(zc$Q8&kCX&O2lANfVq{Cazl0>p0oVF=y$*48iRG8#7j~ZHdZSdsIFoS#?Fr zt!0bWs_Ke(t=zI)Q} zSCj~;yR7X=((h$wV++vvcIBw*@{1V{4z+Th&bxp4rjA&*##FDpB@30lOq*&vYr>q9 zB}@_$5)M0Kj$ODAFzxQMYd=3f4_tkCRHG^%LftG*RqzQ$GFfTFaV#%gZfuY^%#+RhM4Oc<|-r z<(c1RzLYUCH*cS;?w@w^>8{1aVAGc^kG@tlOMlX?x%LCbr` zE%Y)o&&A%#e|RskXQy4M%j*aMmdBaJK=FR_Kvd`pkY8rmt&RD~C;F{U*dd>%i z&+lwp%9OT`??AI=QpG*Xa)m4ZG#XY<+cx{`v#Y_u!Rt$3x6F+9H&mNbEW6+C^qJp` zci0m28%`_j7H8<`K9P9;`~NGe{+l%LFOl5;RQTLZ`*vtTi>2Ke(cyb7RLgX1Iv?V#j`zdQ`&X$^Pbsj zxB726Rc)KbVA$R5QX=v3WW=iPdp(Sc);P^*)yqE-&-#?>O;W`RHSY zd5j;-b>bxDSqc~qgem=6w)NY;u)X2Y_KELV3W_s+&r>Pf;>#fzD`^+#QgR}yI)25e z!tBFrC0`~BBGzpy?&xq)-@D%M^Yh=LN3xQh^)jsEn|}56*Y+^YIep9UtPPw0wf6sd*|&}(T?ui>*ghWC-T{9WW3FK8+*4|}+={GMfb_N1<}X`e$D2JB!+ zU|?`b$zK|D^Pbf+(dobTu8CUvZBa*!d5oIU)i+L2k-@>ipBd_ewX`IX_laCz7Nf`i zMSI?j7K6`=larI97d>^^V!3JWweVW`{+#xCHR6hA&=!>91)+)d|s&)j^~y6QQ7kEjlz=N=0&n9-I}@KFN@&a zxuFKDPg;I=TYPcHw2r!YTY5Y0%uF?E_!jPQIqJ7-nqA+~`b#xCeCL`o*x1_J|6jXv z=gxZfa-oU$y#qXNyRW;p{Zh~Eys4E{RaIOEmTOHt#oOxi&`j`V_fc&{ncL9}aU4w# zCcOV$xcyb?-1c?<53LQ+y3O5sXg327^Nd`x+3Ur-k6u?(R~Ns;x9wWRJae80Tnx&N y0yzf9ez=$zT3Y_B?_P^kfoLwBa%$~=Chvph^FB&kt7l+fVDNPHb6Mw<&;$Vg*?npN literal 0 HcmV?d00001 diff --git a/bin/Icons/ui/square.png b/bin/Icons/ui/square.png new file mode 100644 index 0000000000000000000000000000000000000000..0be20e14c9796487acbec23cfd4b516d70833794 GIT binary patch literal 3385 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 NgQu&X%Q~loCIA+9O`ZS% literal 0 HcmV?d00001 diff --git a/bin/Icons/ui/start.png b/bin/Icons/ui/start.png new file mode 100644 index 0000000000000000000000000000000000000000..3822fd85ff33829767a01fca014ea8ac39b11d37 GIT binary patch literal 2606 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 LtDnm{r-UW|-G%Sx literal 0 HcmV?d00001 diff --git a/bin/Icons/ui/triangle.png b/bin/Icons/ui/triangle.png new file mode 100644 index 0000000000000000000000000000000000000000..50ba8572d8747365eb280888e602c2eb97051432 GIT binary patch literal 3784 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 We^6`|&l&~>1_n=8KbLh*2~7Zcffqjj literal 0 HcmV?d00001 diff --git a/rpcs3/Emu/Cell/Modules/cellMsgDialog.cpp b/rpcs3/Emu/Cell/Modules/cellMsgDialog.cpp index c28d259d1c..b5cb274c18 100644 --- a/rpcs3/Emu/Cell/Modules/cellMsgDialog.cpp +++ b/rpcs3/Emu/Cell/Modules/cellMsgDialog.cpp @@ -2,6 +2,7 @@ #include "Emu/System.h" #include "Emu/IdManager.h" #include "Emu/Cell/PPUModule.h" +#include "Emu/RSX/GSRender.h" #include "cellSysutil.h" #include "cellMsgDialog.h" @@ -60,13 +61,6 @@ s32 cellMsgDialogOpen2(u32 type, vm::cptr msgString, vm::ptr(Emu.GetCallbacks().get_msg_dialog); - - if (!dlg) - { - return CELL_SYSUTIL_ERROR_BUSY; - } - if (_type.se_mute_on) { // TODO @@ -81,6 +75,33 @@ s32 cellMsgDialogOpen2(u32 type, vm::cptr msgString, vm::ptr()) + { + if (auto dlg = rsxthr->shell_open_message_dialog()) + { + dlg->show(msgString.get_ptr(), _type, [callback, userData](s32 status) + { + if (callback) + { + sysutil_register_cb([=](ppu_thread& ppu) -> s32 + { + callback(ppu, status, userData); + return CELL_OK; + }); + } + }); + + return CELL_OK; + } + } + + const auto dlg = fxm::import(Emu.GetCallbacks().get_msg_dialog); + + if (!dlg) + { + return CELL_SYSUTIL_ERROR_BUSY; + } + dlg->type = _type; dlg->on_close = [callback, userData, wptr = std::weak_ptr(dlg)](s32 status) @@ -213,6 +234,33 @@ s32 cellMsgDialogClose(f32 delay) { cellSysutil.warning("cellMsgDialogClose(delay=%f)", delay); + extern u64 get_system_time(); + const u64 wait_until = get_system_time() + static_cast(std::max(delay, 0.0f) * 1000); + + if (auto rsxthr = fxm::get()) + { + if (auto dlg = rsxthr->shell_get_current_dialog()) + { + thread_ctrl::spawn("cellMsgDialogClose() Thread", [=] + { + while (get_system_time() < wait_until) + { + if (Emu.IsStopped()) + return; + + if (rsxthr->shell_get_current_dialog() != dlg) + return; + + std::this_thread::sleep_for(1ms); + } + + dlg->close(); + }); + + return CELL_OK; + } + } + const auto dlg = fxm::get(); if (!dlg) @@ -220,10 +268,6 @@ s32 cellMsgDialogClose(f32 delay) return CELL_MSGDIALOG_ERROR_DIALOG_NOT_OPENED; } - extern u64 get_system_time(); - - const u64 wait_until = get_system_time() + static_cast(std::max(delay, 0.0f) * 1000); - thread_ctrl::spawn("cellMsgDialogClose() Thread", [=]() { while (dlg->state == MsgDialogState::Open && get_system_time() < wait_until) @@ -243,6 +287,14 @@ s32 cellMsgDialogAbort() { cellSysutil.warning("cellMsgDialogAbort()"); + if (auto rsxthr = fxm::get()) + { + if (rsxthr->shell_close_dialog()) + { + return CELL_OK; + } + } + const auto dlg = fxm::get(); if (!dlg) @@ -263,6 +315,15 @@ s32 cellMsgDialogProgressBarSetMsg(u32 progressBarIndex, vm::cptr msgStrin { cellSysutil.warning("cellMsgDialogProgressBarSetMsg(progressBarIndex=%d, msgString=%s)", progressBarIndex, msgString); + if (auto rsxthr = fxm::get()) + { + if (auto dlg2 = rsxthr->shell_get_current_dialog()) + { + if (auto casted = dynamic_cast(dlg2)) + return casted->progress_bar_set_message(progressBarIndex, msgString.get_ptr()); + } + } + const auto dlg = fxm::get(); if (!dlg) @@ -287,6 +348,15 @@ s32 cellMsgDialogProgressBarReset(u32 progressBarIndex) { cellSysutil.warning("cellMsgDialogProgressBarReset(progressBarIndex=%d)", progressBarIndex); + if (auto rsxthr = fxm::get()) + { + if (auto dlg2 = rsxthr->shell_get_current_dialog()) + { + if (auto casted = dynamic_cast(dlg2)) + return casted->progress_bar_reset(progressBarIndex); + } + } + const auto dlg = fxm::get(); if (!dlg) @@ -311,6 +381,15 @@ s32 cellMsgDialogProgressBarInc(u32 progressBarIndex, u32 delta) { cellSysutil.warning("cellMsgDialogProgressBarInc(progressBarIndex=%d, delta=%d)", progressBarIndex, delta); + if (auto rsxthr = fxm::get()) + { + if (auto dlg2 = rsxthr->shell_get_current_dialog()) + { + if (auto casted = dynamic_cast(dlg2)) + return casted->progress_bar_increment(progressBarIndex, (f32)delta); + } + } + const auto dlg = fxm::get(); if (!dlg) diff --git a/rpcs3/Emu/Cell/Modules/cellSaveData.cpp b/rpcs3/Emu/Cell/Modules/cellSaveData.cpp index 1c3615aac9..2f45e39e1b 100644 --- a/rpcs3/Emu/Cell/Modules/cellSaveData.cpp +++ b/rpcs3/Emu/Cell/Modules/cellSaveData.cpp @@ -355,18 +355,7 @@ static NEVER_INLINE s32 savedata_op(ppu_thread& ppu, u32 operation, u32 version, while (funcList) { // Display Save Data List asynchronously in the GUI thread. - atomic_t dlg_result(false); - - Emu.CallAfter([&]() - { - selected = Emu.GetCallbacks().get_save_dialog()->ShowSaveDataList(save_entries, focused, operation, listSet); - dlg_result = true; - }); - - while (!dlg_result) - { - thread_ctrl::wait_for(1000); - } + selected = Emu.GetCallbacks().get_save_dialog()->ShowSaveDataList(save_entries, focused, operation, listSet); // UI returns -1 for new save games if (selected == -1) diff --git a/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp b/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp index f7f1ee6112..46c4738ef3 100644 --- a/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp +++ b/rpcs3/Emu/Cell/Modules/sceNpTrophy.cpp @@ -534,9 +534,8 @@ error_code sceNpTrophyUnlockTrophy(u32 context, u32 handle, s32 trophyId, vm::pt sceNpTrophy.error("Failed to get info for trophy dialog. Error code %x", ret); *details = SceNpTrophyDetails(); } - Emu.CallAfter([det = *details, trophyIconData]() { - Emu.GetCallbacks().get_trophy_notification_dialog()->ShowTrophyNotification(det, trophyIconData); - }); + + Emu.GetCallbacks().get_trophy_notification_dialog()->ShowTrophyNotification(*details, trophyIconData); } return CELL_OK; diff --git a/rpcs3/Emu/RSX/D3D12/D3D12GSRender.cpp b/rpcs3/Emu/RSX/D3D12/D3D12GSRender.cpp index b1d3d6d729..5b34c42d2e 100644 --- a/rpcs3/Emu/RSX/D3D12/D3D12GSRender.cpp +++ b/rpcs3/Emu/RSX/D3D12/D3D12GSRender.cpp @@ -301,7 +301,7 @@ void D3D12GSRender::on_exit() return GSRender::on_exit(); } -void D3D12GSRender::do_local_task() +void D3D12GSRender::do_local_task(bool) { //TODO m_frame->clear_wm_events(); diff --git a/rpcs3/Emu/RSX/D3D12/D3D12GSRender.h b/rpcs3/Emu/RSX/D3D12/D3D12GSRender.h index 18a94718e3..7674cd13f5 100644 --- a/rpcs3/Emu/RSX/D3D12/D3D12GSRender.h +++ b/rpcs3/Emu/RSX/D3D12/D3D12GSRender.h @@ -173,7 +173,7 @@ private: protected: virtual void on_init_thread() override; virtual void on_exit() override; - virtual void do_local_task() override; + virtual void do_local_task(bool idle) override; virtual bool do_method(u32 cmd, u32 arg) override; virtual void end() override; virtual void flip(int buffer) override; diff --git a/rpcs3/Emu/RSX/GL/GLGSRender.cpp b/rpcs3/Emu/RSX/GL/GLGSRender.cpp index 7417ba8e81..be3fe8b014 100644 --- a/rpcs3/Emu/RSX/GL/GLGSRender.cpp +++ b/rpcs3/Emu/RSX/GL/GLGSRender.cpp @@ -32,6 +32,7 @@ GLGSRender::GLGSRender() : GSRender() m_vertex_cache.reset(new gl::weak_vertex_cache()); supports_multidraw = !g_cfg.video.strict_rendering_mode; + supports_native_ui = (bool)g_cfg.misc.use_native_interface; } extern CellGcmContextData current_context; @@ -614,9 +615,6 @@ void GLGSRender::on_init_thread() { GSRender::on_init_thread(); - m_frame->disable_wm_event_queue(); - m_frame->hide(); - gl::init(); //Enable adaptive vsync if vsync is requested @@ -756,16 +754,71 @@ void GLGSRender::on_init_thread() glEnable(GL_CLIP_DISTANCE0 + 5); m_depth_converter.create(); + m_ui_renderer.create(); m_gl_texture_cache.initialize(); m_thread_id = std::this_thread::get_id(); - m_shaders_cache->load(); + if (!supports_native_ui) + { + m_frame->disable_wm_event_queue(); + m_frame->hide(); - m_frame->enable_wm_event_queue(); - m_frame->show(); + m_shaders_cache->load(nullptr); + + m_frame->enable_wm_event_queue(); + m_frame->show(); + } + else + { + struct native_helper : gl::shader_cache::progress_dialog_helper + { + rsx::thread *owner = nullptr; + rsx::overlays::message_dialog *dlg = nullptr; + + native_helper(GLGSRender *ptr) : + owner(ptr) {} + + void create() override + { + MsgDialogType type = {}; + type.disable_cancel = true; + type.progress_bar_count = 1; + + dlg = owner->shell_open_message_dialog(); + dlg->show("Loading precompiled shaders from disk...", type, [](s32 status) + { + if (status != CELL_OK) + Emu.Stop(); + }); + } + + void update_msg(u32 processed, u32 entry_count) override + { + dlg->progress_bar_set_message(0, fmt::format("Loading pipeline object %u of %u", processed, entry_count)); + owner->flip(0); + } + + void inc_value(u32 value) override + { + dlg->progress_bar_increment(0, (f32)value); + owner->flip(0); + } + + void close() override + { + dlg->return_code = CELL_OK; + dlg->close(); + } + } + helper(this); + + m_frame->enable_wm_event_queue(); + m_shaders_cache->load(&helper); + } } + void GLGSRender::on_exit() { m_prog_buffer.clear(); @@ -826,6 +879,7 @@ void GLGSRender::on_exit() m_text_printer.close(); m_gl_texture_cache.destroy(); m_depth_converter.destroy(); + m_ui_renderer.destroy(); for (u32 i = 0; i < occlusion_query_count; ++i) { @@ -963,8 +1017,22 @@ void GLGSRender::load_program(u32 vertex_base, u32 vertex_count) m_program->use(); if (m_prog_buffer.check_cache_missed()) + { m_shaders_cache->store(pipeline_properties, vertex_program, fragment_program); + //Notify the user with HUD notification + if (!m_custom_ui) + { + //Create notification but do not draw it at this time. No need to spam flip requests + m_custom_ui = std::make_unique(); + } + else if (auto casted = dynamic_cast(m_custom_ui.get())) + { + //Probe the notification + casted->touch(); + } + } + u8 *buf; u32 vertex_state_offset; u32 vertex_constants_offset; @@ -1244,6 +1312,13 @@ void GLGSRender::flip(int buffer) gl::screen.clear(gl::buffers::color); m_flip_fbo.blit(gl::screen, screen_area, areai(aspect_ratio).flipped_vertical(), gl::buffers::color, gl::filter::linear); + if (m_custom_ui) + { + gl::screen.bind(); + glViewport(0, 0, m_frame->client_width(), m_frame->client_height()); + m_ui_renderer.run(m_frame->client_width(), m_frame->client_height(), 0, *m_custom_ui.get()); + } + if (g_cfg.video.overlay) { gl::screen.bind(); @@ -1334,7 +1409,7 @@ void GLGSRender::on_notify_memory_unmapped(u32 address_base, u32 size) } } -void GLGSRender::do_local_task() +void GLGSRender::do_local_task(bool idle) { m_frame->clear_wm_events(); @@ -1354,6 +1429,15 @@ void GLGSRender::do_local_task() lock.unlock(); q.cv.notify_one(); } + + if (m_custom_ui) + { + if (!in_begin_end && idle && native_ui_flip_request.load()) + { + native_ui_flip_request.store(false); + flip((s32)current_display_buffer); + } + } } work_item& GLGSRender::post_flush_request(u32 address, gl::texture_cache::thrashed_set& flush_data) @@ -1415,4 +1499,9 @@ void GLGSRender::get_occlusion_query_result(rsx::occlusion_query_info* query) glGetQueryObjectiv((GLuint)query->driver_handle, GL_QUERY_RESULT, &result); query->result += result; +} + +void GLGSRender::shell_do_cleanup() +{ + m_ui_renderer.remove_temp_resources(); } \ No newline at end of file diff --git a/rpcs3/Emu/RSX/GL/GLGSRender.h b/rpcs3/Emu/RSX/GL/GLGSRender.h index 8c3cac44bc..f99313fae7 100644 --- a/rpcs3/Emu/RSX/GL/GLGSRender.h +++ b/rpcs3/Emu/RSX/GL/GLGSRender.h @@ -294,6 +294,7 @@ private: gl::text_writer m_text_printer; gl::depth_convert_pass m_depth_converter; + gl::ui_overlay_renderer m_ui_renderer; std::mutex queue_guard; std::list work_queue; @@ -361,12 +362,14 @@ protected: void flip(int buffer) override; u64 timestamp() const override; - void do_local_task() override; + void do_local_task(bool idle) override; bool on_access_violation(u32 address, bool is_writing) override; void on_notify_memory_unmapped(u32 address_base, u32 size) override; void notify_tile_unbound(u32 tile) override; - virtual std::array, 4> copy_render_targets_to_memory() override; - virtual std::array, 2> copy_depth_stencil_buffer_to_memory() override; + std::array, 4> copy_render_targets_to_memory() override; + std::array, 2> copy_depth_stencil_buffer_to_memory() override; + + void shell_do_cleanup() override; }; diff --git a/rpcs3/Emu/RSX/GL/GLHelpers.cpp b/rpcs3/Emu/RSX/GL/GLHelpers.cpp index b5e4f08996..e892f72c31 100644 --- a/rpcs3/Emu/RSX/GL/GLHelpers.cpp +++ b/rpcs3/Emu/RSX/GL/GLHelpers.cpp @@ -557,4 +557,9 @@ namespace gl fmt::throw_exception("unknown primitive type" HERE); } } + + attrib_t vao::operator[](u32 index) const noexcept + { + return attrib_t(index); + } } diff --git a/rpcs3/Emu/RSX/GL/GLHelpers.h b/rpcs3/Emu/RSX/GL/GLHelpers.h index d687a1be3d..4884a76df1 100644 --- a/rpcs3/Emu/RSX/GL/GLHelpers.h +++ b/rpcs3/Emu/RSX/GL/GLHelpers.h @@ -58,6 +58,8 @@ namespace gl void enable_debugging(); capabilities& get_driver_caps(); + bool is_primitive_native(rsx::primitive_type in); + GLenum draw_mode(rsx::primitive_type in); class exception : public std::exception { @@ -489,6 +491,7 @@ namespace gl }; class vao; + class attrib_t; class buffer_pointer { @@ -1167,6 +1170,43 @@ namespace gl { return{ (vao*)this }; } + + attrib_t operator [] (u32 index) const noexcept; + }; + + class attrib_t + { + GLint m_location; + + public: + attrib_t(GLint location) + : m_location(location) + { + } + + GLint location() const + { + return m_location; + } + + void operator = (float rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib1f(location(), rhs); } + void operator = (double rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib1d(location(), rhs); } + + void operator = (const color1f& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib1f(location(), rhs.r); } + void operator = (const color1d& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib1d(location(), rhs.r); } + void operator = (const color2f& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib2f(location(), rhs.r, rhs.g); } + void operator = (const color2d& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib2d(location(), rhs.r, rhs.g); } + void operator = (const color3f& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib3f(location(), rhs.r, rhs.g, rhs.b); } + void operator = (const color3d& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib3d(location(), rhs.r, rhs.g, rhs.b); } + void operator = (const color4f& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib4f(location(), rhs.r, rhs.g, rhs.b, rhs.a); } + void operator = (const color4d& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib4d(location(), rhs.r, rhs.g, rhs.b, rhs.a); } + + void operator = (buffer_pointer& pointer) const + { + pointer.get_vao().enable_for_attribute(m_location); + glVertexAttribPointer(location(), pointer.size(), (GLenum)pointer.get_type(), pointer.normalize(), + pointer.stride(), (const void*)(size_t)pointer.offset()); + } }; class texture_view; @@ -1221,8 +1261,7 @@ namespace gl enum class format { - red = GL_RED, - r = GL_R, + r = GL_RED, rg = GL_RG, rgb = GL_RGB, rgba = GL_RGBA, @@ -1629,9 +1668,9 @@ namespace gl copy_from(nullptr, format, type, pixel_settings); } - void copy_from(void* dst, texture::format format, texture::type type) + void copy_from(void* src, texture::format format, texture::type type) { - copy_from(dst, format, type, pixel_unpack_settings()); + copy_from(src, format, type, pixel_unpack_settings()); } void copy_from(const buffer& buf, texture::format format, texture::type type) @@ -1872,9 +1911,6 @@ namespace gl settings& border_color(color4f value); }; - GLenum draw_mode(rsx::primitive_type in); - bool is_primitive_native(rsx::primitive_type in); - enum class indices_type { ubyte = GL_UNSIGNED_BYTE, @@ -2270,43 +2306,8 @@ namespace gl void operator = (const color3f& rhs) const { glProgramUniform3f(m_program.id(), location(), rhs.r, rhs.g, rhs.b); } void operator = (const color4i& rhs) const { glProgramUniform4i(m_program.id(), location(), rhs.r, rhs.g, rhs.b, rhs.a); } void operator = (const color4f& rhs) const { glProgramUniform4f(m_program.id(), location(), rhs.r, rhs.g, rhs.b, rhs.a); } - }; - - class attrib_t - { - GLuint m_program; - GLint m_location; - - public: - attrib_t(GLuint program, GLint location) - : m_program(program) - , m_location(location) - { - } - - GLint location() const - { - return m_location; - } - - void operator = (float rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib1f(location(), rhs); } - void operator = (double rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib1d(location(), rhs); } - - void operator = (const color1f& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib1f(location(), rhs.r); } - void operator = (const color1d& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib1d(location(), rhs.r); } - void operator = (const color2f& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib2f(location(), rhs.r, rhs.g); } - void operator = (const color2d& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib2d(location(), rhs.r, rhs.g); } - void operator = (const color3f& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib3f(location(), rhs.r, rhs.g, rhs.b); } - void operator = (const color3d& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib3d(location(), rhs.r, rhs.g, rhs.b); } - void operator = (const color4f& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib4f(location(), rhs.r, rhs.g, rhs.b, rhs.a); } - void operator = (const color4d& rhs) const { glDisableVertexAttribArray(location()); glVertexAttrib4d(location(), rhs.r, rhs.g, rhs.b, rhs.a); } - - void operator =(buffer_pointer& pointer) const - { - pointer.get_vao().enable_for_attribute(location()); - glVertexAttribPointer(location(), pointer.size(), (GLenum)pointer.get_type(), pointer.normalize(), - pointer.stride(), (const void*)(size_t)pointer.offset()); - } + void operator = (const areaf& rhs) const { glProgramUniform4f(m_program.id(), location(), rhs.x1, rhs.y1, rhs.x2, rhs.y2); } + void operator = (const areai& rhs) const { glProgramUniform4i(m_program.id(), location(), rhs.x1, rhs.y1, rhs.x2, rhs.y2); } }; class uniforms_t @@ -2471,12 +2472,12 @@ namespace gl attrib_t operator[](GLint location) { - return{ m_program.id(), location }; + return{ location }; } attrib_t operator[](const std::string &name) { - return{ m_program.id(), location(name) }; + return{ location(name) }; } void swap(attribs_t& attribs) diff --git a/rpcs3/Emu/RSX/GL/GLOverlays.h b/rpcs3/Emu/RSX/GL/GLOverlays.h index 451a5aaac6..428a15a867 100644 --- a/rpcs3/Emu/RSX/GL/GLOverlays.h +++ b/rpcs3/Emu/RSX/GL/GLOverlays.h @@ -2,6 +2,9 @@ #include "stdafx.h" #include "GLHelpers.h" +#include "../overlays.h" + +extern u64 get_system_time(); namespace gl { @@ -16,8 +19,14 @@ namespace gl gl::fbo fbo; + gl::vao m_vao; + gl::buffer m_vertex_data_buffer; + bool compiled = false; + u32 num_drawable_elements = 4; + GLenum primitives = GL_TRIANGLE_STRIP; + void create() { if (!compiled) @@ -37,6 +46,20 @@ namespace gl fbo.create(); + m_vertex_data_buffer.create(); + + int old_vao; + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &old_vao); + + m_vao.create(); + m_vao.bind(); + + m_vao.array_buffer = m_vertex_data_buffer; + auto ptr = buffer_pointer(&m_vao); + m_vao[0] = ptr; + + glBindVertexArray(old_vao); + compiled = true; } } @@ -50,6 +73,8 @@ namespace gl fs.remove(); fbo.remove(); + m_vao.remove(); + m_vertex_data_buffer.remove(); compiled = false; } @@ -61,12 +86,24 @@ namespace gl virtual void bind_resources() {} virtual void cleanup_resources() {} - virtual void emit_geometry() + virtual void upload_vertex_data(f32* data, u32 elements_count) { - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + elements_count <<= 2; + m_vertex_data_buffer.data(elements_count, data); } - virtual void run(u16 w, u16 h, GLuint target_texture, bool depth_target) + virtual void emit_geometry() + { + int old_vao; + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &old_vao); + + m_vao.bind(); + glDrawArrays(primitives, 0, num_drawable_elements); + + glBindVertexArray(old_vao); + } + + virtual void run(u16 w, u16 h, GLuint target_texture, bool depth_target, bool use_blending = false) { if (!compiled) { @@ -81,22 +118,32 @@ namespace gl GLboolean color_writes[4]; GLboolean depth_write; - glGetIntegerv(GL_FRAMEBUFFER_BINDING, &old_fbo); - glBindFramebuffer(GL_FRAMEBUFFER, fbo.id()); + GLint blend_src_rgb; + GLint blend_src_a; + GLint blend_dst_rgb; + GLint blend_dst_a; + GLint blend_eq_a; + GLint blend_eq_rgb; - if (depth_target) + if (target_texture) { - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, target_texture, 0); - glDrawBuffer(GL_NONE); - } - else - { - GLenum buffer = GL_COLOR_ATTACHMENT0; - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, target_texture, 0); - glDrawBuffers(1, &buffer); + glGetIntegerv(GL_FRAMEBUFFER_BINDING, &old_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, fbo.id()); + + if (depth_target) + { + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, target_texture, 0); + glDrawBuffer(GL_NONE); + } + else + { + GLenum buffer = GL_COLOR_ATTACHMENT0; + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, target_texture, 0); + glDrawBuffers(1, &buffer); + } } - if (glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) + if (!target_texture || glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE) { // Push rasterizer state glGetIntegerv(GL_VIEWPORT, viewport); @@ -111,6 +158,16 @@ namespace gl GLboolean blend_enabled = glIsEnabled(GL_BLEND); GLboolean stencil_test_enabled = glIsEnabled(GL_STENCIL_TEST); + if (use_blending) + { + glGetIntegerv(GL_BLEND_SRC_RGB, &blend_src_rgb); + glGetIntegerv(GL_BLEND_SRC_ALPHA, &blend_src_a); + glGetIntegerv(GL_BLEND_DST_RGB, &blend_dst_rgb); + glGetIntegerv(GL_BLEND_DST_ALPHA, &blend_dst_a); + glGetIntegerv(GL_BLEND_EQUATION_RGB, &blend_eq_rgb); + glGetIntegerv(GL_BLEND_EQUATION_ALPHA, &blend_eq_a); + } + // Set initial state glViewport(0, 0, w, h); glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); @@ -122,9 +179,21 @@ namespace gl if (scissor_enabled) glDisable(GL_SCISSOR_TEST); if (cull_face_enabled) glDisable(GL_CULL_FACE); - if (blend_enabled) glDisable(GL_BLEND); if (stencil_test_enabled) glDisable(GL_STENCIL_TEST); + if (use_blending) + { + if (!blend_enabled) + glEnable(GL_BLEND); + + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glBlendEquation(GL_FUNC_ADD); + } + else if (blend_enabled) + { + glDisable(GL_BLEND); + } + // Render program_handle.use(); on_load(); @@ -132,12 +201,16 @@ namespace gl emit_geometry(); // Clean up - if (depth_target) - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); - else - glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + if (target_texture) + { + if (depth_target) + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); + else + glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, 0, 0); + + glBindFramebuffer(GL_FRAMEBUFFER, old_fbo); + } - glBindFramebuffer(GL_FRAMEBUFFER, old_fbo); glUseProgram((GLuint)program); glViewport(viewport[0], viewport[1], viewport[2], viewport[3]); @@ -148,8 +221,20 @@ namespace gl if (!depth_test_enabled) glDisable(GL_DEPTH_TEST); if (scissor_enabled) glEnable(GL_SCISSOR_TEST); if (cull_face_enabled) glEnable(GL_CULL_FACE); - if (blend_enabled) glEnable(GL_BLEND); if (stencil_test_enabled) glEnable(GL_STENCIL_TEST); + + if (use_blending) + { + if (!blend_enabled) + glDisable(GL_BLEND); + + glBlendFuncSeparate(blend_src_rgb, blend_dst_rgb, blend_src_a, blend_dst_a); + glBlendEquationSeparate(blend_eq_rgb, blend_eq_a); + } + else if (blend_enabled) + { + glEnable(GL_BLEND); + } } else { @@ -237,4 +322,251 @@ namespace gl overlay_pass::run(w, h, target, false); } }; -} \ No newline at end of file + + struct ui_overlay_renderer : public overlay_pass + { + u32 num_elements = 0; + std::vector> resources; + std::unordered_map> temp_image_cache; + std::unordered_map> font_cache; + bool is_font_draw = false; + + ui_overlay_renderer() + { + vs_src = + { + "#version 420\n\n" + "layout(location=0) in vec4 in_pos;\n" + "layout(location=0) out vec2 tc0;\n" + "layout(location=1) out vec4 clip_rect;\n" + "uniform vec4 ui_scale;\n" + "uniform vec4 clip_bounds;\n" + "\n" + "void main()\n" + "{\n" + " tc0.xy = in_pos.zw;\n" + " clip_rect = (clip_bounds * ui_scale.zwzw);\n" + " clip_rect.yw = ui_scale.yy - clip_rect.wy; //invert y axis\n" + " vec4 pos = vec4((in_pos.xy * ui_scale.zw) / ui_scale.xy, 0., 1.);\n" + " pos.y = (1. - pos.y); //invert y axis\n" + " gl_Position = (pos + pos) - 1.;\n" + "}\n" + }; + + fs_src = + { + "#version 420\n\n" + "layout(binding=31) uniform sampler2D fs0;\n" + "layout(location=0) in vec2 tc0;\n" + "layout(location=1) in vec4 clip_rect;\n" + "layout(location=0) out vec4 ocol;\n" + "uniform vec4 color;\n" + "uniform float time;\n" + "uniform int read_texture;\n" + "uniform int pulse_glow;\n" + "uniform int clip_region;\n" + "\n" + "void main()\n" + "{\n" + " if (clip_region != 0)\n" + " {" + " if (gl_FragCoord.x < clip_rect.x || gl_FragCoord.x > clip_rect.z ||\n" + " gl_FragCoord.y < clip_rect.y || gl_FragCoord.y > clip_rect.w)\n" + " {\n" + " discard;\n" + " return;\n" + " }\n" + " }\n" + "\n" + " vec4 diff_color = color;\n" + " if (pulse_glow != 0)\n" + " diff_color.a *= (sin(time) + 1.f) * 0.5f;\n" + "\n" + " if (read_texture != 0)\n" + " ocol = texture(fs0, tc0) * diff_color;\n" + " else\n" + " ocol = diff_color;\n" + "}\n" + }; + } + + gl::texture* load_simple_image(rsx::overlays::image_info* desc, bool temp_resource) + { + auto tex = std::make_unique(gl::texture::target::texture2D); + tex->create(); + tex->config() + .size({ desc->w, desc->h }) + .format(gl::texture::format::rgba) + .type(gl::texture::type::uint_8_8_8_8) + .wrap(gl::texture::wrap::clamp_to_border, gl::texture::wrap::clamp_to_border, gl::texture::wrap::clamp_to_border) + .swizzle(gl::texture::channel::a, gl::texture::channel::b, gl::texture::channel::g, gl::texture::channel::r) + .filter(gl::min_filter::linear, gl::filter::linear) + .apply(); + tex->copy_from(desc->data, gl::texture::format::rgba, gl::texture::type::uint_8_8_8_8); + + if (!temp_resource) + { + resources.push_back(std::move(tex)); + } + else + { + u64 key = (u64)desc; + temp_image_cache[key] = std::move(tex); + } + + return resources.back().get(); + } + + void create() + { + overlay_pass::create(); + + rsx::overlays::resource_config configuration; + configuration.load_files(); + + for (const auto &res : configuration.texture_raw_data) + { + load_simple_image(res.get(), false); + } + + configuration.free_resources(); + } + + void destroy() + { + temp_image_cache.clear(); + resources.clear(); + font_cache.clear(); + overlay_pass::destroy(); + } + + void remove_temp_resources() + { + temp_image_cache.clear(); + } + + gl::texture* find_font(rsx::overlays::font *font) + { + u64 key = (u64)font; + auto found = font_cache.find(key); + if (found != font_cache.end()) + return found->second.get(); + + //Create font file + auto tex = std::make_unique(gl::texture::target::texture2D); + tex->create(); + tex->config() + .size({ (int)font->width, (int)font->height }) + .format(gl::texture::format::r) + .type(gl::texture::type::ubyte) + .internal_format(gl::texture::internal_format::r8) + .wrap(gl::texture::wrap::clamp_to_border, gl::texture::wrap::clamp_to_border, gl::texture::wrap::clamp_to_border) + .swizzle(gl::texture::channel::r, gl::texture::channel::r, gl::texture::channel::r, gl::texture::channel::r) + .filter(gl::min_filter::linear, gl::filter::linear) + .apply(); + tex->copy_from(font->glyph_data.data(), gl::texture::format::r, gl::texture::type::ubyte); + + auto result = tex.get(); + font_cache[key] = std::move(tex); + + return result; + } + + gl::texture* find_temp_image(rsx::overlays::image_info *desc) + { + auto key = (u64)desc; + auto cached = temp_image_cache.find(key); + if (cached != temp_image_cache.end()) + { + return cached->second.get(); + } + else + { + return load_simple_image(desc, true); + } + } + + void emit_geometry() override + { + if (!is_font_draw) + { + overlay_pass::emit_geometry(); + } + else + { + int num_quads = num_drawable_elements / 4; + std::vector firsts; + std::vector counts; + + firsts.resize(num_quads); + counts.resize(num_quads); + + for (int n = 0; n < num_quads; ++n) + { + firsts[n] = (n * 4); + counts[n] = 4; + } + + int old_vao; + glGetIntegerv(GL_VERTEX_ARRAY_BINDING, &old_vao); + + m_vao.bind(); + glMultiDrawArrays(GL_TRIANGLE_STRIP, firsts.data(), counts.data(), num_quads); + + glBindVertexArray(old_vao); + } + } + + void run(u16 w, u16 h, GLuint target, rsx::overlays::user_interface& ui) + { + program_handle.uniforms["ui_scale"] = color4f((f32)ui.virtual_width, (f32)ui.virtual_height, 1.f, 1.f); + program_handle.uniforms["time"] = (f32)(get_system_time() / 1000) * 0.005f; + for (auto &cmd : ui.get_compiled().draw_commands) + { + upload_vertex_data((f32*)cmd.second.data(), (u32)cmd.second.size() * 4u); + num_drawable_elements = (u32)cmd.second.size(); + is_font_draw = false; + GLint texture_exists = GL_TRUE; + + glActiveTexture(GL_TEXTURE31); + switch (cmd.first.texture_ref) + { + case rsx::overlays::image_resource_id::game_icon: + case rsx::overlays::image_resource_id::backbuffer: + //TODO + case rsx::overlays::image_resource_id::none: + { + texture_exists = GL_FALSE; + glBindTexture(GL_TEXTURE_2D, GL_NONE); + break; + } + case rsx::overlays::image_resource_id::raw_image: + { + glBindTexture(GL_TEXTURE_2D, find_temp_image((rsx::overlays::image_info*)cmd.first.external_data_ref)->id()); + break; + } + case rsx::overlays::image_resource_id::font_file: + { + is_font_draw = true; + glBindTexture(GL_TEXTURE_2D, find_font(cmd.first.font_ref)->id()); + break; + } + default: + { + glBindTexture(GL_TEXTURE_2D, resources[cmd.first.texture_ref - 1]->id()); + break; + } + } + + program_handle.uniforms["color"] = cmd.first.color; + program_handle.uniforms["read_texture"] = texture_exists; + program_handle.uniforms["pulse_glow"] = (s32)cmd.first.pulse_glow; + program_handle.uniforms["clip_region"] = (s32)cmd.first.clip_region; + program_handle.uniforms["clip_bounds"] = cmd.first.clip_rect; + overlay_pass::run(w, h, target, false, true); + } + + ui.update(); + } + }; +} diff --git a/rpcs3/Emu/RSX/GL/GLRenderTargets.cpp b/rpcs3/Emu/RSX/GL/GLRenderTargets.cpp index 10e00a58d1..700d2c8ee1 100644 --- a/rpcs3/Emu/RSX/GL/GLRenderTargets.cpp +++ b/rpcs3/Emu/RSX/GL/GLRenderTargets.cpp @@ -31,13 +31,13 @@ color_format rsx::internals::surface_color_format_to_gl(rsx::surface_color_forma return{ ::gl::texture::type::f32, ::gl::texture::format::rgba, true, 4, 4 }; case rsx::surface_color_format::b8: - return{ ::gl::texture::type::ubyte, ::gl::texture::format::red, false, 1, 1 }; + return{ ::gl::texture::type::ubyte, ::gl::texture::format::r, false, 1, 1 }; case rsx::surface_color_format::g8b8: return{ ::gl::texture::type::ubyte, ::gl::texture::format::rg, false, 2, 1 }; case rsx::surface_color_format::x32: - return{ ::gl::texture::type::f32, ::gl::texture::format::red, true, 1, 4 }; + return{ ::gl::texture::type::f32, ::gl::texture::format::r, true, 1, 4 }; default: LOG_ERROR(RSX, "Surface color buffer: Unsupported surface color format (0x%x)", (u32)color_format); diff --git a/rpcs3/Emu/RSX/GL/GLTextureCache.h b/rpcs3/Emu/RSX/GL/GLTextureCache.h index ca54ff8077..e028a43239 100644 --- a/rpcs3/Emu/RSX/GL/GLTextureCache.h +++ b/rpcs3/Emu/RSX/GL/GLTextureCache.h @@ -142,7 +142,6 @@ namespace gl switch (fmt_) { - case texture::format::red: case texture::format::r: break; case texture::format::rg: diff --git a/rpcs3/Emu/RSX/RSXThread.cpp b/rpcs3/Emu/RSX/RSXThread.cpp index bb65cb1bb4..ae86c25072 100644 --- a/rpcs3/Emu/RSX/RSXThread.cpp +++ b/rpcs3/Emu/RSX/RSXThread.cpp @@ -481,9 +481,6 @@ namespace rsx // TODO: exit condition while (!Emu.IsStopped()) { - //Execute backend-local tasks first - do_local_task(); - //Wait for external pause events if (external_interrupt_lock.load()) { @@ -491,6 +488,9 @@ namespace rsx while (external_interrupt_lock.load()) _mm_pause(); } + //Execute backend-local tasks first + do_local_task(ctrl->put.load() == internal_get.load()); + //Set up restore state if needed if (sync_point_request) { @@ -2179,7 +2179,13 @@ namespace rsx void thread::pause() { external_interrupt_lock.store(true); - while (!external_interrupt_ack.load()) _mm_pause(); + while (!external_interrupt_ack.load()) + { + if (Emu.IsStopped()) + break; + + _mm_pause(); + } external_interrupt_ack.store(false); } @@ -2187,4 +2193,67 @@ namespace rsx { external_interrupt_lock.store(false); } + + //TODO: Move these helpers into a better class dedicated to shell interface handling (use idm?) + //They are not dependent on rsx at all + rsx::overlays::save_dialog* thread::shell_open_save_dialog() + { + if (supports_native_ui) + { + auto ptr = new rsx::overlays::save_dialog(); + m_custom_ui.reset(ptr); + return ptr; + } + else + { + return nullptr; + } + } + + rsx::overlays::message_dialog* thread::shell_open_message_dialog() + { + if (supports_native_ui) + { + auto ptr = new rsx::overlays::message_dialog(); + m_custom_ui.reset(ptr); + return ptr; + } + else + { + return nullptr; + } + } + + rsx::overlays::trophy_notification* thread::shell_open_trophy_notification() + { + if (supports_native_ui) + { + auto ptr = new rsx::overlays::trophy_notification(); + m_custom_ui.reset(ptr); + return ptr; + } + else + { + return nullptr; + } + } + + rsx::overlays::user_interface* thread::shell_get_current_dialog() + { + //TODO: Only get dialog type interfaces + return m_custom_ui.get(); + } + + bool thread::shell_close_dialog() + { + //TODO: Only get dialog type interfaces + if (m_custom_ui) + { + m_invalidated_ui = std::move(m_custom_ui); + shell_do_cleanup(); + return true; + } + + return false; + } } diff --git a/rpcs3/Emu/RSX/RSXThread.h b/rpcs3/Emu/RSX/RSXThread.h index 36d3845c48..c836c469d8 100644 --- a/rpcs3/Emu/RSX/RSXThread.h +++ b/rpcs3/Emu/RSX/RSXThread.h @@ -12,6 +12,7 @@ #include "RSXFragmentProgram.h" #include "rsx_methods.h" #include "rsx_utils.h" +#include "overlays.h" #include #include "Utilities/Thread.h" @@ -216,6 +217,7 @@ namespace rsx bool skip_frame = false; bool supports_multidraw = false; + bool supports_native_ui = false; //occlusion query bool zcull_surface_active = false; @@ -230,6 +232,9 @@ namespace rsx rsx::gcm_framebuffer_info m_depth_surface_info; bool framebuffer_status_valid = false; + std::unique_ptr m_custom_ui; + std::unique_ptr m_invalidated_ui; + public: RsxDmaControl* ctrl = nullptr; atomic_t internal_get{ 0 }; @@ -237,6 +242,9 @@ namespace rsx atomic_t external_interrupt_lock{ false }; atomic_t external_interrupt_ack{ false }; + //native UI interrupts + atomic_t native_ui_flip_request{ false }; + GcmTileInfo tiles[limits::tiles_count]; GcmZcullInfo zculls[limits::zculls_count]; @@ -328,8 +336,9 @@ namespace rsx /** * Execute a backend local task queue + * Idle argument checks that the FIFO queue is in an idle state */ - virtual void do_local_task() {} + virtual void do_local_task(bool idle) {} public: virtual std::string get_name() const override; @@ -419,6 +428,8 @@ namespace rsx public: //std::future add_internal_task(std::function callback); //void invoke(std::function callback); + void add_user_interface(std::shared_ptr iface); + void remove_user_interface(); /** * Fill buffer with 4x4 scale offset matrix. @@ -484,5 +495,14 @@ namespace rsx void pause(); void unpause(); + + //HLE vsh stuff + //TODO: Move into a separate helper + virtual rsx::overlays::save_dialog* shell_open_save_dialog(); + virtual rsx::overlays::message_dialog* shell_open_message_dialog(); + virtual rsx::overlays::trophy_notification* shell_open_trophy_notification(); + virtual rsx::overlays::user_interface* shell_get_current_dialog(); + virtual bool shell_close_dialog(); + virtual void shell_do_cleanup(){} }; } diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.cpp b/rpcs3/Emu/RSX/VK/VKGSRender.cpp index 2edccd8d43..8293750951 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.cpp +++ b/rpcs3/Emu/RSX/VK/VKGSRender.cpp @@ -646,25 +646,30 @@ VKGSRender::VKGSRender() : GSRender() for (u32 i = 0; i < m_swap_chain->get_swap_image_count(); ++i) { + VkClearColorValue clear_color{}; + VkImageSubresourceRange range = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; + vk::change_image_layout(*m_current_command_buffer, m_swap_chain->get_swap_chain_image(i), VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, - vk::get_image_subresource_range(0, 0, 1, 1, VK_IMAGE_ASPECT_COLOR_BIT)); + range); - VkClearColorValue clear_color{}; - auto range = vk::get_image_subresource_range(0, 0, 1, 1, VK_IMAGE_ASPECT_COLOR_BIT); vkCmdClearColorImage(*m_current_command_buffer, m_swap_chain->get_swap_chain_image(i), VK_IMAGE_LAYOUT_GENERAL, &clear_color, 1, &range); vk::change_image_layout(*m_current_command_buffer, m_swap_chain->get_swap_chain_image(i), VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, - vk::get_image_subresource_range(0, 0, 1, 1, VK_IMAGE_ASPECT_COLOR_BIT)); + range); } m_current_frame = &frame_context_storage[0]; m_texture_cache.initialize((*m_device), m_memory_type_mapping, m_optimal_tiling_supported_formats, m_swap_chain->get_present_queue(), - m_texture_upload_buffer_ring_info, m_texture_upload_buffer_ring_info.heap.get()); + m_texture_upload_buffer_ring_info); + + m_ui_renderer.reset(new vk::ui_overlay_renderer()); + m_ui_renderer->create(*m_current_command_buffer, m_memory_type_mapping, m_texture_upload_buffer_ring_info); supports_multidraw = !g_cfg.video.strict_rendering_mode; + supports_native_ui = (bool)g_cfg.misc.use_native_interface; } VKGSRender::~VKGSRender() @@ -743,6 +748,10 @@ VKGSRender::~VKGSRender() //Overlay text handler m_text_writer.reset(); + //Overlay UI renderer + m_ui_renderer->destroy(); + m_ui_renderer.reset(); + //RGBA->depth cast helper m_depth_converter->destroy(); m_depth_converter.reset(); @@ -1597,13 +1606,63 @@ void VKGSRender::on_init_thread() GSRender::on_init_thread(); rsx_thread = std::this_thread::get_id(); - m_frame->disable_wm_event_queue(); - m_frame->hide(); + if (!supports_native_ui) + { + m_frame->disable_wm_event_queue(); + m_frame->hide(); + m_shaders_cache->load(nullptr, *m_device, pipeline_layout); + m_frame->enable_wm_event_queue(); + m_frame->show(); + } + else + { + struct native_helper : vk::shader_cache::progress_dialog_helper + { + rsx::thread *owner = nullptr; + rsx::overlays::message_dialog *dlg = nullptr; - m_shaders_cache->load(*m_device, pipeline_layout); + native_helper(VKGSRender *ptr) : + owner(ptr) {} - m_frame->enable_wm_event_queue(); - m_frame->show(); + void create() override + { + MsgDialogType type = {}; + type.disable_cancel = true; + type.progress_bar_count = 1; + + dlg = owner->shell_open_message_dialog(); + dlg->show("Loading precompiled shaders from disk...", type, [](s32 status) + { + if (status != CELL_OK) + Emu.Stop(); + }); + } + + void update_msg(u32 processed, u32 entry_count) override + { + dlg->progress_bar_set_message(0, fmt::format("Loading pipeline object %u of %u", processed, entry_count)); + owner->flip(0); + } + + void inc_value(u32 value) override + { + dlg->progress_bar_increment(0, (f32)value); + owner->flip(0); + } + + void close() override + { + dlg->return_code = CELL_OK; + dlg->close(); + } + } + helper(this); + + //TODO: Handle window resize messages during loading on GPUs without OUT_OF_DATE_KHR support + m_frame->disable_wm_event_queue(); + m_shaders_cache->load(&helper, *m_device, pipeline_layout); + m_frame->enable_wm_event_queue(); + } } void VKGSRender::on_exit() @@ -1951,6 +2010,7 @@ void VKGSRender::process_swap_request(frame_context_t *ctx, bool free_resources) } m_depth_converter->free_resources(); + m_ui_renderer->free_resources(); ctx->buffer_views_to_clean.clear(); ctx->samplers_to_clean.clear(); @@ -1975,8 +2035,16 @@ void VKGSRender::process_swap_request(frame_context_t *ctx, bool free_resources) ctx->swap_command_buffer = nullptr; } -void VKGSRender::do_local_task() +void VKGSRender::do_local_task(bool idle) { + //TODO: Guard this + if (m_overlay_cleanup_requests.size()) + { + flush_command_queue(true); + m_ui_renderer->remove_temp_resources(); + m_overlay_cleanup_requests.clear(); + } + if (m_flush_commands) { std::lock_guard lock(m_flush_queue_mutex); @@ -2082,6 +2150,16 @@ void VKGSRender::do_local_task() } #endif + + if (m_custom_ui) + { + if (!in_begin_end && native_ui_flip_request.load()) + { + native_ui_flip_request.store(false); + flush_command_queue(true); + flip((s32)current_display_buffer); + } + } } bool VKGSRender::do_method(u32 cmd, u32 arg) @@ -2255,8 +2333,22 @@ void VKGSRender::load_program(u32 vertex_count, u32 vertex_base) m_program = m_prog_buffer->getGraphicPipelineState(vertex_program, fragment_program, properties, *m_device, pipeline_layout).get(); if (m_prog_buffer->check_cache_missed()) + { m_shaders_cache->store(properties, vertex_program, fragment_program); + //Notify the user with HUD notification + if (!m_custom_ui) + { + //Create notification but do not draw it at this time. No need to spam flip requests + m_custom_ui = std::make_unique(); + } + else if (auto casted = dynamic_cast(m_custom_ui.get())) + { + //Probe the notification + casted->touch(); + } + } + vk::leave_uninterruptible(); const size_t fragment_constants_sz = m_prog_buffer->get_fragment_constants_buffer_size(fragment_program); @@ -2740,14 +2832,14 @@ void VKGSRender::reinitialize_swapchain() { vk::change_image_layout(*m_current_command_buffer, m_swap_chain->get_swap_chain_image(i), VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, - vk::get_image_subresource_range(0, 0, 1, 1, VK_IMAGE_ASPECT_COLOR_BIT)); + { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); VkClearColorValue clear_color{}; auto range = vk::get_image_subresource_range(0, 0, 1, 1, VK_IMAGE_ASPECT_COLOR_BIT); vkCmdClearColorImage(*m_current_command_buffer, m_swap_chain->get_swap_chain_image(i), VK_IMAGE_LAYOUT_GENERAL, &clear_color, 1, &range); vk::change_image_layout(*m_current_command_buffer, m_swap_chain->get_swap_chain_image(i), VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, - vk::get_image_subresource_range(0, 0, 1, 1, VK_IMAGE_ASPECT_COLOR_BIT)); + { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }); } //Will have to block until rendering is completed @@ -2827,9 +2919,6 @@ void VKGSRender::flip(int buffer) u32 buffer_width = display_buffers[buffer].width; u32 buffer_height = display_buffers[buffer].height; - u32 buffer_pitch = display_buffers[buffer].pitch; - - areai screen_area = coordi({}, { (int)buffer_width, (int)buffer_height }); coordi aspect_ratio; @@ -2905,22 +2994,49 @@ void VKGSRender::flip(int buffer) //Blit contents to screen.. vk::image* image_to_flip = nullptr; - if (std::get<1>(m_rtts.m_bound_render_targets[0]) != nullptr) - image_to_flip = std::get<1>(m_rtts.m_bound_render_targets[0]); - else if (std::get<1>(m_rtts.m_bound_render_targets[1]) != nullptr) - image_to_flip = std::get<1>(m_rtts.m_bound_render_targets[1]); + rsx::tiled_region buffer_region = get_tiled_address(display_buffers[buffer].offset, CELL_GCM_LOCATION_LOCAL); + u32 absolute_address = buffer_region.address + buffer_region.base; + + if (auto render_target_texture = m_rtts.get_texture_from_render_target_if_applicable(absolute_address)) + { + image_to_flip = render_target_texture; + } + else if (auto surface = m_texture_cache.find_texture_from_dimensions(absolute_address)) + { + //Hack - this should be the first location to check for output + //The render might have been done offscreen or in software and a blit used to display + image_to_flip = surface->get_raw_texture(); + } VkImage target_image = m_swap_chain->get_swap_chain_image(m_current_frame->present_image); if (image_to_flip) { - vk::copy_scaled_image(*m_current_command_buffer, image_to_flip->value, target_image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, + VkImageLayout target_layout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; + VkImageSubresourceRange range = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; + + if (aspect_ratio.x) + { + VkClearColorValue clear_black {}; + vk::change_image_layout(*m_current_command_buffer, target_image, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, range); + vkCmdClearColorImage(*m_current_command_buffer, target_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, &clear_black, 1, &range); + + target_layout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; + } + + vk::copy_scaled_image(*m_current_command_buffer, image_to_flip->value, target_image, image_to_flip->current_layout, target_layout, 0, 0, image_to_flip->width(), image_to_flip->height(), aspect_ratio.x, aspect_ratio.y, aspect_ratio.width, aspect_ratio.height, 1, VK_IMAGE_ASPECT_COLOR_BIT, false); + + if (target_layout != VK_IMAGE_LAYOUT_PRESENT_SRC_KHR) + { + vk::change_image_layout(*m_current_command_buffer, target_image, target_layout, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, range); + } } else { //No draw call was issued! - VkImageSubresourceRange range = vk::get_image_subresource_range(0, 0, 1, 1, VK_IMAGE_ASPECT_COLOR_BIT); - VkClearColorValue clear_black = { 0 }; + //TODO: Upload raw bytes from cpu for rendering + VkImageSubresourceRange range = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; + VkClearColorValue clear_black {}; vk::change_image_layout(*m_current_command_buffer, m_swap_chain->get_swap_chain_image(m_current_frame->present_image), VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_IMAGE_LAYOUT_GENERAL, range); vkCmdClearColorImage(*m_current_command_buffer, m_swap_chain->get_swap_chain_image(m_current_frame->present_image), VK_IMAGE_LAYOUT_GENERAL, &clear_black, 1, &range); vk::change_image_layout(*m_current_command_buffer, m_swap_chain->get_swap_chain_image(m_current_frame->present_image), VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, range); @@ -2928,10 +3044,10 @@ void VKGSRender::flip(int buffer) std::unique_ptr direct_fbo; std::vector> swap_image_view; - if (g_cfg.video.overlay) + if (g_cfg.video.overlay || m_custom_ui) { //Change the image layout whilst setting up a dependency on waiting for the blit op to finish before we start writing - auto subres = vk::get_image_subresource_range(0, 0, 1, 1, VK_IMAGE_ASPECT_COLOR_BIT); + VkImageSubresourceRange subres = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; VkImageMemoryBarrier barrier = {}; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; @@ -2966,19 +3082,27 @@ void VKGSRender::flip(int buffer) direct_fbo.reset(new vk::framebuffer_holder(*m_device, single_target_pass, m_client_width, m_client_height, std::move(swap_image_view))); } - m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 0, direct_fbo->width(), direct_fbo->height(), "draw calls: " + std::to_string(m_draw_calls)); - m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 18, direct_fbo->width(), direct_fbo->height(), "draw call setup: " + std::to_string(m_setup_time) + "us"); - m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 36, direct_fbo->width(), direct_fbo->height(), "vertex upload time: " + std::to_string(m_vertex_upload_time) + "us"); - m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 54, direct_fbo->width(), direct_fbo->height(), "texture upload time: " + std::to_string(m_textures_upload_time) + "us"); - m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 72, direct_fbo->width(), direct_fbo->height(), "draw call execution: " + std::to_string(m_draw_time) + "us"); - m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 90, direct_fbo->width(), direct_fbo->height(), "submit and flip: " + std::to_string(m_flip_time) + "us"); + if (m_custom_ui) + { + m_ui_renderer->run(*m_current_command_buffer, direct_fbo->width(), direct_fbo->height(), direct_fbo.get(), single_target_pass, m_memory_type_mapping, m_texture_upload_buffer_ring_info, *m_custom_ui); + } - auto num_dirty_textures = m_texture_cache.get_unreleased_textures_count(); - auto texture_memory_size = m_texture_cache.get_texture_memory_in_use() / (1024 * 1024); - auto tmp_texture_memory_size = m_texture_cache.get_temporary_memory_in_use() / (1024 * 1024); - m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 126, direct_fbo->width(), direct_fbo->height(), "Unreleased textures: " + std::to_string(num_dirty_textures)); - m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 144, direct_fbo->width(), direct_fbo->height(), "Texture cache memory: " + std::to_string(texture_memory_size) + "M"); - m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 162, direct_fbo->width(), direct_fbo->height(), "Temporary texture memory: " + std::to_string(tmp_texture_memory_size) + "M"); + if (g_cfg.video.overlay) + { + m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 0, direct_fbo->width(), direct_fbo->height(), "draw calls: " + std::to_string(m_draw_calls)); + m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 18, direct_fbo->width(), direct_fbo->height(), "draw call setup: " + std::to_string(m_setup_time) + "us"); + m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 36, direct_fbo->width(), direct_fbo->height(), "vertex upload time: " + std::to_string(m_vertex_upload_time) + "us"); + m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 54, direct_fbo->width(), direct_fbo->height(), "texture upload time: " + std::to_string(m_textures_upload_time) + "us"); + m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 72, direct_fbo->width(), direct_fbo->height(), "draw call execution: " + std::to_string(m_draw_time) + "us"); + m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 90, direct_fbo->width(), direct_fbo->height(), "submit and flip: " + std::to_string(m_flip_time) + "us"); + + auto num_dirty_textures = m_texture_cache.get_unreleased_textures_count(); + auto texture_memory_size = m_texture_cache.get_texture_memory_in_use() / (1024 * 1024); + auto tmp_texture_memory_size = m_texture_cache.get_temporary_memory_in_use() / (1024 * 1024); + m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 126, direct_fbo->width(), direct_fbo->height(), "Unreleased textures: " + std::to_string(num_dirty_textures)); + m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 144, direct_fbo->width(), direct_fbo->height(), "Texture cache memory: " + std::to_string(texture_memory_size) + "M"); + m_text_writer->print_text(*m_current_command_buffer, *direct_fbo, 0, 162, direct_fbo->width(), direct_fbo->height(), "Temporary texture memory: " + std::to_string(tmp_texture_memory_size) + "M"); + } vk::change_image_layout(*m_current_command_buffer, target_image, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, subres); m_framebuffers_to_clean.push_back(std::move(direct_fbo)); @@ -3088,4 +3212,10 @@ void VKGSRender::get_occlusion_query_result(rsx::occlusion_query_info* query) m_occlusion_query_pool.reset_queries(*m_current_command_buffer, data.indices); m_occlusion_map.erase(query->driver_handle); +} + +void VKGSRender::shell_do_cleanup() +{ + //TODO: Guard this + m_overlay_cleanup_requests.push_back(0); } \ No newline at end of file diff --git a/rpcs3/Emu/RSX/VK/VKGSRender.h b/rpcs3/Emu/RSX/VK/VKGSRender.h index e7e054f85a..976e02306a 100644 --- a/rpcs3/Emu/RSX/VK/VKGSRender.h +++ b/rpcs3/Emu/RSX/VK/VKGSRender.h @@ -136,6 +136,7 @@ private: std::unique_ptr m_text_writer; std::unique_ptr m_depth_converter; + std::unique_ptr m_ui_renderer; std::mutex m_sampler_mutex; u64 surface_store_tag = 0; @@ -287,6 +288,8 @@ private: //Vertex layout rsx::vertex_input_layout m_vertex_layout; + + std::vector m_overlay_cleanup_requests; #if !defined(_WIN32) && defined(HAVE_VULKAN) Display *m_display_handle = nullptr; @@ -341,10 +344,12 @@ protected: bool do_method(u32 id, u32 arg) override; void flip(int buffer) override; - void do_local_task() override; + void do_local_task(bool idle) override; bool scaled_image_from_memory(rsx::blit_src_info& src, rsx::blit_dst_info& dst, bool interpolate) override; void notify_tile_unbound(u32 tile) override; bool on_access_violation(u32 address, bool is_writing) override; void on_notify_memory_unmapped(u32 address_base, u32 size) override; + + void shell_do_cleanup() override; }; diff --git a/rpcs3/Emu/RSX/VK/VKHelpers.h b/rpcs3/Emu/RSX/VK/VKHelpers.h index 7b18ae35b0..f0400d2188 100644 --- a/rpcs3/Emu/RSX/VK/VKHelpers.h +++ b/rpcs3/Emu/RSX/VK/VKHelpers.h @@ -455,7 +455,7 @@ namespace vk struct image_view { - VkImageView value; + VkImageView value = VK_NULL_HANDLE; VkImageViewCreateInfo info = {}; image_view(VkDevice dev, VkImage image, VkImageViewType view_type, VkFormat format, VkComponentMapping mapping, VkImageSubresourceRange range) @@ -477,6 +477,34 @@ namespace vk CHECK_RESULT(vkCreateImageView(m_device, &info, nullptr, &value)); } + image_view(VkDevice dev, vk::image* resource, VkImageSubresourceRange range = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}, VkComponentMapping mapping = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_A }) + : m_device(dev) + { + info.format = resource->info.format; + info.image = resource->value; + info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; + info.components = mapping; + info.subresourceRange = range; + + switch (resource->info.imageType) + { + case VK_IMAGE_TYPE_1D: + info.viewType = VK_IMAGE_VIEW_TYPE_1D; + break; + case VK_IMAGE_TYPE_2D: + if (resource->info.flags == VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT) + info.viewType = VK_IMAGE_VIEW_TYPE_CUBE; + else + info.viewType = VK_IMAGE_VIEW_TYPE_2D; + break; + case VK_IMAGE_TYPE_3D: + info.viewType = VK_IMAGE_VIEW_TYPE_3D; + break; + } + + CHECK_RESULT(vkCreateImageView(m_device, &info, nullptr, &value)); + } + ~image_view() { vkDestroyImageView(m_device, value, nullptr); @@ -1181,7 +1209,7 @@ namespace vk m_instance = nullptr; //Check that some critical entry-points have been loaded into memory indicating prescence of a loader - loader_exists = (&vkCreateInstance != nullptr); + loader_exists = (vkCreateInstance != nullptr); } ~context() @@ -1279,8 +1307,8 @@ namespace vk instance_info.pApplicationInfo = &app; instance_info.enabledLayerCount = static_cast(layers.size()); instance_info.ppEnabledLayerNames = layers.data(); - instance_info.enabledExtensionCount = fast? 0: static_cast(extensions.size()); - instance_info.ppEnabledExtensionNames = fast? nullptr: extensions.data(); + instance_info.enabledExtensionCount = fast ? 0 : static_cast(extensions.size()); + instance_info.ppEnabledExtensionNames = fast ? nullptr : extensions.data(); VkInstance instance; if (vkCreateInstance(&instance_info, nullptr, &instance) != VK_SUCCESS) @@ -1704,5 +1732,5 @@ namespace vk */ void copy_mipmaped_image_using_buffer(VkCommandBuffer cmd, VkImage dst_image, const std::vector& subresource_layout, int format, bool is_swizzled, u16 mipmap_count, - VkImageAspectFlags flags, vk::vk_data_heap &upload_heap, vk::buffer* upload_buffer); + VkImageAspectFlags flags, vk::vk_data_heap &upload_heap); } diff --git a/rpcs3/Emu/RSX/VK/VKOverlays.h b/rpcs3/Emu/RSX/VK/VKOverlays.h index 23cee8087e..dec6017f7f 100644 --- a/rpcs3/Emu/RSX/VK/VKOverlays.h +++ b/rpcs3/Emu/RSX/VK/VKOverlays.h @@ -4,6 +4,8 @@ #include "VKFragmentProgram.h" #include "VKRenderTargets.h" +#include "../overlays.h" + namespace vk { //TODO: Refactor text print class to inherit from this base class @@ -21,6 +23,8 @@ namespace vk std::unordered_map> m_program_cache; std::unique_ptr m_sampler; std::unique_ptr m_draw_fbo; + std::unique_ptr m_vao; + std::unique_ptr m_ubo; vk::render_device* m_device = nullptr; std::string vs_src; @@ -32,33 +36,43 @@ namespace vk bool write_color = true; bool write_depth = true; bool no_depth_test = true; + bool enable_blend = false; } renderpass_config; bool initialized = false; bool compiled = false; + u32 num_drawable_elements = 4; + u32 first_vertex = 0; + void init_descriptors() { - VkDescriptorPoolSize descriptor_pools[1] = + VkDescriptorPoolSize descriptor_pool_sizes[2] = { { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 120 }, + { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 120 }, }; //Reserve descriptor pools - m_descriptor_pool.create(*m_device, descriptor_pools, 1); + m_descriptor_pool.create(*m_device, descriptor_pool_sizes, 2); - VkDescriptorSetLayoutBinding bindings[1] = {}; + VkDescriptorSetLayoutBinding bindings[2] = {}; bindings[0].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; bindings[0].descriptorCount = 1; bindings[0].stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; bindings[0].binding = 0; + bindings[1].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; + bindings[1].descriptorCount = 1; + bindings[1].stageFlags = VK_SHADER_STAGE_VERTEX_BIT; + bindings[1].binding = 1; + VkDescriptorSetLayoutCreateInfo infos = {}; infos.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; infos.pBindings = bindings; - infos.bindingCount = 1; + infos.bindingCount = 2; CHECK_RESULT(vkCreateDescriptorSetLayout(*m_device, &infos, nullptr, &m_descriptor_layout)); @@ -70,6 +84,45 @@ namespace vk CHECK_RESULT(vkCreatePipelineLayout(*m_device, &layout_info, nullptr, &m_pipeline_layout)); } + virtual void update_uniforms(vk::glsl::program *program) + { + } + + virtual std::vector get_vertex_inputs() + { + if (!m_vao) + { + auto memory_types = vk::get_memory_mapping(m_device->gpu()); + m_vao = std::make_unique(*m_device, 1 * 0x100000, memory_types.host_visible_coherent, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, 0); + m_ubo = std::make_unique(*m_device, 8 * 0x100000, memory_types.host_visible_coherent, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, 0); + } + + return{}; + } + + virtual std::vector get_fragment_inputs() + { + std::vector fs_inputs; + fs_inputs.push_back({ ::glsl::program_domain::glsl_fragment_program, vk::glsl::program_input_type::input_type_texture,{},{}, 0, "fs0" }); + fs_inputs.push_back({ ::glsl::program_domain::glsl_fragment_program, vk::glsl::program_input_type::input_type_uniform_buffer,{},{}, 1, "static_data" }); + return fs_inputs; + } + + void upload_vertex_data(f32 *data, u32 first, u32 count) + { + verify(HERE), (first + count) <= 65536; + if (!m_vao) + { + auto memory_types = vk::get_memory_mapping(m_device->gpu()); + m_vao = std::make_unique(*m_device, 1 * 0x100000, memory_types.host_visible_coherent, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, 0); + m_ubo = std::make_unique(*m_device, 8 * 0x100000, memory_types.host_visible_coherent, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, 0); + } + + auto dst = m_vao->map((first * 4), VK_WHOLE_SIZE); + std::memcpy(dst, data, count * sizeof(f32)); + m_vao->unmap(); + } + vk::glsl::program* build_pipeline(VkRenderPass render_pass) { if (!compiled) @@ -101,8 +154,14 @@ namespace vk dynamic_state_descriptors[dynamic_state_info.dynamicStateCount++] = VK_DYNAMIC_STATE_SCISSOR; dynamic_state_info.pDynamicStates = dynamic_state_descriptors; + VkVertexInputBindingDescription vb = { 0, 16, VK_VERTEX_INPUT_RATE_VERTEX }; + VkVertexInputAttributeDescription via = { 0, 0, VK_FORMAT_R32G32B32A32_SFLOAT, 0 }; VkPipelineVertexInputStateCreateInfo vi = {}; vi.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; + vi.vertexBindingDescriptionCount = 1; + vi.pVertexBindingDescriptions = &vb; + vi.vertexAttributeDescriptionCount = 1; + vi.pVertexAttributeDescriptions = &via; VkPipelineViewportStateCreateInfo vp = {}; vp.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; @@ -125,8 +184,19 @@ namespace vk VkPipelineColorBlendAttachmentState att = {}; if (renderpass_config.write_color) + { att.colorWriteMask = 0xf; + if (renderpass_config.enable_blend) + { + att.blendEnable = VK_TRUE; + att.alphaBlendOp = VK_BLEND_OP_ADD; + att.colorBlendOp = VK_BLEND_OP_ADD; + att.dstAlphaBlendFactor = att.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; + att.srcAlphaBlendFactor = att.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; + } + } + VkPipelineColorBlendStateCreateInfo cs = {}; cs.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; cs.attachmentCount = renderpass_config.color_attachments; @@ -158,17 +228,14 @@ namespace vk CHECK_RESULT(vkCreateGraphicsPipelines(*m_device, nullptr, 1, &info, NULL, &pipeline)); - const std::vector unused; - std::vector fs_inputs; - fs_inputs.push_back({ ::glsl::program_domain::glsl_fragment_program, vk::glsl::program_input_type::input_type_texture, {}, {}, 0, "fs0"}); - auto program = std::make_unique(*m_device, pipeline, unused, fs_inputs); + auto program = std::make_unique(*m_device, pipeline, get_vertex_inputs(), get_fragment_inputs()); auto result = program.get(); m_program_cache[render_pass] = std::move(program); return result; } - void load_program(vk::command_buffer cmd, VkRenderPass pass, vk::image_view *src) + void load_program(vk::command_buffer cmd, VkRenderPass pass, VkImageView src) { vk::glsl::program *program = nullptr; auto found = m_program_cache.find(pass); @@ -195,11 +262,18 @@ namespace vk VK_FALSE, 0.f, 1.f, 0.f, 0.f, VK_FILTER_LINEAR, VK_FILTER_LINEAR, VK_SAMPLER_MIPMAP_MODE_NEAREST, VK_BORDER_COLOR_FLOAT_OPAQUE_BLACK); } - VkDescriptorImageInfo info = { m_sampler->value, src->value, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL }; + VkDescriptorImageInfo info = { m_sampler->value, src, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL }; program->bind_uniform(info, "fs0", m_descriptor_set); + program->bind_uniform({ m_ubo->value, first_vertex * 128, 128 }, 1, m_descriptor_set); + + update_uniforms(program); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, program->pipeline); vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline_layout, 0, 1, &m_descriptor_set, 0, nullptr); + + VkBuffer buffers = m_vao->value; + VkDeviceSize offsets = 0; + vkCmdBindVertexBuffers(cmd, 0, 1, &buffers, &offsets); } void create(vk::render_device &dev) @@ -274,10 +348,13 @@ namespace vk return result; } - void run(vk::command_buffer &cmd, u16 w, u16 h, vk::image* target, vk::image_view* src, VkRenderPass render_pass, std::list>& framebuffer_resources) + virtual void emit_geometry(vk::command_buffer &cmd) { - vk::framebuffer *fbo = get_framebuffer(target, render_pass, framebuffer_resources); + vkCmdDraw(cmd, num_drawable_elements, 1, first_vertex, 0); + } + void run(vk::command_buffer &cmd, u16 w, u16 h, vk::framebuffer* fbo, VkImageView src, VkRenderPass render_pass) + { load_program(cmd, render_pass, src); VkViewport vp{}; @@ -300,9 +377,20 @@ namespace vk rp_begin.renderArea.extent.height = h; vkCmdBeginRenderPass(cmd, &rp_begin, VK_SUBPASS_CONTENTS_INLINE); - vkCmdDraw(cmd, 4, 1, 0, 0); + emit_geometry(cmd); vkCmdEndRenderPass(cmd); } + + void run(vk::command_buffer &cmd, u16 w, u16 h, vk::image* target, VkImageView src, VkRenderPass render_pass, std::list>& framebuffer_resources) + { + vk::framebuffer *fbo = get_framebuffer(target, render_pass, framebuffer_resources); + run(cmd, w, h, fbo, src, render_pass); + } + + void run(vk::command_buffer &cmd, u16 w, u16 h, vk::image* target, vk::image_view* src, VkRenderPass render_pass, std::list>& framebuffer_resources) + { + run(cmd, w, h, target, src->value, render_pass, framebuffer_resources); + } }; struct depth_convert_pass : public overlay_pass @@ -344,4 +432,288 @@ namespace vk m_fragment_shader.id = 100003; } }; + + struct ui_overlay_renderer : public overlay_pass + { + f32 m_time = 0.f; + color4f m_scale_offset; + color4f m_color; + bool m_pulse_glow = false; + bool m_skip_texture_read = false; + bool m_clip_enabled; + areaf m_clip_region; + + std::vector> resources; + std::unordered_map> font_cache; + std::unordered_map> view_cache; + std::vector> temp_image_cache; + std::unordered_map> temp_view_cache; + + ui_overlay_renderer() + { + vs_src = + { + "#version 450\n" + "#extension GL_ARB_separate_shader_objects : enable\n" + "layout(location=0) in vec4 in_pos;\n" + "layout(std140, set=0, binding=1) uniform static_data{ vec4 regs[8]; };\n" + "layout(location=0) out vec2 tc0;\n" + "layout(location=1) out vec4 color;\n" + "layout(location=2) out vec4 parameters;\n" + "layout(location=3) out vec4 clip_rect;\n" + "\n" + "void main()\n" + "{\n" + " tc0.xy = in_pos.zw;\n" + " color = regs[1];\n" + " parameters = regs[2];\n" + " clip_rect = regs[3] * regs[0].zwzw;\n" + " vec4 pos = vec4((in_pos.xy * regs[0].zw) / regs[0].xy, 0.5, 1.);\n" + " gl_Position = (pos + pos) - 1.;\n" + "}\n" + }; + + fs_src = + { + "#version 420\n" + "#extension GL_ARB_separate_shader_objects : enable\n" + "layout(set=0, binding=0) uniform sampler2D fs0;\n" + "layout(location=0) in vec2 tc0;\n" + "layout(location=1) in vec4 color;\n" + "layout(location=2) in vec4 parameters;\n" + "layout(location=3) in vec4 clip_rect;\n" + "layout(location=0) out vec4 ocol;\n" + "\n" + "void main()\n" + "{\n" + " if (parameters.w != 0)\n" + " {" + " if (gl_FragCoord.x < clip_rect.x || gl_FragCoord.x > clip_rect.z ||\n" + " gl_FragCoord.y < clip_rect.y || gl_FragCoord.y > clip_rect.w)\n" + " {\n" + " discard;\n" + " return;\n" + " }\n" + " }\n" + "\n" + " vec4 diff_color = color;\n" + " if (parameters.y != 0)\n" + " diff_color.a *= (sin(parameters.x) + 1.f) * 0.5f;\n" + "\n" + " if (parameters.z != 0)\n" + " ocol = texture(fs0, tc0) * diff_color;\n" + " else\n" + " ocol = diff_color;\n" + "}\n" + }; + + renderpass_config.color_attachments = 1; + renderpass_config.write_color = true; + renderpass_config.write_depth = false; + renderpass_config.enable_blend = true; + + m_vertex_shader.id = 100004; + m_fragment_shader.id = 100005; + } + + vk::image_view* upload_simple_texture(vk::render_device &dev, vk::command_buffer &cmd, vk::memory_type_mapping &memory_types, + vk::vk_data_heap& upload_heap, u64 key, int w, int h, bool font, bool temp, void *pixel_src) + { + const VkFormat format = (font) ? VK_FORMAT_R8_UNORM : VK_FORMAT_B8G8R8A8_UNORM; + const u32 pitch = (font) ? w : w * 4; + const u32 data_size = pitch * h; + const auto offset = upload_heap.alloc<512>(data_size); + const auto addr = upload_heap.map(offset, data_size); + + const VkComponentMapping bgra = { VK_COMPONENT_SWIZZLE_B, VK_COMPONENT_SWIZZLE_G, VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_A }; + const VkComponentMapping rrrr = { VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_R, VK_COMPONENT_SWIZZLE_R }; + const VkComponentMapping mapping = (font) ? rrrr : bgra; + + const VkImageSubresourceRange range = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 }; + + auto tex = std::make_unique(dev, memory_types.device_local, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, + VK_IMAGE_TYPE_2D, format, w, h, 1, 1, 1, VK_SAMPLE_COUNT_1_BIT, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, + 0); + + if (pixel_src && data_size) + std::memcpy(addr, pixel_src, data_size); + else if (data_size) + std::memset(addr, 0, data_size); + + upload_heap.unmap(); + + VkBufferImageCopy region; + region.imageSubresource = { VK_IMAGE_ASPECT_COLOR_BIT, 0, 0, 1 }; + region.bufferOffset = offset; + region.bufferRowLength = w; + region.bufferImageHeight = h; + region.imageOffset = {}; + region.imageExtent = { (u32)w, (u32)h, 1u}; + + change_image_layout(cmd, tex.get(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, range); + vkCmdCopyBufferToImage(cmd, upload_heap.heap->value, tex->value, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); + change_image_layout(cmd, tex.get(), VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, range); + + auto view = std::make_unique(dev, tex.get(), range, mapping); + + auto result = view.get(); + + if (!temp || font) + view_cache[key] = std::move(view); + else + temp_view_cache[key] = std::move(view); + + if (font) + font_cache[key] = std::move(tex); + else if (!temp) + resources.push_back(std::move(tex)); + else + temp_image_cache.push_back(std::move(tex)); + + return result; + } + + void create(vk::command_buffer &cmd, vk::memory_type_mapping &memory_types, vk::vk_data_heap &upload_heap) + { + auto& dev = cmd.get_command_pool().get_owner(); + overlay_pass::create(dev); + + rsx::overlays::resource_config configuration; + configuration.load_files(); + + u64 storage_key = 1; + for (const auto &res : configuration.texture_raw_data) + { + upload_simple_texture(dev, cmd, memory_types, upload_heap, storage_key++, res->w, res->h, false, false, res->data); + } + + configuration.free_resources(); + } + + void destroy() + { + temp_image_cache.clear(); + temp_view_cache.clear(); + + resources.clear(); + font_cache.clear(); + view_cache.clear(); + + overlay_pass::destroy(); + } + + void remove_temp_resources() + { + temp_image_cache.clear(); + temp_view_cache.clear(); + } + + vk::image_view* find_font(rsx::overlays::font *font, vk::command_buffer &cmd, vk::memory_type_mapping &memory_types, vk::vk_data_heap &upload_heap) + { + u64 key = (u64)font; + auto found = view_cache.find(key); + if (found != view_cache.end()) + return found->second.get(); + + //Create font file + return upload_simple_texture(cmd.get_command_pool().get_owner(), cmd, memory_types, upload_heap, key, font->width, font->height, true, false, font->glyph_data.data()); + } + + vk::image_view* find_temp_image(rsx::overlays::image_info *desc, vk::command_buffer &cmd, vk::memory_type_mapping &memory_types, vk::vk_data_heap &upload_heap) + { + u64 key = (u64)desc; + auto found = temp_view_cache.find(key); + if (found != temp_view_cache.end()) + return found->second.get(); + + return upload_simple_texture(cmd.get_command_pool().get_owner(), cmd, memory_types, upload_heap, key, desc->w, desc->h, false, true, desc->data); + } + + void update_uniforms(vk::glsl::program *program) override + { + auto dst = (f32*)m_ubo->map(first_vertex * 128, 128); + dst[0] = m_scale_offset.r; + dst[1] = m_scale_offset.g; + dst[2] = m_scale_offset.b; + dst[3] = m_scale_offset.a; + dst[4] = m_color.r; + dst[5] = m_color.g; + dst[6] = m_color.b; + dst[7] = m_color.a; + dst[8] = m_time; + dst[9] = m_pulse_glow? 1.f : 0.f; + dst[10] = m_skip_texture_read? 0.f : 1.f; + dst[11] = m_clip_enabled ? 1.f : 0.f; + dst[12] = m_clip_region.x1; + dst[13] = m_clip_region.y1; + dst[14] = m_clip_region.x2; + dst[15] = m_clip_region.y2; + m_ubo->unmap(); + } + + void emit_geometry(vk::command_buffer &cmd) + { + //Split into groups of 4 + auto tmp_first = first_vertex; + auto num_quads = num_drawable_elements / 4; + + for (u32 n = 0; n < num_quads; ++n) + { + vkCmdDraw(cmd, 4, 1, tmp_first, 0); + tmp_first += 4; + } + } + + void run(vk::command_buffer &cmd, u16 w, u16 h, vk::framebuffer* target, VkRenderPass render_pass, vk::memory_type_mapping &memory_types, + vk::vk_data_heap &upload_heap, rsx::overlays::user_interface &ui) + { + m_scale_offset = color4f((f32)ui.virtual_width, (f32)ui.virtual_height, 1.f, 1.f); + m_time = (f32)(get_system_time() / 1000) * 0.005f; + + u32 vertex_data_offset = 0; + first_vertex = 0; + + for (auto &command : ui.get_compiled().draw_commands) + { + num_drawable_elements = (u32)command.second.size(); + const u32 value_count = num_drawable_elements * 4; + + upload_vertex_data((f32*)command.second.data(), vertex_data_offset, value_count); + + m_skip_texture_read = false; + m_color = command.first.color; + m_pulse_glow = command.first.pulse_glow; + m_clip_enabled = command.first.clip_region; + m_clip_region = command.first.clip_rect; + + auto src = vk::null_image_view(cmd); + switch (command.first.texture_ref) + { + case rsx::overlays::image_resource_id::game_icon: + case rsx::overlays::image_resource_id::backbuffer: + //TODO + case rsx::overlays::image_resource_id::none: + m_skip_texture_read = true; + break; + case rsx::overlays::image_resource_id::font_file: + src = find_font(command.first.font_ref, cmd, memory_types, upload_heap)->value; + break; + case rsx::overlays::image_resource_id::raw_image: + src = find_temp_image((rsx::overlays::image_info*)command.first.external_data_ref, cmd, memory_types, upload_heap)->value; + break; + default: + src = view_cache[command.first.texture_ref]->value; + break; + } + + overlay_pass::run(cmd, w, h, target, src, render_pass); + + vertex_data_offset += value_count; + first_vertex += num_drawable_elements; + } + + ui.update(); + } + }; } diff --git a/rpcs3/Emu/RSX/VK/VKTexture.cpp b/rpcs3/Emu/RSX/VK/VKTexture.cpp index bc5e793ee4..93d90fb2cb 100644 --- a/rpcs3/Emu/RSX/VK/VKTexture.cpp +++ b/rpcs3/Emu/RSX/VK/VKTexture.cpp @@ -149,7 +149,7 @@ namespace vk void copy_mipmaped_image_using_buffer(VkCommandBuffer cmd, VkImage dst_image, const std::vector& subresource_layout, int format, bool is_swizzled, u16 mipmap_count, - VkImageAspectFlags flags, vk::vk_data_heap &upload_heap, vk::buffer* upload_buffer) + VkImageAspectFlags flags, vk::vk_data_heap &upload_heap) { u32 mipmap_level = 0; u32 block_in_pixel = get_format_block_size_in_texel(format); @@ -160,10 +160,10 @@ namespace vk u32 image_linear_size = row_pitch * layout.height_in_block * layout.depth; size_t offset_in_buffer = upload_heap.alloc<512>(image_linear_size); - void *mapped_buffer = upload_buffer->map(offset_in_buffer, image_linear_size); + void *mapped_buffer = upload_heap.map(offset_in_buffer, image_linear_size); gsl::span mapped{ (gsl::byte*)mapped_buffer, ::narrow(image_linear_size) }; upload_texture_subresource(mapped, layout, format, is_swizzled, 256); - upload_buffer->unmap(); + upload_heap.unmap(); VkBufferImageCopy copy_info = {}; copy_info.bufferOffset = offset_in_buffer; @@ -176,7 +176,7 @@ namespace vk copy_info.imageSubresource.mipLevel = mipmap_level % mipmap_count; copy_info.bufferRowLength = block_in_pixel * row_pitch / block_size_in_bytes; - vkCmdCopyBufferToImage(cmd, upload_buffer->value, dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©_info); + vkCmdCopyBufferToImage(cmd, upload_heap.heap->value, dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©_info); mipmap_level++; } } diff --git a/rpcs3/Emu/RSX/VK/VKTextureCache.h b/rpcs3/Emu/RSX/VK/VKTextureCache.h index a2cd9b75eb..0cddd96a4a 100644 --- a/rpcs3/Emu/RSX/VK/VKTextureCache.h +++ b/rpcs3/Emu/RSX/VK/VKTextureCache.h @@ -425,7 +425,6 @@ namespace vk vk::gpu_formats_support m_formats_support; VkQueue m_submit_queue; vk_data_heap* m_texture_upload_heap; - vk::buffer* m_texture_upload_buffer; //Stuff that has been dereferenced goes into these std::list m_discardable_storage; @@ -787,7 +786,7 @@ namespace vk } vk::copy_mipmaped_image_using_buffer(cmd, image->value, subresource_layout, gcm_format, input_swizzled, mipmaps, subres_range.aspectMask, - *m_texture_upload_heap, m_texture_upload_buffer); + *m_texture_upload_heap); vk::leave_uninterruptible(); @@ -848,14 +847,13 @@ namespace vk public: void initialize(vk::render_device& device, vk::memory_type_mapping& memory_types, vk::gpu_formats_support& formats_support, - VkQueue submit_queue, vk::vk_data_heap& upload_heap, vk::buffer* upload_buffer) + VkQueue submit_queue, vk::vk_data_heap& upload_heap) { m_memory_types = memory_types; m_formats_support = formats_support; m_device = &device; m_submit_queue = submit_queue; m_texture_upload_heap = &upload_heap; - m_texture_upload_buffer = upload_buffer; } void destroy() override diff --git a/rpcs3/Emu/RSX/overlay_controls.h b/rpcs3/Emu/RSX/overlay_controls.h new file mode 100644 index 0000000000..67482c120a --- /dev/null +++ b/rpcs3/Emu/RSX/overlay_controls.h @@ -0,0 +1,1456 @@ +#pragma once +#include "Utilities/types.h" +#include "Utilities/geometry.h" + +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#include +#endif + +// STB_IMAGE_IMPLEMENTATION and STB_TRUETYPE_IMPLEMENTATION defined externally +#include +#include + +// Definitions for common UI controls and their routines +namespace rsx +{ + namespace overlays + { + enum image_resource_id : u8 + { + //NOTE: 1 - 252 are user defined + none = 0, //No image + raw_image = 252, //Raw image data passed via image_info struct + font_file = 253, //Font file + game_icon = 254, //Use game icon + backbuffer = 255 //Use current backbuffer contents + }; + + struct vertex + { + float values[4]; + + vertex() {} + + vertex(float x, float y) + { + vec2(x, y); + } + + vertex(float x, float y, float z) + { + vec3(x, y, z); + } + + vertex(float x, float y, float z, float w) + { + vec4(x, y, z, w); + } + + float& operator[](int index) + { + return values[index]; + } + + void vec2(float x, float y) + { + values[0] = x; + values[1] = y; + values[2] = 0.f; + values[3] = 1.f; + } + + void vec3(float x, float y, float z) + { + values[0] = x; + values[1] = y; + values[2] = z; + values[3] = 1.f; + } + + void vec4(float x, float y, float z, float w) + { + values[0] = x; + values[1] = y; + values[2] = z; + values[3] = w; + } + + void operator += (const vertex& other) + { + values[0] += other.values[0]; + values[1] += other.values[1]; + values[2] += other.values[2]; + values[3] += other.values[3]; + } + + void operator -= (const vertex& other) + { + values[0] -= other.values[0]; + values[1] -= other.values[1]; + values[2] -= other.values[2]; + values[3] -= other.values[3]; + } + }; + + struct font + { + const u32 width = 1024; + const u32 height = 1024; + const u32 oversample = 2; + const u32 char_count = 256; //16x16 grid at max 48pt + + f32 size_pt = 12.f; + f32 size_px = 16.f; //Default font 12pt size + f32 em_size = 0.f; + std::string font_name; + std::vector pack_info; + std::vector glyph_data; + bool initialized = false; + + font(const char *ttf_name, f32 size) + { + //Init glyph + std::vector bytes; +#ifdef _WIN32 + std::string font_dir = "C:/Windows/Fonts/"; + std::string fallback_font = "C:/Windows/Fonts/Arial.ttf"; +#else + char *home = getenv("HOME"); + if (home == nullptr) + home = getpwuid(getuid())->pw_dir; + + std::string font_dir = home; + if (home[font_dir.length() - 1] == '/') + font_dir += ".fonts/"; + else + font_dir += "/.fonts/"; + + std::string fallback_font = "/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"; +#endif + std::string requested_file = font_dir + ttf_name + ".ttf"; + std::string file_path = requested_file; + + if (!fs::is_file(requested_file)) + { + if (fs::is_file(fallback_font)) + { + //TODO: Multiple fallbacks + file_path = fallback_font; + } + else + { + LOG_ERROR(RSX, "Failed to initialize font '%s.ttf'", ttf_name); + return; + } + } + + fs::file f(file_path); + f.read(bytes, f.size()); + + glyph_data.resize(width * height); + pack_info.resize(256); + + stbtt_pack_context context; + if (!stbtt_PackBegin(&context, glyph_data.data(), width, height, 0, 1, nullptr)) + { + LOG_ERROR(RSX, "Font packing failed"); + return; + } + + stbtt_PackSetOversampling(&context, oversample, oversample); + + //Convert pt to px + size_px = ceilf((f32)size * 96.f / 72.f); + size_pt = size; + + if (!stbtt_PackFontRange(&context, bytes.data(), 0, size_px, 0, 256, pack_info.data())) + { + LOG_ERROR(RSX, "Font packing failed"); + stbtt_PackEnd(&context); + return; + } + + stbtt_PackEnd(&context); + + font_name = ttf_name; + initialized = true; + + f32 unused; + get_char('m', em_size, unused); + } + + stbtt_aligned_quad get_char(char c, f32 &x_advance, f32 &y_advance) + { + if (!initialized) + return{}; + + stbtt_aligned_quad quad; + stbtt_GetPackedQuad(pack_info.data(), width, height, c, &x_advance, &y_advance, &quad, true); + return quad; + } + + std::vector render_text(const char *text, u16 text_limit = UINT16_MAX, bool wrap = false) + { + if (!initialized) + { + return{}; + } + + std::vector result; + + int i = 0; + f32 x_advance = 0.f, y_advance = 0.f; + bool skip_whitespace = false; + + while (true) + { + if (char c = text[i++]) + { + if ((u32)c >= char_count) + { + //Unsupported glyph, render null for now + c = ' '; + } + + switch (c) + { + case '\n': + { + y_advance += size_px + 2.f; + x_advance = 0.f; + continue; + } + case '\r': + { + x_advance = 0.f; + continue; + } + default: + { + stbtt_aligned_quad quad; + if (skip_whitespace && text[i - 1] == ' ') + { + quad = {}; + } + else + { + quad = get_char(c, x_advance, y_advance); + skip_whitespace = false; + } + + if (quad.x1 > text_limit) + { + bool wrapped = false; + bool non_whitespace_break = false; + + if (wrap) + { + //scan previous chars + for (int j = i - 1, nb_chars = 0; j > 0; j--, nb_chars++) + { + if (text[j] == '\n') + break; + + if (text[j] == ' ') + { + non_whitespace_break = true; + continue; + } + + if (non_whitespace_break) + { + if (nb_chars > 1) + { + nb_chars--; + + auto first_affected = result.size() - (nb_chars * 4); + f32 base_x = result[first_affected].values[0]; + + for (size_t n = first_affected; n < result.size(); ++n) + { + auto char_index = n / 4; + if (text[char_index] == ' ') + { + //Skip character + result[n++].vec2(0.f, 0.f); + result[n++].vec2(0.f, 0.f); + result[n++].vec2(0.f, 0.f); + result[n].vec2(0.f, 0.f); + continue; + } + + result[n].values[0] -= base_x; + result[n].values[1] += size_px + 2.f; + } + + x_advance = result.back().values[0]; + } + else + { + x_advance = 0.f; + } + + wrapped = true; + y_advance += size_px + 2.f; + + if (text[i - 1] == ' ') + { + quad = {}; + skip_whitespace = true; + } + else + { + quad = get_char(c, x_advance, y_advance); + } + + break; + } + } + } + + if (!wrapped) + { + //TODO: Ellipsize + break; + } + } + + result.push_back({ quad.x0, quad.y0, quad.s0, quad.t0 }); + result.push_back({ quad.x1, quad.y0, quad.s1, quad.t0 }); + result.push_back({ quad.x0, quad.y1, quad.s0, quad.t1 }); + result.push_back({ quad.x1, quad.y1, quad.s1, quad.t1 }); + break; + } + } //switch + } + else + { + break; + } + } + + return result; + } + + }; + + //TODO: Singletons are cancer + class fontmgr + { + private: + std::vector> fonts; + static fontmgr *m_instance; + + font* find(const char *name, int size) + { + for (auto &f : fonts) + { + if (f->font_name == name && + f->size_pt == size) + return f.get(); + } + + fonts.push_back(std::make_unique(name, (f32)size)); + return fonts.back().get(); + } + + public: + + fontmgr() {} + ~fontmgr() + { + if (m_instance) + { + delete m_instance; + m_instance = nullptr; + } + } + + static font* get(const char *name, int size) + { + if (m_instance == nullptr) + m_instance = new fontmgr; + + return m_instance->find(name, size); + } + }; + + struct image_info + { + int w = 0, h = 0; + int bpp = 0; + u8* data = nullptr; + + image_info(image_info&) = delete; + + image_info(const char* filename) + { + if (!fs::is_file(filename)) + { + LOG_ERROR(RSX, "Image resource file `%s' not found", filename); + return; + } + + std::vector bytes; + fs::file f(filename); + f.read(bytes, f.size()); + data = stbi_load_from_memory(bytes.data(), (s32)f.size(), &w, &h, &bpp, STBI_rgb_alpha); + } + + image_info(const std::vector& bytes) + { + data = stbi_load_from_memory(bytes.data(), (s32)bytes.size(), &w, &h, &bpp, STBI_rgb_alpha); + } + + ~image_info() + { + stbi_image_free(data); + data = nullptr; + w = h = bpp = 0; + } + }; + + struct resource_config + { + enum standard_image_resource : u8 + { + fade_top = 1, + fade_bottom, + cross, + circle, + triangle, + square, + save, + new_entry + }; + + //Define resources + std::vector texture_resource_files; + std::vector> texture_raw_data; + + resource_config() + { + texture_resource_files.push_back("fade_top.png"); + texture_resource_files.push_back("fade_bottom.png"); + texture_resource_files.push_back("cross.png"); + texture_resource_files.push_back("circle.png"); + texture_resource_files.push_back("triangle.png"); + texture_resource_files.push_back("square.png"); + texture_resource_files.push_back("save.png"); + texture_resource_files.push_back("new.png"); + } + + void load_files() + { + for (const auto &res : texture_resource_files) + { + //First check the global config dir + auto info = std::make_unique((fs::get_config_dir() + "Icons/ui/" + res).c_str()); + + if (info->data == nullptr) + { + //Resource was not found in config dir, try and grab from relative path (linux) + info = std::make_unique(("Icons/ui/" + res).c_str()); + + if (info->data != nullptr) + { + //Install the image to config dir + auto dst_dir = fs::get_config_dir() + "Icons/ui/"; + auto src = "Icons/ui/" + res; + auto dst = dst_dir + res; + + if (!fs::is_dir(dst_dir)) + { + auto root_folder = fs::get_config_dir() + "Icons/"; + if (!fs::is_dir(root_folder)) + fs::create_dir(root_folder); + + fs::create_dir(dst_dir); + } + + fs::copy_file(src, dst, true); + } + } + + texture_raw_data.push_back(std::move(info)); + } + } + + void free_resources() + { + texture_raw_data.clear(); + } + }; + + struct compiled_resource + { + struct command_config + { + color4f color = { 1.f, 1.f, 1.f, 1.f }; + bool pulse_glow = false; + + areaf clip_rect = {}; + bool clip_region = false; + + u8 texture_ref = image_resource_id::none; + font *font_ref = nullptr; + void *external_data_ref = nullptr; + + command_config() {} + + command_config(u8 ref) + { + texture_ref = ref; + font_ref = nullptr; + } + + command_config(font *ref) + { + texture_ref = image_resource_id::font_file; + font_ref = ref; + } + }; + + std::vector>> draw_commands; + + void add(const compiled_resource& other) + { + auto old_size = draw_commands.size(); + draw_commands.resize(old_size + other.draw_commands.size()); + std::copy(other.draw_commands.begin(), other.draw_commands.end(), draw_commands.begin() + old_size); + } + + void add(const compiled_resource& other, f32 x_offset, f32 y_offset) + { + auto old_size = draw_commands.size(); + draw_commands.resize(old_size + other.draw_commands.size()); + std::copy(other.draw_commands.begin(), other.draw_commands.end(), draw_commands.begin() + old_size); + + for (size_t n = old_size; n < draw_commands.size(); ++n) + { + for (auto &v : draw_commands[n].second) + { + v += vertex(x_offset, y_offset, 0.f, 0.f); + } + } + } + + void add(const compiled_resource& other, f32 x_offset, f32 y_offset, const areaf& clip_rect) + { + auto old_size = draw_commands.size(); + draw_commands.resize(old_size + other.draw_commands.size()); + std::copy(other.draw_commands.begin(), other.draw_commands.end(), draw_commands.begin() + old_size); + + for (size_t n = old_size; n < draw_commands.size(); ++n) + { + for (auto &v : draw_commands[n].second) + { + v += vertex(x_offset, y_offset, 0.f, 0.f); + } + + draw_commands[n].first.clip_rect = clip_rect; + draw_commands[n].first.clip_region = true; + } + } + }; + + struct overlay_element + { + enum text_align + { + left = 0, + center + }; + + u16 x = 0; + u16 y = 0; + u16 w = 0; + u16 h = 0; + + std::string text; + font* font_ref = nullptr; + text_align alignment = left; + bool wrap_text = false; + bool clip_text = true; + + color4f back_color = { 0.f, 0.f, 0.f, 1.f }; + color4f fore_color = { 1.f, 1.f, 1.f, 1.f }; + bool pulse_effect_enabled = false; + + compiled_resource compiled_resources; + bool is_compiled = false; + + f32 padding_left = 0.f; + f32 padding_right = 0.f; + f32 padding_top = 0.f; + f32 padding_bottom = 0.f; + + overlay_element() {} + overlay_element(u16 _w, u16 _h) : w(_w), h(_h) {} + + virtual void refresh() + { + //Just invalidate for draw when get_compiled() is called + is_compiled = false; + } + + virtual void translate(s16 _x, s16 _y) + { + x = (u16)(x + _x); + y = (u16)(y + _y); + + is_compiled = false; + } + + virtual void scale(f32 _x, f32 _y, bool origin_scaling) + { + if (origin_scaling) + { + x = (u16)(_x * x); + y = (u16)(_y * y); + } + + w = (u16)(_x * w); + h = (u16)(_y * h); + + is_compiled = false; + } + + virtual void set_pos(u16 _x, u16 _y) + { + x = _x; + y = _y; + + is_compiled = false; + } + + virtual void set_size(u16 _w, u16 _h) + { + w = _w; + h = _h; + + is_compiled = false; + } + + virtual void set_padding(f32 left, f32 right, f32 top, f32 bottom) + { + padding_left = left; + padding_right = right; + padding_top = top; + padding_bottom = bottom; + + is_compiled = false; + } + + virtual void set_padding(f32 padding) + { + padding_left = padding_right = padding_top = padding_bottom = padding; + is_compiled = false; + } + + virtual void set_text(const std::string& text) + { + this->text = text; + is_compiled = false; + } + + virtual void set_text(const char* text) + { + this->text = text; + is_compiled = false; + } + + virtual void set_font(const char* font_name, u16 font_size) + { + font_ref = fontmgr::get(font_name, font_size); + is_compiled = false; + } + + virtual void align_text(text_align align) + { + alignment = align; + is_compiled = false; + } + + virtual void set_wrap_text(bool state) + { + wrap_text = state; + is_compiled = false; + } + + virtual std::vector render_text(const char *string, f32 x, f32 y) + { + auto renderer = font_ref; + if (!renderer) renderer = fontmgr::get("Arial", 12); + + f32 text_extents_w = 0.f; + u16 clip_width = clip_text ? w : UINT16_MAX; + std::vector result = renderer->render_text(string, clip_width, wrap_text); + + if (result.size() > 0) + { + for (auto &v : result) + { + //Check for real text region extent + //TODO: Ellipsis + text_extents_w = std::max(v.values[0], text_extents_w); + + //Apply transform. + //(0, 0) has text sitting 50% off the top left corner (text is outside the rect) hence the offset by text height / 2 + v.values[0] += x + padding_left; + v.values[1] += y + padding_top + (f32)renderer->size_px * 0.5f; + } + + if (alignment == center) + { + //Scan for lines and measure them + //Reposition them to the center + std::vector> lines; + u32 line_begin = 0; + u32 ctr = 0; + + for (auto c : text) + { + switch (c) + { + case '\r': + continue; + case '\n': + lines.push_back({ line_begin, ctr }); + line_begin = ctr; + continue; + default: + ctr += 4; + } + } + + lines.push_back({ line_begin, ctr }); + const auto max_region_w = std::max(text_extents_w, w); + + for (auto p : lines) + { + if (p.first >= p.second) + continue; + + const f32 line_length = result[p.second - 1].values[0] - result[p.first].values[0]; + const bool wrapped = fabs(result[p.second - 1].values[1] - result[p.first + 3].values[1]) >= (renderer->size_px * 0.5f); + + if (wrapped) + continue; + + if (line_length < max_region_w) + { + f32 offset = (max_region_w - line_length) * 0.5f; + for (auto n = p.first; n < p.second; ++n) + { + result[n].values[0] += offset; + } + } + } + } + } + + return result; + } + + virtual compiled_resource& get_compiled() + { + if (!is_compiled) + { + compiled_resources = {}; + compiled_resources.draw_commands.push_back({}); + + auto &config = compiled_resources.draw_commands.front().first; + config.color = back_color; + config.pulse_glow = pulse_effect_enabled; + + auto& verts = compiled_resources.draw_commands.front().second; + verts.resize(4); + verts[0].vec4(x + padding_left, y + padding_bottom, 0.f, 0.f); + verts[1].vec4(x + w - padding_right, y + padding_bottom, 1.f, 0.f); + verts[2].vec4(x + padding_left, y + h - padding_top, 0.f, 1.f); + verts[3].vec4(x + w - padding_right, y + h - padding_top, 1.f, 1.f); + + if (!text.empty()) + { + compiled_resources.draw_commands.push_back({}); + compiled_resources.draw_commands.back().first = font_ref? font_ref : fontmgr::get("Arial", 12); + compiled_resources.draw_commands.back().first.color = fore_color; + compiled_resources.draw_commands.back().second = render_text(text.c_str(), (f32)x, (f32)y); + + if (compiled_resources.draw_commands.back().second.size() == 0) + compiled_resources.draw_commands.pop_back(); + } + + is_compiled = true; + } + + return compiled_resources; + } + + void measure_text(u16& width, u16& height, bool ignore_word_wrap = false) const + { + if (text.empty()) + { + width = height = 0; + return; + } + + auto renderer = font_ref; + if (!renderer) renderer = fontmgr::get("Arial", 12); + + f32 text_width = 0.f; + f32 unused = 0.f; + f32 max_w = 0.f; + f32 last_word = 0.f; + height = (u16)renderer->size_px; + + for (auto c : text) + { + if (c == '\n') + { + height += (u16)renderer->size_px + 2; + max_w = std::max(max_w, text_width); + text_width = 0.f; + last_word = 0.f; + continue; + } + + if (c == ' ') + { + last_word = text_width; + } + + renderer->get_char(c, text_width, unused); + + if (!ignore_word_wrap && wrap_text && text_width >= w) + { + if ((text_width - last_word) < w) + { + max_w = std::max(max_w, last_word); + text_width -= (last_word + renderer->em_size); + height += (u16)renderer->size_px + 2; + } + } + } + + max_w = std::max(max_w, text_width); + width = (u16)ceilf(max_w); + } + + }; + + struct animation_base + { + float duration = 0.f; + float t = 0.f; + overlay_element *ref = nullptr; + + virtual void update(float /*elapsed*/) {} + void reset() { t = 0.f; } + }; + + struct layout_container : public overlay_element + { + std::vector> m_items; + u16 advance_pos = 0; + u16 pack_padding = 0; + u16 scroll_offset_value = 0; + bool auto_resize = true; + + virtual overlay_element* add_element(std::unique_ptr&, int = -1) = 0; + + layout_container() + { + //Transparent by default + back_color.a = 0.f; + } + + void translate(s16 _x, s16 _y) override + { + overlay_element::translate(_x, _y); + + for (auto &itm : m_items) + itm->translate(_x, _y); + } + + void set_pos(u16 _x, u16 _y) override + { + s16 dx = (s16)(_x - x); + s16 dy = (s16)(_y - y); + translate(dx, dy); + } + + compiled_resource& get_compiled() override + { + if (!is_compiled) + { + compiled_resource result = overlay_element::get_compiled(); + + for (auto &itm : m_items) + result.add(itm->get_compiled()); + + compiled_resources = result; + } + + return compiled_resources; + } + + virtual u16 get_scroll_offset_px() = 0; + }; + + struct vertical_layout : public layout_container + { + overlay_element* add_element(std::unique_ptr& item, int offset = -1) + { + if (auto_resize) + { + item->set_pos(item->x + x, h + pack_padding + y); + h += item->h + pack_padding; + w = std::max(w, item->w); + } + else + { + item->set_pos(item->x + x, advance_pos + pack_padding + y); + advance_pos += item->h + pack_padding; + } + + if (offset < 0) + { + m_items.push_back(std::move(item)); + return m_items.back().get(); + } + else + { + auto result = item.get(); + m_items.insert(m_items.begin() + offset, std::move(item)); + return result; + } + } + + compiled_resource& get_compiled() override + { + if (scroll_offset_value == 0 && auto_resize) + return layout_container::get_compiled(); + + if (!is_compiled) + { + compiled_resource result = overlay_element::get_compiled(); + const f32 global_y_offset = (f32)-scroll_offset_value; + + for (auto &item : m_items) + { + const s32 item_y_limit = (s32)(item->y + item->h) - scroll_offset_value - y; + const s32 item_y_base = (s32)item->y - scroll_offset_value - y; + + if (item_y_limit < 0 || item_y_base > h) + { + //Out of bounds + continue; + } + else if (item_y_limit > h || item_y_base < 0) + { + //Partial render + areaf clip_rect = { (f32)x, (f32)y, (f32)(x + w), (f32)(y + h) }; + result.add(item->get_compiled(), 0.f, global_y_offset, clip_rect); + } + else + { + //Normal + result.add(item->get_compiled(), 0.f, global_y_offset); + } + } + + compiled_resources = result; + } + + return compiled_resources; + } + + u16 get_scroll_offset_px() override + { + return scroll_offset_value; + } + }; + + struct horizontal_layout : public layout_container + { + overlay_element* add_element(std::unique_ptr& item, int offset = -1) + { + if (auto_resize) + { + item->set_pos(w + pack_padding + x, item->y + y); + w += item->w + pack_padding; + h = std::max(h, item->h); + } + else + { + item->set_pos(advance_pos + pack_padding + x, item->y + y); + advance_pos += item->w + pack_padding; + } + + if (offset < 0) + { + m_items.push_back(std::move(item)); + return m_items.back().get(); + } + else + { + auto result = item.get(); + m_items.insert(m_items.begin() + offset, std::move(item)); + return result; + } + } + + compiled_resource& get_compiled() override + { + if (scroll_offset_value == 0 && auto_resize) + return layout_container::get_compiled(); + + if (!is_compiled) + { + compiled_resource result = overlay_element::get_compiled(); + const f32 global_x_offset = (f32)-scroll_offset_value; + + for (auto &item : m_items) + { + const s32 item_x_limit = (s32)(item->x + item->w) - scroll_offset_value - w; + const s32 item_x_base = (s32)item->x - scroll_offset_value - w; + + if (item_x_limit < 0 || item_x_base > h) + { + //Out of bounds + continue; + } + else if (item_x_limit > h || item_x_base < 0) + { + //Partial render + areaf clip_rect = { (f32)x, (f32)y, (f32)(x + w), (f32)(y + h) }; + result.add(item->get_compiled(), global_x_offset, 0.f, clip_rect); + } + else + { + //Normal + result.add(item->get_compiled(), global_x_offset, 0.f); + } + } + + compiled_resources = result; + } + + return compiled_resources; + } + + u16 get_scroll_offset_px() override + { + return scroll_offset_value; + } + }; + + //Controls + struct spacer : public overlay_element + { + using overlay_element::overlay_element; + compiled_resource& get_compiled() override + { + //No draw + return compiled_resources; + } + }; + + struct image_view : public overlay_element + { + private: + u8 image_resource_ref = image_resource_id::none; + void *external_ref = nullptr; + + public: + using overlay_element::overlay_element; + + compiled_resource& get_compiled() override + { + if (!is_compiled) + { + auto &result = overlay_element::get_compiled(); + result.draw_commands.front().first = image_resource_ref; + result.draw_commands.front().first.color = fore_color; + result.draw_commands.front().first.external_data_ref = external_ref; + } + + return compiled_resources; + } + + void set_image_resource(u8 resource_id) + { + image_resource_ref = resource_id; + external_ref = nullptr; + } + + void set_raw_image(image_info *raw_image) + { + image_resource_ref = image_resource_id::raw_image; + external_ref = raw_image; + } + }; + + struct image_button : public image_view + { + u16 text_offset = 0; + + image_button() + { + //Do not clip text to region extents + //TODO: Define custom clipping region or use two controls to emulate + clip_text = false; + } + + image_button(u16 _w, u16 _h) + { + clip_text = false; + set_size(_w, _h); + } + + void set_size(u16 w, u16 h) override + { + image_view::set_size(h, h); + text_offset = (h / 2) + 10; //By default text is at the horizontal center + } + + compiled_resource& get_compiled() override + { + if (!is_compiled) + { + auto& compiled = image_view::get_compiled(); + for (auto &cmd : compiled.draw_commands) + { + if (cmd.first.texture_ref == image_resource_id::font_file) + { + //Text, translate geometry to the right + const f32 text_height = font_ref ? font_ref->size_px : 16.f; + const f32 offset_y = (h > text_height) ? (f32)(h - text_height) : ((f32)h - text_height); + + for (auto &v : cmd.second) + { + v.values[0] += text_offset + 15.f; + v.values[1] += offset_y + 5.f; + } + } + } + } + + return compiled_resources; + } + }; + + struct label : public overlay_element + { + label() {} + + label(const char *text) + { + this->text = text; + } + + void auto_resize(bool grow_only = false, u16 limit_w = UINT16_MAX, u16 limit_h = UINT16_MAX) + { + u16 new_width, new_height; + measure_text(new_width, new_height, true); + + if (new_width > limit_w && wrap_text) + measure_text(new_width, new_height, false); + + if (grow_only) + { + new_width = std::max(w, new_width); + new_height = std::max(h, new_height); + } + + w = std::min(new_width, limit_w); + h = std::min(new_height, limit_h); + } + }; + + struct progress_bar : public overlay_element + { + private: + overlay_element indicator; + label text_view; + + f32 m_limit = 100.f; + f32 m_value = 0.f; + + public: + using overlay_element::overlay_element; + + void inc(f32 value) + { + set_value(m_value + value); + } + + void dec(f32 value) + { + set_value(m_value - value); + } + + void set_limit(f32 limit) + { + m_limit = limit; + is_compiled = false; + } + + void set_value(f32 value) + { + if (value < 0.f) + m_value = 0.f; + else if (value > m_limit) + m_value = m_limit; + else + m_value = value; + + f32 indicator_width = (w * m_value) / m_limit; + indicator.set_size((u16)indicator_width, h); + is_compiled = false; + } + + void set_pos(u16 _x, u16 _y) override + { + u16 text_w, text_h; + text_view.measure_text(text_w, text_h); + text_h += 5; + + overlay_element::set_pos(_x, _y + text_h); + indicator.set_pos(_x, _y + text_h); + text_view.set_pos(_x, _y); + } + + void set_size(u16 _w, u16 _h) override + { + overlay_element::set_size(_w, _h); + text_view.set_size(_w, text_view.h); + set_value(m_value); + } + + void translate(s16 dx, s16 dy) override + { + set_pos(x + dx, y + dy); + } + + void set_text(const char* str) override + { + text_view.set_text(str); + text_view.align_text(text_align::center); + + u16 text_w, text_h; + text_view.measure_text(text_w, text_h); + text_view.set_size(w, text_h); + + set_pos(text_view.x, text_view.y); + is_compiled = false; + } + + void set_text(const std::string& str) override + { + text_view.set_text(str); + text_view.align_text(text_align::center); + + u16 text_w, text_h; + text_view.measure_text(text_w, text_h); + text_view.set_size(w, text_h); + + set_pos(text_view.x, text_view.y); + is_compiled = false; + } + + compiled_resource& get_compiled() override + { + if (!is_compiled) + { + auto& compiled = overlay_element::get_compiled(); + compiled.add(text_view.get_compiled()); + + indicator.back_color = fore_color; + indicator.refresh(); + compiled.add(indicator.get_compiled()); + } + + return compiled_resources; + } + }; + + struct list_view : public vertical_layout + { + private: + std::unique_ptr m_scroll_indicator_top; + std::unique_ptr m_scroll_indicator_bottom; + std::unique_ptr m_cancel_btn; + std::unique_ptr m_accept_btn; + std::unique_ptr m_highlight_box; + + u16 m_elements_height = 0; + s16 m_selected_entry = -1; + u16 m_elements_count = 0; + + public: + list_view(u16 width, u16 height) + { + w = width; + h = height; + + m_scroll_indicator_top = std::make_unique(width, 5); + m_scroll_indicator_bottom = std::make_unique(width, 5); + m_accept_btn = std::make_unique(120, 20); + m_cancel_btn = std::make_unique(120, 20); + m_highlight_box = std::make_unique(width, 0); + + m_scroll_indicator_top->set_size(width, 40); + m_scroll_indicator_bottom->set_size(width, 40); + m_accept_btn->set_size(120, 30); + m_cancel_btn->set_size(120, 30); + + m_scroll_indicator_top->set_image_resource(resource_config::standard_image_resource::fade_top); + m_scroll_indicator_bottom->set_image_resource(resource_config::standard_image_resource::fade_bottom); + m_accept_btn->set_image_resource(resource_config::standard_image_resource::cross); + m_cancel_btn->set_image_resource(resource_config::standard_image_resource::circle); + + m_scroll_indicator_bottom->set_pos(0, height - 40); + m_accept_btn->set_pos(30, height + 20); + m_cancel_btn->set_pos(180, height + 20); + + m_accept_btn->text = "Select"; + m_cancel_btn->text = "Cancel"; + + auto fnt = fontmgr::get("Arial", 16); + m_accept_btn->font_ref = fnt; + m_cancel_btn->font_ref = fnt; + + auto_resize = false; + back_color = { 0.15f, 0.15f, 0.15f, 0.8f }; + + m_highlight_box->back_color = { .5f, .5f, .8f, 0.2f }; + m_highlight_box->pulse_effect_enabled = true; + m_scroll_indicator_top->fore_color.a = 0.f; + m_scroll_indicator_bottom->fore_color.a = 0.f; + } + + void update_selection() + { + auto current_element = m_items[m_selected_entry * 2].get(); + + //Calculate bounds + auto min_y = current_element->y - y; + auto max_y = current_element->y + current_element->h + pack_padding + 2 - y; + + if (min_y < scroll_offset_value) + { + scroll_offset_value = min_y; + } + else if (max_y > (h + scroll_offset_value)) + { + scroll_offset_value = max_y - h - 2; + } + + if ((scroll_offset_value + h + 2) >= m_elements_height) + m_scroll_indicator_bottom->fore_color.a = 0.f; + else + m_scroll_indicator_bottom->fore_color.a = 0.5f; + + if (scroll_offset_value == 0) + m_scroll_indicator_top->fore_color.a = 0.f; + else + m_scroll_indicator_top->fore_color.a = 0.5f; + + m_highlight_box->set_pos(current_element->x, current_element->y); + m_highlight_box->h = current_element->h + pack_padding; + m_highlight_box->y -= scroll_offset_value; + + m_highlight_box->refresh(); + m_scroll_indicator_top->refresh(); + m_scroll_indicator_bottom->refresh(); + refresh(); + } + + void select_next() + { + if (m_selected_entry < (m_elements_count - 1)) + { + m_selected_entry++; + update_selection(); + } + } + + void select_previous() + { + if (m_selected_entry > 0) + { + m_selected_entry--; + update_selection(); + } + } + + void add_entry(std::unique_ptr& entry) + { + //Add entry view + add_element(entry); + m_elements_count++; + + //Add separator + auto separator = std::make_unique(); + separator->back_color = fore_color; + separator->w = w; + separator->h = 2; + add_element(separator); + + if (m_selected_entry < 0) + m_selected_entry = 0; + + m_elements_height = advance_pos; + update_selection(); + } + + int get_selected_index() + { + return m_selected_entry; + } + + std::string get_selected_item() + { + if (m_selected_entry < 0) + return{}; + + return m_items[m_selected_entry]->text; + } + + void translate(s16 _x, s16 _y) override + { + layout_container::translate(_x, _y); + m_scroll_indicator_top->translate(_x, _y); + m_scroll_indicator_bottom->translate(_x, _y); + m_accept_btn->translate(_x, _y); + m_cancel_btn->translate(_x, _y); + } + + compiled_resource& get_compiled() + { + if (!is_compiled) + { + auto compiled = vertical_layout::get_compiled(); + compiled.add(m_highlight_box->get_compiled()); + compiled.add(m_scroll_indicator_top->get_compiled()); + compiled.add(m_scroll_indicator_bottom->get_compiled()); + compiled.add(m_accept_btn->get_compiled()); + compiled.add(m_cancel_btn->get_compiled()); + + compiled_resources = compiled; + } + + return compiled_resources; + } + }; + } +} diff --git a/rpcs3/Emu/RSX/overlays.cpp b/rpcs3/Emu/RSX/overlays.cpp new file mode 100644 index 0000000000..d95af226db --- /dev/null +++ b/rpcs3/Emu/RSX/overlays.cpp @@ -0,0 +1,31 @@ +#include "stdafx.h" +#include "overlays.h" +#include "GSRender.h" + +namespace rsx +{ + namespace overlays + { + //Singleton instance declaration + fontmgr* fontmgr::m_instance = nullptr; + + void user_interface::close() + { + //Force unload + exit = true; + if (auto rsxthr = fxm::get()) + rsxthr->shell_close_dialog(); + + if (on_close) + on_close(return_code); + } + + void user_interface::refresh() + { + if (auto rsxthr = fxm::get()) + { + rsxthr->native_ui_flip_request.store(true); + } + } + } +} diff --git a/rpcs3/Emu/RSX/overlays.h b/rpcs3/Emu/RSX/overlays.h new file mode 100644 index 0000000000..234ec613b4 --- /dev/null +++ b/rpcs3/Emu/RSX/overlays.h @@ -0,0 +1,762 @@ +#pragma once +#include "overlay_controls.h" + +#include "../../Utilities/Thread.h" +#include "../Io/PadHandler.h" +#include "Emu/Memory/vm.h" +#include "Emu/IdManager.h" +#include "pad_thread.h" + +#include "Emu/Cell/ErrorCodes.h" +#include "Emu/Cell/Modules/cellSaveData.h" +#include "Emu/Cell/Modules/cellMsgDialog.h" +#include "Emu/Cell/Modules/sceNpTrophy.h" + +#include + +extern u64 get_system_time(); + +// Definition of user interface implementations +namespace rsx +{ + namespace overlays + { + struct user_interface + { + //Move this somewhere to avoid duplication + enum selection_code + { + new_save = -1, + canceled = -2, + error = -3 + }; + + enum pad_button : u8 + { + dpad_up = 0, + dpad_down, + dpad_left, + dpad_right, + triangle, + circle, + square, + cross + }; + + u16 virtual_width = 1280; + u16 virtual_height = 720; + + u64 input_timestamp = 0; + bool exit = false; + + s32 return_code = CELL_OK; + std::function on_close; + + virtual compiled_resource get_compiled() = 0; + + void close(); + void refresh(); + + virtual void update(){} + + virtual void on_button_pressed(pad_button button_press) + { + close(); + }; + + s32 run_input_loop() + { + const auto handler = fxm::get(); + if (!handler) + { + LOG_ERROR(RSX, "Pad handler expected but none initialized!"); + return selection_code::error; + } + + const PadInfo& rinfo = handler->GetInfo(); + if (rinfo.max_connect == 0 || !rinfo.now_connect) + return selection_code::error; + + std::array button_state; + button_state.fill(true); + + while (!exit) + { + if (Emu.IsStopped()) + return selection_code::canceled; + + if (Emu.IsPaused()) + { + std::this_thread::sleep_for(10ms); + continue; + } + + for (const auto &pad : handler->GetPads()) + { + for (auto &button : pad->m_buttons) + { + u8 button_id = 255; + if (button.m_offset == CELL_PAD_BTN_OFFSET_DIGITAL1) + { + switch (button.m_outKeyCode) + { + case CELL_PAD_CTRL_LEFT: + button_id = pad_button::dpad_left; + break; + case CELL_PAD_CTRL_RIGHT: + button_id = pad_button::dpad_right; + break; + case CELL_PAD_CTRL_DOWN: + button_id = pad_button::dpad_down; + break; + case CELL_PAD_CTRL_UP: + button_id = pad_button::dpad_up; + break; + } + } + else if (button.m_offset == CELL_PAD_BTN_OFFSET_DIGITAL2) + { + switch (button.m_outKeyCode) + { + case CELL_PAD_CTRL_TRIANGLE: + button_id = pad_button::triangle; + break; + case CELL_PAD_CTRL_CIRCLE: + button_id = pad_button::circle; + break; + case CELL_PAD_CTRL_SQUARE: + button_id = pad_button::square; + break; + case CELL_PAD_CTRL_CROSS: + button_id = pad_button::cross; + break; + } + } + + if (button_id < 255) + { + if (button.m_pressed != button_state[button_id]) + if (button.m_pressed) on_button_pressed(static_cast(button_id)); + + button_state[button_id] = button.m_pressed; + } + + if (button.m_flush) + { + button.m_pressed = false; + button.m_flush = false; + button.m_value = 0; + } + + if (exit) + return 0; + } + } + + refresh(); + } + + //Unreachable + return 0; + } + }; + + struct fps_display : user_interface + { + label m_display; + + fps_display() + { + m_display.w = 150; + m_display.h = 30; + m_display.set_font("Arial", 16); + m_display.set_pos(1100, 20); + } + + void update(std::string current_fps) + { + m_display.text = current_fps; + m_display.refresh(); + } + + compiled_resource get_compiled() override + { + return m_display.get_compiled(); + } + }; + + struct save_dialog : public user_interface + { + private: + struct save_dialog_entry : horizontal_layout + { + private: + std::unique_ptr icon_data; + + public: + save_dialog_entry(const char* text1, const char* text2, u8 resource_id, const std::vector& icon_buf) + { + std::unique_ptr image = std::make_unique(); + image->set_size(160, 110); + image->set_padding(36.f, 36.f, 11.f, 11.f); //Square image, 88x88 + + if (resource_id != image_resource_id::raw_image) + { + static_cast(image.get())->set_image_resource(resource_id); + } + else if (icon_buf.size()) + { + image->set_padding(0.f, 0.f, 11.f, 11.f); //Half sized icon, 320x176->160x88 + icon_data = std::make_unique(icon_buf); + static_cast(image.get())->set_raw_image(icon_data.get()); + } + else + { + //Fallback + static_cast(image.get())->set_image_resource(resource_config::standard_image_resource::save); + } + + std::unique_ptr text_stack = std::make_unique(); + std::unique_ptr padding = std::make_unique(); + std::unique_ptr header_text = std::make_unique