From 9424b29d0af5b34c7b2bbf0799e6e359e84cc417 Mon Sep 17 00:00:00 2001 From: Halbear Date: Fri, 25 Sep 2026 23:37:22 +0100 Subject: [PATCH] Jarvis, Optimise my shit --- .gitignore | 2 + gradle/renderer-validation.init.gradle | 10 + .../GuiShaders/gui_frag.glsl.spv | Bin 752 -> 0 bytes .../GuiShaders/gui_vertex.glsl.spv | Bin 1276 -> 0 bytes .../Texture/blueleaf_set_2_translucent.png | Bin 3641 -> 3719 bytes .../Texture/test_cube_translucency.png | Bin 2219 -> 2429 bytes .../meshGenerationShared.glsl | 35 +- .../resetVoxelDrawShared.glsl | 23 +- .../voxelGenerationShared.glsl | 64 ++- .../shaders/bloom_extract_frag.glsl | 29 + .../shaders/bloom_pass_frag.glsl | 59 +- .../shaders/lighting_with_shadows_frag.glsl | 44 +- .../multi-sampled_bloom_extract_frag.glsl | 34 ++ .../multi-sampled_post_process_frag.glsl | 88 +-- .../shaders/packed_scene_vertex.glsl | 5 + .../shaders/post_process_frag.glsl | 75 +-- .../shaders/screen_space_reflection_frag.glsl | 12 +- .../EngineResources/shaders/shadow_frag.glsl | 4 +- .../shaders/shadow_vertex_packed.glsl | 61 +-- .../shaders/ssao_blur_frag.glsl | 4 +- .../EngineResources/shaders/ssao_frag.glsl | 50 +- .../shaders/ssr_composite_frag.glsl | 34 ++ .../ProgramResources/ServerCache/UUIDs.json | 1 + .../EngineCore/Display/VulkanRenderer.java | 56 +- .../EngineCore/Logic/EngineConfig.java | 20 +- .../Logic/RenderQualitySettings.java | 38 ++ .../Terrain4J/EngineCore/Main/GameCore.java | 30 +- .../Profiling/VulkanFrameTimings.java | 86 +++ .../RenderingAPI/OpenGL/GlModels/GLMesh.java | 54 +- .../FaceRangeAllocator.java | 50 ++ .../GeneratedVoxelChunk.java | 20 + .../SharedVoxelTerrainResources.java | 90 ++-- .../VoxelChunkGenerator.java | 186 +++++-- .../VoxelTerrainGeneration/VoxelDrawData.java | 35 ++ .../VoxelTerrainGeneration/VoxelMeshPool.java | 126 ++--- .../VoxelWorldManager.java | 291 +++++----- .../Computing/VulkanUtil/ComputePipeline.java | 2 - .../DeferredSceneRender.java | 4 +- .../MultiRenderTargetAttachments.java | 6 +- .../Forward/ForwardSceneRender.java | 4 +- .../RenderPasses/Lighting/LightRenderer.java | 4 +- .../PostProcessing/PostProcess.java | 505 ++++++------------ .../ScreenSpace/AmbientOcclusionRenderer.java | 87 ++- .../ScreenSpace/ReflectionsRenderer.java | 101 +++- .../RenderPasses/Shadows/ShadowRenderer.java | 30 +- .../RenderPasses/Shadows/ShadowUtils.java | 189 ++++--- .../Rendering/Shader/ShaderCompiler.java | 12 +- .../Vulkan/Rendering/Shader/ShaderModule.java | 12 +- .../Vulkan/Structure/DeviceLayers/Device.java | 13 +- terrain4j.properties | 16 +- 50 files changed, 1469 insertions(+), 1232 deletions(-) create mode 100644 gradle/renderer-validation.init.gradle delete mode 100644 resources/EngineResources/GuiShaders/gui_frag.glsl.spv delete mode 100644 resources/EngineResources/GuiShaders/gui_vertex.glsl.spv create mode 100644 resources/EngineResources/shaders/bloom_extract_frag.glsl create mode 100644 resources/EngineResources/shaders/multi-sampled_bloom_extract_frag.glsl create mode 100644 resources/EngineResources/shaders/ssr_composite_frag.glsl create mode 100644 resources/ProgramResources/ServerCache/UUIDs.json create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/Logic/RenderQualitySettings.java create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/Profiling/VulkanFrameTimings.java create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/FaceRangeAllocator.java create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/GeneratedVoxelChunk.java create mode 100644 src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/VoxelDrawData.java diff --git a/.gitignore b/.gitignore index 4da8767..8189200 100644 --- a/.gitignore +++ b/.gitignore @@ -29,6 +29,7 @@ build/ .idea/**/dbnavigator.xml *.t4jlog *.spv +*.spv.options # Gradle .idea/**/gradle.xml .idea/**/libraries @@ -90,3 +91,4 @@ fabric.properties /resources/models/Forest/ /resources/models/tree/ /resources/models/woman/ +/docs/ diff --git a/gradle/renderer-validation.init.gradle b/gradle/renderer-validation.init.gradle new file mode 100644 index 0000000..752e87a --- /dev/null +++ b/gradle/renderer-validation.init.gradle @@ -0,0 +1,10 @@ +def validationRun = System.currentTimeMillis() +gradle.projectsEvaluated { + allprojects { + tasks.withType(Test).configureEach { + binaryResultsDirectory.set(layout.buildDirectory.dir("renderer-validation/${validationRun}/binary")) + reports.junitXml.outputLocation.set(layout.buildDirectory.dir("renderer-validation/${validationRun}/xml")) + reports.html.outputLocation.set(layout.buildDirectory.dir("renderer-validation/${validationRun}/html")) + } + } +} \ No newline at end of file diff --git a/resources/EngineResources/GuiShaders/gui_frag.glsl.spv b/resources/EngineResources/GuiShaders/gui_frag.glsl.spv deleted file mode 100644 index 153627e45f5b9f0a63e74375098ea7cc15647226..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 752 zcmYk3PfG$(6veN9GtGZlS_GlBmlhR4)JAGH11=0&4x&>Gj5DHR)CcH`^{Ltfo!_Y7 zgFE+~^Ugc>zB{?><(^r_Dps`X>qGq{Nh^B5O%PSMJ zaS(B^uc#=F6b)%PQxWZQayI+X#k`fxaXn`^blfLzGM{_POFo=9_8Z7)t5`6gR zN;|qBnuM$AuAm^rZta44rhdFio)_cyK^(>lK|@DRLA0jOHH6>dC|Qoivw7%mbj7sq zAbRmXlR+FWrc3TSx{vQUKnsi?m^py)>nT`Ue6fZpW**!7Elq-1^wOzVx^HV=6xUUB zKhRp^}xK5 zMSq9N`0)<(EoHpm(Svz2+^zlqI^M#f=U*`eVfe*R^a!lTj<*g XH1OXWuA@CZoIR=v^MU(Q7k7$(^u;|@ diff --git a/resources/EngineResources/GuiShaders/gui_vertex.glsl.spv b/resources/EngineResources/GuiShaders/gui_vertex.glsl.spv deleted file mode 100644 index 2f7f2fbfb7ed790e958e7e9c98fc7847c81746a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1276 zcmZ9K&2Q5{5XHA~VoIScEwtscxCF|lDh_~<5FivGbBNSbAg)%FSg?|@Biljc1_%Bw zB>t*iD)D}6uLx{3nt5;EOlD>sboytESu*F$s(E0Vvtc@7jJVMGc=&#JP}S3eCr=-v zSTlhWqFI-U)0KT0+no5U40c_%A={E&mIeBC`N*JD=Z2k`|zUTUs zeo$mZDT$b_>)YlHMearMpR#0>mLJoyPLI)rElI5bz~ zNAv3QpvbFwoY(N7l<=HtGR`!*n9y98pZ%weRg)QaWgIZGfq4@!yI;h-1DO5C>W_Jd z-|1h7In7Hp`Skp^e&BlZdf#W?Wf|pnT&+F#!cE<6#i8ya*^c_rN9Hg!BH?9uz9RS+ zEoLS-?pNfQ#ovSJ4~}}P^3><;(XYvq3r{^TJMi@`C??n6gV_n(u2k%RJ8E5$Cl8KV zV7_6vE#YnXNJj3%7VjwLpr=>%=WDYs=Aia9dFF&e3+9dCxWCcH=&8+&dop9b|MK`& zgI_<=+o9{r(7=1m9mSky(y(*(#9@XLF+KU0V#z-*W4Et#OCMnF*vF=fJ#oiA!9MSb zV&;6KYV^(b!Yu56ggXeheu41?)*+`w!-#r;x Z?wFY!&=2o_U&g!95B-DDpQ^%h*&hd~W1Rp1 diff --git a/resources/EngineResources/Texture/blueleaf_set_2_translucent.png b/resources/EngineResources/Texture/blueleaf_set_2_translucent.png index c1b5625fb65c2e9245a260991299726721dd551c..31f20564fd0438db6efaa28f636d1c67a07a2a80 100644 GIT binary patch delta 3706 zcmV-=4u$c#9ETl{Fn zkt}z?vJh^OXrND<>QbdkoBCW?O1prNsTV~Y$Pl^MU142eaXFC9dJ80j3_9j7&Tle1 z-n9)ml6#V6@9ymEy!XEM&6_vt5CDcRP#F6(tWYTQ;Cn~8|9`&^7~4N4wrztkb}u84 z@~u{jA8&4MA{L7Q05nbWv=7T2=(-MF*9SgCY$ICxJ~HeXwr!(cuVZg-50+&O?T;}A z#u&D@w*df?lan|&I6$k_LMD?zyTwh=J{kAL%j4@c21;a3KeSPgQ zjBVQkJv55<{6y0y7&@>I9P^d3<2qBuLA(2S*4al}_0O-4RpMQ|iG>xNRn55Hbq|<2}930^A z@DT6czsJqZ4c@-82mV!&GPGh;SnVC z48|BY%&3yhW_|mUnSN?&3OhSH0Dzx={u!2K@$Xl!ULl=MV{&p5tyaqu0{?X!hYu>7 z&Gtc79De|zLli$A5ETuDLVU2cZ9~&E=(;|VSE+xotz}sd$1Jvs#bQV#5@@wrI6OSW z4?p|>^azncU}t9srBVqS8yf(CFezvlhDSjP3Bxe@j3S}}0^G%Ra>zi6Wg37iVjrYD zwPlRKaU8g=>*>p~EUc`oaF(B(oW!eFulR2O*nipC!T$a}78e&0i9}E;l|C7O2K(W| z2du8HdX(DV-$yo^*y)O2ZNF?~R z&CN{!!1D4kSAKJI6N`(BSXo&?B9TBW7DFTw0sImnQkK3A!|;T`)zy`U*ko^rLPR+V zi+`i%2!golx_vxD=V|bwVf+wB^2;y3008RsIu8>J7Evq~QLopr zxw*;57q%=5S65fib-hpVXf%p9Z{GBE8h_-`VdqIPg7qL|$lgCWIpN+Pq_8NZQVEKp zc*2MbK@tXlXf%pqvFOXpnM|hd9JM2ZIgSHe*SV*~c0q(G3ME>y*tYFKL9tluL!_?j zuq>rnbfr=W-+lKT;_-N3AE8hP zmSsWHG|!@^*=+JDk=6?7bQ)7rQ~k@~e@hfaK_n94xxY-1L5UND^#y%5n@xVq7~_j1 zS$TXZ1rhR;VHi*p#TW8}cz~qUYT@0xcX;>i9nbx3+s5479A6AAFE0l#hkyTVp{$co zK%_=fV9)|A6bkVg!1v#OkHf=5KuD0@zI}_ixjD?u&GAf86!xWlN~IFhb)B7@oD6Ko z7-O$rzh>QTm%V=dnl+nErt3QIqf{y}0Q{Wv3A`}$!#)MwqZ@$?U>F9oEX(s;#u#fh zn`~=qi*0Rfv1YT$7-LM=b$_PoI#)jE6Y3dY;QBtOy!AQCUZaSZT+s}HUJ5Lh!=|RD zJk?~ed>14GIbc|+>%PT}hlr>IWrE@i5ekKPK}0-;pg1jkY221&d5p?KK!!9OTbA{Y zz+ZmOAP5_V;b}*opfDzjl8aK#VW>_aFm2ltY@$rbl_pvVXW@CnqN(^-D$~ zA>x$BOy}kM!|0``)^#1f|Ni?(bw}5AvA(_z z+qPlbHrChI5sSs}_U&6fK3&%li9`bX^LUXwFf1GhPJ<9YZADMZ9;au@kRBC9vbU*U z5s`;@)J!TMbnJ%2#R@Cyq+c_<1-z3b;a8b6YBTT3~=B5{UDKdef&0R&{OEEufD?7)fGCOPGB2I zJn^UqxX=E+D1Y)wQ{Gz;tHuCenkM&tqtQU4(eUlp4y$t57RXHb$V71LLR()9W_@Mm$bCv3(C(KW1efn))}U0@^hwXdXD9O z8Dp%|=`hnY`P$>NBj6$X`(k@J=7)t3dArl;K+`m+s(%X8H2cCP6bf;KwA*ctqR*NL z(f&T|?#QXAxb+>2#e5eo;(g*YKghChly7u(xQviUfs{C0Z7d%?*%jImtFap=- zw04(cTz`&xDrr%D!Y~YGnkJ8L!!Y{doW4!dWSvfjU;peJAihs{-~D}Y+M`gS--coE z7cA(7N0Mwdi*~!sxhEQp;^^qe69(CA))OK}M@N{SpNC->@RY@AW08ak9P)Yi6hFkW zmPlNQL;~@69499yfg782zr1QBq7=hP+yoW3z<&+H;02iZ`FT&@q&$hp6JZFiz0x~6 zehQAVo_8Dv*=&}Vut(Y7r+(yFfs7%NnO`ZMW`J+M{TArOxjYNZ&(GuN=%|k)%H?uD z2|jc^Ps+SXf}WT#hX)EHD5}RaF*`$JxTd0?TAFyf0N%`R`mV=Sg<_ zh>u8VWq1c}H{EUx-pGfk6~%VkzBmp_r_|?93LN}+wB4Xa=9G$ z)F3|a6Y?lcjbiMZrpZGf8jZp*43x`d&%J;A@rMTmYinyT3+7D1v`n<7X<~MEmKz}&jbe6o))NABP5h=dVf4I7UPcezolXbI zWD==V3Xr@{MuHIQVtkK}kMnjE4x-27@i-3+5~|`73dFa>gNh&!7+o~N>3``d4*>`$ zp*CccwY9asra>aXLZN_awTdTCp5W%@2Di7jfB<>)_<_Djsmset#N%-ugBsMG0qAl>bDp{gpn-7e10&jA3>o;~B~sH&>m zNU{top^4lediw0_td9i!+K5I8qZil8!~~Ry3G7xX*xA{^Qa;bUUnmqfF95*U*ccC) zTrTJF>gDAnhkK*Z;6x~rBm`0v#cwl1Hio>;7nM{L#p7jBNMYF^VSiF`w^H$tAWeS& z@bT`B=K@RlJXgL}tKstU5~`|traXc=dYKu9%mV1aU3`t!Pif+Upx5XY7Z-h%C#7kL zEgB(QtJRQ7rI1V}Ik7nx7XZLXZ?{sx^vn#0K7=Ic`1lyp)6*CqALmMrkB{?tpFB)hSuwKPnj+Z3D%?( z9TWViO){Cp*w~n7hEP=%uIq9H5F#>}3{+M1%mff-2pItcPk%qfM3ECDdhsbJyF|}; z6au0F!vMH~wOS3;YL!oiWDr6J0NmW%pwsE#$&)91hM**f=6@0H6asXOW(Ge4_$f-hLMH*=17UGyp`{o{{6dKNy8KpL7ycv^sD8IUT2O?&86F%3qK6>ukH|p* zO+ji8hPeocQ!m!+cGQ$k-MV%TyBKZ&EtCzzMn)P%VjFJJffP(+FdU?S1d5wvJInn; z^j_~yq$Im>n+F(@$m8+8_rCY;-Me!JfWQYRLcAMRCX>1Lvw!0}|IszZ_K!)k*@O_{ zN=hK`?RJ}gHyRBT3IzawWm!u5tloj^x^P{0=u6}_vbL`xqu$YMHW7v)_V@P@1i{Gu zgb)xyV0(KT05CZ@iGza!wA*dmzkeTL7$OYAf#(zkxULJ^wgm`Y0SqwaiqLJ;lx3}5i2L}gu`}QrCmX`4O=by7uWVay6LZZ)|J9qHQ zFTVf)R#sNvc^)jwLMD?L@Eu}^WmzZ|ivt5{Hk$x2aDVSAE@N31vtX2zN+py^B^(?a z;PCJeuV25$+qZA=`t@sk|NZwkI5;hS&Xyn*L={JUH(^Zg|E zNE3R85P}smu9VB=#QyY1KQ%Rlot+&3zz;wCfFKC?^WMFCD3wZ>oSa0v-Bw)SqbQ1a zP~~!YfPb>`0Eiv3B*g(4XeN{4!8V&sSe6CXb;q(Q^)I&#f&lWE<#vTa0mWhw?RFc7 zhllv~+i!t>KqME~+1Y{PIH*>u0Dvq3^gK@iNG{=d-hfhMRzRY=+)j5HXueDX&_(Wp zz*Acx1fnQH9LGvuK@ebhd6}d9RuoV#Uc5lLT;`XptgPUVKmJIK%H;3OW>aAXDQHwQ4}t*2Vvz%`t|-~+a$m(_ zk?%Dc4FJH>(h`GjG#Z$npU3j@GK$3_3WWj;!vK;JAp%REp64lU(ChV-z@}S677CQ( zM1OJg4M7x-<9Hy>&~+NTte7Omk^K1Mj{tx$4B1VHEm`z^o2+k|=0M=nMYcq>T4ig{ z^(cyz!JDQD*L4v^5rhzk;~0+PBu#+qMygAsUSa zk1rbp0eZb2T-O}{p3mp;^y$-qPSY$p>VG-`BU%q*hHm|nlM}Z7Fu<}njsw#)6*nS9 zkYoWMpU=a#?Zn7@|Ni}fYt)Vu7DW+U*JVq~?a~6KER<;J(rh*r7TC5uz)0715d^`& z^Fa{6FboB}5CW}M3tL-Te<>h}B6K<(WuP>0IWwgDw{G160A>bV*JFX_d8k&ah=1c) z8FUmy$}r^HQzESuN~IE}rltm$!~d3;rU}C^IQQ2BWLn|`X?;PTtyYVF3n6%sq=UzYQV=0a zd7cN;G!rR5EDn&g+ikph^$M?Ez2e-z*=%BVc9s`IOG``X%i(`pDC;B^P=8RPDKKpT zmdRwe2JrRQU*qub5Rek2moHypc6JuCv$LEj$|640KgV%|>$>9PKFZIUAKk?4#Zd?hZ0MGM85CqD5g%G0EYKg6_EwQz= zC0ebP5JCvob%pD?3_k5I(|=dM(Di*6WrFeyk;!DZAR_;U1e}(>H0~e>6s58Y=#-}4K@j{S^w-}rjKZGh zDedSl&5az#5kU|nwfjo}vVZB{hbiE?F{IUMDd2-3U|=UFCu8+XN`E4uz^RX!uItZd z(N9BN*M((SN-i#|n=e|ma~)6>($ev=q-<0Y|i_V<${3`5Q>&{qN}Lho~KZVsoXrwnjz zZVtZh^L=_(8bhwVd>uDemzT7(;!DfVg%HZUQR@uf_xYOd`^s;PA!~QLU09ZdnVA{*zCYkLnM{Tm(tqi6m__e35u*Kl+TGDpQF-gT zP$(oWT;%8EQyz>{=LxU6zb{XFa#>3bT*YD$w{PFZ z$;nCT#wI3^HRlgM!*3+dP^ozY1GIL>-LiXxQD zWiDZlv%gRM=(7STLk?z1rFfbFzWnk_pzr7UEHF1WhvVbpff%u|u`w6~ue+Wn@Vbn% zzpwXAQZAQyDkVn9MzU>t0GvJxbh}+8P}1kw*%=^bf<&eN^$!rp*6&?h;J=@J1^`@b zwSO!N4;L1&zP_$prxRI~N(Jld>-?MguT(1Bugsvd93KGY=jU0$^o)G+o8kbpIUfBP zpwsEFdz_x0GWgZiRb+b?7YtGef&YB`u`won+0I=f56#_+5qwM>>@3RZ!^LcolhmDO5 z<=NkV|D6>?;62Ynb|RN!FhBqNGtMtBd9ZOD};QJG~9HJ;fyK&WDi+ybc_I;u^ZhEPq&ATT?31daUq$AJfy*tb}|%kLl@Y#Rce|{7G$i z8k7^4=X-ky!w`<+uoYzMlakPj7p_~IF7Nqy9@Lqc%@R|m@#bEX_&~*$(H`}&p!u(U{V`d zDOvRW`r*O?9xg0kBA0u&c7MLOVCxGZI4+=XRr#N*WUGc@$m{)jz0QGfd;|1d zN0+3{3|$%eI$s89nxvYye2g{;G{u6nV|4TL^NHXIG%c}ZC1k7BDqPov<9|3D*q-n00RRWR ziChkk9zSO4LrRirwHg)|7hxC%gEUQ(=Y6ugUJ0UvCQ0ZxEgh|uB`vL{GsB;L`e~{X zY}-bmP++{ERG6m1XPcXBVLAEfU0h%ymt*SFW-2Lzc5}(scXxL=(V=NkX9g(&8ubYv zF+vt1IIS1J|!_SMTCKviZJrL%cL!nz`cw|X zP}v@*B#2^$Om_-;bdP2Rq(pliELnZnkvGe-hJuI0QoUW427mW#bCX|ketC&Tqro|W zEVWt<>)(BcwY4>l8I?+ffx51Xv$HcqQG}zTqk(};O--?a} z_U~azfaY=>10FqojE4&ga9x+DMftp)Oy6gln>fF`ytpcZ}rj+Xx5RZH^OzGurQ&MAzd=0 zDro4Eb`e=h)>k;~=e+~VRQT-QapTt+sVmGeLJ{&h*uboU8Jv)Po&eEs?r-EJ2c zDW6WKk;~;U91hWJHeuT~`u#otAe+si*XzmuNu^TK2tg1al}g24FM90CJ-{?guGW+qTE%qOl(YLEtz};5g3IH4&V+dIQ86B%95K0M9hd5a_ADIp=}nI6)8u0p~p6 zoX7qzt%=NEy~3oBZQFru+W|&sPezC{P}EDv2&BAf1brIRL;g46LlI$Tey$+-;hf|6_!uiID^i6Xr2!9e#s$e-628gTk)nlq*XpNd7$Ox+9 z(R3F*PUl^DDGWf?`h*3xZ36(_zkiQxHjCkKh+Hm*X0wS;pFRNq?%cT}*GEx4S_u+9 zAQA?1fdLqydM=lfD9<@Zzu$*#+Y;?bdc7WWUB}|$qQn5laU{aWWdXx5u(h=nixF3z z1%F6X03_s(LwjnH)V?7I0<>B!xkk+blmSYm5-{o)wzjrH>#rOGlK7}#IPDmf$^?RQ z5dyPF5Cq_y%MfU_T5^s=#RT#M!!Qs8!R#3DyF>(IY}QKyBZbu(Kvgh$e<8P5c!8N+ z?^ovO!x&4<{Z;p-?`a_rSu@+kg%E)dVt=9m;*KS0fH=jXMz~;2G5{23n7Cs!PRalm zDIaHqKMsOI2pM<2?&?}ySHxqZ^^uYU?- zOkO`u48s6p3@={1kYgml2x*!&@xo+;X*ZUXR4Ns;+ieU6gQ;r@g#s93A&i*W^(KV? zxw;TSVugytlTzd?A@DOHtFF705STPil^3~RQl24%fDmHp?sLw;7(<~@2oa`<7(mx` zFviAWU(>X)Qu#7)Ad|_U-EL1^V}BS1G))^@msl?l#eyIRU>F9Dj*ddjh_37MoTH$q{_1`34&Fv1H}=7JPWLK=+*gb=9L>wj2ZUx%*i zsMqVj1;G2)GeS_5_kADh>+53*d!C1H-@f76wQDjwNd6fQpxwqu;e~|-n5GFKgfxop z`_OeAjYb1&YimGK9`L*L*6-43Q4vC*SS+GgEW-0Vc%FxTzYie<`u#rMy?Y1G^Fr%M z5&=QR*wpkh>7AWv@ACYPaDUEmaBzV2^>x(ib$FhKVzCGSC>D#*bsfI%V|#mBo^z=f zpjtZaSdvvCb%W&Yp69``EEJ1Htgo*FBQRWBTMHFJiDE!>N7eQ?D!Q&Gw);W|=(>)h zqa&H-XynvhAwKWH#Rl`#+YQUBG*o)7pFgr(qF2= zQSRWfsIjQ>YX47$pmE}LKpg!=tup=)J4B+sKt&SGR+p9qNYP0(g0Ab(G;QYVFvcVy zq6!fts`9SuqFSwDcYk+R&dqE9Ap|(*lKfD~5t2%!GL{j1-w)l~?+S5&P>d~v7;BBI z)hg=sI%>5VJkOIxnArfN7%4-k?ILc#1| zd@pbG9c@b|$===`0EFB&PN=>YLLigLz;#{81^oBlf6#S3)_?eaMK{>#bfh8Tgt|d` zou*o?0;8P|f&iZ9$!3VkV=9N|nh@F9*#SoH`8ke*H*em=t|P@sK`6C9b$zd2zXkw2 zfBrn|wexI%&CN~Z^LaVW=kpUop<1n?P$)nMfrEnsSeAu*_wGq{h}=I4j(<%EOcU32 zQLEKL%HO_yJAXut5q18@>VR2gXJ-e`o;?dGA7_Y0qXFOdQ7jhGXf$A1)>sWf87i)2 zpGyM};JtkL5@%;;z({#|=T{Z#1dGKY_V)Iq@}%svr$v}I4A|V<#M#*yR##W$Zl9f< z;mMOHc>DHkX#D8Wqo4Xu##rcC0M!W;4s&CGot+(YI)5EJe*74(UcJKV>M8)>^XJb1 zfYsGi9Oby z{0t+k7CqBS;b}q!nPh;u<^-3PmS#GiF?Qh-<6lJT08P`t7(*tL3B_X68{%$olI-vA zV`*t=CL?H?b|E+?VgQxch-*1j_ao1k_Do=RcNeFpr#L-51xB8*zrTNBORS2@{ZEts Y17Q{QTEkxH)c^nh07*qoM6N<$f_<8eKmY&$ delta 2195 zcmV;E2yFNL5~~rAFnR1r-ckLQiC=@sREQL;a4^ z`D7=xQoDU1Ea^^`-h1DB_ueM~fLf-l(P%i&(Rm?+;+&_>Rewrd^`6V2aHG*sBs9i3 zSB*wP2_f`68gDcj+7RaXzs>-rGNz(*Y)S;rL?)V zTCJG~a3E#z2;iJ2;B3R&_I{dTN-5Q9wN$IsnmWfa(&7!^GDx*rO~BvwzPT@i(B3CB zT>&ABR~U{YD1U^|MsOL(MhO{#=9|}_4giQF`&?iJt<|p{O!oWf z4uO8Zug{T~IiXM}0OPPwO3j}G%vigMP`uA8{eC}D1DFch&)4tw^?qiha9aO8t1q9{ z+Fo@t;Z48OLcp3c-_50zN=lh(0M}TS25>268)3$rY!Z;mFs?B=&dLC@ly@27k0(JX zrGJjQIF2*@?|#3JFbs8k)3KQZsbw~EfH`L_4^TaBd&Pvl9S*dXXMq6I!_{haYVI*f zmdtVh=Ugi`3-(#V-=$!hVd;FeT1^~h8Ulw#f*?Q?MTnvZ=jZ2;QsVsl93MV>KomuZ`6QVnLC*Qq<;UdTE_42OMF@eTqa#!* z6?8fsL{Wqw2mk;<5P&g;IF9k?(Ib7$wdMfR(yp;At3v7q3GPu8VSRlaL4OdSQmFvr zWLPW~6NONwIl%U)*&f@%7|YE2Qc5t!(Cv2hGDn_gjKTMP+`W4jqtOVAF$@L+%@Sp5 zfH;n|5vF-RYawU@z{SM{eBakz_kAB17Z=Fq^O`cpxN0QHqzyG&Uz-(@QaV|pteBx$ zZFRL;)nB5~vl#czrWZHg=YQJFFt=DAGol+W@4P`G2(rERf{l|$+>fYxt7zud2`yQG5WRJ(llu{n*flg-dsZUy_6EZ@9U2*0pR1u zk6?^BjsI74gHaS|L%4*xLHewY<|?J2ltL6mx*4MKm<+nqgb2my*47pd4i2!lx96Nk zij#scYJckb-o1MV0DpM-@?`?GON{_h=HTD}Fg$BZtyV)E$H?dNC>D$Qx?-^i-}e#6 zF}An2vAw-L!ACB&0Z7bN)+OzB8>gqIkW%94=m_iU>$rFCUV<7U>imz@0rN_|UdNj^ zZxYJ84AJZL5XUisAV9Cz!}|LAL=8eKl&fW5N&^topFMkq;eT)ljFsQo+M1Y4onR0I z*xTFF%9FCw-WFlmlHlg%CWgZyN~Mza_Ha1F{{BAp_xBUWl-yhJ)|%p#Py8V zN{drjnl@%rW#$8Z|NdR?(?^hJXJ=SlT}@mw8jTRg@qa`wK!~7M;%U#Zw4C7T>M9No z5B2erCr_O7Y_Hmi&Z6d)niITp=gwT$bIxbJG0vn8@H`Kkb6s}A;bGTHkZCVePEJnX z`~Ic7# voxelTypeCount) return true; return voxelReg.materials[type - 1u].BlockType >= 1u; } uint getVoxel(ivec3 pos) { - if (pos.x < 0 || pos.x >= CHUNK_SIZE) return 0u; - if (pos.y < 0 || pos.y >= CHUNK_SIZE) return 0u; - if (pos.z < 0 || pos.z >= CHUNK_SIZE) return 0u; + if (pos.x < -1 || pos.x > CHUNK_SIZE) return 0u; + if (pos.y < -1 || pos.y > CHUNK_SIZE) return 0u; + if (pos.z < -1 || pos.z > CHUNK_SIZE) return 0u; return voxels[flatten(pos)]; } @@ -175,7 +164,7 @@ void emitFace(ivec3 voxelPos, uint faceIndex, uint voxelType) { return; } - uint localFace = atomicAdd(counters.faceCount, 1u); + uint localFace = atomicAdd(counters.faceCount[slot], 1u); if (localFace >= MAX_VISIBLE_FACES_PER_CHUNK) { return; @@ -183,8 +172,6 @@ void emitFace(ivec3 voxelPos, uint faceIndex, uint voxelType) { Voxel voxel = voxelReg.materials[voxelType - 1u]; faces[faceOffset + localFace] = packFace(uvec3(voxelPos), faceIndex, voxel); - - atomicAdd(drawCmds[indirectCommandIndex].vertexCount, VERTICES_PER_FACE); } void main() { diff --git a/resources/EngineResources/VoxelComputeShaders/resetVoxelDrawShared.glsl b/resources/EngineResources/VoxelComputeShaders/resetVoxelDrawShared.glsl index d3b34f9..740f672 100644 --- a/resources/EngineResources/VoxelComputeShaders/resetVoxelDrawShared.glsl +++ b/resources/EngineResources/VoxelComputeShaders/resetVoxelDrawShared.glsl @@ -2,19 +2,8 @@ layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; -struct DrawCommand { - uint vertexCount; - uint instanceCount; - uint firstVertex; - uint firstInstance; -}; - -layout(std430, binding = 0) buffer DrawCommands { - DrawCommand drawCmds[]; -}; - -layout(std430, binding = 1) buffer Counters { - uint faceCount; +layout(std430, binding = 0) buffer Counters { + uint faceCount[]; } counters; layout(push_constant) uniform ChunkInfo { @@ -22,17 +11,11 @@ layout(push_constant) uniform ChunkInfo { int slot; uint faceOffset; uint voxelTypeCount; - uint indirectCommandIndex; uint biomeCount; uint structureCount; uint worldSeed; }; void main() { - drawCmds[indirectCommandIndex].vertexCount = 0u; - drawCmds[indirectCommandIndex].instanceCount = 1u; - drawCmds[indirectCommandIndex].firstVertex = faceOffset * 6u; - drawCmds[indirectCommandIndex].firstInstance = 0u; - - counters.faceCount = 0u; + counters.faceCount[slot] = 0u; } \ No newline at end of file diff --git a/resources/EngineResources/VoxelComputeShaders/voxelGenerationShared.glsl b/resources/EngineResources/VoxelComputeShaders/voxelGenerationShared.glsl index 52a9b60..2fe9cbc 100644 --- a/resources/EngineResources/VoxelComputeShaders/voxelGenerationShared.glsl +++ b/resources/EngineResources/VoxelComputeShaders/voxelGenerationShared.glsl @@ -2,7 +2,9 @@ layout(local_size_x = 4, local_size_y = 4, local_size_z = 4) in; const int CHUNK_SIZE = 16; -const int CHUNK_AREA = CHUNK_SIZE * CHUNK_SIZE; +const int SCRATCH_SIZE = CHUNK_SIZE + 2; +const int SCRATCH_AREA = SCRATCH_SIZE * SCRATCH_SIZE; +const int SCRATCH_VOXEL_COUNT = SCRATCH_SIZE * SCRATCH_SIZE * SCRATCH_SIZE; const int STRUCTURE_CELL_SIZE = 16; const int REPLACE_AIR_ONLY = 1; const int REPLACE_FOLIAGE = 2; @@ -14,7 +16,6 @@ layout(push_constant) uniform ChunkInfo { int slot; uint faceOffset; uint voxelTypeCount; - uint indirectCommandIndex; uint biomeCount; uint structureCount; uint worldSeed; @@ -453,35 +454,44 @@ StructureVoxelResult getStructureVoxelAt(ivec3 worldPos, uint biome) { ivec3 baseCell = getStructureCell3D(worldPos, spacing); for (int ox = -1; ox <= 1; ox++) { - for (int oz = -1; oz <= 1; oz++) { - ivec3 cell = baseCell + ivec3(ox, 0, oz); + for (int oy = -1; oy <= 1; oy++) { + for (int oz = -1; oz <= 1; oz++) { + ivec3 cell = baseCell + ivec3(ox, oy, oz); - uint seed = hash31(cell + ivec3(0, int(structureId) * 97, 0)) ^ worldSeed; + uint seed = hash31(cell + ivec3(0, int(structureId) * 97, 0)) ^ worldSeed; - if (!cellSpawnsStructure(seed, s)) { - continue; - } + if (!cellSpawnsStructure(seed, s)) { + continue; + } - ivec2 anchorXZ = getStructureAnchorXZ(cell.xz, seed, s.spawnSpacing); + ivec2 anchorXZ = getStructureAnchorXZ(cell.xz, seed, s.spawnSpacing); - int referenceY = cell.y * 75; - float anchorHeight = getSurfaceHeight(anchorXZ.x, anchorXZ.y, referenceY); - int anchorY = int(floor(anchorHeight)) + 1; + // Match getStructureBlock's uncentered footprint before sampling terrain. + ivec2 localXZ = worldPos.xz - anchorXZ; + if (localXZ.x < 0 || localXZ.x >= int(s.sizeX) || + localXZ.y < 0 || localXZ.y >= int(s.sizeZ)) { + continue; + } - ivec3 origin = ivec3(anchorXZ.x, anchorY, anchorXZ.y); + int referenceY = cell.y * 75; + float anchorHeight = getSurfaceHeight(int(anchorXZ.x - s.sizeX/2), int(anchorXZ.y - s.sizeZ/2), referenceY); + int anchorY = int(floor(anchorHeight)) + 1; - if (!isValidStructureAnchor(origin, structureId, biome)) { - continue; - } + ivec3 origin = ivec3(anchorXZ.x, anchorY, anchorXZ.y); - ivec3 local = worldPos - origin; + if (!isValidStructureAnchor(origin, structureId, biome)) { + continue; + } - uint block = getStructureBlock(structureId, local); + ivec3 local = worldPos - origin; - if (block != 0u) { - result.block = block; - result.flags = s.flags; - return result; + uint block = getStructureBlock(structureId, local); + + if (block != 0u) { + result.block = block; + result.flags = s.flags; + return result; + } } } } @@ -491,10 +501,13 @@ StructureVoxelResult getStructureVoxelAt(ivec3 worldPos, uint biome) { } void main() { - ivec3 localPos = ivec3(gl_GlobalInvocationID.xyz); + if (any(greaterThanEqual(gl_GlobalInvocationID.xyz, uvec3(SCRATCH_SIZE)))) { + return; + } + ivec3 localPos = ivec3(gl_GlobalInvocationID.xyz) - ivec3(1); ivec3 worldPos = chunkPos * CHUNK_SIZE + localPos; - uint biome = GetBiome(vec3(worldPos.z, worldPos.y, worldPos.x) * 0.05, biomeCount); + uint biome = GetBiome(vec3(worldPos.z, worldPos.y, worldPos.x) * 0.005, biomeCount); Biome currentBiome = BiomeReg.biomes[biome]; float YZone = floor(worldPos.y/150.0)*150.0; @@ -577,6 +590,7 @@ void main() { } } - uint index = uint((localPos.x * CHUNK_AREA) + (localPos.y * CHUNK_SIZE) + localPos.z); + uint index = uint(slot * SCRATCH_VOXEL_COUNT + (localPos.x + 1) * SCRATCH_AREA + + (localPos.y + 1) * SCRATCH_SIZE + localPos.z + 1); voxels[index] = voxelType; } \ No newline at end of file diff --git a/resources/EngineResources/shaders/bloom_extract_frag.glsl b/resources/EngineResources/shaders/bloom_extract_frag.glsl new file mode 100644 index 0000000..cc9e726 --- /dev/null +++ b/resources/EngineResources/shaders/bloom_extract_frag.glsl @@ -0,0 +1,29 @@ +#version 450 + +layout(location = 0) in vec2 inTextCoord; +layout(location = 0) out vec4 outBloomColour; +layout(set = 0, binding = 0) uniform sampler2D inputTexture; + +layout(push_constant) uniform PassConfig { + int horizontal; + float GAMMA_CONST; + float Exposure; + float blur_radius; + vec2 bloomSize; +} config; + +vec3 extractBloom(vec2 uv) { + vec3 color = max(texture(inputTexture, uv).rgb, vec3(0.0)); + float brightness = dot(color, vec3(0.2126, 0.7152, 0.0722)); + float contribution = max(brightness - 1.0, 0.0); + return color * (contribution / (contribution + 0.5)); +} + +void main() { + vec2 offset = 0.25 / config.bloomSize; + vec3 bloom = extractBloom(inTextCoord + vec2(-offset.x, -offset.y)) + + extractBloom(inTextCoord + vec2( offset.x, -offset.y)) + + extractBloom(inTextCoord + vec2(-offset.x, offset.y)) + + extractBloom(inTextCoord + vec2( offset.x, offset.y)); + outBloomColour = vec4(bloom * 0.25, 1.0); +} \ No newline at end of file diff --git a/resources/EngineResources/shaders/bloom_pass_frag.glsl b/resources/EngineResources/shaders/bloom_pass_frag.glsl index ee83d34..652aab7 100644 --- a/resources/EngineResources/shaders/bloom_pass_frag.glsl +++ b/resources/EngineResources/shaders/bloom_pass_frag.glsl @@ -1,61 +1,30 @@ #version 450 + layout(location = 0) out vec4 FragColor; - layout(location = 0) in vec2 TexCoords; +layout(set = 0, binding = 0) uniform sampler2D bloomImage; -layout(set = 0, binding = 0) uniform sampler2D image; -layout(set = 0, binding = 1) uniform sampler2D bloomImage; -layout(set = 1 , binding = 0) uniform PassConfig{ - int ApplyToFinalImage; +layout(push_constant) uniform PassConfig { int horizontal; float GAMMA_CONST; float Exposure; float blur_radius; - vec3 padding; + vec2 bloomSize; } config; +const float weight[5] = float[](0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216); - -const float weight[5] = float[] (0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216); - -vec3 safeBloomSample(vec2 uv) -{ - vec3 value = texture(bloomImage, uv).rgb; - return clamp(value, vec3(0.0), vec3(8.0)); +vec3 safeBloomSample(vec2 uv) { + return max(texture(bloomImage, uv).rgb, vec3(0.0)); } -void main() -{ - if(config.ApplyToFinalImage != 0){ - vec3 bloomColour = safeBloomSample(TexCoords); - vec3 hdrColour = texture(image, TexCoords).rgb; - hdrColour += bloomColour; - vec3 result = vec3(1.0) - exp(-hdrColour * config.Exposure); - result = pow(result, vec3(1.0 / config.GAMMA_CONST)); - FragColor = vec4(result, 1.0); - return; - } - - float blurRadius = config.blur_radius; - vec2 tex_offset = blurRadius / vec2(textureSize(bloomImage, 0)); +void main() { + vec2 tex_offset = config.blur_radius / vec2(textureSize(bloomImage, 0)); + vec2 direction = config.horizontal != 0 ? vec2(tex_offset.x, 0.0) : vec2(0.0, tex_offset.y); vec3 result = safeBloomSample(TexCoords) * weight[0]; - - if(config.horizontal != 0) - { - for(int i = 1; i < 5; ++i) - { - result += safeBloomSample(TexCoords + vec2(tex_offset.x * i, 0.0)) * weight[i]; - result += safeBloomSample(TexCoords - vec2(tex_offset.x * i, 0.0)) * weight[i]; - } + for (int i = 1; i < 5; i++) { + result += safeBloomSample(TexCoords + direction * i) * weight[i]; + result += safeBloomSample(TexCoords - direction * i) * weight[i]; } - else - { - for(int i = 1; i < 5; ++i) - { - result += safeBloomSample(TexCoords + vec2(0.0, tex_offset.y * i)) * weight[i]; - result += safeBloomSample(TexCoords - vec2(0.0, tex_offset.y * i)) * weight[i]; - } - } - FragColor = vec4(result, 1.0); -} \ No newline at end of file +} diff --git a/resources/EngineResources/shaders/lighting_with_shadows_frag.glsl b/resources/EngineResources/shaders/lighting_with_shadows_frag.glsl index 2065e0d..dfea7c0 100644 --- a/resources/EngineResources/shaders/lighting_with_shadows_frag.glsl +++ b/resources/EngineResources/shaders/lighting_with_shadows_frag.glsl @@ -68,9 +68,12 @@ float chebyshevUpperBound(vec2 moments, float t) { return p_max; } -float calcVisibility(vec4 worldPosition, uint cascadeIndex, float ShadowBias, vec2 texelSize) { +float calcVisibility(vec4 worldPosition, uint cascadeIndex, vec2 texelSize) { vec4 shadowMapPosition = shadows.cascadeshadows[cascadeIndex].projViewMatrix * worldPosition; + if (!(shadowMapPosition.w > 0.0) || any(isnan(shadowMapPosition)) || any(isinf(shadowMapPosition))) { + return 1.0; + } shadowMapPosition.xyz /= shadowMapPosition.w; vec2 uv = vec2( @@ -82,17 +85,27 @@ float calcVisibility(vec4 worldPosition, uint cascadeIndex, float ShadowBias, ve if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0 || - depth < ShadowBias || depth > 1.0) { + depth < 0.0 || depth > 1.0) { return 1.0; } float shadow = 0.0; +// vec2 moments = texture(shadowSampler, vec3((uv), cascadeIndex)).rg; +// float visibility = chebyshevUpperBound(moments, depth); +// shadow += visibility; + for(int x = -1; x <= 1; ++x) { for(int y = -1; y <= 1; ++y) { - vec2 moments = texture(shadowSampler, vec3((uv + vec2(x, y) * texelSize), cascadeIndex)).rg; + vec2 sampleUV = uv + vec2(x, y) * texelSize; + if (any(lessThan(sampleUV, vec2(0.0))) || any(greaterThan(sampleUV, vec2(1.0)))) { + shadow += 1.0; + continue; + } + sampleUV = clamp(sampleUV, texelSize * 0.5, vec2(1.0) - texelSize * 0.5); + vec2 moments = texture(shadowSampler, vec3(sampleUV, cascadeIndex)).rg; float visibility = chebyshevUpperBound(moments, depth); shadow += visibility; } @@ -212,8 +225,6 @@ void main() { // outFragColor = vec4(emissive,1); // return; - float ShadowBias = 0.05f; - float opacityf = Opacity.x + Opacity.y + Opacity.z; opacityf = opacityf/3.0; @@ -228,18 +239,21 @@ void main() { vec3 F0 = vec3(0.04); F0 = mix(F0, albedo, metallic); - uint cascadeIndex = 0; - vec4 viewPos = sceneInfo.viewMatrix * worldPosW; - for (uint i = 0; i < SHADOW_MAP_CASCADE_COUNT - 1; ++i) { - if (viewPos.z < shadows.cascadeshadows[i].splitDistance.x) { - cascadeIndex = i + 1; + uint cascadeIndex = SHADOW_MAP_CASCADE_COUNT; + vec4 viewPos = sceneInfo.viewMatrix * vec4(worldPos, 1.0); + float lastSplit = shadows.cascadeshadows[SHADOW_MAP_CASCADE_COUNT - 1].splitDistance.x; + float shadow = 1.0; + if (lastSplit < 0.0 && viewPos.z < 0.0 && viewPos.z >= lastSplit) { + cascadeIndex = 0; + for (uint i = 0; i < SHADOW_MAP_CASCADE_COUNT - 1; ++i) { + if (viewPos.z < shadows.cascadeshadows[i].splitDistance.x) { + cascadeIndex = i + 1; + } } + vec2 texelSizeShadow = 1.0 / textureSize(shadowSampler, 0).rg; + shadow = calcVisibility(vec4(worldPos, 1), cascadeIndex, texelSizeShadow); } - vec2 texelSizeShadow = 1.0 / textureSize(shadowSampler, 0).rg; - - float shadow = calcVisibility(vec4(worldPos, 1), cascadeIndex,ShadowBias, texelSizeShadow); - vec3 Lo = vec3(0.0); for (uint i = 0; i < sceneInfo.numLights; i++) { Light light = lights.lights[i]; @@ -254,7 +268,7 @@ void main() { outFragColor = vec4(Lo + ambient, 1.0f); outFragColor = vec4(outFragColor.xyz/2 + (outFragColor.xyz/2) * vec3(ssao,ssao,ssao),1); - if (DEBUG_SHADOWS == 1) { + if (DEBUG_SHADOWS == 1 && cascadeIndex < SHADOW_MAP_CASCADE_COUNT) { switch (cascadeIndex) { case 0: outFragColor.rgb *= vec3(1.0f, 0.25f, 0.25f); diff --git a/resources/EngineResources/shaders/multi-sampled_bloom_extract_frag.glsl b/resources/EngineResources/shaders/multi-sampled_bloom_extract_frag.glsl new file mode 100644 index 0000000..30fa138 --- /dev/null +++ b/resources/EngineResources/shaders/multi-sampled_bloom_extract_frag.glsl @@ -0,0 +1,34 @@ +#version 450 + +layout(location = 0) in vec2 inTextCoord; +layout(location = 0) out vec4 outBloomColour; +layout(set = 0, binding = 0) uniform sampler2DMS inputTexture; + +layout(push_constant) uniform PassConfig { + int horizontal; + float GAMMA_CONST; + float Exposure; + float blur_radius; + vec2 bloomSize; +} config; + +vec3 extractBloom(vec2 uv) { + ivec2 size = textureSize(inputTexture); + ivec2 pixel = clamp(ivec2(uv * vec2(size)), ivec2(0), size - 1); + int samples = textureSamples(inputTexture); + vec3 color = vec3(0.0); + for (int i = 0; i < samples; i++) color += texelFetch(inputTexture, pixel, i).rgb; + color = max(color / float(samples), vec3(0.0)); + float brightness = dot(color, vec3(0.2126, 0.7152, 0.0722)); + float contribution = max(brightness - 1.0, 0.0); + return color * (contribution / (contribution + 0.5)); +} + +void main() { + vec2 offset = 0.25 / config.bloomSize; + vec3 bloom = extractBloom(inTextCoord + vec2(-offset.x, -offset.y)) + + extractBloom(inTextCoord + vec2( offset.x, -offset.y)) + + extractBloom(inTextCoord + vec2(-offset.x, offset.y)) + + extractBloom(inTextCoord + vec2( offset.x, offset.y)); + outBloomColour = vec4(bloom * 0.25, 1.0); +} \ No newline at end of file diff --git a/resources/EngineResources/shaders/multi-sampled_post_process_frag.glsl b/resources/EngineResources/shaders/multi-sampled_post_process_frag.glsl index f219f97..b684865 100644 --- a/resources/EngineResources/shaders/multi-sampled_post_process_frag.glsl +++ b/resources/EngineResources/shaders/multi-sampled_post_process_frag.glsl @@ -1,72 +1,42 @@ #version 450 -layout(constant_id = 0) const int USE_AA = 0; - -const float GAMMA_CONST = 0.4545; -const float SPAN_MAX = 8.0; -const float REDUCE_MIN = 1.0/128.0; -const float REDUCE_MUL = 1.0/32.0; - layout(location = 0) in vec2 inTextCoord; layout(location = 0) out vec4 outFragColor; -layout(location = 1) out vec4 outBloomColour; - layout(set = 0, binding = 0) uniform sampler2DMS inputTexture; +layout(set = 0, binding = 1) uniform sampler2D bloomImage; -layout(set = 1, binding = 0) uniform ScreenSize{ - vec2 size; -} screenSize; +layout(push_constant) uniform PassConfig { + int horizontal; + float GAMMA_CONST; + float Exposure; + float blur_radius; + vec2 bloomSize; +} config; -vec4 gamma(vec4 color){ - return color = vec4(pow(color.rgb,vec3(GAMMA_CONST)),color.a); -} -vec3 HDR(float Gamma, float Exposure, sampler2D Texture, vec2 TexCoords){ - vec3 hdrColor = texture(Texture, TexCoords).rgb; - vec3 mapped = vec3(1.0) - exp(-hdrColor * Exposure); - mapped = pow(mapped, vec3(1.0 / Gamma)); - return mapped; -} -vec4 msaa(int sampleCount, sampler2DMS textureIn,vec2 TextCoord){ - ivec2 pixelCoords = ivec2(TextCoord * textureSize(textureIn)); +vec4 msaa(int sampleCount, sampler2DMS textureIn, vec2 TextCoord) { + ivec2 size = textureSize(textureIn); + ivec2 pixelCoords = clamp(ivec2(TextCoord * vec2(size)), ivec2(0), size - 1); vec4 colorSum = vec4(0.0); - - for(int i = 0; i < sampleCount; ++i) { - vec4 sampleColor = texelFetch(textureIn, pixelCoords, i); - colorSum += sampleColor; - } + for (int i = 0; i < sampleCount; i++) colorSum += texelFetch(textureIn, pixelCoords, i); return colorSum / float(sampleCount); } -void main(){ - ivec2 pixelCoords = ivec2(inTextCoord * textureSize(inputTexture)); +vec3 bloomAt(ivec2 pixel, ivec2 size) { + return texelFetch(bloomImage, clamp(pixel, ivec2(0), size - 1), 0).rgb; +} - outFragColor = texelFetch(inputTexture, pixelCoords, 0); +vec3 upsampleBloom(vec2 uv) { + ivec2 size = textureSize(bloomImage, 0); + vec2 pixel = uv * vec2(size) - 0.5; + ivec2 base = ivec2(floor(pixel)); + vec2 blend = fract(pixel); + return mix(mix(bloomAt(base, size), bloomAt(base + ivec2(1, 0), size), blend.x), + mix(bloomAt(base + ivec2(0, 1), size), bloomAt(base + ivec2(1, 1), size), blend.x), blend.y); +} - if(USE_AA == 0){ - outFragColor = texelFetch(inputTexture,pixelCoords,0); - } - if(USE_AA == 1){ - outFragColor = texelFetch(inputTexture,pixelCoords,0); - } - if(USE_AA == 2){ - outFragColor = msaa(2,inputTexture,inTextCoord); - } - if(USE_AA == 3){ - outFragColor = msaa(4,inputTexture,inTextCoord); - } - if(USE_AA == 4){ - outFragColor = msaa(8,inputTexture,inTextCoord); - } - - vec3 color = outFragColor.rgb; - - float brightness = dot(color, vec3(0.2126, 0.7152, 0.0722)); - - float threshold = 1.0; - float softKnee = 0.5; - - float contribution = max(brightness - threshold, 0.0); - contribution = contribution / max(contribution + softKnee, 0.0001); - - outBloomColour = vec4(color * contribution, 1.0); -} \ No newline at end of file +void main() { + vec4 sceneColor = msaa(textureSamples(inputTexture), inputTexture, inTextCoord); + vec3 hdrColour = max(sceneColor.rgb + upsampleBloom(inTextCoord), vec3(0.0)); + vec3 result = vec3(1.0) - exp(-hdrColour * config.Exposure); + outFragColor = vec4(pow(result, vec3(1.0 / config.GAMMA_CONST)), sceneColor.a); +} diff --git a/resources/EngineResources/shaders/packed_scene_vertex.glsl b/resources/EngineResources/shaders/packed_scene_vertex.glsl index d8e961f..5d2c518 100644 --- a/resources/EngineResources/shaders/packed_scene_vertex.glsl +++ b/resources/EngineResources/shaders/packed_scene_vertex.glsl @@ -111,6 +111,11 @@ void main() { vec3 localPos = vec3(voxelX, voxelY, voxelZ) + FACE_CORNERS[faceId][cornerIndex]; vec3 Offset = vec3(0,0,0); + if (gl_InstanceIndex != 0) { + uint encoded = uint(gl_InstanceIndex); + Offset = vec3(int(encoded & 1023u) - 512, int((encoded >> 10u) & 1023u) - 512, + int((encoded >> 20u) & 1023u) - 512) * 16.0; + } vec3 worldPos = localPos + Offset + push_constants.ModelOffset.xyz * 16.0; vec4 worldPosVec4 = vec4(worldPos, 1.0); diff --git a/resources/EngineResources/shaders/post_process_frag.glsl b/resources/EngineResources/shaders/post_process_frag.glsl index 38ef3ac..cfbea64 100644 --- a/resources/EngineResources/shaders/post_process_frag.glsl +++ b/resources/EngineResources/shaders/post_process_frag.glsl @@ -2,36 +2,27 @@ layout(constant_id = 0) const int USE_AA = 0; -const float GAMMA_CONST = 0.4545; -const float Exposure = 2.0; const float SPAN_MAX = 8.0; const float REDUCE_MIN = 1.0/128.0; const float REDUCE_MUL = 1.0/32.0; layout(location = 0) in vec2 inTextCoord; layout(location = 0) out vec4 outFragColor; -layout(location = 1) out vec4 outBloomColour; layout(set = 0, binding = 0) uniform sampler2D inputTexture; +layout(set = 0, binding = 1) uniform sampler2D bloomImage; -layout(set = 1, binding = 0) uniform ScreenSize{ - vec2 size; -} screenSize; - -vec4 gamma(vec4 color){ - return color = vec4(pow(color.rgb,vec3(GAMMA_CONST)),color.a); -} - -vec3 HDR(float Gamma, float Exposure, sampler2D Texture, vec2 TexCoords){ - vec3 hdrColor = texture(Texture, TexCoords).rgb; - vec3 mapped = vec3(1.0) - exp(-hdrColor * Exposure); - mapped = pow(mapped, vec3(1.0 / Gamma)); - return mapped; -} +layout(push_constant) uniform PassConfig { + int horizontal; + float GAMMA_CONST; + float Exposure; + float blur_radius; + vec2 bloomSize; +} config; // Sourced from: https://mini.gmshaders.com/p/gm-shaders-mini-fxaa vec4 fxaa(sampler2D tex, vec2 uv) { - vec2 u_texel = 1.0 / screenSize.size; + vec2 u_texel = 1.0 / vec2(textureSize(inputTexture, 0)); //Sample center and 4 corners vec3 rgbCC = texture(tex, uv).rgb; @@ -79,36 +70,22 @@ vec4 fxaa(sampler2D tex, vec2 uv) { return ((lumaB < lumaMin) || (lumaB > lumaMax)) ? A : B; } +vec3 bloomAt(ivec2 pixel, ivec2 size) { + return texelFetch(bloomImage, clamp(pixel, ivec2(0), size - 1), 0).rgb; +} + +vec3 upsampleBloom(vec2 uv) { + ivec2 size = textureSize(bloomImage, 0); + vec2 pixel = uv * vec2(size) - 0.5; + ivec2 base = ivec2(floor(pixel)); + vec2 blend = fract(pixel); + return mix(mix(bloomAt(base, size), bloomAt(base + ivec2(1, 0), size), blend.x), + mix(bloomAt(base + ivec2(0, 1), size), bloomAt(base + ivec2(1, 1), size), blend.x), blend.y); +} + void main(){ - - if(USE_AA == 1){ - outFragColor = fxaa(inputTexture, inTextCoord); - } - else if(USE_AA == 2){ - - outFragColor = texture(inputTexture, inTextCoord); - } - else if(USE_AA == 3){ - - outFragColor = texture(inputTexture, inTextCoord); - } - else if(USE_AA == 4){ - - outFragColor = texture(inputTexture, inTextCoord); - } - else { - outFragColor = texture(inputTexture, inTextCoord); - } - - vec3 color = outFragColor.rgb; - - float brightness = dot(color, vec3(0.2126, 0.7152, 0.0722)); - - float threshold = 1.0; - float softKnee = 0.5; - - float contribution = max(brightness - threshold, 0.0); - contribution = contribution / max(contribution + softKnee, 0.0001); - - outBloomColour = vec4(color * contribution, 1.0); + vec4 sceneColor = USE_AA == 1 ? fxaa(inputTexture, inTextCoord) : texture(inputTexture, inTextCoord); + vec3 hdrColour = max(sceneColor.rgb + upsampleBloom(inTextCoord), vec3(0.0)); + vec3 result = vec3(1.0) - exp(-hdrColour * config.Exposure); + outFragColor = vec4(pow(result, vec3(1.0 / config.GAMMA_CONST)), sceneColor.a); } \ No newline at end of file diff --git a/resources/EngineResources/shaders/screen_space_reflection_frag.glsl b/resources/EngineResources/shaders/screen_space_reflection_frag.glsl index c49cca1..f387c31 100644 --- a/resources/EngineResources/shaders/screen_space_reflection_frag.glsl +++ b/resources/EngineResources/shaders/screen_space_reflection_frag.glsl @@ -10,9 +10,7 @@ layout(set = 0, binding = 3) uniform sampler2D pbrSampler; layout(set = 1, binding = 0) uniform CameraProperties { mat4 projection; - mat4 invProjection; mat4 view; - mat4 invView; float ssrStepSize; float ssrMaxDistance; int maxSteps; @@ -30,13 +28,12 @@ float edgeFade(vec2 uv) { } void main() { - vec4 sceneColor = texture(albedoSampler, fragTexCoord); + outColor = vec4(0.0); vec4 posSample = texture(worldPosSampler, fragTexCoord); vec4 normalSample = texture(normalSampler, fragTexCoord); vec4 pbrSample = texture(pbrSampler, fragTexCoord); if (posSample.a == 0.0 || length(posSample.xyz) == 0.0 || length(normalSample.xyz) < 0.001) { - outColor = sceneColor; return; } @@ -54,11 +51,10 @@ void main() { float materialReflection = reflectiveness + (refractiveness - 1.05); materialReflection *= mix(0.75, 1.0, metallic); - materialReflection *= smoothstep(0.7, 0.05, roughness); + materialReflection *= 1.0 - smoothstep(0.05, 0.7, roughness); materialReflection *= mix(0.45, 1.0, fresnel); if (materialReflection <= MIN_REFLECTION) { - outColor = sceneColor; return; } @@ -66,7 +62,6 @@ void main() { vec3 reflectDir = normalize(reflect(viewDir, worldNormal)); if (dot(reflectDir, worldNormal) <= 0.0) { - outColor = sceneColor; return; } @@ -115,7 +110,6 @@ void main() { } if (!hitFound) { - outColor = sceneColor; return; } @@ -124,5 +118,5 @@ void main() { float fade = edgeFade(hitUV); float weight = clamp(materialReflection * fade, 0.0, 0.85); - outColor = vec4(mix(sceneColor.rgb, reflectedColor, weight), sceneColor.a); + outColor = vec4(reflectedColor * weight, weight); } \ No newline at end of file diff --git a/resources/EngineResources/shaders/shadow_frag.glsl b/resources/EngineResources/shaders/shadow_frag.glsl index e2993f6..c4b648a 100644 --- a/resources/EngineResources/shaders/shadow_frag.glsl +++ b/resources/EngineResources/shaders/shadow_frag.glsl @@ -65,7 +65,7 @@ void main() } else { albedo = material.diffuseColor; } - if (albedo.a < 0.5 || Translucency > 0.5 || Opacity < 0.5) { + if (albedo.a < 0.5 || Translucency > 0.95 || Opacity < 0.5) { discard; } @@ -77,5 +77,5 @@ void main() float dy = dFdy(depth); moment2 += 0.25 * (dx * dx + dy * dy); - outFragColor = vec2(moment1, moment2); + outFragColor = vec2(moment1, moment2) * (1 - Translucency); } \ No newline at end of file diff --git a/resources/EngineResources/shaders/shadow_vertex_packed.glsl b/resources/EngineResources/shaders/shadow_vertex_packed.glsl index 7fc691a..d00c9b8 100644 --- a/resources/EngineResources/shaders/shadow_vertex_packed.glsl +++ b/resources/EngineResources/shaders/shadow_vertex_packed.glsl @@ -80,59 +80,11 @@ const vec3 FACE_CORNERS[8][4] = vec3[8][4]( vec3[4](vec3(0.0, 0.0, 1.0), vec3(0.0, 1.0, 1.0), vec3(1.0, 1.0, 0.0), vec3(1.0, 0.0, 0.0)) // Face 7 (cross model pane 2) ); -vec3 buildFaceCornerPosition(uvec3 voxelPos, uint faceId, uint cornerIndex) { - vec3 p = vec3(voxelPos); - - if (faceId == 0u) { - if (cornerIndex == 0u) return p + vec3(0.0, 1.0, 0.0); - if (cornerIndex == 1u) return p + vec3(0.0, 1.0, 1.0); - if (cornerIndex == 2u) return p + vec3(1.0, 1.0, 1.0); - return p + vec3(1.0, 1.0, 0.0); - } - - if (faceId == 1u) { - if (cornerIndex == 0u) return p + vec3(0.0, 0.0, 0.0); - if (cornerIndex == 1u) return p + vec3(1.0, 0.0, 0.0); - if (cornerIndex == 2u) return p + vec3(1.0, 0.0, 1.0); - return p + vec3(0.0, 0.0, 1.0); - } - - if (faceId == 2u) { - if (cornerIndex == 0u) return p + vec3(1.0, 0.0, 0.0); - if (cornerIndex == 1u) return p + vec3(1.0, 1.0, 0.0); - if (cornerIndex == 2u) return p + vec3(1.0, 1.0, 1.0); - return p + vec3(1.0, 0.0, 1.0); - } - - if (faceId == 3u) { - if (cornerIndex == 0u) return p + vec3(0.0, 0.0, 0.0); - if (cornerIndex == 1u) return p + vec3(0.0, 0.0, 1.0); - if (cornerIndex == 2u) return p + vec3(0.0, 1.0, 1.0); - return p + vec3(0.0, 1.0, 0.0); - } - - if (faceId == 4u) { - if (cornerIndex == 0u) return p + vec3(0.0, 0.0, 1.0); - if (cornerIndex == 1u) return p + vec3(1.0, 0.0, 1.0); - if (cornerIndex == 2u) return p + vec3(1.0, 1.0, 1.0); - return p + vec3(0.0, 1.0, 1.0); - } - - if (cornerIndex == 0u) return p + vec3(0.0, 0.0, 0.0); - if (cornerIndex == 1u) return p + vec3(0.0, 1.0, 0.0); - if (cornerIndex == 2u) return p + vec3(1.0, 1.0, 0.0); - return p + vec3(1.0, 0.0, 0.0); -} - void main() { uint faceRecordIndex = uint(gl_VertexIndex) / 6u; uint triangleVertex = (uint(gl_VertexIndex))% 6u; uint cornerIndex = TRI_TO_CORNER[triangleVertex]; - //uint faceRecordIndex = uint(gl_VertexIndex) / 6u; - //uint triangleVertex = uint(gl_VertexIndex) % 6u; - //uint cornerIndex = TRI_TO_CORNER[triangleVertex]; - uint packedFace = faces[faceRecordIndex]; uint voxelX = packedFace & 0xFu; @@ -143,14 +95,6 @@ void main() { uint texIdxX = (packedFace >> 26u) & 0x7u; uint texIdxY = (packedFace >> 29u) & 0x7u; - // (voxelPos.x & 0xFu) | - // ((voxelPos.y & 0xFu) << 4u) | - // ((voxelPos.z & 0xFu) << 8u) | - // ((faceIndex & 0x7u) << 12u) | - // ((materialId & 0x7FFu) << 15u) | - // ((textureIndexX & 0x7u) << 26u) | - // ((textureIndexY & 0x7u) << 29u); - vec2 texID = vec2((texIdxX)/4.0,(texIdxY)/4.0); vec2 scalar = vec2(1.0/4.0,1.0/4.0); @@ -158,6 +102,11 @@ void main() { vec3 localPos = vec3(voxelX, voxelY, voxelZ) + FACE_CORNERS[faceId][cornerIndex]; vec3 worldPos = localPos + push_constants.ModelOffset.xyz * 16.0; + if (gl_InstanceIndex != 0) { + uint encoded = uint(gl_InstanceIndex); + worldPos += vec3(int(encoded & 1023u) - 512, int((encoded >> 10u) & 1023u) - 512, + int((encoded >> 20u) & 1023u) - 512) * 16.0; + } outTextCoord = CORNER_UVS_GRASS_BLOCK[faceId][cornerIndex] * scalar + texID; outMaterialIdx = matId; diff --git a/resources/EngineResources/shaders/ssao_blur_frag.glsl b/resources/EngineResources/shaders/ssao_blur_frag.glsl index 40b5edc..de551ad 100644 --- a/resources/EngineResources/shaders/ssao_blur_frag.glsl +++ b/resources/EngineResources/shaders/ssao_blur_frag.glsl @@ -7,7 +7,7 @@ layout(set = 0, binding = 0) uniform sampler2D ssaoSampler; layout(set = 0, binding = 1) uniform sampler2D viewPosSampler; void main() { - vec2 texelSize = 2.0 / vec2(textureSize(ssaoSampler, 0)); + vec2 texelSize = 1.0 / vec2(textureSize(ssaoSampler, 0)); float centerAO = texture(ssaoSampler, inTextCoord).r; float centerDepth = texture(viewPosSampler, inTextCoord).z; @@ -15,7 +15,7 @@ void main() { float totalAO = centerAO; float totalWeight = 1.0; - float weights[4] = float[](0.227027, 0.1216216, 0.054054, 0.016216); + float weights[4] = float[](0.1216216, 0.1945946, 0.1945946, 0.1216216); int offsets[4] = int[](-2, -1, 1, 2); const float Sharpness = 20.0; diff --git a/resources/EngineResources/shaders/ssao_frag.glsl b/resources/EngineResources/shaders/ssao_frag.glsl index 467fab6..40ab2aa 100644 --- a/resources/EngineResources/shaders/ssao_frag.glsl +++ b/resources/EngineResources/shaders/ssao_frag.glsl @@ -2,7 +2,6 @@ layout(location = 0) in vec2 inTextCoord; layout(location = 0) out float outAO; -layout(location = 1) out vec4 viewPos; layout(set = 0, binding = 0) uniform sampler2D posSampler; layout(set = 0, binding = 1) uniform sampler2D normalSampler; @@ -19,65 +18,52 @@ layout(set = 2, binding = 0) uniform SSAOInfo { float radius; //140 float bias; //144 int kernelSize; //148 - float ResolutionScale; //152; - vec2 Padding; //160; } ssao; void main() { - - vec2 ScreenSize = ssao.screenSize * ssao.ResolutionScale; - - int KERNEL_SIZE = ssao.kernelSize; + int KERNEL_SIZE = clamp(ssao.kernelSize, 1, 64); vec3 fragPos = texture(posSampler, inTextCoord).xyz; - vec3 worldNorm = normalize(texture(normalSampler, inTextCoord).xyz); + vec3 worldNorm = texture(normalSampler, inTextCoord).xyz; - if (dot(worldNorm, worldNorm) < 0.001) { + if (dot(worldNorm, worldNorm) < 0.001 || fragPos.z >= 0.0) { outAO = 1.0; return; } vec3 normal = normalize(mat3(ssao.view) * worldNorm); - viewPos = vec4(fragPos, 1); - - vec2 noiseScale = ScreenSize / 4.0; - vec3 randomVec = texture(noiseSampler, inTextCoord * noiseScale).xyz; - vec3 tangent = normalize(randomVec - normal * dot(randomVec, normal)); + vec3 randomVec = texelFetch(noiseSampler, ivec2(gl_FragCoord.xy) % 4, 0).xyz; + vec3 tangent = randomVec - normal * dot(randomVec, normal); + if (dot(tangent, tangent) < 0.0001) { + tangent = cross(normal, abs(normal.z) < 0.9 ? vec3(0, 0, 1) : vec3(0, 1, 0)); + } + tangent = normalize(tangent); vec3 bitangent = cross(normal, tangent); mat3 TBN = mat3(tangent, bitangent, normal); float fragDepth = -fragPos.z; - float minOptimalDistance = 15; - float maxKernelSize = float(ssao.kernelSize); - - // Calculate a dynamic loop limit based on proximity - // If closer than 1.5 units, smoothly scale down the loop count - int dynamicKernelSize = int(KERNEL_SIZE); - if (fragDepth < minOptimalDistance) { - float proximityFactor = clamp(fragDepth / minOptimalDistance, 0.2, 1.0); - dynamicKernelSize = int(maxKernelSize * proximityFactor); - } - - // Force a hard minimum baseline of samples so SSAO doesn't completely disappear - dynamicKernelSize = max(dynamicKernelSize, 16); - float occlusion = 0.0; float Passes = 0.0f; - for (int i = 0; i < dynamicKernelSize; i++) { - vec3 samplePos = fragPos + (TBN * kernel.samples[i].xyz) * ssao.radius; + for (int i = 0; i < KERNEL_SIZE; i++) { + // Cover the complete radius distribution, even with a smaller sample budget. + int sampleIndex = i * 64 / KERNEL_SIZE; + vec3 samplePos = fragPos + (TBN * kernel.samples[sampleIndex].xyz) * ssao.radius; vec4 offset = ssao.projection * vec4(samplePos, 1.0); + if (offset.w <= 0.0) continue; offset.xyz /= offset.w; offset.xyz = offset.xyz * 0.5 + 0.5; - vec2 sampleUV = clamp(vec2(offset.x, 1.0 - offset.y), 0.0, 1.0); + vec2 sampleUV = vec2(offset.x, 1.0 - offset.y); + if (any(lessThan(sampleUV, vec2(0))) || any(greaterThan(sampleUV, vec2(1)))) continue; vec3 sampleViewPos = texture(posSampler, sampleUV).xyz; + if (sampleViewPos.z >= 0.0) continue; float sampleDepth = -sampleViewPos.z; float depthDelta = abs(fragDepth - sampleDepth); if (depthDelta > ssao.radius * 2.0) { continue; } - float rangeCheck = smoothstep(0.0, 1.0, ssao.radius / depthDelta); + float rangeCheck = smoothstep(0.0, 1.0, ssao.radius / max(depthDelta, 0.0001)); float isOccluded = step(samplePos.z + ssao.bias, sampleViewPos.z); occlusion += isOccluded * rangeCheck; Passes += 1.0; diff --git a/resources/EngineResources/shaders/ssr_composite_frag.glsl b/resources/EngineResources/shaders/ssr_composite_frag.glsl new file mode 100644 index 0000000..7a0d7df --- /dev/null +++ b/resources/EngineResources/shaders/ssr_composite_frag.glsl @@ -0,0 +1,34 @@ +#version 450 + +layout(location = 0) in vec2 fragTexCoord; +layout(location = 0) out vec4 outColor; + +layout(set = 0, binding = 0) uniform sampler2D sceneSampler; +layout(set = 0, binding = 1) uniform sampler2D reflectionSampler; +layout(set = 0, binding = 2) uniform sampler2D pbrSampler; +layout(set = 0, binding = 3) uniform sampler2D normalSampler; + +vec4 reflectionAt(ivec2 pixel, ivec2 size) { + return texelFetch(reflectionSampler, clamp(pixel, ivec2(0), size - 1), 0); +} + +void main() { + vec4 sceneColor = texture(sceneSampler, fragTexCoord); + vec4 pbr = texture(pbrSampler, fragTexCoord); + vec4 normal = texture(normalSampler, fragTexCoord); + float materialReflection = (pbr.a + normal.a - 1.05) * (1.0 - smoothstep(0.05, 0.7, pbr.g)); + if (dot(normal.xyz, normal.xyz) < 0.001 || materialReflection <= 0.01) { + outColor = sceneColor; + return; + } + + // The shared sampler is nearest-filtered. Upsample only premultiplied reflections. + ivec2 size = textureSize(reflectionSampler, 0); + vec2 pixel = fragTexCoord * vec2(size) - 0.5; + ivec2 base = ivec2(floor(pixel)); + vec2 blend = fract(pixel); + vec4 reflection = mix( + mix(reflectionAt(base, size), reflectionAt(base + ivec2(1, 0), size), blend.x), + mix(reflectionAt(base + ivec2(0, 1), size), reflectionAt(base + ivec2(1, 1), size), blend.x), blend.y); + outColor = vec4(sceneColor.rgb * (1.0 - reflection.a) + reflection.rgb, sceneColor.a); +} \ No newline at end of file diff --git a/resources/ProgramResources/ServerCache/UUIDs.json b/resources/ProgramResources/ServerCache/UUIDs.json new file mode 100644 index 0000000..0637a08 --- /dev/null +++ b/resources/ProgramResources/ServerCache/UUIDs.json @@ -0,0 +1 @@ +[] \ No newline at end of file diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Display/VulkanRenderer.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Display/VulkanRenderer.java index 66ca558..81f6ab7 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Display/VulkanRenderer.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Display/VulkanRenderer.java @@ -5,6 +5,7 @@ import net.halbear.Terrain4J.EngineCore.Logic.EngineInstance; import net.halbear.Terrain4J.EngineCore.Logic.InitData; import net.halbear.Terrain4J.EngineCore.Logic.Rendering.SkyBox; import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext; +import net.halbear.Terrain4J.EngineCore.Profiling.VulkanFrameTimings; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Computing.VoxelTerrainGeneration.Biome; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Computing.VoxelTerrainGeneration.Voxel; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Computing.VoxelTerrainGeneration.VoxelChunkGenerator; @@ -56,9 +57,11 @@ public class VulkanRenderer implements Renderer { private final CommandPool[] CommandPools; private final Fence[] Fences; private final Queue.GraphicsQueue GraphicsQueue; + private final VulkanFrameTimings gpuTimings; + public VulkanFrameTimings GetGpuTimings(){return gpuTimings;} private final Semaphore[] PresentCompleteSemaphores; private final Queue.PresentQueue PresentQueue; - private final Semaphore[] RenderCompleteSemaphores; + private Semaphore[] RenderCompleteSemaphores; private final SceneRenderer sceneRender; private final AmbientOcclusionRenderer ssaoRenderer; private final ReflectionsRenderer ssrRender; @@ -104,6 +107,7 @@ public class VulkanRenderer implements Renderer { RendererContext = new VulkanContext(engineInstance.window()); CurrentFrame = 0; GraphicsQueue = new Queue.GraphicsQueue(RendererContext, 0); + gpuTimings = new VulkanFrameTimings(RendererContext, GraphicsQueue.GetQueueFamilyIndex()); PresentQueue = new Queue.PresentQueue(RendererContext, 0); CommandPools= new CommandPool[VulkanUtils.MAX_IN_FLIGHT]; CommandBuffers= new CommandBuffer[VulkanUtils.MAX_IN_FLIGHT]; @@ -390,9 +394,11 @@ public class VulkanRenderer implements Renderer { private void RecordingStart(CommandPool commandPool, CommandBuffer commandBuffer){ commandPool.Reset(RendererContext); commandBuffer.BeginRecording(); + gpuTimings.begin(RendererContext, commandBuffer.GetVulkanCommandBuffer(), CurrentFrame); } private void RecordingStop(CommandBuffer commandBuffer){ + gpuTimings.mark(commandBuffer.GetVulkanCommandBuffer(), CurrentFrame, "UI and present copy"); commandBuffer.EndRecording(); } @@ -401,6 +407,7 @@ public class VulkanRenderer implements Renderer { Logger.debug("Waiting Vulkan Context"); VoxelWorldManager.Cleanup(RendererContext); + gpuTimings.cleanup(RendererContext); sceneRender.cleanup(RendererContext); if(ssaoRenderer != null)ssaoRenderer.cleanup(RendererContext); @@ -453,26 +460,32 @@ public class VulkanRenderer implements Renderer { var CommandPool = CommandPools[CurrentFrame]; var CommandBuffer = CommandBuffers[CurrentFrame]; - RecordingStart(CommandPool, CommandBuffer); - VoxelWorldManager.RecordGeneration(RendererContext,CommandBuffer); - sceneRender.Render(engineInstance,RendererContext,CommandBuffer, modelsCache,materialsCache,CurrentFrame); - boolean RenderShadows = ShadowRenderer.AllowShadowRendering() && EngineConfig.getInstance().RenderShadows(); - if(RenderShadows) {shadowRender.render(engineInstance, RendererContext, CommandBuffer, modelsCache, materialsCache, CurrentFrame); } - ssaoRenderer.Render(RendererContext,engineInstance, CommandBuffer, sceneRender.GetMRTAttachments(), CurrentFrame); - if(RenderShadows) { - lightRenderer.render(engineInstance, RendererContext, CommandBuffer, sceneRender.GetMRTAttachments(), - shadowRender.getShadowAttachment(), ssaoRenderer.getSSAOBlurAttachment(), CurrentFrame, shadowRender.getCascadeShadows(CurrentFrame)); - } else lightRenderer.render(engineInstance, RendererContext, CommandBuffer, sceneRender.GetMRTAttachments(),ssaoRenderer.getSSAOBlurAttachment(),CurrentFrame); - ssrRender.Render( RendererContext, engineInstance, CommandBuffer, CurrentFrame); - PostProcessor.Render(RendererContext,CommandBuffer,ssrRender.GetSSRAttachment()); - spriteRenderer.Render(engineInstance,RendererContext,CommandBuffer,PostProcessor.GetAttachment(),modelsCache,CurrentFrame); - GuiRender.Render(RendererContext,CommandBuffer,CurrentFrame,PostProcessor.GetAttachment()); - int ImageIndex; if (Resize || (ImageIndex = swapChain.FetchNextImage(RendererContext.GetDevice(), PresentCompleteSemaphores[imageAcquisitionIndex])) < 0){ resize(engineInstance); return; } + RecordingStart(CommandPool, CommandBuffer); + VoxelWorldManager.RecordGeneration(RendererContext,CommandBuffer,CurrentFrame); + gpuTimings.mark(CommandBuffer.GetVulkanCommandBuffer(), CurrentFrame, "voxel generation"); + sceneRender.Render(engineInstance,RendererContext,CommandBuffer, modelsCache,materialsCache,CurrentFrame); + gpuTimings.mark(CommandBuffer.GetVulkanCommandBuffer(), CurrentFrame, "geometry"); + boolean RenderShadows = ShadowRenderer.AllowShadowRendering() && EngineConfig.getInstance().RenderShadows(); + if(RenderShadows) {shadowRender.render(engineInstance, RendererContext, CommandBuffer, modelsCache, materialsCache, CurrentFrame); } + gpuTimings.mark(CommandBuffer.GetVulkanCommandBuffer(), CurrentFrame, "shadows"); + ssaoRenderer.Render(RendererContext,engineInstance, CommandBuffer, sceneRender.GetMRTAttachments(), CurrentFrame); + gpuTimings.mark(CommandBuffer.GetVulkanCommandBuffer(), CurrentFrame, "SSAO"); + if(RenderShadows) { + lightRenderer.render(engineInstance, RendererContext, CommandBuffer, sceneRender.GetMRTAttachments(), + shadowRender.getShadowAttachment(), ssaoRenderer.getSSAOBlurAttachment(), CurrentFrame, shadowRender.getCascadeShadows(CurrentFrame)); + } else lightRenderer.render(engineInstance, RendererContext, CommandBuffer, sceneRender.GetMRTAttachments(),ssaoRenderer.getSSAOBlurAttachment(),CurrentFrame); + gpuTimings.mark(CommandBuffer.GetVulkanCommandBuffer(), CurrentFrame, "lighting"); + ssrRender.Render( RendererContext, engineInstance, CommandBuffer, CurrentFrame); + gpuTimings.mark(CommandBuffer.GetVulkanCommandBuffer(), CurrentFrame, "SSR"); + PostProcessor.Render(RendererContext,CommandBuffer,ssrRender.GetSSRAttachment()); + gpuTimings.mark(CommandBuffer.GetVulkanCommandBuffer(), CurrentFrame, "bloom and tone mapping"); + spriteRenderer.Render(engineInstance,RendererContext,CommandBuffer,PostProcessor.GetAttachment(),modelsCache,CurrentFrame); + GuiRender.Render(RendererContext,CommandBuffer,CurrentFrame,PostProcessor.GetAttachment()); swapChainRender.Render(RendererContext,CommandBuffer,PostProcessor.GetAttachment(),ImageIndex); @@ -495,7 +508,7 @@ public class VulkanRenderer implements Renderer { return; } RecordingStart(CommandPool, CommandBuffer); - VoxelWorldManager.RecordGeneration(RendererContext,CommandBuffer); + VoxelWorldManager.RecordGeneration(RendererContext,CommandBuffer,CurrentFrame); sceneRender.Render(engineInstance,RendererContext,CommandBuffer, modelsCache,materialsCache,CurrentFrame); PostProcessor.Render(RendererContext,CommandBuffer,sceneRender.GetAttachmentColour()); spriteRenderer.Render(engineInstance,RendererContext,CommandBuffer,PostProcessor.GetAttachment(),modelsCache,CurrentFrame); @@ -548,7 +561,7 @@ public class VulkanRenderer implements Renderer { private void resize(EngineInstance engineInstance){ Window window = engineInstance.window(); - if(window.getWidth() == 0 && window.getHeight() == 0){ + if(window.getWidth() == 0 || window.getHeight() == 0){ return; } Resize = false; @@ -557,6 +570,7 @@ public class VulkanRenderer implements Renderer { Arrays.asList(RenderCompleteSemaphores).forEach(i->i.cleanup(RendererContext)); Arrays.asList(PresentCompleteSemaphores).forEach(i->i.cleanup(RendererContext)); + RenderCompleteSemaphores = new Semaphore[RendererContext.GetSwapChain().GetImageCount()]; for(int i = 0; i < VulkanUtils.MAX_IN_FLIGHT; i++){ PresentCompleteSemaphores[i] = new Semaphore(RendererContext); } @@ -569,13 +583,13 @@ public class VulkanRenderer implements Renderer { sceneRender.Resize(engineInstance,RendererContext); if(Deferred) { List attachments = new ArrayList<>(sceneRender.GetMRTAttachments().GetColourAttachments()); - attachments.add(ssaoRenderer.getSSAOBlurAttachment()); - attachments.add(shadowRender.getShadowAttachment()); - lightRenderer.resize(RendererContext, attachments); List ssaoAttachments = new ArrayList<>(); ssaoAttachments.add(attachments.get(7)); ssaoAttachments.add(attachments.get(2)); ssaoRenderer.resize(RendererContext, ssaoAttachments); + attachments.add(ssaoRenderer.getSSAOBlurAttachment()); + attachments.add(shadowRender.getShadowAttachment()); + lightRenderer.resize(RendererContext, attachments); ssaoAttachments.clear(); ssaoAttachments.add(lightRenderer.getAttachment()); ssaoAttachments.add(attachments.get(0)); diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/EngineConfig.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/EngineConfig.java index 6a3f203..83598b8 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/EngineConfig.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/EngineConfig.java @@ -90,11 +90,24 @@ public class EngineConfig { public float SoundVolume = 0.0f; public float WeaponVolume = 0.0f; public float AmbientVolume = 0.0f; - public float MaxShadowDistance = 16384.0f; + public float MaxShadowDistance = 96.0f; + private RenderQualitySettings renderQuality = RenderQualitySettings.fromProperties(new Properties()); + + public float GetSSAOScale(){return renderQuality.ssaoScale();} + public int GetSSAOSamples(){return renderQuality.ssaoSamples();} + public float GetSSRScale(){return renderQuality.ssrScale();} + public int GetSSRMaxSteps(){return renderQuality.ssrSteps();} + public float GetBloomScale(){return renderQuality.bloomScale();} + public int GetBloomPasses(){return renderQuality.bloomPasses();} + public int GetVoxelChunkRadius(){return renderQuality.chunkRadius();} + public int GetVoxelGenerationsPerFrame(){return renderQuality.generationsPerFrame();} + public int GetVoxelFacePoolMiB(){return renderQuality.facePoolMiB();} public float MaxShadowDistance(){return MaxShadowDistance;} - public void MaxShadowDistance(float newDistance){ MaxShadowDistance = newDistance;} + public void MaxShadowDistance(float newDistance){ + MaxShadowDistance = Float.isFinite(newDistance) ? Math.clamp(newDistance, 1.0f, 16384.0f) : 96.0f; + } public boolean RenderShadows = true; @@ -305,6 +318,7 @@ public class EngineConfig { } } if(SuccessfulLoad) { + renderQuality = RenderQualitySettings.fromProperties(EngineConfigVar); IconName = (EngineConfigVar.getOrDefault("Software_Icon", "ProgramIcon.png").toString()); LoadingScreenLocation = (EngineConfigVar.getOrDefault("LoadingScreenLocation", LoadingScreenLocation).toString()); IconPath = (EngineConfigVar.getOrDefault("Software_Icon_Path", "/WindowResources/Icon/").toString()); @@ -333,7 +347,7 @@ public class EngineConfig { FOV = (float)(DegToRad * Float.parseFloat(EngineConfigVar.getOrDefault("field_of_view", 60.0f).toString())); zNearPlane = (Float.parseFloat(EngineConfigVar.getOrDefault("z_near_plane", 1.0f).toString())); zFarPlane = (Float.parseFloat(EngineConfigVar.getOrDefault("z_far_plane", 100.0f).toString())); - MaxShadowDistance = (Float.parseFloat(EngineConfigVar.getOrDefault("max_shadow_distance", 16384.0f).toString())); + MaxShadowDistance(Float.parseFloat(EngineConfigVar.getOrDefault("max_shadow_distance", 96.0f).toString())); AAValue = (Integer.parseInt(EngineConfigVar.getOrDefault("anti_alias_mode", 1).toString())); renderer = Integer.parseInt(EngineConfigVar.getOrDefault("Renderer", 1).toString()) == 0 ? Renderer.Forward: Renderer.Deferred; ShadowMapSize = Integer.parseInt(EngineConfigVar.getOrDefault("ShadowMapSize", 2048).toString()); diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/RenderQualitySettings.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/RenderQualitySettings.java new file mode 100644 index 0000000..667f242 --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Logic/RenderQualitySettings.java @@ -0,0 +1,38 @@ +package net.halbear.Terrain4J.EngineCore.Logic; + +import java.util.Properties; + +public record RenderQualitySettings(float ssaoScale, int ssaoSamples, float ssrScale, int ssrSteps, + float bloomScale, int bloomPasses, int chunkRadius, + int generationsPerFrame, int facePoolMiB) { + public static RenderQualitySettings fromProperties(Properties properties) { + int passes = integer(properties, "bloom_passes", 2, 0, 10); + return new RenderQualitySettings( + decimal(properties, "ssao_scale", 0.5f, 0.25f, 1), + integer(properties, "ssao_samples", 16, 4, 64), + decimal(properties, "ssr_scale", 0.5f, 0.25f, 1), + integer(properties, "ssr_steps", 24, 4, 128), + decimal(properties, "bloom_scale", 0.25f, 0.125f, 1), + passes + (passes & 1), + integer(properties, "voxel_chunk_radius", 16, 1, 32), + integer(properties, "voxel_generations_per_frame", 16, 16, 64), + integer(properties, "voxel_face_pool_mib", 256, 32, 1024)); + } + + private static int integer(Properties properties, String key, int fallback, int min, int max) { + try { + return Math.clamp(Integer.parseInt(properties.getProperty(key, Integer.toString(fallback))), min, max); + } catch (NumberFormatException ignored) { + return fallback; + } + } + + private static float decimal(Properties properties, String key, float fallback, float min, float max) { + try { + float value = Float.parseFloat(properties.getProperty(key, Float.toString(fallback))); + return Float.isFinite(value) ? Math.clamp(value, min, max) : fallback; + } catch (NumberFormatException ignored) { + return fallback; + } + } +} \ No newline at end of file diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Main/GameCore.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Main/GameCore.java index 53ff5d8..179d414 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/Main/GameCore.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Main/GameCore.java @@ -118,7 +118,7 @@ public class GameCore implements GameLogic { // ModelData treeModel = ModelLoader.LoadModel("resources/models/Forest/forest.json"); // models.add(treeModel); // Actor3D treeEntity = new Actor3D("treeEntity", treeModel.ID(), new Vector3f(0.0f, 00.0f, 0.0f)); -// treeEntity.SetScale(0.1f); +// treeEntity.SetScale(10f); // scene.AddActor(treeEntity); // materials.addAll(ModelLoader.LoadMaterials("resources/models/Forest/forest_mat.json")); MelonaData = ModelLoader.LoadModel("resources/models/cube/Cube.json"); @@ -200,19 +200,19 @@ public class GameCore implements GameLogic { } scene.GetLightingManager().GetAmbientLightColour().set(1.0f, 0.9f, 0.75f); - scene.GetLightingManager().SetAmbientLightIntensity(0.2f); + scene.GetLightingManager().SetAmbientLightIntensity(0.3f); // scene.GetLightingManager().GetAmbientLightColour().set(0.3f, 0.35f, 0.5f); // scene.GetLightingManager().SetAmbientLightIntensity(0.015f); List lights = new ArrayList<>(); //SkyLight = new Light(new Vector3f(1.5f, 1.20f, 1.0f),new Vector3f(0.0f, -1.0f, 0.3f), true, 4.00f); - SkyLight = new Light(new Vector3f(2.75f, 1.75f, 0.3f),new Vector3f(0.0f, -1.0f, 0.0f), true, 1.5f); + SkyLight = new Light(new Vector3f(2.25f, 1.75f, 0.5f),new Vector3f(0.0f, -1.0f, 0.0f), true, 1.5f); // SkyLight = new Light(new Vector3f(0.15f, 0.80f, 1.5f),new Vector3f(0.0f, -1.0f, 0.3f), true, 5.00f); SkyLight.SetType(Light.LightType.Directional); lights.add(SkyLight); - lights.add(SkyLight); + //lights.add(SkyLight); // // lights.add(new Light(new Vector3f(1.9f + (float)Math.random(),1.5f,1.0f),new Vector3f(-433, 445f, -424f).div(20.0f),false,3000.0f/10.0f+ (float)(Math.random() * 100.0/10.0f))); // lights.add(new Light(new Vector3f(1.9f+ (float)Math.random(),1.5f,1.0f),new Vector3f(-210, 445f, 460f).div(20.0f),false,700.0f/10.0f + (float)(Math.random() * 150.0f/10.0f))); @@ -686,12 +686,12 @@ public class GameCore implements GameLogic { Project3D projection = engineInstance.scene().GetProjection(); Vector3f CamRot = cam.GetRotation(); Vector3i ChunkPos = new Vector3i((int)Math.floor(CamPos.x/ (float) VoxelChunkGenerator.CHUNK_SIZE),(int)Math.floor(CamPos.y/ (float)VoxelChunkGenerator.CHUNK_SIZE),(int)Math.floor(CamPos.z/ (float)VoxelChunkGenerator.CHUNK_SIZE)); - if(ChunkPos.x != VoxelWorldManager.LastPosition.x || ChunkPos.y != VoxelWorldManager.LastPosition.y || ChunkPos.z != VoxelWorldManager.LastPosition.z || CamRot.x != LastCamRotation.x || CamRot.y != LastCamRotation.y || CamRot.z != LastCamRotation.z) { - VoxelWorldManager.UnloadFarChunks(ChunkPos, 16,cam.GetViewMatrix(),projection.GetProjectionMatrix()); - VoxelWorldManager.GenerateChunks(ChunkPos, new Vector3i(16,16,16),cam.GetViewMatrix(),projection.GetProjectionMatrix()); - } else { - VoxelWorldManager.UnloadFarChunks(ChunkPos, 96,cam.GetViewMatrix(),projection.GetProjectionMatrix()); - VoxelWorldManager.GenerateChunks(ChunkPos, new Vector3i(48,16,48),cam.GetViewMatrix(),projection.GetProjectionMatrix()); + if (!PrimaryRuntime.IsServer && !RenderThread.Headless) { + int radius = EngineConfig.getInstance().GetVoxelChunkRadius(); + if (!ChunkPos.equals(VoxelWorldManager.LastPosition)) { + VoxelWorldManager.UnloadFarChunks(ChunkPos, radius, cam.GetViewMatrix(), projection.GetProjectionMatrix()); + } + VoxelWorldManager.GenerateChunks(ChunkPos, new Vector3i(radius), cam.GetViewMatrix(), projection.GetProjectionMatrix()); } LastCamRotation.set(CamRot); if(PrimaryRuntime.IsServer){ @@ -753,6 +753,16 @@ public class GameCore implements GameLogic { DeviceInfo[4] = String.format(" VRAM: " + GPUProfiler.GetVramCapacity()); Metrics.add(ISceneDetails); Metrics.add(DeviceInfo); + if (PrimaryRuntime.GetRenderThread().GetRenderer() instanceof net.halbear.Terrain4J.EngineCore.Display.VulkanRenderer renderer) { + List gpu = new ArrayList<>(); + gpu.add("GPU TIMESTAMPS (ms, completed frame):"); + renderer.GetGpuTimings().milliseconds().forEach((pass, time) -> gpu.add(String.format(" %s: %.3f", pass, time))); + gpu.add(String.format(" Chunks: %d visible / %d meshed / %d pending; %d generated/frame", + VoxelWorldManager.GetVisibleLastFrame(), VoxelWorldManager.GetRenderableChunkCount(), + VoxelWorldManager.GetRequestedChunkCount(), VoxelWorldManager.GetGeneratedLastFrame())); + gpu.add(String.format(" Packed faces: %.1f MiB", VoxelWorldManager.GetUsedFaceBytes() / (1024.0 * 1024.0))); + Metrics.add(gpu.toArray(String[]::new)); + } SettingPerformanceMetrics.addAll(Metrics); SettingPerformanceMetrics.addAll(EngineConfig.getInstance().GetMemoryProfiling()); SettingPerformanceMetrics.addAll(EngineConfig.getInstance().GetSystemProfiling()); diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/Profiling/VulkanFrameTimings.java b/src/main/java/net/halbear/Terrain4J/EngineCore/Profiling/VulkanFrameTimings.java new file mode 100644 index 0000000..dbb864d --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/Profiling/VulkanFrameTimings.java @@ -0,0 +1,86 @@ +package net.halbear.Terrain4J.EngineCore.Profiling; + +import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext; +import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanUtils; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VkCommandBuffer; +import org.lwjgl.vulkan.VkPhysicalDeviceProperties; +import org.lwjgl.vulkan.VkQueryPoolCreateInfo; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import static org.lwjgl.vulkan.VK13.*; + +public final class VulkanFrameTimings { + private static final int QUERY_COUNT = 16; + private final long[] pools = new long[VulkanUtils.MAX_IN_FLIGHT]; + private final String[][] names = new String[VulkanUtils.MAX_IN_FLIGHT][QUERY_COUNT]; + private final int[] counts = new int[VulkanUtils.MAX_IN_FLIGHT]; + private final float timestampPeriod; + private final int validBits; + private volatile Map milliseconds = Map.of(); + + public VulkanFrameTimings(VulkanContext context, int queueFamily) { + validBits = context.GetPhysicalDevice().GetQueueFamilyProperties().get(queueFamily).timestampValidBits(); + try (var stack = MemoryStack.stackPush()) { + var properties = VkPhysicalDeviceProperties.calloc(stack); + vkGetPhysicalDeviceProperties(context.GetPhysicalDevice().GetPhysicalDevice(), properties); + timestampPeriod = properties.limits().timestampPeriod(); + if (validBits == 0 || timestampPeriod <= 0) return; + var info = VkQueryPoolCreateInfo.calloc(stack).sType$Default() + .queryType(VK_QUERY_TYPE_TIMESTAMP).queryCount(QUERY_COUNT); + var result = stack.mallocLong(1); + for (int i = 0; i < pools.length; i++) { + VulkanUtils.vkCheck(vkCreateQueryPool(context.GetDevice().FetchVulkanDevice(), info, null, result), "Create GPU timing queries"); + pools[i] = result.get(0); + } + } + } + + // The caller has already waited on this frame slot's fence; never wait for queries here. + public void begin(VulkanContext context, VkCommandBuffer command, int frame) { + if (pools[frame] == 0) return; + if (counts[frame] > 1) { + try (var stack = MemoryStack.stackPush()) { + var result = stack.mallocLong(counts[frame]); + int status = vkGetQueryPoolResults(context.GetDevice().FetchVulkanDevice(), pools[frame], + 0, counts[frame], result, Long.BYTES, VK_QUERY_RESULT_64_BIT); + if (status == VK_SUCCESS) { + Map times = new LinkedHashMap<>(); + for (int i = 1; i < counts[frame]; i++) { + times.put(names[frame][i], elapsed(result.get(i - 1), result.get(i), validBits, timestampPeriod)); + } + times.put("total", elapsed(result.get(0), result.get(counts[frame] - 1), validBits, timestampPeriod)); + milliseconds = Collections.unmodifiableMap(times); + } + } + } + counts[frame] = 0; + vkCmdResetQueryPool(command, pools[frame], 0, QUERY_COUNT); + mark(command, frame, "start"); + } + + public void mark(VkCommandBuffer command, int frame, String name) { + if (pools[frame] == 0) return; + int index = counts[frame]++; + if (index >= QUERY_COUNT) throw new IllegalStateException("Too many GPU timing markers"); + names[frame][index] = name; + vkCmdWriteTimestamp2(command, VK_PIPELINE_STAGE_2_ALL_COMMANDS_BIT, pools[frame], index); + } + + static double elapsed(long start, long end, int bits, float period) { + long delta = end - start; + if (bits < 64) delta &= (1L << bits) - 1; + return delta * (double) period / 1_000_000.0; + } + + public Map milliseconds() {return milliseconds;} + + public void cleanup(VulkanContext context) { + for (long pool : pools) { + if (pool != 0) vkDestroyQueryPool(context.GetDevice().FetchVulkanDevice(), pool, null); + } + } +} \ No newline at end of file diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/OpenGL/GlModels/GLMesh.java b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/OpenGL/GlModels/GLMesh.java index 3126226..ca4db1c 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/OpenGL/GlModels/GLMesh.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/OpenGL/GlModels/GLMesh.java @@ -37,56 +37,16 @@ public class GLMesh { verticesBuffer.put(0, vertices); glBufferData(GL_ARRAY_BUFFER, verticesBuffer, GL_STATIC_DRAW); - glEnableVertexAttribArray(0); - glVertexAttribPointer( - 0, - 3, - GL_FLOAT, - false, - 14 * Float.BYTES, - 0L - ); - + glVertexAttribPointer(0, 3, GL_FLOAT, false, 14 * Float.BYTES, 0L); glEnableVertexAttribArray(1); - glVertexAttribPointer( - 1, - 3, - GL_FLOAT, - false, - 14 * Float.BYTES, - 3L * Float.BYTES - ); - + glVertexAttribPointer(1, 3, GL_FLOAT, false, 14 * Float.BYTES, 3L * Float.BYTES); glEnableVertexAttribArray(2); - glVertexAttribPointer( - 2, - 3, - GL_FLOAT, - false, - 14 * Float.BYTES, - 6L * Float.BYTES - ); - + glVertexAttribPointer(2, 3, GL_FLOAT, false, 14 * Float.BYTES, 6L * Float.BYTES); glEnableVertexAttribArray(3); - glVertexAttribPointer( - 3, - 3, - GL_FLOAT, - false, - 14 * Float.BYTES, - 9L * Float.BYTES - ); - + glVertexAttribPointer(3, 3, GL_FLOAT, false, 14 * Float.BYTES, 9L * Float.BYTES); glEnableVertexAttribArray(4); - glVertexAttribPointer( - 4, - 2, - GL_FLOAT, - false, - 14 * Float.BYTES, - 12L * Float.BYTES - ); + glVertexAttribPointer(4, 2, GL_FLOAT, false, 14 * Float.BYTES, 12L * Float.BYTES); vboId = glGenBuffers(); VBO_ID_List.add(vboId); @@ -94,23 +54,19 @@ public class GLMesh { indicesBuffer.put(0, indices); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, vboId); glBufferData(GL_ELEMENT_ARRAY_BUFFER, indicesBuffer, GL_STATIC_DRAW); - glBindBuffer(GL_ARRAY_BUFFER, 0); glBindVertexArray(0); MemoryUtil.memFree(verticesBuffer); MemoryUtil.memFree(indicesBuffer); } - public void CleanUp() { VBO_ID_List.forEach(GL30::glDeleteBuffers); glDeleteVertexArrays(VAO_ID); } - public int GetVertexCount() { return VertexCount; } - public final int GetVaoID() { return VAO_ID; } diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/FaceRangeAllocator.java b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/FaceRangeAllocator.java new file mode 100644 index 0000000..08d1d3f --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/FaceRangeAllocator.java @@ -0,0 +1,50 @@ +package net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Computing.VoxelTerrainGeneration; + +import java.util.TreeMap; + +public final class FaceRangeAllocator { + private final TreeMap free = new TreeMap<>(); + private final TreeMap allocated = new TreeMap<>(); + private int freeFaces; + + public FaceRangeAllocator(int capacity) { + if (capacity <= 0) throw new IllegalArgumentException("Face capacity must be positive"); + free.put(0, capacity); + freeFaces = capacity; + } + + public int allocate(int count) { + if (count <= 0) throw new IllegalArgumentException("Face count must be positive"); + for (var range : free.entrySet()) { + if (range.getValue() < count) continue; + int offset = range.getKey(); + int remaining = range.getValue() - count; + free.remove(offset); + if (remaining > 0) free.put(offset + count, remaining); + allocated.put(offset, count); + freeFaces -= count; + return offset; + } + return -1; + } + + public void free(int offset) { + Integer count = allocated.remove(offset); + if (count == null) throw new IllegalArgumentException("Unknown face allocation"); + freeFaces += count; + var previous = free.lowerEntry(offset); + if (previous != null && previous.getKey() + previous.getValue() == offset) { + offset = previous.getKey(); + count += previous.getValue(); + free.remove(previous.getKey()); + } + var next = free.ceilingEntry(offset); + if (next != null && offset + count == next.getKey()) { + count += next.getValue(); + free.remove(next.getKey()); + } + free.put(offset, count); + } + + public int freeFaces() {return freeFaces;} +} \ No newline at end of file diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/GeneratedVoxelChunk.java b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/GeneratedVoxelChunk.java new file mode 100644 index 0000000..581e8d6 --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/GeneratedVoxelChunk.java @@ -0,0 +1,20 @@ +package net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Computing.VoxelTerrainGeneration; + +import org.joml.Vector3i; + +import java.util.Objects; + +/** Faces remain valid until recordGenerateChunks reuses their frame slot. */ +public record GeneratedVoxelChunk(Vector3i position, int faceCount, long sourceBuffer, long sourceOffsetBytes) { + public GeneratedVoxelChunk { + position = new Vector3i(Objects.requireNonNull(position)); + if (faceCount < 0 || faceCount > VoxelChunkGenerator.MAX_FACES_PER_CHUNK) { + throw new IllegalArgumentException("Invalid generated face count: " + Integer.toUnsignedLong(faceCount)); + } + } + + @Override + public Vector3i position() { + return new Vector3i(position); + } +} \ No newline at end of file diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/SharedVoxelTerrainResources.java b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/SharedVoxelTerrainResources.java index bb81fb0..e9c334f 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/SharedVoxelTerrainResources.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/SharedVoxelTerrainResources.java @@ -8,6 +8,7 @@ import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Rendering.Pipeline.P import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Rendering.Shader.*; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Structure.DeviceLayers.Device; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanBuffer; +import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanUtils; import org.lwjgl.system.MemoryUtil; import org.lwjgl.util.shaderc.Shaderc; @@ -15,17 +16,21 @@ import java.nio.ByteBuffer; import java.util.List; import static org.lwjgl.util.vma.Vma.VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; +import static org.lwjgl.util.vma.Vma.VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT; import static org.lwjgl.util.vma.Vma.VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; +import static org.lwjgl.util.vma.Vma.VMA_MEMORY_USAGE_AUTO_PREFER_HOST; import static org.lwjgl.vulkan.VK13.*; public class SharedVoxelTerrainResources { public static final int CHUNK_SIZE = 16; public static final int VOXEL_COUNT = CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE; + public static final int SCRATCH_SIZE = CHUNK_SIZE + 2; + public static final int SCRATCH_VOXEL_COUNT = SCRATCH_SIZE * SCRATCH_SIZE * SCRATCH_SIZE; public static final int DRAW_INDIRECT_COMMAND_SIZE = 4 * Integer.BYTES; public static final int COUNTER_BUFFER_SIZE = Integer.BYTES; - public static final int TERRAIN_GENERATION_PUSH_CONSTANT_SIZE = Integer.BYTES * 10; + public static final int TERRAIN_GENERATION_PUSH_CONSTANT_SIZE = Integer.BYTES * 9; public static final int MAX_VOXEL_TYPES = 256; public static final int MAX_BIOME_TYPES = 256; @@ -46,8 +51,11 @@ public class SharedVoxelTerrainResources { private final VoxelMeshPool meshPool; - private final VulkanBuffer voxelData; - private final VulkanBuffer counters; + // Each frame has disjoint, fixed-size job slices; no dispatch shares voxel/counter storage. + private final VulkanBuffer[] voxelData = new VulkanBuffer[VulkanUtils.MAX_IN_FLIGHT]; + private final VulkanBuffer[] counters = new VulkanBuffer[VulkanUtils.MAX_IN_FLIGHT]; + private final VulkanBuffer[] scratchFaces = new VulkanBuffer[VulkanUtils.MAX_IN_FLIGHT]; + private final VulkanBuffer[] countReadback = new VulkanBuffer[VulkanUtils.MAX_IN_FLIGHT]; private final VulkanBuffer voxelRegistryBuffer; private final VulkanBuffer biomeRegistryBuffer; private final VulkanBuffer structureRegistryBuffer; @@ -76,11 +84,22 @@ public class SharedVoxelTerrainResources { public SharedVoxelTerrainResources(VulkanContext VkCtx, int maxResidentChunks) { meshPool = new VoxelMeshPool(VkCtx, maxResidentChunks); - voxelData = new VulkanBuffer(VkCtx, (long) VOXEL_COUNT * Integer.BYTES, - VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, 0, 0); - - counters = new VulkanBuffer(VkCtx, COUNTER_BUFFER_SIZE, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, - VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, 0, 0); + for (int frame = 0; frame < VulkanUtils.MAX_IN_FLIGHT; frame++) { + voxelData[frame] = new VulkanBuffer(VkCtx, + (long) SCRATCH_VOXEL_COUNT * Integer.BYTES * VoxelChunkGenerator.MAX_BATCH_SIZE, + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, 0, 0); + counters[frame] = new VulkanBuffer(VkCtx, (long) COUNTER_BUFFER_SIZE * VoxelChunkGenerator.MAX_BATCH_SIZE, + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, 0, 0); + scratchFaces[frame] = new VulkanBuffer(VkCtx, + VoxelChunkGenerator.SCRATCH_FACE_BYTES_PER_JOB * VoxelChunkGenerator.MAX_BATCH_SIZE, + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_SRC_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, 0, 0); + countReadback[frame] = new VulkanBuffer(VkCtx, (long) COUNTER_BUFFER_SIZE * VoxelChunkGenerator.MAX_BATCH_SIZE, + VK_BUFFER_USAGE_TRANSFER_DST_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_HOST, + VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + } voxelRegistryBuffer = new VulkanBuffer(VkCtx, (long) MAX_VOXEL_TYPES * VOXEL_REG_DATA_SIZE, VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, @@ -104,15 +123,13 @@ public class SharedVoxelTerrainResources { }); resetLayout = new DescriptorSetLayout(VkCtx, new DescriptorSetLayout.LayoutInformation[]{ - new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, 1, VK_SHADER_STAGE_COMPUTE_BIT), - new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, 1, VK_SHADER_STAGE_COMPUTE_BIT) + new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, 1, VK_SHADER_STAGE_COMPUTE_BIT) }); meshGenerationLayout = new DescriptorSetLayout(VkCtx, new DescriptorSetLayout.LayoutInformation[]{ new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, 1, VK_SHADER_STAGE_COMPUTE_BIT), new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1, 1, VK_SHADER_STAGE_COMPUTE_BIT), - new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2, 1, VK_SHADER_STAGE_COMPUTE_BIT), - new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 3, 1, VK_SHADER_STAGE_COMPUTE_BIT) + new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 2, 1, VK_SHADER_STAGE_COMPUTE_BIT) }); faceGraphicsLayout = new DescriptorSetLayout(VkCtx, new DescriptorSetLayout.LayoutInformation[]{ @@ -165,18 +182,16 @@ public class SharedVoxelTerrainResources { DescriptorAllocator allocator = VkCtx.GetDescriptorAllocator(); Device device = VkCtx.GetDevice(); - DescriptorSet resetSet = allocator.AddDescriptorSet(device, DESC_ID_RESET, resetLayout); - resetSet.SetBuffer(device, meshPool.indirectPool(), meshPool.indirectPool().GetRequestedSize(), 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); - resetSet.SetBuffer(device, counters, counters.GetRequestedSize(), 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); - - DescriptorSet voxelSet = allocator.AddDescriptorSet(device, DESC_ID_VOXEL_GENERATION, voxelGenerationLayout); - voxelSet.SetBuffer(device, voxelData, voxelData.GetRequestedSize(), 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); - - DescriptorSet meshSet = allocator.AddDescriptorSet(device, DESC_ID_MESH, meshGenerationLayout); - meshSet.SetBuffer(device, voxelData, voxelData.GetRequestedSize(), 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); - meshSet.SetBuffer(device, meshPool.facePool(), meshPool.facePool().GetRequestedSize(), 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); - meshSet.SetBuffer(device, meshPool.indirectPool(), meshPool.indirectPool().GetRequestedSize(), 2, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); - meshSet.SetBuffer(device, counters, counters.GetRequestedSize(), 3, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); + DescriptorSet[] resetSets = allocator.AddDescriptorSets(device, DESC_ID_RESET, VulkanUtils.MAX_IN_FLIGHT, resetLayout); + DescriptorSet[] voxelSets = allocator.AddDescriptorSets(device, DESC_ID_VOXEL_GENERATION, VulkanUtils.MAX_IN_FLIGHT, voxelGenerationLayout); + DescriptorSet[] meshSets = allocator.AddDescriptorSets(device, DESC_ID_MESH, VulkanUtils.MAX_IN_FLIGHT, meshGenerationLayout); + for (int frame = 0; frame < VulkanUtils.MAX_IN_FLIGHT; frame++) { + resetSets[frame].SetBuffer(device, counters[frame], counters[frame].GetRequestedSize(), 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); + voxelSets[frame].SetBuffer(device, voxelData[frame], voxelData[frame].GetRequestedSize(), 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); + meshSets[frame].SetBuffer(device, voxelData[frame], voxelData[frame].GetRequestedSize(), 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); + meshSets[frame].SetBuffer(device, scratchFaces[frame], scratchFaces[frame].GetRequestedSize(), 1, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); + meshSets[frame].SetBuffer(device, counters[frame], counters[frame].GetRequestedSize(), 2, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); + } DescriptorSet faceGraphicsSet = allocator.AddDescriptorSet(device, DESC_ID_FACE_GRAPHICS, faceGraphicsLayout); faceGraphicsSet.SetBuffer(device, meshPool.facePool(), meshPool.facePool().GetRequestedSize(), 0, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER); @@ -311,12 +326,20 @@ public class SharedVoxelTerrainResources { return meshPool; } - public VulkanBuffer voxelData() { - return voxelData; + public VulkanBuffer voxelData(int currentFrame) { + return voxelData[currentFrame]; } - public VulkanBuffer counters() { - return counters; + public VulkanBuffer counters(int currentFrame) { + return counters[currentFrame]; + } + + public VulkanBuffer scratchFaces(int currentFrame) { + return scratchFaces[currentFrame]; + } + + public VulkanBuffer countReadback(int currentFrame) { + return countReadback[currentFrame]; } public Pipeline voxelGenerationPipeline() { @@ -363,11 +386,18 @@ public class SharedVoxelTerrainResources { faceGraphicsLayout.CleanUp(VkCtx); voxelDeclarationLayout.CleanUp(VkCtx); biomeDeclarationLayout.CleanUp(VkCtx); + structureDeclarationLayout.CleanUp(VkCtx); - voxelData.cleanup(VkCtx); - counters.cleanup(VkCtx); + for (int frame = 0; frame < VulkanUtils.MAX_IN_FLIGHT; frame++) { + voxelData[frame].cleanup(VkCtx); + counters[frame].cleanup(VkCtx); + scratchFaces[frame].cleanup(VkCtx); + countReadback[frame].cleanup(VkCtx); + } voxelRegistryBuffer.cleanup(VkCtx); biomeRegistryBuffer.cleanup(VkCtx); + structureRegistryBuffer.cleanup(VkCtx); + structureVoxelDataBuffer.cleanup(VkCtx); meshPool.cleanup(VkCtx); } } \ No newline at end of file diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/VoxelChunkGenerator.java b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/VoxelChunkGenerator.java index 2fa1643..1295d25 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/VoxelChunkGenerator.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/VoxelChunkGenerator.java @@ -4,11 +4,18 @@ import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Rendering.Shader.DescriptorAllocator; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Structure.DeviceLayers.CommandBuffer; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanUtils; +import org.joml.Vector3i; import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; +import org.lwjgl.vulkan.VkBufferCopy; import org.lwjgl.vulkan.VkCommandBuffer; import java.nio.ByteBuffer; +import java.nio.ByteOrder; import java.nio.LongBuffer; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; import static org.lwjgl.vulkan.VK13.*; @@ -16,83 +23,164 @@ public class VoxelChunkGenerator { public static final int CHUNK_SIZE = 16; public static final int LOCAL_SIZE = 4; public static final int DISPATCH_SIZE = CHUNK_SIZE / LOCAL_SIZE; + public static final int MAX_BATCH_SIZE = 64; + public static final int MAX_FACES_PER_CHUNK = CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE * 6; + public static final long SCRATCH_FACE_BYTES_PER_JOB = (long) MAX_FACES_PER_CHUNK * Integer.BYTES; + public static final int VOXEL_DISPATCH_SIZE = (SharedVoxelTerrainResources.SCRATCH_SIZE + LOCAL_SIZE - 1) / LOCAL_SIZE; private final SharedVoxelTerrainResources resources; + private final GenerationBatch[] pending = new GenerationBatch[VulkanUtils.MAX_IN_FLIGHT]; public VoxelChunkGenerator(SharedVoxelTerrainResources resources) { this.resources = resources; + for (int frame = 0; frame < pending.length; frame++) { + pending[frame] = new GenerationBatch(); + } } - public void recordGenerateChunk(VulkanContext VkCtx,CommandBuffer commandBuffer, RenderChunk chunk) { + /** + * Call only after currentFrame's submission fence signals. This never waits on the GPU. + * Results are consumed once, including empty chunks. Copy their faces before recording + * another batch into this frame slot; other frame slots do not invalidate them. + */ + public List collectCompleted(VulkanContext VkCtx, int currentFrame) { + GenerationBatch batch = pending[Objects.checkIndex(currentFrame, pending.length)]; + if (batch.positions.isEmpty()) { + return List.of(); + } + long address = resources.countReadback(currentFrame).MapMemory(VkCtx); + ByteBuffer counts = MemoryUtil.memByteBuffer(address, batch.positions.size() * Integer.BYTES); + return batch.collect(counts, resources.scratchFaces(currentFrame).GetBuffer()); + } + + /** + * Records one batch (at most MAX_BATCH_SIZE), without submission or waits. The caller + * limits scheduling using EngineConfig.GetVoxelGenerationsPerFrame(), collects this + * frame's previous results after its fence, and records scratch-to-pool copies first. + */ + public void recordGenerateChunks(VulkanContext VkCtx, CommandBuffer commandBuffer, + List positions, int currentFrame) { + GenerationBatch batch = pending[Objects.checkIndex(currentFrame, pending.length)]; + batch.stage(positions); + if (batch.positions.isEmpty()) { + return; + } + try (MemoryStack stack = MemoryStack.stackPush()) { VkCommandBuffer cmd = commandBuffer.GetVulkanCommandBuffer(); DescriptorAllocator allocator = VkCtx.GetDescriptorAllocator(); - ByteBuffer pushConstants = stack.malloc(SharedVoxelTerrainResources.TERRAIN_GENERATION_PUSH_CONSTANT_SIZE); - - int indirectCommandIndex = (int) (chunk.indirectCommandOffsetBytes() / VoxelMeshPool.DRAW_INDIRECT_COMMAND_SIZE); - int faceOffset = (int) (chunk.faceOffsetBytes() / VoxelMeshPool.FACE_RECORD_SIZE_BYTES); - - pushConstants.putInt(0, chunk.position().x); - pushConstants.putInt(4, chunk.position().y); - pushConstants.putInt(8, chunk.position().z); - pushConstants.putInt(12, chunk.slot()); - pushConstants.putInt(16, faceOffset); pushConstants.putInt(20, VoxelWorldManager.GetVoxelTypeCount()); - pushConstants.putInt(24, indirectCommandIndex); - pushConstants.putInt(28, VoxelWorldManager.GetBiomeTypeCount()); - pushConstants.putInt(32, VoxelWorldManager.GetStructureTypeCount()); - pushConstants.putInt(36, VoxelWorldManager.GetWorldSeed()); + pushConstants.putInt(24, VoxelWorldManager.GetBiomeTypeCount()); + pushConstants.putInt(28, VoxelWorldManager.GetStructureTypeCount()); + pushConstants.putInt(32, VoxelWorldManager.GetWorldSeed()); LongBuffer resetDescriptorSet = stack.longs(allocator.GetDescriptorSet( - resources.resetDescriptorId()).GetVkDescriptorSet()); + resources.resetDescriptorId(), currentFrame).GetVkDescriptorSet()); LongBuffer voxelDescriptorSet = stack.longs( - allocator.GetDescriptorSet(resources.voxelGenerationDescriptorId()).GetVkDescriptorSet(), + allocator.GetDescriptorSet(resources.voxelGenerationDescriptorId(), currentFrame).GetVkDescriptorSet(), allocator.GetDescriptorSet(resources.biomeRegDescriptorID()).GetVkDescriptorSet(), - allocator.GetDescriptorSet(resources.structureRegDescriptorID()).GetVkDescriptorSet() - ); + allocator.GetDescriptorSet(resources.structureRegDescriptorID()).GetVkDescriptorSet()); LongBuffer meshDescriptorSet = stack.longs( - allocator.GetDescriptorSet(resources.meshDescriptorId()).GetVkDescriptorSet(), + allocator.GetDescriptorSet(resources.meshDescriptorId(), currentFrame).GetVkDescriptorSet(), allocator.GetDescriptorSet(resources.voxelRegDescriptorID()).GetVkDescriptorSet()); - vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE,resources.resetPipeline().GetVulkanPipeline()); - vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE,resources.resetPipeline().GetVulkanPipelineLayout(),0, resetDescriptorSet,null ); - vkCmdPushConstants( cmd, resources.resetPipeline().GetVulkanPipelineLayout(),VK_SHADER_STAGE_COMPUTE_BIT,0, pushConstants ); - vkCmdDispatch(cmd, 1, 1, 1); - + // Includes the parent's scratch-to-pool transfers earlier in this command buffer. VulkanUtils.BufferBarriers(stack, cmd, new long[]{ - resources.meshPool().indirectPoolHandle(), - resources.counters().GetBuffer() - }, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, + resources.scratchFaces(currentFrame).GetBuffer(), + resources.voxelData(currentFrame).GetBuffer(), + resources.counters(currentFrame).GetBuffer() + }, VK_PIPELINE_STAGE_2_TRANSFER_BIT | VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, - VK_ACCESS_2_SHADER_WRITE_BIT, - VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_SHADER_WRITE_BIT - ); + VK_ACCESS_2_TRANSFER_READ_BIT | VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_SHADER_WRITE_BIT, + VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_SHADER_WRITE_BIT); - vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE,resources.voxelGenerationPipeline().GetVulkanPipeline()); - vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE,resources.voxelGenerationPipeline().GetVulkanPipelineLayout(),0, voxelDescriptorSet,null ); - vkCmdPushConstants( cmd, resources.voxelGenerationPipeline().GetVulkanPipelineLayout(),VK_SHADER_STAGE_COMPUTE_BIT,0, pushConstants ); - vkCmdDispatch(cmd, DISPATCH_SIZE, DISPATCH_SIZE, DISPATCH_SIZE); + long resetLayout = resources.resetPipeline().GetVulkanPipelineLayout(); + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, resources.resetPipeline().GetVulkanPipeline()); + vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, resetLayout, 0, resetDescriptorSet, null); + for (int job = 0; job < batch.positions.size(); job++) { + putJobConstants(pushConstants, batch.positions.get(job), job); + vkCmdPushConstants(cmd, resetLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, pushConstants); + vkCmdDispatch(cmd, 1, 1, 1); + } - VulkanUtils.BufferBarrier(stack, cmd,resources.voxelData().GetBuffer(), + VulkanUtils.BufferBarrier(stack, cmd, resources.counters(currentFrame).GetBuffer(), + VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, + VK_ACCESS_2_SHADER_WRITE_BIT, VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_SHADER_WRITE_BIT); + + long voxelLayout = resources.voxelGenerationPipeline().GetVulkanPipelineLayout(); + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, resources.voxelGenerationPipeline().GetVulkanPipeline()); + vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, voxelLayout, 0, voxelDescriptorSet, null); + for (int job = 0; job < batch.positions.size(); job++) { + putJobConstants(pushConstants, batch.positions.get(job), job); + vkCmdPushConstants(cmd, voxelLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, pushConstants); + vkCmdDispatch(cmd, VOXEL_DISPATCH_SIZE, VOXEL_DISPATCH_SIZE, VOXEL_DISPATCH_SIZE); + } + + VulkanUtils.BufferBarrier(stack, cmd, resources.voxelData(currentFrame).GetBuffer(), VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_ACCESS_2_SHADER_WRITE_BIT, VK_ACCESS_2_SHADER_READ_BIT); - vkCmdBindPipeline(cmd,VK_PIPELINE_BIND_POINT_COMPUTE,resources.meshGenerationPipeline().GetVulkanPipeline() ); - - vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE,resources.meshGenerationPipeline().GetVulkanPipelineLayout(),0, meshDescriptorSet,null ); - - vkCmdPushConstants( cmd, resources.meshGenerationPipeline().GetVulkanPipelineLayout(),VK_SHADER_STAGE_COMPUTE_BIT,0, pushConstants ); - - vkCmdDispatch(cmd, DISPATCH_SIZE, DISPATCH_SIZE, DISPATCH_SIZE); + long meshLayout = resources.meshGenerationPipeline().GetVulkanPipelineLayout(); + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, resources.meshGenerationPipeline().GetVulkanPipeline()); + vkCmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, meshLayout, 0, meshDescriptorSet, null); + for (int job = 0; job < batch.positions.size(); job++) { + putJobConstants(pushConstants, batch.positions.get(job), job); + vkCmdPushConstants(cmd, meshLayout, VK_SHADER_STAGE_COMPUTE_BIT, 0, pushConstants); + vkCmdDispatch(cmd, DISPATCH_SIZE, DISPATCH_SIZE, DISPATCH_SIZE); + } VulkanUtils.BufferBarriers(stack, cmd, new long[]{ - resources.voxelData().GetBuffer(), resources.counters().GetBuffer(), - resources.meshPool().facePoolHandle(), resources.meshPool().indirectPoolHandle() - }, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT | - VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_DRAW_INDIRECT_BIT, - VK_ACCESS_2_SHADER_WRITE_BIT, VK_ACCESS_2_SHADER_STORAGE_READ_BIT | - VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_INDIRECT_COMMAND_READ_BIT); + resources.scratchFaces(currentFrame).GetBuffer(), resources.counters(currentFrame).GetBuffer() + }, VK_PIPELINE_STAGE_2_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_2_TRANSFER_BIT, + VK_ACCESS_2_SHADER_WRITE_BIT, VK_ACCESS_2_TRANSFER_READ_BIT); + + VkBufferCopy.Buffer copy = VkBufferCopy.calloc(1, stack) + .srcOffset(0).dstOffset(0).size((long) batch.positions.size() * Integer.BYTES); + vkCmdCopyBuffer(cmd, resources.counters(currentFrame).GetBuffer(), + resources.countReadback(currentFrame).GetBuffer(), copy); + VulkanUtils.BufferBarrier(stack, cmd, resources.countReadback(currentFrame).GetBuffer(), + VK_PIPELINE_STAGE_2_TRANSFER_BIT, VK_PIPELINE_STAGE_2_HOST_BIT, + VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_ACCESS_2_HOST_READ_BIT); + } + } + + static void putJobConstants(ByteBuffer pushConstants, Vector3i position, int job) { + Objects.checkIndex(job, MAX_BATCH_SIZE); + pushConstants.putInt(0, position.x); + pushConstants.putInt(4, position.y); + pushConstants.putInt(8, position.z); + pushConstants.putInt(12, job); + pushConstants.putInt(16, job * MAX_FACES_PER_CHUNK); + } + + static final class GenerationBatch { + private List positions = List.of(); + + void stage(List requested) { + if (!positions.isEmpty()) { + throw new IllegalStateException("Collect completed generation before reusing this frame slot"); + } + if (requested.size() > MAX_BATCH_SIZE) { + throw new IllegalArgumentException("Voxel generation batch exceeds " + MAX_BATCH_SIZE); + } + List snapshot = new ArrayList<>(requested.size()); + for (Vector3i position : requested) { + snapshot.add(new Vector3i(Objects.requireNonNull(position))); + } + positions = List.copyOf(snapshot); + } + + List collect(ByteBuffer counts, long sourceBuffer) { + ByteBuffer nativeCounts = counts.duplicate().order(ByteOrder.nativeOrder()); + List completed = new ArrayList<>(positions.size()); + for (int job = 0; job < positions.size(); job++) { + int faceCount = nativeCounts.getInt(job * Integer.BYTES); + completed.add(new GeneratedVoxelChunk(positions.get(job), faceCount, + sourceBuffer, job * SCRATCH_FACE_BYTES_PER_JOB)); + } + positions = List.of(); + return List.copyOf(completed); } } } \ No newline at end of file diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/VoxelDrawData.java b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/VoxelDrawData.java new file mode 100644 index 0000000..1374aa6 --- /dev/null +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/VoxelDrawData.java @@ -0,0 +1,35 @@ +package net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Computing.VoxelTerrainGeneration; + +import org.joml.FrustumIntersection; +import org.joml.Matrix4f; +import org.joml.Vector3i; + +public final class VoxelDrawData { + private VoxelDrawData() {} + + public static int encodeOffset(Vector3i position, Vector3i center) { + int x = position.x - center.x + 512; + int y = position.y - center.y + 512; + int z = position.z - center.z + 512; + if (x < 0 || x > 1023 || y < 0 || y > 1023 || z < 0 || z > 1023) { + throw new IllegalArgumentException("Chunk outside indirect draw coordinate range"); + } + return x | (y << 10) | (z << 20); + } + + public static FrustumIntersection frustum(Matrix4f matrix) { + // JOML's frustum test expects -w <= z <= w; Vulkan clips 0 <= z <= w. + Matrix4f clip = new Matrix4f(matrix); + clip.m02(2 * matrix.m02() - matrix.m03()); + clip.m12(2 * matrix.m12() - matrix.m13()); + clip.m22(2 * matrix.m22() - matrix.m23()); + clip.m32(2 * matrix.m32() - matrix.m33()); + return new FrustumIntersection(clip); + } + + public static boolean withinRadius(Vector3i position, Vector3i center, int radius) { + return Math.abs((long) position.x - center.x) <= radius + && Math.abs((long) position.y - center.y) <= radius + && Math.abs((long) position.z - center.z) <= radius; + } +} \ No newline at end of file diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/VoxelMeshPool.java b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/VoxelMeshPool.java index 4af7e19..2d6decd 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/VoxelMeshPool.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/VoxelMeshPool.java @@ -1,114 +1,88 @@ package net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Computing.VoxelTerrainGeneration; +import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig; import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanBuffer; +import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanUtils; import org.joml.Matrix4f; -import org.joml.Vector3f; import org.joml.Vector3i; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.vulkan.VkPhysicalDeviceProperties; +import org.tinylog.Logger; import java.util.ArrayDeque; import java.util.Queue; -import static org.lwjgl.util.vma.Vma.VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE; +import static org.lwjgl.util.vma.Vma.*; import static org.lwjgl.vulkan.VK13.*; -public class VoxelMeshPool{ +public class VoxelMeshPool { public static final int CHUNK_SIZE = 16; - public static final int MAX_VISIBLE_FACES_PER_CHUNK = CHUNK_SIZE * CHUNK_SIZE * CHUNK_SIZE * 6; public static final int FACE_RECORD_SIZE_BYTES = Integer.BYTES; public static final int DRAW_INDIRECT_COMMAND_SIZE = 4 * Integer.BYTES; private final int maxChunks; - - private final long faceSlotSizeBytes; - private final long indirectSlotSizeBytes; - private final VulkanBuffer facePool; - private final VulkanBuffer indirectPool; - + private final VulkanBuffer[][] drawBuffers = new VulkanBuffer[VulkanUtils.MAX_IN_FLIGHT][2]; + private final FaceRangeAllocator ranges; private final Queue freeSlots = new ArrayDeque<>(); public VoxelMeshPool(VulkanContext VkCtx, int maxChunks) { this.maxChunks = maxChunks; - - this.faceSlotSizeBytes = (long) MAX_VISIBLE_FACES_PER_CHUNK * FACE_RECORD_SIZE_BYTES; - this.indirectSlotSizeBytes = DRAW_INDIRECT_COMMAND_SIZE; - - long facePoolSize = faceSlotSizeBytes * maxChunks; - long indirectPoolSize = indirectSlotSizeBytes * maxChunks; - facePool = new VulkanBuffer(VkCtx,facePoolSize,VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,0,0); - - - indirectPool = new VulkanBuffer(VkCtx,indirectPoolSize,VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, - VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE,0,0); - - for (int i = 0; i < maxChunks; i++) { - freeSlots.add(i); + long requestedBytes = (long) EngineConfig.getInstance().GetVoxelFacePoolMiB() * 1024 * 1024; + long facePoolSize; + try (var stack = MemoryStack.stackPush()) { + var properties = VkPhysicalDeviceProperties.calloc(stack); + vkGetPhysicalDeviceProperties(VkCtx.GetPhysicalDevice().GetPhysicalDevice(), properties); + facePoolSize = Math.min(requestedBytes, Integer.toUnsignedLong(properties.limits().maxStorageBufferRange())); } + facePoolSize -= facePoolSize % FACE_RECORD_SIZE_BYTES; + ranges = new FaceRangeAllocator(Math.toIntExact(facePoolSize / FACE_RECORD_SIZE_BYTES)); + facePool = new VulkanBuffer(VkCtx, facePoolSize, + VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, + VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE, 0, 0); + for (int frame = 0; frame < drawBuffers.length; frame++) { + for (int pass = 0; pass < 2; pass++) { + drawBuffers[frame][pass] = new VulkanBuffer(VkCtx, (long) maxChunks * DRAW_INDIRECT_COMMAND_SIZE, + VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, VMA_MEMORY_USAGE_AUTO, + VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, + VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); + } + } + for (int i = 0; i < maxChunks; i++) freeSlots.add(i); + Logger.info("Voxel face pool: {} MiB, {} resident chunk IDs", facePoolSize / (1024 * 1024), maxChunks); } - public RenderChunk allocate(Vector3i position) { - Integer slot = freeSlots.poll(); - - if (slot == null) { - return null; + public RenderChunk allocate(Vector3i position, int faceCount) { + if (faceCount <= 0 || faceCount > MAX_VISIBLE_FACES_PER_CHUNK) { + throw new IllegalArgumentException("Invalid generated face count: " + faceCount); } - return new RenderChunk( - new Vector3i(position), - slot, - faceOffsetBytes(slot), - MAX_VISIBLE_FACES_PER_CHUNK, - indirectCommandOffsetBytes(slot), - new Matrix4f().identity().translate( - new Vector3f( - position.x * CHUNK_SIZE, - position.y * CHUNK_SIZE, - position.z * CHUNK_SIZE - ) - ) - ); + if (freeSlots.isEmpty()) return null; + int faceOffset = ranges.allocate(faceCount); + if (faceOffset < 0) return null; + int slot = freeSlots.remove(); + return new RenderChunk(new Vector3i(position), slot, (long) faceOffset * FACE_RECORD_SIZE_BYTES, + faceCount, (long) slot * DRAW_INDIRECT_COMMAND_SIZE, + new Matrix4f().translation(position.x * CHUNK_SIZE, position.y * CHUNK_SIZE, position.z * CHUNK_SIZE)); } public void free(RenderChunk chunk) { + ranges.free(Math.toIntExact(chunk.faceOffsetBytes() / FACE_RECORD_SIZE_BYTES)); freeSlots.add(chunk.slot()); } - public long faceOffsetBytes(int slot) { - return faceSlotSizeBytes * slot; - } - - public long indirectCommandOffsetBytes(int slot) { - return indirectSlotSizeBytes * slot; - } - - public VulkanBuffer facePool() { - return facePool; - } - - public VulkanBuffer indirectPool() { - return indirectPool; - } - - public long facePoolHandle() { - return facePool.GetBuffer(); - } - - public long indirectPoolHandle() { - return indirectPool.GetBuffer(); - } - - public int maxChunks() { - return maxChunks; - } - - public int freeSlotCount() { - return freeSlots.size(); - } + public VulkanBuffer facePool() {return facePool;} + public long facePoolHandle() {return facePool.GetBuffer();} + public VulkanBuffer drawBuffer(int frame, boolean shadowPass) {return drawBuffers[frame][shadowPass ? 1 : 0];} + public int maxChunks() {return maxChunks;} + public int freeSlotCount() {return freeSlots.size();} + public long usedFaceBytes() {return facePool.GetRequestedSize() - (long) ranges.freeFaces() * FACE_RECORD_SIZE_BYTES;} public void cleanup(VulkanContext VkCtx) { facePool.cleanup(VkCtx); - indirectPool.cleanup(VkCtx); + for (VulkanBuffer[] frame : drawBuffers) { + for (VulkanBuffer buffer : frame) buffer.cleanup(VkCtx); + } } } diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/VoxelWorldManager.java b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/VoxelWorldManager.java index 9dc1f36..a7eecab 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/VoxelWorldManager.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VoxelTerrainGeneration/VoxelWorldManager.java @@ -1,11 +1,15 @@ package net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Computing.VoxelTerrainGeneration; +import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig; import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Rendering.Pipeline.Pipeline; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Rendering.VkModel.MaterialsCache; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Structure.DeviceLayers.CommandBuffer; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanUtils; import org.joml.*; +import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; +import org.lwjgl.vulkan.VkBufferCopy; import org.lwjgl.vulkan.VkCommandBuffer; import org.tinylog.Logger; @@ -21,13 +25,23 @@ import static org.lwjgl.vulkan.VK13.*; public class VoxelWorldManager { public static final int CHUNK_SIZE = 16; - private static final int MAX_RESIDENT_CHUNKS = 8192 * 3; - private static final int MAX_CHUNK_GENERATIONS_PER_FRAME = 128; + private static int MAX_CHUNK_GENERATIONS_PER_FRAME = EngineConfig.getInstance().GetVoxelGenerationsPerFrame(); + + public static int GetMaxChunkGenerationsPerFrame(){return MAX_CHUNK_GENERATIONS_PER_FRAME;} + public static void SetMaxChunkGenerationsPerFrame(int value){MAX_CHUNK_GENERATIONS_PER_FRAME = Math.clamp(value, 16, 64);} private static final ConcurrentLinkedQueue RequestedChunks = new ConcurrentLinkedQueue<>(); private static final ConcurrentHashMap RenderChunks = new ConcurrentHashMap<>(); private static final ConcurrentHashMap KnownChunks = new ConcurrentHashMap<>(); private static final ConcurrentHashMap CulledChunks = new ConcurrentHashMap<>(); + private static final ConcurrentLinkedQueue RetiredChunks = new ConcurrentLinkedQueue<>(); + private static final Vector3i ResidentCenter = new Vector3i(); + private static int ResidentRadius = EngineConfig.getInstance().GetVoxelChunkRadius(); + private static VulkanContext RenderContext; + private static int DrawFrame; + private static volatile int GeneratedLastFrame; + private static volatile int VisibleLastFrame; + private static boolean PoolFull; private static final List VoxelRegistry = new CopyOnWriteArrayList<>(); private static final ConcurrentHashMap VoxelNameToIndex = new ConcurrentHashMap<>(); @@ -188,8 +202,10 @@ public class VoxelWorldManager { return; } - Resources = new SharedVoxelTerrainResources(VkCtx, MAX_RESIDENT_CHUNKS); + int diameter = EngineConfig.getInstance().GetVoxelChunkRadius() * 2 + 1; + Resources = new SharedVoxelTerrainResources(VkCtx, diameter * diameter * diameter); Generator = new VoxelChunkGenerator(Resources); + RenderContext = VkCtx; GraphicsPushConstants = org.lwjgl.system.MemoryUtil.memAlloc( VulkanUtils.MATRIX4X4_SIZE + @@ -197,30 +213,15 @@ public class VoxelWorldManager { ); } - public static void GenerateChunk(Vector3i Position) { + public static synchronized void GenerateChunk(Vector3i Position) { Vector3i key = new Vector3i(Position); - - if (KnownChunks.containsKey(key)) { - return; - } - - RequestedChunks.add(key); - KnownChunks.put(key, true); + if (KnownChunks.putIfAbsent(key, true) == null) RequestedChunks.add(key); } public static int GenerationOffset = 0; public static Vector3i LastPosition = new Vector3i(0, 0, 0); static volatile int IterationX = 0; - static Vector3i ChunkPos = new Vector3i(0, 0, 0); - static Vector3i ChunkPos2 = new Vector3i(0, 0, 0); - - private static final FrustumIntersection frustumIntersection = new FrustumIntersection(); - private static final Matrix4f viewProjectionMatrix = new Matrix4f(); - private static Vector3f max = new Vector3f(); - private static Vector3f max2 = new Vector3f(); - private static Vector3f min = new Vector3f(); - private static Vector3f min2 = new Vector3f(); public static synchronized void ResetIterationX() { @@ -228,52 +229,71 @@ public class VoxelWorldManager { } public static synchronized void GenerateChunks(Vector3i Position, Vector3i Radius, Matrix4f cameraView,Matrix4f projectionMatrix) { - - projectionMatrix.mul(cameraView, viewProjectionMatrix); - frustumIntersection.set(viewProjectionMatrix); - - int ChunkX = Position.x; - int ChunkY = Position.y; - int ChunkY2 = Position.y; - int ChunkZ = Position.z; - for (int Ry = 0; Ry <= Radius.y; Ry++) - { - for (int Rx = -IterationX; Rx <= IterationX; Rx++) - { - for (int Rz = -IterationX; Rz <= IterationX; Rz++) - { - ChunkX = Position.x + Rx; - ChunkY = Position.y + Ry; - ChunkY2 = Position.y - Ry; - ChunkZ = Position.z + Rz; - min.set(ChunkX * CHUNK_SIZE, ChunkY * CHUNK_SIZE, ChunkZ * CHUNK_SIZE); - min2.set(ChunkX * CHUNK_SIZE, ChunkY2 * CHUNK_SIZE, ChunkZ * CHUNK_SIZE); - max.set(min.x + CHUNK_SIZE, min.y + CHUNK_SIZE, min.z + CHUNK_SIZE); - max2.set(min2.x + CHUNK_SIZE, min2.y + CHUNK_SIZE, min2.z + CHUNK_SIZE); - if (Rx == -IterationX || Rz == -IterationX || Rx == IterationX || Rz == IterationX) - { - if(frustumIntersection.testAab(min.x, min.y, min.z, max.x, max.y, max.z)) { - GenerateChunk(ChunkPos.set(ChunkX, ChunkY, ChunkZ)); - } - if(frustumIntersection.testAab(min2.x, min2.y, min2.z, max2.x, max2.y, max2.z)) { - GenerateChunk(ChunkPos2.set(ChunkX, ChunkY2, ChunkZ)); - } - } + if (!Position.equals(LastPosition)) IterationX = 0; + int radius = Math.max(Radius.x, Math.max(Radius.y, Radius.z)); + if (IterationX > radius) return; + int shell = IterationX++; + for (int x = -Math.min(shell, Radius.x); x <= Math.min(shell, Radius.x); x++) { + for (int y = -Math.min(shell, Radius.y); y <= Math.min(shell, Radius.y); y++) { + for (int z = -Math.min(shell, Radius.z); z <= Math.min(shell, Radius.z); z++) { + if (Math.max(Math.abs(x), Math.max(Math.abs(y), Math.abs(z))) != shell) continue; + GenerateChunk(new Vector3i(Position.x + x, Position.y + y, Position.z + z)); } } } - IterationX++; - if (IterationX > Radius.x || Position.x != LastPosition.x || Position.y != LastPosition.y || Position.z != LastPosition.z) - { - IterationX = 0; - } LastPosition.set(Position); } - public static void RecordGeneration(VulkanContext VkCtx, CommandBuffer commandBuffer) { + public static synchronized void RecordGeneration(VulkanContext VkCtx, CommandBuffer commandBuffer, int currentFrame) { if (Resources == null) { Init(VkCtx); } + DrawFrame = currentFrame; + RenderChunk retired; + while ((retired = RetiredChunks.poll()) != null) { + Resources.meshPool().free(retired); + PoolFull = false; + } + + try (var stack = MemoryStack.stackPush()) { + var cmd = commandBuffer.GetVulkanCommandBuffer(); + var completed = Generator.collectCompleted(VkCtx, currentFrame); + if (!completed.isEmpty()) { + // Reused ranges may still be read by an earlier submission on this queue. + VulkanUtils.BufferBarrier(stack, cmd, Resources.meshPool().facePoolHandle(), + VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT | VK_PIPELINE_STAGE_2_TRANSFER_BIT, + VK_PIPELINE_STAGE_2_TRANSFER_BIT, + VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_ACCESS_2_TRANSFER_WRITE_BIT); + var copy = VkBufferCopy.calloc(1, stack); + for (var generated : completed) { + Vector3i position = generated.position(); + if (!VoxelDrawData.withinRadius(position, ResidentCenter, ResidentRadius)) { + KnownChunks.remove(position); + continue; + } + if (generated.faceCount() == 0) { + CulledChunks.put(position, VoxelChunkVisibility.EMPTY_AIR); + continue; + } + RenderChunk chunk = Resources.meshPool().allocate(position, generated.faceCount()); + if (chunk == null) { + if (!PoolFull) Logger.warn("Voxel face budget exhausted; pending chunks retained. Increase voxel_face_pool_mib or reduce radius."); + PoolFull = true; + RequestedChunks.add(position); + continue; + } + copy.srcOffset(generated.sourceOffsetBytes()).dstOffset(chunk.faceOffsetBytes()) + .size((long) generated.faceCount() * VoxelMeshPool.FACE_RECORD_SIZE_BYTES); + vkCmdCopyBuffer(cmd, generated.sourceBuffer(), Resources.meshPool().facePoolHandle(), copy); + RenderChunks.put(position, chunk); + } + VulkanUtils.BufferBarrier(stack, cmd, Resources.meshPool().facePoolHandle(), + VK_PIPELINE_STAGE_2_TRANSFER_BIT, VK_PIPELINE_STAGE_2_VERTEX_SHADER_BIT, + VK_ACCESS_2_TRANSFER_WRITE_BIT, VK_ACCESS_2_SHADER_READ_BIT); + } + } + + if (RegistryDirty || BiomeRegistryDirty || StructureRegistryDirty) VkCtx.GetDevice().waitIdle(); if (RegistryDirty) { RegistryDirty = false; @@ -293,40 +313,18 @@ public class VoxelWorldManager { } - int generatedThisFrame = 0; - - while (!RequestedChunks.isEmpty() && generatedThisFrame < MAX_CHUNK_GENERATIONS_PER_FRAME) { + List batch = new ArrayList<>(MAX_CHUNK_GENERATIONS_PER_FRAME); + while (!PoolFull && batch.size() < MAX_CHUNK_GENERATIONS_PER_FRAME) { Vector3i position = RequestedChunks.poll(); - - if (position == null) { - break; + if (position == null) break; + if (!VoxelDrawData.withinRadius(position, ResidentCenter, ResidentRadius)) { + KnownChunks.remove(position); + continue; } - -// VoxelChunkVisibility visibility = VoxelTerrainHeightSampler.classifyChunk( -// position.x, -// position.y, -// position.z, -// VoxelChunkGenerator.CHUNK_SIZE -// ); -// -// if (visibility == VoxelChunkVisibility.EMPTY_AIR) { -// CulledChunks.put(new Vector3i(position), visibility); -// continue; -// } - - RenderChunk renderChunk = Resources.meshPool().allocate(position); - - if (renderChunk == null) { - Logger.warn("Voxel mesh pool full. Could not allocate chunk {}", position); - RequestedChunks.add(position); - break; - } - - Generator.recordGenerateChunk(VkCtx, commandBuffer, renderChunk); - RenderChunks.put(new Vector3i(position), renderChunk); - - generatedThisFrame++; + batch.add(position); } + GeneratedLastFrame = batch.size(); + Generator.recordGenerateChunks(VkCtx, commandBuffer, batch, currentFrame); } public static SharedVoxelTerrainResources Resources() { @@ -334,23 +332,56 @@ public class VoxelWorldManager { } public static void RecordRender(VkCommandBuffer CommandBuffer, Pipeline graphicsPipeline, MaterialsCache materialsCache, boolean shadowPass) { - if (Resources == null) { - return; + RecordRender(CommandBuffer, graphicsPipeline, materialsCache, shadowPass, new Matrix4f[0]); + } + + public static synchronized void RecordRender(VkCommandBuffer CommandBuffer, Pipeline graphicsPipeline, + MaterialsCache materialsCache, boolean shadowPass, Matrix4f[] clipMatrices) { + if (Resources == null) return; + var device = RenderContext.GetDevice(); + int material = Math.max(0, materialsCache.GetPosition("VoxelTerrain")); + FrustumIntersection[] frustums = new FrustumIntersection[clipMatrices.length]; + for (int i = 0; i < frustums.length; i++) frustums[i] = VoxelDrawData.frustum(clipMatrices[i]); + var buffer = Resources.meshPool().drawBuffer(DrawFrame, shadowPass); + ByteBuffer data = MemoryUtil.memByteBuffer(buffer.MapMemory(RenderContext), (int) buffer.GetRequestedSize()); + boolean instanced = device.drawIndirectFirstInstance(); + List visible = new ArrayList<>(); + for (RenderChunk chunk : RenderChunks.values()) { + Vector3i position = chunk.position(); + float x = position.x * (float) CHUNK_SIZE; + float y = position.y * (float) CHUNK_SIZE; + float z = position.z * (float) CHUNK_SIZE; + boolean inView = frustums.length == 0; + for (FrustumIntersection frustum : frustums) { + if (frustum.testAab(x, y, z, x + CHUNK_SIZE, y + CHUNK_SIZE, z + CHUNK_SIZE)) { + inView = true; + break; + } + } + if (!inView) continue; + int offset = visible.size() * VoxelMeshPool.DRAW_INDIRECT_COMMAND_SIZE; + data.putInt(offset, chunk.maxFaceCount() * 6); + data.putInt(offset + 4, 1); + data.putInt(offset + 8, Math.toIntExact(chunk.faceOffsetBytes() / 4 * 6)); + data.putInt(offset + 12, instanced ? VoxelDrawData.encodeOffset(position, ResidentCenter) : 0); + visible.add(chunk); } - - int terrainMaterialIndex; - - if (materialsCache.GetPosition("VoxelTerrain") < 0) { - terrainMaterialIndex = 0; + buffer.UnMapMemory(RenderContext); + if (!shadowPass) VisibleLastFrame = visible.size(); + if (instanced) { + SetPushConstants(CommandBuffer, new Vector4f(ResidentCenter, 0), graphicsPipeline, material, shadowPass); + int batchSize = device.multiDrawIndirect() ? device.maxDrawIndirectCount() : 1; + for (int first = 0; first < visible.size(); first += batchSize) { + vkCmdDrawIndirect(CommandBuffer, buffer.GetBuffer(), (long) first * VoxelMeshPool.DRAW_INDIRECT_COMMAND_SIZE, + Math.min(batchSize, visible.size() - first), VoxelMeshPool.DRAW_INDIRECT_COMMAND_SIZE); + } } else { - terrainMaterialIndex = materialsCache.GetPosition("VoxelTerrain"); + for (int i = 0; i < visible.size(); i++) { + SetPushConstants(CommandBuffer, new Vector4f(visible.get(i).position(), 0), graphicsPipeline, material, shadowPass); + vkCmdDrawIndirect(CommandBuffer, buffer.GetBuffer(), (long) i * VoxelMeshPool.DRAW_INDIRECT_COMMAND_SIZE, + 1, VoxelMeshPool.DRAW_INDIRECT_COMMAND_SIZE); + } } - - RenderChunks.forEach((position, chunk) -> { - SetPushConstants(CommandBuffer, new Vector4f(position, 0), graphicsPipeline, terrainMaterialIndex, shadowPass); - vkCmdDrawIndirect(CommandBuffer, Resources.meshPool().indirectPoolHandle(), chunk.indirectCommandOffsetBytes(), - 1, VoxelMeshPool.DRAW_INDIRECT_COMMAND_SIZE); - }); } private static void SetPushConstants(VkCommandBuffer CmdHandle, Vector4f ModelMatrix, Pipeline VkPipeline, int MaterialIndex, boolean shadowPass){ @@ -366,48 +397,22 @@ public class VoxelWorldManager { public static synchronized void UnloadFarChunks(Vector3i centerChunk, int unloadRadius,Matrix4f cameraView,Matrix4f projectionMatrix) { - int maxDistSq = unloadRadius * unloadRadius; - projectionMatrix.mul(cameraView, viewProjectionMatrix); - frustumIntersection.set(viewProjectionMatrix); + ResidentCenter.set(centerChunk); + ResidentRadius = unloadRadius; RenderChunks.entrySet().removeIf(entry -> { - Vector3i pos = entry.getKey(); - min.set(pos.x * CHUNK_SIZE, pos.y * CHUNK_SIZE, pos.z * CHUNK_SIZE); - max.set(min.x + CHUNK_SIZE, min.y + CHUNK_SIZE, min.z + CHUNK_SIZE); - boolean InView = frustumIntersection.testAab(min.x, min.y, min.z, max.x, max.y, max.z); - if (!InView) { - RenderChunk chunk = entry.getValue(); - Resources.meshPool().free(chunk); + if (!VoxelDrawData.withinRadius(pos, centerChunk, unloadRadius)) { + RetiredChunks.add(entry.getValue()); KnownChunks.remove(pos); return true; } - int dx = pos.x - centerChunk.x; - int dy = pos.y - centerChunk.y; - int dz = pos.z - centerChunk.z; - - int distSq = dx * dx + dy * dy + dz * dz; - if (distSq > maxDistSq) { - RenderChunk chunk = entry.getValue(); - - Resources.meshPool().free(chunk); - KnownChunks.remove(pos); - - return true; - } - return false; }); CulledChunks.entrySet().removeIf(entry -> { Vector3i pos = entry.getKey(); - int dx = pos.x - centerChunk.x; - int dy = pos.y - centerChunk.y; - int dz = pos.z - centerChunk.z; - - int distSq = dx * dx + dy * dy + dz * dz; - - if (distSq > maxDistSq) { + if (!VoxelDrawData.withinRadius(pos, centerChunk, unloadRadius)) { KnownChunks.remove(pos); return true; } @@ -416,13 +421,7 @@ public class VoxelWorldManager { }); RequestedChunks.removeIf(pos -> { - int dx = pos.x - centerChunk.x; - int dy = pos.y - centerChunk.y; - int dz = pos.z - centerChunk.z; - - int distSq = dx * dx + dy * dy + dz * dz; - - if (distSq > maxDistSq) { + if (!VoxelDrawData.withinRadius(pos, centerChunk, unloadRadius)) { KnownChunks.remove(pos); return true; } @@ -434,6 +433,9 @@ public class VoxelWorldManager { public static int GetRenderableChunkCount() { return RenderChunks.size(); } + public static int GetGeneratedLastFrame() {return GeneratedLastFrame;} + public static int GetVisibleLastFrame() {return VisibleLastFrame;} + public static synchronized long GetUsedFaceBytes() {return Resources == null ? 0 : Resources.meshPool().usedFaceBytes();} public static int GetCulledChunkCount() { return CulledChunks.size(); @@ -452,7 +454,7 @@ public class VoxelWorldManager { } - public static void Cleanup(VulkanContext VkCtx) { + public static synchronized void Cleanup(VulkanContext VkCtx) { if (Resources != null) { Resources.cleanup(VkCtx); Resources = null; @@ -466,5 +468,12 @@ public class VoxelWorldManager { RequestedChunks.clear(); RenderChunks.clear(); KnownChunks.clear(); + CulledChunks.clear(); + RetiredChunks.clear(); + Generator = null; + RenderContext = null; + IterationX = 0; + PoolFull = false; + RegistryDirty = BiomeRegistryDirty = StructureRegistryDirty = true; } } \ No newline at end of file diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VulkanUtil/ComputePipeline.java b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VulkanUtil/ComputePipeline.java index c116cfa..5cf5c6e 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VulkanUtil/ComputePipeline.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Computing/VulkanUtil/ComputePipeline.java @@ -36,10 +36,8 @@ public class ComputePipeline implements Pipeline { int pushRangeCount = pushConstantsRanges != null ? pushConstantsRanges.length : 0; if (pushRangeCount > 0) { pushRanges = VkPushConstantRange.calloc(pushRangeCount, stack); - for (int i = 0; i < pushRangeCount; i++) { PushConstantsRange range = pushConstantsRanges[i]; - pushRanges.get(i) .stageFlags(range.Stage()) .offset(range.Offset()) diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/DeferredRendering/DeferredSceneRender.java b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/DeferredRendering/DeferredSceneRender.java index 7cb942b..b681119 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/DeferredRendering/DeferredSceneRender.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/DeferredRendering/DeferredSceneRender.java @@ -431,7 +431,9 @@ public class DeferredSceneRender implements SceneRenderer {//dynamic rendering vkCmdBindDescriptorSets(CommandHandle, VK_PIPELINE_BIND_POINT_GRAPHICS, VkVoxelPipeline.GetVulkanPipelineLayout(), 0, voxelDescriptorSets, null); - VoxelWorldManager.RecordRender(CommandHandle, VkVoxelPipeline, materialsCache, false); + VoxelWorldManager.RecordRender(CommandHandle, VkVoxelPipeline, materialsCache, false, + new Matrix4f[]{engineInstance.scene().GetProjection().GetProjectionMatrix() + .mul(engineInstance.scene().GetCamera().GetViewMatrix(), new Matrix4f())}); Matrix4f skyboxViewMatrix = new Matrix4f().identity(); skyboxViewMatrix.set(engineInstance.scene().GetCamera().GetViewMatrix()); diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/DeferredRendering/MultiRenderTargetAttachments.java b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/DeferredRendering/MultiRenderTargetAttachments.java index fea9e05..67f4156 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/DeferredRendering/MultiRenderTargetAttachments.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/DeferredRendering/MultiRenderTargetAttachments.java @@ -16,10 +16,10 @@ public class MultiRenderTargetAttachments { public static final int DEPTH_FORMAT = VK_FORMAT_D32_SFLOAT; public static final int REFLECT_REFRACT = VK_FORMAT_R16G16_SFLOAT; public static final int NORMAL_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT; - public static final int PBR_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT; + public static final int PBR_FORMAT = VK_FORMAT_R8G8B8A8_UNORM; public static final int EMISSIVE_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT; - public static final int TRANSLUCECNY_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT; - public static final int OPACITY_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT; + public static final int TRANSLUCECNY_FORMAT = VK_FORMAT_R8G8B8A8_UNORM; + public static final int OPACITY_FORMAT = VK_FORMAT_R8G8B8A8_UNORM; public static final int POSITION_FORMAT = VK_FORMAT_R32G32B32A32_SFLOAT; public static final int VIEW_POS_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT; public static final int SSAO_RAW_ATTACHMENT = VK_FORMAT_R8_UNORM; diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/Forward/ForwardSceneRender.java b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/Forward/ForwardSceneRender.java index 2441f62..427268a 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/Forward/ForwardSceneRender.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/Forward/ForwardSceneRender.java @@ -428,7 +428,9 @@ public class ForwardSceneRender implements SceneRenderer {//dynamic rendering vkCmdBindDescriptorSets(CommandHandle, VK_PIPELINE_BIND_POINT_GRAPHICS, VkVoxelPipeline.GetVulkanPipelineLayout(), 0, voxelDescriptorSets, null); - VoxelWorldManager.RecordRender(CommandHandle, VkVoxelPipeline, materialsCache, false); + VoxelWorldManager.RecordRender(CommandHandle, VkVoxelPipeline, materialsCache, false, + new Matrix4f[]{engineInstance.scene().GetProjection().GetProjectionMatrix() + .mul(engineInstance.scene().GetCamera().GetViewMatrix(), new Matrix4f())}); Matrix4f skyboxViewMatrix = new Matrix4f().identity(); diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/Lighting/LightRenderer.java b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/Lighting/LightRenderer.java index 8470029..867aa81 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/Lighting/LightRenderer.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/Lighting/LightRenderer.java @@ -40,7 +40,7 @@ import static org.lwjgl.vulkan.VK10.*; import static org.lwjgl.vulkan.VK13.*; public class LightRenderer { - private static final int COLOUR_FORMAT = VK_FORMAT_R32G32B32A32_SFLOAT; + private static final int COLOUR_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT; private static final String DESC_ID_ATT = "LIGHT_DESC_ID_ATT"; private static final String DESC_ID_NOSHADOW_ATT = "LIGHT_DESC_ID_NOSHADOW_ATT"; private static final String DESC_ID_SHADOW_MATRICES = "LIGHT_DESC_ID_SHADOW_MATRICES"; @@ -172,7 +172,7 @@ public class LightRenderer { var vtxBuffStruct = new EmptyVertexBufferStruct(); var buildInfo = new PipelineBuildInfo(shaderModules, vtxBuffStruct.GetVertexInput(),new int[]{COLOUR_FORMAT}) .SetDescriptorSetLayouts(descSetLayouts) - .BlendingIsUsed(true) + .BlendingIsUsed(false) .SetBlendingMethod(1) .SetDepthWrite(false) .SetDepthTest(false); diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/PostProcessing/PostProcess.java b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/PostProcessing/PostProcess.java index fad9602..977c57e 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/PostProcessing/PostProcess.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/PostProcessing/PostProcess.java @@ -8,23 +8,16 @@ import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Rendering.Pipeline.I import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Rendering.Pipeline.Images.Image; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Rendering.Pipeline.Pipeline; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Rendering.Pipeline.PipelineBuildInfo; +import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Rendering.Pipeline.PushConstantsRange; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Rendering.Shader.*; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Structure.DeviceLayers.CommandBuffer; -import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Structure.DeviceLayers.Device; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Structure.DisplayToScreen.ImageView; -import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Structure.SwapChain.SwapChain; -import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanBuffer; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanUtils; import org.lwjgl.system.MemoryStack; -import org.lwjgl.system.MemoryUtil; import org.lwjgl.util.shaderc.Shaderc; import org.lwjgl.vulkan.*; -import org.tinylog.Logger; import java.nio.ByteBuffer; -import java.nio.FloatBuffer; -import java.nio.LongBuffer; -import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -33,125 +26,97 @@ import static org.lwjgl.vulkan.VK13.*; public class PostProcess { public static final int COLOUR_FORMAT = VK_FORMAT_R16G16B16A16_SFLOAT; + private static final int PASS_CONSTANT_SIZE = 24; private static final String DESCRIPTOR_ID_ATTACHMENT = "POST_DESC_ID_ATT"; - private static final String DESCRIPTOR_ID_BLOOM_PROPERTIES_HORIZONTAL = "POST_DESC_ID_BLOOM_PROPERTIES_HORIZONTAL"; - private static final String DESCRIPTOR_ID_BLOOM_PROPERTIES_VERTICAL = "POST_DESC_ID_BLOOM_PROPERTIES_VERTICAL"; - private static final String DESCRIPTOR_ID_BLOOM_PROPERTIES_FINAL = "POST_DESC_ID_BLOOM_PROPERTIES_FINAL"; private static final String DESCRIPTOR_ID_BLOOM_ATTACHMENT = "POST_DESC_ID_BLOOM_ATT"; private static final String DESCRIPTOR_ID_BLOOM_PONG_ATTACHMENT = "POST_DESC_ID_BLOOM_PONG_ATT"; private static final String DESCRIPTOR_ID_BLOOM_IMAGE_ATTACHMENT = "POST_DESC_ID_BLOOM_IMAGE_ATT"; - private static final String DESCRIPTOR_ID_SCREEN_SIZE = "POST_DESC_ID_SCREEN_SIZE"; private static final String MULTI_PASS_FRAGMENT_SHADER_FILE_GLSL = "resources/EngineResources/shaders/multi-sampled_post_process_frag.glsl"; - private static final String MULTI_PASS_FRAGMENT_SHADER_FILE_SPV = MULTI_PASS_FRAGMENT_SHADER_FILE_GLSL + ".spv"; private static final String BLOOM_FRAGMENT_SHADER_FILE_GLSL = "resources/EngineResources/shaders/bloom_pass_frag.glsl"; - private static final String BLOOM_FRAGMENT_SHADER_FILE_SPV = BLOOM_FRAGMENT_SHADER_FILE_GLSL + ".spv"; private static final String SINGLE_PASS_FRAGMENT_SHADER_FILE_GLSL = "resources/EngineResources/shaders/post_process_frag.glsl"; - private static final String SINGLE_PASS_FRAGMENT_SHADER_FILE_SPV = SINGLE_PASS_FRAGMENT_SHADER_FILE_GLSL + ".spv"; + private static final String EXTRACT_FRAGMENT_SHADER_FILE_GLSL = "resources/EngineResources/shaders/bloom_extract_frag.glsl"; + private static final String MULTI_EXTRACT_FRAGMENT_SHADER_FILE_GLSL = "resources/EngineResources/shaders/multi-sampled_bloom_extract_frag.glsl"; private static final String VERTEX_SHADER_FILE_GLSL = "resources/EngineResources/shaders/basic_screen_vertex.glsl"; - private static final String VERTEX_SHADER_FILE_SPV = VERTEX_SHADER_FILE_GLSL + ".spv"; private final DescriptorSetLayout AttachmentDescriptorSetLayout; - private final DescriptorSetLayout[] MultiAttachmentDescriptorSetLayout = new DescriptorSetLayout[2]; + private final DescriptorSetLayout MultiAttachmentDescriptorSetLayout; private final VkClearValue ClearValueColour; - private final DescriptorSetLayout FragmentUniformDescriptorSetLayout; private final Pipeline pipeline; private final Pipeline bloomPipeline; - private final VulkanBuffer ScreenSizeBuffer; - private final VulkanBuffer BloomPropertiesHorizontalBuffer; - private final VulkanBuffer BloomPropertiesVerticalBuffer; - private final VulkanBuffer BloomPropertiesFinalBuffer; + private final Pipeline extractionPipeline; private final SpecializationConstants specConstants; private final TextureSampler textureSampler; - private Attachment ColourAttachment; - private VkRenderingAttachmentInfo.Buffer ColourAttachmentInfo; - private VkRenderingAttachmentInfo.Buffer FinalOutputInfo; private Attachment BloomPingAttachment; private Attachment BloomPongAttachment; private Attachment FinalAttachment; private VkRenderingAttachmentInfo.Buffer BloomPingAttachmentInfo; private VkRenderingAttachmentInfo.Buffer BloomPongAttachmentInfo; - private VkRenderingInfo RenderingInfo; + private VkRenderingAttachmentInfo.Buffer FinalOutputInfo; private VkRenderingInfo SingleRenderingInfo; private VkRenderingInfo BloomPingRenderingInfo; private VkRenderingInfo BloomPongRenderingInfo; + private boolean attachmentsInitialized; + private int bloomPasses; + + public static volatile float GAMMA = 1.75f; + public static volatile float EXPOSURE = 2.5f; + public static volatile float BLOOM_RADIUS = 2.5f; public PostProcess(VulkanContext VkCtx, Attachment SrcAttachment){ ClearValueColour = VkClearValue.calloc(); - ClearValueColour.color(c->c.float32(0,0.0f).float32(1,0.0f).float32(2,0.0f).float32(3,0.0f)); - - ColourAttachment = CreateColourAttachment(VkCtx); - BloomPingAttachment = CreateColourAttachment(VkCtx); - BloomPongAttachment = CreateColourAttachment(VkCtx); - FinalAttachment = CreateColourAttachment(VkCtx); - - List attachments = new ArrayList<>(); - attachments.add(ColourAttachment); - attachments.add(BloomPingAttachment); - ColourAttachmentInfo = CreateColourAttachmentInfos(attachments,ClearValueColour); - BloomPingAttachmentInfo = CreateColourAttachmentInfo(BloomPingAttachment,ClearValueColour); - BloomPongAttachmentInfo = CreateColourAttachmentInfo(BloomPongAttachment,ClearValueColour); - FinalOutputInfo = CreateColourAttachmentInfo(FinalAttachment,ClearValueColour); - RenderingInfo = CreateRenderInfo(ColourAttachment,ColourAttachmentInfo); - BloomPingRenderingInfo = CreateRenderInfo(BloomPingAttachment, BloomPingAttachmentInfo); - BloomPongRenderingInfo = CreateRenderInfo(BloomPongAttachment, BloomPongAttachmentInfo); - SingleRenderingInfo = CreateRenderInfo(FinalAttachment,FinalOutputInfo); - - var textureSamplerInfo = new TextureSamplerInfo(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, - VK_BORDER_COLOR_INT_OPAQUE_BLACK, 1, true); - textureSampler = new TextureSampler(VkCtx, textureSamplerInfo); - - DescriptorSetLayout.LayoutInformation[] descSetLayouts = new DescriptorSetLayout.LayoutInformation[2]; - for (int i = 0; i < 2; i++) { - descSetLayouts[i] = new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, i, 1, VK_SHADER_STAGE_FRAGMENT_BIT); - } - - var LayoutInfo = new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER,0,1,VK_SHADER_STAGE_FRAGMENT_BIT); - AttachmentDescriptorSetLayout = new DescriptorSetLayout(VkCtx, LayoutInfo); - MultiAttachmentDescriptorSetLayout[0] = new DescriptorSetLayout(VkCtx, descSetLayouts); - CreateAttachmentDescriptorSet(VkCtx, AttachmentDescriptorSetLayout, SrcAttachment,textureSampler, DESCRIPTOR_ID_ATTACHMENT); - CreateAttachmentDescriptorSets(VkCtx, MultiAttachmentDescriptorSetLayout[0], new Attachment[]{BloomPingAttachment, BloomPingAttachment},textureSampler, DESCRIPTOR_ID_BLOOM_ATTACHMENT); - CreateAttachmentDescriptorSets(VkCtx, MultiAttachmentDescriptorSetLayout[0], new Attachment[]{BloomPongAttachment, BloomPongAttachment},textureSampler, DESCRIPTOR_ID_BLOOM_PONG_ATTACHMENT); - CreateAttachmentDescriptorSets(VkCtx, MultiAttachmentDescriptorSetLayout[0], new Attachment[]{ColourAttachment, BloomPingAttachment},textureSampler, DESCRIPTOR_ID_BLOOM_IMAGE_ATTACHMENT); - - LayoutInfo = new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0,1,VK_SHADER_STAGE_FRAGMENT_BIT); - FragmentUniformDescriptorSetLayout = new DescriptorSetLayout(VkCtx, LayoutInfo); - ScreenSizeBuffer = VulkanUtils.CreateHostVisibleBuffer(VkCtx, VulkanUtils.VEC2_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,DESCRIPTOR_ID_SCREEN_SIZE,FragmentUniformDescriptorSetLayout); - BloomPropertiesHorizontalBuffer = VulkanUtils.CreateHostVisibleBuffer(VkCtx, VulkanUtils.BOOLEAN_SIZE * 2 + VulkanUtils.FLOAT_SIZE * 2+ VulkanUtils.VEC4_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,DESCRIPTOR_ID_BLOOM_PROPERTIES_HORIZONTAL,FragmentUniformDescriptorSetLayout); - BloomPropertiesVerticalBuffer = VulkanUtils.CreateHostVisibleBuffer(VkCtx, VulkanUtils.BOOLEAN_SIZE * 2 + VulkanUtils.FLOAT_SIZE * 2 + VulkanUtils.VEC4_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,DESCRIPTOR_ID_BLOOM_PROPERTIES_VERTICAL,FragmentUniformDescriptorSetLayout); - BloomPropertiesFinalBuffer = VulkanUtils.CreateHostVisibleBuffer(VkCtx, VulkanUtils.BOOLEAN_SIZE * 2 + VulkanUtils.FLOAT_SIZE * 2+ VulkanUtils.VEC4_SIZE, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,DESCRIPTOR_ID_BLOOM_PROPERTIES_FINAL,FragmentUniformDescriptorSetLayout); - SetScreenSizeBuffer(VkCtx); - SetBloomPropertiesBuffer(VkCtx, BloomPropertiesHorizontalBuffer, true, false); - SetBloomPropertiesBuffer(VkCtx, BloomPropertiesVerticalBuffer, false, false); - SetBloomPropertiesBuffer(VkCtx, BloomPropertiesFinalBuffer, false, true); + ClearValueColour.color(c -> c.float32(0, 0.0f).float32(1, 0.0f).float32(2, 0.0f).float32(3, 0.0f)); + CreateAttachments(VkCtx); + textureSampler = new TextureSampler(VkCtx, new TextureSamplerInfo(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, + VK_BORDER_COLOR_INT_OPAQUE_BLACK, 1, false)); + AttachmentDescriptorSetLayout = new DescriptorSetLayout(VkCtx, + new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, 1, VK_SHADER_STAGE_FRAGMENT_BIT)); + MultiAttachmentDescriptorSetLayout = new DescriptorSetLayout(VkCtx, new DescriptorSetLayout.LayoutInformation[]{ + new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, 1, VK_SHADER_STAGE_FRAGMENT_BIT), + new DescriptorSetLayout.LayoutInformation(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, 1, VK_SHADER_STAGE_FRAGMENT_BIT)}); + UpdateDescriptors(VkCtx, SrcAttachment); specConstants = new SpecializationConstants(); - ShaderModule[] shaderModules = CreateShaderModules(VkCtx, specConstants); - pipeline = CreatePipeline(VkCtx, shaderModules, new DescriptorSetLayout[]{AttachmentDescriptorSetLayout, FragmentUniformDescriptorSetLayout}); - Arrays.asList(shaderModules).forEach(shader->shader.CleanUp(VkCtx)); - shaderModules = CreateBloomShaderModules(VkCtx, specConstants); - bloomPipeline = CreateSingleOutputPipeline(VkCtx, shaderModules, new DescriptorSetLayout[]{MultiAttachmentDescriptorSetLayout[0], FragmentUniformDescriptorSetLayout}); - Logger.debug("Post Process Renderer Pipeline -> [{}]",pipeline.GetVulkanPipeline()); + boolean multisampled = !EngineConfig.getInstance().DeferredRendering() && EngineConfig.getInstance().RenderAA() > 1; + pipeline = CreatePipeline(VkCtx, multisampled ? MULTI_PASS_FRAGMENT_SHADER_FILE_GLSL : SINGLE_PASS_FRAGMENT_SHADER_FILE_GLSL, + MultiAttachmentDescriptorSetLayout, specConstants.GetSpecializationInfo()); + bloomPipeline = CreatePipeline(VkCtx, BLOOM_FRAGMENT_SHADER_FILE_GLSL, AttachmentDescriptorSetLayout, null); + extractionPipeline = CreatePipeline(VkCtx, multisampled ? MULTI_EXTRACT_FRAGMENT_SHADER_FILE_GLSL : EXTRACT_FRAGMENT_SHADER_FILE_GLSL, + AttachmentDescriptorSetLayout, null); } - private static VkRenderingAttachmentInfo.Buffer CreateColourAttachmentInfos(List attachments, VkClearValue ClearValue){ - int numAttachments = attachments.size(); - VkRenderingAttachmentInfo.Buffer result = VkRenderingAttachmentInfo.calloc(numAttachments); - for (int i = 0; i < numAttachments; ++i) { - result.get(i) - .sType$Default() - .imageView(attachments.get(i).GetVkImageView().GetVulkanImageView()) - .imageLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) - .loadOp(VK_ATTACHMENT_LOAD_OP_CLEAR) - .storeOp(VK_ATTACHMENT_STORE_OP_STORE) - .clearValue(ClearValue); + static int ScaledExtent(int size, float scale) { + return Math.max(1, Math.round(size * scale)); + } + + static int ValidateBloomPasses(int passes) { + if (passes < 0 || (passes & 1) != 0) { + throw new IllegalArgumentException("Bloom requires horizontal/vertical pass pairs, or zero to disable it"); } - return result; + return passes; } - private static Attachment CreateColourAttachment(VulkanContext VkCtx){ - SwapChain swapChain = VkCtx.GetSwapChain(); - VkExtent2D SwapChainExtent = swapChain.GetSwapChainExtent(); - return new Attachment(VkCtx,SwapChainExtent.width(),SwapChainExtent.height(),COLOUR_FORMAT,VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); + private void CreateAttachments(VulkanContext VkCtx){ + var extent = VkCtx.GetSwapChain().GetSwapChainExtent(); + var config = EngineConfig.getInstance(); + float scale = config.GetBloomScale(); + int width = ScaledExtent(extent.width(), scale); + int height = ScaledExtent(extent.height(), scale); + bloomPasses = ValidateBloomPasses(config.GetBloomPasses()); + attachmentsInitialized = false; + BloomPingAttachment = CreateColourAttachment(VkCtx, width, height); + BloomPongAttachment = CreateColourAttachment(VkCtx, width, height); + FinalAttachment = CreateColourAttachment(VkCtx, extent.width(), extent.height()); + BloomPingAttachmentInfo = CreateColourAttachmentInfo(BloomPingAttachment, ClearValueColour); + if (bloomPasses == 0) BloomPingAttachmentInfo.get(0).loadOp(VK_ATTACHMENT_LOAD_OP_CLEAR); + BloomPongAttachmentInfo = CreateColourAttachmentInfo(BloomPongAttachment, ClearValueColour); + FinalOutputInfo = CreateColourAttachmentInfo(FinalAttachment, ClearValueColour); + BloomPingRenderingInfo = CreateRenderInfo(BloomPingAttachment, BloomPingAttachmentInfo); + BloomPongRenderingInfo = CreateRenderInfo(BloomPongAttachment, BloomPongAttachmentInfo); + SingleRenderingInfo = CreateRenderInfo(FinalAttachment, FinalOutputInfo); + } + + private static Attachment CreateColourAttachment(VulkanContext VkCtx, int width, int height){ + return new Attachment(VkCtx, width, height, COLOUR_FORMAT, VK_IMAGE_USAGE_SAMPLED_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT); } private static VkRenderingAttachmentInfo.Buffer CreateColourAttachmentInfo(Attachment SrcAttachment, VkClearValue ClearValue){ @@ -159,283 +124,163 @@ public class PostProcess { .sType$Default() .imageView(SrcAttachment.GetVkImageView().GetVulkanImageView()) .imageLayout(VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) - .loadOp(VK_ATTACHMENT_LOAD_OP_CLEAR) + .loadOp(VK_ATTACHMENT_LOAD_OP_DONT_CARE) .storeOp(VK_ATTACHMENT_STORE_OP_STORE) .clearValue(ClearValue); } - private static Pipeline CreatePipeline(VulkanContext VkCtx, ShaderModule[] shaderModules, DescriptorSetLayout[] descriptorSetLayouts){ + private static Pipeline CreatePipeline(VulkanContext VkCtx, String fragmentShader, DescriptorSetLayout descriptorSetLayout, + VkSpecializationInfo specializationInfo){ + if (EngineConfig.getInstance().RecompileShaders()) { + ShaderCompiler.CompileGLSLShaderOnChange(VERTEX_SHADER_FILE_GLSL, Shaderc.shaderc_glsl_vertex_shader); + ShaderCompiler.CompileGLSLShaderOnChange(fragmentShader, Shaderc.shaderc_glsl_fragment_shader); + } + ShaderModule[] shaderModules = { + new ShaderModule(VkCtx, VK_SHADER_STAGE_VERTEX_BIT, VERTEX_SHADER_FILE_GLSL + ".spv", null), + new ShaderModule(VkCtx, VK_SHADER_STAGE_FRAGMENT_BIT, fragmentShader + ".spv", specializationInfo) + }; var VertexBufferStruct = new EmptyVertexBufferStruct(); - var BuildInfo = new PipelineBuildInfo(shaderModules, VertexBufferStruct.GetVertexInput(), new int[]{COLOUR_FORMAT, COLOUR_FORMAT}) - .SetDescriptorSetLayouts(descriptorSetLayouts) - .BlendingIsUsed(true); - var PipeLine = new DefaultPipeline(VkCtx, BuildInfo); - VertexBufferStruct.CleanUp(); - return PipeLine; - } - - private static Pipeline CreateSingleOutputPipeline(VulkanContext VkCtx, ShaderModule[] shaderModules, DescriptorSetLayout[] descriptorSetLayouts){ - var VertexBufferStruct = new EmptyVertexBufferStruct(); - var BuildInfo = new PipelineBuildInfo(shaderModules, VertexBufferStruct.GetVertexInput(), new int[]{COLOUR_FORMAT}) - .SetDescriptorSetLayouts(descriptorSetLayouts) - .BlendingIsUsed(true); - var PipeLine = new DefaultPipeline(VkCtx, BuildInfo); - VertexBufferStruct.CleanUp(); - return PipeLine; + try { + var BuildInfo = new PipelineBuildInfo(shaderModules, VertexBufferStruct.GetVertexInput(), new int[]{COLOUR_FORMAT}) + .SetDescriptorSetLayouts(new DescriptorSetLayout[]{descriptorSetLayout}) + .SetPushConstantRanges(new PushConstantsRange[]{new PushConstantsRange(VK_SHADER_STAGE_FRAGMENT_BIT, 0, PASS_CONSTANT_SIZE)}) + .SetDepthTest(false).SetDepthWrite(false).BlendingIsUsed(false); + return new DefaultPipeline(VkCtx, BuildInfo); + } finally { + VertexBufferStruct.CleanUp(); + Arrays.stream(shaderModules).forEach(shader -> shader.CleanUp(VkCtx)); + } } private static VkRenderingInfo CreateRenderInfo(Attachment ColourAttachment, VkRenderingAttachmentInfo.Buffer ColourAttachmentInfo){ - VkRenderingInfo renderingInfo; - try(var MemStack = MemoryStack.stackPush()){ - Image image = ColourAttachment.GetVkImage(); - VkExtent2D extent2D = VkExtent2D.calloc(MemStack).width(image.GetWidth()).height(image.GetHeight()); - var RenderArea = VkRect2D.calloc(MemStack).extent(extent2D); - - renderingInfo = VkRenderingInfo.calloc() - .sType$Default() - .renderArea(RenderArea) - .layerCount(1) - .pColorAttachments(ColourAttachmentInfo); - } - return renderingInfo; + Image image = ColourAttachment.GetVkImage(); + return VkRenderingInfo.calloc().sType$Default() + .renderArea(area -> area.offset(offset -> offset.set(0, 0)).extent(extent -> extent.set(image.GetWidth(), image.GetHeight()))) + .layerCount(1).pColorAttachments(ColourAttachmentInfo); } - private static void CreateAttachmentDescriptorSets(VulkanContext VkCtx, DescriptorSetLayout descriptorSetLayout, Attachment[] attachment, TextureSampler textureSampler, String descriptorID){ + private void UpdateDescriptors(VulkanContext VkCtx, Attachment SrcAttachment){ + CreateAttachmentDescriptorSets(VkCtx, AttachmentDescriptorSetLayout, List.of(SrcAttachment), DESCRIPTOR_ID_ATTACHMENT); + CreateAttachmentDescriptorSets(VkCtx, AttachmentDescriptorSetLayout, List.of(BloomPingAttachment), DESCRIPTOR_ID_BLOOM_ATTACHMENT); + CreateAttachmentDescriptorSets(VkCtx, AttachmentDescriptorSetLayout, List.of(BloomPongAttachment), DESCRIPTOR_ID_BLOOM_PONG_ATTACHMENT); + CreateAttachmentDescriptorSets(VkCtx, MultiAttachmentDescriptorSetLayout, List.of(SrcAttachment, BloomPingAttachment), DESCRIPTOR_ID_BLOOM_IMAGE_ATTACHMENT); + } + + private void CreateAttachmentDescriptorSets(VulkanContext VkCtx, DescriptorSetLayout descriptorSetLayout, + List attachments, String descriptorID){ DescriptorAllocator descriptorAllocator = VkCtx.GetDescriptorAllocator(); - Device device = VkCtx.GetDevice(); - DescriptorSet descriptorSet = descriptorAllocator.AddDescriptorSets(device,descriptorID,1 , descriptorSetLayout)[0]; - List images = new ArrayList<>(); - for(int i = 0; i < attachment.length; i++) { - images.add(attachment[i].GetVkImageView()); - } - descriptorSet.SetImages(device,images,textureSampler,0); + DescriptorSet descriptorSet = descriptorAllocator.GetDescriptorSet(descriptorID); + if (descriptorSet == null) descriptorSet = descriptorAllocator.AddDescriptorSet(VkCtx.GetDevice(), descriptorID, descriptorSetLayout); + List images = attachments.stream().map(Attachment::GetVkImageView).toList(); + descriptorSet.SetImages(VkCtx.GetDevice(), images, textureSampler, 0); } - - private static void CreateAttachmentDescriptorSet(VulkanContext VkCtx, DescriptorSetLayout descriptorSetLayout, Attachment attachment, TextureSampler textureSampler, String descriptorID){ - DescriptorAllocator descriptorAllocator = VkCtx.GetDescriptorAllocator(); - Device device = VkCtx.GetDevice(); - DescriptorSet descriptorSet = descriptorAllocator.AddDescriptorSets(device,descriptorID,1 , descriptorSetLayout)[0]; - descriptorSet.SetImage(device,attachment.GetVkImageView(),textureSampler,0); + private static void BeginWrite(MemoryStack stack, VkCommandBuffer command, Attachment attachment, boolean initialized){ + VulkanUtils.ImageBarrier(stack, command, attachment.GetVkImage().getVulkanImage(), + initialized ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + initialized ? VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT : VK_PIPELINE_STAGE_2_NONE, + VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, + initialized ? VK_ACCESS_2_SHADER_READ_BIT : VK_ACCESS_2_NONE, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, + VK_IMAGE_ASPECT_COLOR_BIT); } - private static ShaderModule[] CreateBloomShaderModules(VulkanContext VkCtx, SpecializationConstants specConstants){ - if(EngineConfig.getInstance().RecompileShaders()){ - ShaderCompiler.CompileGLSLShaderOnChange(VERTEX_SHADER_FILE_GLSL, Shaderc.shaderc_glsl_vertex_shader); - ShaderCompiler.CompileGLSLShaderOnChange(BLOOM_FRAGMENT_SHADER_FILE_GLSL, Shaderc.shaderc_glsl_fragment_shader); - } - return new ShaderModule[]{ - new ShaderModule(VkCtx,VK_SHADER_STAGE_VERTEX_BIT,VERTEX_SHADER_FILE_SPV,null), - new ShaderModule(VkCtx,VK_SHADER_STAGE_FRAGMENT_BIT,BLOOM_FRAGMENT_SHADER_FILE_SPV,null) - }; - + private static void EndWrite(MemoryStack stack, VkCommandBuffer command, Attachment attachment){ + VulkanUtils.ImageBarrier(stack, command, attachment.GetVkImage().getVulkanImage(), + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT, + VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_2_SHADER_READ_BIT, VK_IMAGE_ASPECT_COLOR_BIT); } - private static ShaderModule[] CreateShaderModules(VulkanContext VkCtx, SpecializationConstants specConstants){ - String ShaderPath = SINGLE_PASS_FRAGMENT_SHADER_FILE_GLSL; - if(EngineConfig.getInstance().RecompileShaders()){ - ShaderCompiler.CompileGLSLShaderOnChange(VERTEX_SHADER_FILE_GLSL, Shaderc.shaderc_glsl_vertex_shader); - if(EngineConfig.getInstance().RenderAA() > 1) ShaderPath = MULTI_PASS_FRAGMENT_SHADER_FILE_GLSL; - ShaderCompiler.CompileGLSLShaderOnChange(ShaderPath, Shaderc.shaderc_glsl_fragment_shader); - } - ShaderPath = SINGLE_PASS_FRAGMENT_SHADER_FILE_SPV; - if(EngineConfig.getInstance().RenderAA() > 1) ShaderPath = MULTI_PASS_FRAGMENT_SHADER_FILE_SPV; - return new ShaderModule[]{ - new ShaderModule(VkCtx,VK_SHADER_STAGE_VERTEX_BIT,VERTEX_SHADER_FILE_SPV,null), - new ShaderModule(VkCtx,VK_SHADER_STAGE_FRAGMENT_BIT,ShaderPath,specConstants.GetSpecializationInfo()) - }; - + private static void Draw(VulkanContext VkCtx, MemoryStack stack, VkCommandBuffer command, Pipeline passPipeline, + Attachment target, VkRenderingInfo renderingInfo, String descriptorID, ByteBuffer constants){ + vkCmdBeginRendering(command, renderingInfo); + vkCmdBindPipeline(command, VK_PIPELINE_BIND_POINT_GRAPHICS, passPipeline.GetVulkanPipeline()); + Image image = target.GetVkImage(); + vkCmdSetViewport(command, 0, VkViewport.calloc(1, stack).x(0).y(image.GetHeight()) + .width(image.GetWidth()).height(-image.GetHeight()).minDepth(0.0f).maxDepth(1.0f)); + vkCmdSetScissor(command, 0, VkRect2D.calloc(1, stack) + .offset(offset -> offset.set(0, 0)).extent(extent -> extent.set(image.GetWidth(), image.GetHeight()))); + vkCmdBindDescriptorSets(command, VK_PIPELINE_BIND_POINT_GRAPHICS, passPipeline.GetVulkanPipelineLayout(), 0, + stack.longs(VkCtx.GetDescriptorAllocator().GetDescriptorSet(descriptorID).GetVkDescriptorSet()), null); + vkCmdPushConstants(command, passPipeline.GetVulkanPipelineLayout(), VK_SHADER_STAGE_FRAGMENT_BIT, 0, constants); + vkCmdDraw(command, 3, 1, 0, 0); + vkCmdEndRendering(command); } public void Render(VulkanContext VkCtx, CommandBuffer commandBuffer, Attachment SrcAttachment){ - SetBloomPropertiesBuffer(VkCtx, BloomPropertiesHorizontalBuffer, true, false); - SetBloomPropertiesBuffer(VkCtx, BloomPropertiesVerticalBuffer, false, false); - SetBloomPropertiesBuffer(VkCtx, BloomPropertiesFinalBuffer, false, true); try(var MemStack = MemoryStack.stackPush()){ - SwapChain swapChain = VkCtx.GetSwapChain(); VkCommandBuffer CommandHandle = commandBuffer.GetVulkanCommandBuffer(); + ByteBuffer constants = MemStack.malloc(PASS_CONSTANT_SIZE); + constants.putInt(0, 0); + constants.putFloat(4, Math.max(GAMMA, 0.001f)); + constants.putFloat(8, Math.max(EXPOSURE, 0.0f)); + constants.putFloat(12, Math.max(BLOOM_RADIUS, 0.0f)); + constants.putFloat(16, BloomPingAttachment.GetVkImage().GetWidth()); + constants.putFloat(20, BloomPingAttachment.GetVkImage().GetHeight()); - VulkanUtils.ImageBarrier(MemStack, CommandHandle, SrcAttachment.GetVkImage().getVulkanImage(), - VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT,VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,VK_ACCESS_2_SHADER_READ_BIT,VK_IMAGE_ASPECT_COLOR_BIT); - VulkanUtils.ImageBarrier(MemStack,CommandHandle,ColourAttachment.GetVkImage().getVulkanImage(), - VK_IMAGE_LAYOUT_UNDEFINED,VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT,VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_ACCESS_2_NONE,VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,VK_IMAGE_ASPECT_COLOR_BIT); - VulkanUtils.ImageBarrier(MemStack,CommandHandle,BloomPingAttachment.GetVkImage().getVulkanImage(), - VK_IMAGE_LAYOUT_UNDEFINED,VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT,VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_ACCESS_2_NONE,VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,VK_IMAGE_ASPECT_COLOR_BIT); - VulkanUtils.ImageBarrier(MemStack,CommandHandle,BloomPongAttachment.GetVkImage().getVulkanImage(), - VK_IMAGE_LAYOUT_UNDEFINED,VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT,VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_ACCESS_2_NONE,VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,VK_IMAGE_ASPECT_COLOR_BIT); - vkCmdBeginRendering(CommandHandle,RenderingInfo); - vkCmdBindPipeline(CommandHandle,VK_PIPELINE_BIND_POINT_GRAPHICS,pipeline.GetVulkanPipeline()); + EndWrite(MemStack, CommandHandle, SrcAttachment); + BeginWrite(MemStack, CommandHandle, BloomPingAttachment, attachmentsInitialized); + if (bloomPasses == 0) { + vkCmdBeginRendering(CommandHandle, BloomPingRenderingInfo); + vkCmdEndRendering(CommandHandle); + } else { + Draw(VkCtx, MemStack, CommandHandle, extractionPipeline, BloomPingAttachment, BloomPingRenderingInfo, DESCRIPTOR_ID_ATTACHMENT, constants); + } + EndWrite(MemStack, CommandHandle, BloomPingAttachment); - VkExtent2D SwapChainExtent = swapChain.GetSwapChainExtent(); - int Width = SwapChainExtent.width(); - int Height = SwapChainExtent.height(); - var Viewport = VkViewport.calloc(1,MemStack) - .x(0) - .y(Height) - .height(-Height) - .width(Width) - .minDepth(0.0f) - .maxDepth(1.0f); - vkCmdSetViewport(CommandHandle,0,Viewport); - - var Scissor = VkRect2D.calloc(1,MemStack) - .extent(it->it.width(Width).height(Height)) - .offset(it->it.x(0).y(0)); - vkCmdSetScissor(CommandHandle,0,Scissor); - - DescriptorAllocator descriptorAllocator = VkCtx.GetDescriptorAllocator(); - LongBuffer DescriptorSets = MemStack.mallocLong(2) - .put(0,descriptorAllocator.GetDescriptorSet(DESCRIPTOR_ID_ATTACHMENT).GetVkDescriptorSet()) - .put(1,descriptorAllocator.GetDescriptorSet(DESCRIPTOR_ID_SCREEN_SIZE).GetVkDescriptorSet()); - vkCmdBindDescriptorSets(CommandHandle,VK_PIPELINE_BIND_POINT_GRAPHICS,pipeline.GetVulkanPipelineLayout(),0,DescriptorSets,null); - vkCmdDraw(CommandHandle,3,1,0,0); - vkCmdEndRendering(CommandHandle); - - VulkanUtils.ImageBarrier(MemStack,CommandHandle,ColourAttachment.GetVkImage().getVulkanImage(), - VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT,VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,VK_ACCESS_2_SHADER_READ_BIT,VK_IMAGE_ASPECT_COLOR_BIT); - VulkanUtils.ImageBarrier(MemStack,CommandHandle,BloomPingAttachment.GetVkImage().getVulkanImage(), - VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT,VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,VK_ACCESS_2_SHADER_READ_BIT,VK_IMAGE_ASPECT_COLOR_BIT); - - int Passes = 10; - boolean bloomPongHasBeenWritten = false; - - for(int i = 0; i < Passes; i++){ - boolean writePong = i % 2 == 0; - - Attachment srcBloomAttachment = writePong ? BloomPingAttachment : BloomPongAttachment; + for(int i = 0; i < bloomPasses; i++){ + boolean writePong = (i & 1) == 0; Attachment dstBloomAttachment = writePong ? BloomPongAttachment : BloomPingAttachment; VkRenderingInfo dstRenderingInfo = writePong ? BloomPongRenderingInfo : BloomPingRenderingInfo; String srcDescriptorId = writePong ? DESCRIPTOR_ID_BLOOM_ATTACHMENT : DESCRIPTOR_ID_BLOOM_PONG_ATTACHMENT; - String bloomPropertiesDescriptorId = writePong ? DESCRIPTOR_ID_BLOOM_PROPERTIES_HORIZONTAL : DESCRIPTOR_ID_BLOOM_PROPERTIES_VERTICAL; - - int dstOldLayout = writePong && !bloomPongHasBeenWritten - ? VK_IMAGE_LAYOUT_UNDEFINED - : VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; - - VulkanUtils.ImageBarrier(MemStack,CommandHandle,dstBloomAttachment.GetVkImage().getVulkanImage(), - dstOldLayout,VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT,VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_ACCESS_2_SHADER_READ_BIT,VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - - vkCmdBeginRendering(CommandHandle,dstRenderingInfo); - vkCmdBindPipeline(CommandHandle,VK_PIPELINE_BIND_POINT_GRAPHICS,bloomPipeline.GetVulkanPipeline()); - - vkCmdSetViewport(CommandHandle,0,Viewport); - vkCmdSetScissor(CommandHandle,0,Scissor); - - LongBuffer BloomDescriptorSets = MemStack.mallocLong(2) - .put(0,descriptorAllocator.GetDescriptorSet(srcDescriptorId).GetVkDescriptorSet()) - .put(1,descriptorAllocator.GetDescriptorSet(bloomPropertiesDescriptorId).GetVkDescriptorSet()); - vkCmdBindDescriptorSets(CommandHandle,VK_PIPELINE_BIND_POINT_GRAPHICS,bloomPipeline.GetVulkanPipelineLayout(),0,BloomDescriptorSets,null); - - vkCmdDraw(CommandHandle,3,1,0,0); - vkCmdEndRendering(CommandHandle); - - VulkanUtils.ImageBarrier(MemStack,CommandHandle,dstBloomAttachment.GetVkImage().getVulkanImage(), - VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT,VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT, - VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,VK_ACCESS_2_SHADER_READ_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - - if(writePong){ - bloomPongHasBeenWritten = true; - } + BeginWrite(MemStack, CommandHandle, dstBloomAttachment, attachmentsInitialized || i > 0); + constants.putInt(0, writePong ? 1 : 0); + Draw(VkCtx, MemStack, CommandHandle, bloomPipeline, dstBloomAttachment, dstRenderingInfo, srcDescriptorId, constants); + EndWrite(MemStack, CommandHandle, dstBloomAttachment); } - VulkanUtils.ImageBarrier(MemStack,CommandHandle,FinalAttachment.GetVkImage().getVulkanImage(), - VK_IMAGE_LAYOUT_UNDEFINED,VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT,VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_ACCESS_2_NONE,VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - - vkCmdBeginRendering(CommandHandle,SingleRenderingInfo); - vkCmdBindPipeline(CommandHandle,VK_PIPELINE_BIND_POINT_GRAPHICS,bloomPipeline.GetVulkanPipeline()); - - vkCmdSetViewport(CommandHandle,0,Viewport); - - vkCmdSetScissor(CommandHandle,0,Scissor); - LongBuffer BloomDescriptorSets = MemStack.mallocLong(2) - .put(0,descriptorAllocator.GetDescriptorSet(DESCRIPTOR_ID_BLOOM_IMAGE_ATTACHMENT).GetVkDescriptorSet()) - .put(1,descriptorAllocator.GetDescriptorSet(DESCRIPTOR_ID_BLOOM_PROPERTIES_FINAL).GetVkDescriptorSet()); - vkCmdBindDescriptorSets(CommandHandle,VK_PIPELINE_BIND_POINT_GRAPHICS,bloomPipeline.GetVulkanPipelineLayout(),0,BloomDescriptorSets,null); - vkCmdDraw(CommandHandle,3,1,0,0); - - vkCmdEndRendering(CommandHandle); + // The UI/swapchain owns the final layout. Discard contents, not prior read/write dependencies. + VulkanUtils.ImageBarrier(MemStack, CommandHandle, FinalAttachment.GetVkImage().getVulkanImage(), + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, + VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, VK_IMAGE_ASPECT_COLOR_BIT); + Draw(VkCtx, MemStack, CommandHandle, pipeline, FinalAttachment, SingleRenderingInfo, DESCRIPTOR_ID_BLOOM_IMAGE_ATTACHMENT, constants); + attachmentsInitialized = true; } } + public void Resize(VulkanContext VkCtx, Attachment SrcAttachment){ - RenderingInfo.free();; - ColourAttachment.CleanUp(VkCtx); - ColourAttachmentInfo.free(); - ColourAttachment = CreateColourAttachment(VkCtx); - ColourAttachmentInfo = CreateColourAttachmentInfo(ColourAttachment,ClearValueColour); - RenderingInfo = CreateRenderInfo(ColourAttachment,ColourAttachmentInfo); - - CreateAttachmentDescriptorSet(VkCtx, AttachmentDescriptorSetLayout, SrcAttachment,textureSampler, DESCRIPTOR_ID_ATTACHMENT); - CreateAttachmentDescriptorSets(VkCtx, MultiAttachmentDescriptorSetLayout[0], new Attachment[]{BloomPingAttachment, BloomPingAttachment},textureSampler, DESCRIPTOR_ID_BLOOM_ATTACHMENT); - CreateAttachmentDescriptorSets(VkCtx, MultiAttachmentDescriptorSetLayout[0], new Attachment[]{BloomPongAttachment, BloomPongAttachment},textureSampler, DESCRIPTOR_ID_BLOOM_PONG_ATTACHMENT); - CreateAttachmentDescriptorSets(VkCtx, MultiAttachmentDescriptorSetLayout[0], new Attachment[]{ColourAttachment, BloomPingAttachment},textureSampler, DESCRIPTOR_ID_BLOOM_IMAGE_ATTACHMENT); - SetScreenSizeBuffer(VkCtx); - SetBloomPropertiesBuffer(VkCtx, BloomPropertiesHorizontalBuffer, true, false); - SetBloomPropertiesBuffer(VkCtx, BloomPropertiesVerticalBuffer, false, false); - SetBloomPropertiesBuffer(VkCtx, BloomPropertiesFinalBuffer, false, true); + CleanUpAttachments(VkCtx); + CreateAttachments(VkCtx); + UpdateDescriptors(VkCtx, SrcAttachment); } - private void SetScreenSizeBuffer(VulkanContext VkCtx){ - long MappedMemory = ScreenSizeBuffer.MapMemory(VkCtx); - FloatBuffer dataBuffer = MemoryUtil.memFloatBuffer(MappedMemory,(int)ScreenSizeBuffer.GetRequestedSize()); - VkExtent2D SwapChainExtent = VkCtx.GetSwapChain().GetSwapChainExtent(); - dataBuffer.put(0,SwapChainExtent.width()); - dataBuffer.put(1,SwapChainExtent.height()); - ScreenSizeBuffer.UnMapMemory(VkCtx); - } - - public static float GAMMA = 1.75f; - public static float EXPOSURE = 2.5f; - public static float BLOOM_RADIUS = 2.5f; - - private void SetBloomPropertiesBuffer(VulkanContext VkCtx, VulkanBuffer buffer, boolean horizontal, boolean Complete){ - long MappedMemory = buffer.MapMemory(VkCtx); - ByteBuffer dataBuffer = MemoryUtil.memByteBuffer(MappedMemory,(int)buffer.GetRequestedSize()); - int Offset = 0; - dataBuffer.putInt(Offset,Complete ? 1 : 0); - Offset += VulkanUtils.BOOLEAN_SIZE; - dataBuffer.putInt(Offset,horizontal ? 1 : 0); - Offset += VulkanUtils.BOOLEAN_SIZE; - dataBuffer.putFloat(Offset,GAMMA); - Offset += VulkanUtils.FLOAT_SIZE; - dataBuffer.putFloat(Offset,EXPOSURE); - Offset += VulkanUtils.FLOAT_SIZE; - dataBuffer.putFloat(Offset,BLOOM_RADIUS); - Offset += VulkanUtils.FLOAT_SIZE; - buffer.UnMapMemory(VkCtx); + private void CleanUpAttachments(VulkanContext VkCtx){ + BloomPingRenderingInfo.free(); + BloomPongRenderingInfo.free(); + SingleRenderingInfo.free(); + BloomPingAttachmentInfo.free(); + BloomPongAttachmentInfo.free(); + FinalOutputInfo.free(); + BloomPingAttachment.CleanUp(VkCtx); + BloomPongAttachment.CleanUp(VkCtx); + FinalAttachment.CleanUp(VkCtx); } public Attachment GetAttachment(){return FinalAttachment;} public void CleanUp(VulkanContext VkCtx){ - ClearValueColour.free(); - ColourAttachment.CleanUp(VkCtx); - BloomPingAttachment.CleanUp(VkCtx); - BloomPongAttachment.CleanUp(VkCtx); - FinalAttachment.CleanUp(VkCtx); + CleanUpAttachments(VkCtx); + pipeline.CleanUp(VkCtx); + bloomPipeline.CleanUp(VkCtx); + extractionPipeline.CleanUp(VkCtx); textureSampler.CleanUp(VkCtx); AttachmentDescriptorSetLayout.CleanUp(VkCtx); - FragmentUniformDescriptorSetLayout.CleanUp(VkCtx); - pipeline.CleanUp(VkCtx); - RenderingInfo.free(); - ColourAttachmentInfo.free(); - ScreenSizeBuffer.cleanup(VkCtx); - BloomPropertiesHorizontalBuffer.cleanup(VkCtx); - BloomPropertiesVerticalBuffer.cleanup(VkCtx); - BloomPropertiesFinalBuffer.cleanup(VkCtx); + MultiAttachmentDescriptorSetLayout.CleanUp(VkCtx); + ClearValueColour.free(); specConstants.CleanUp(); } } diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/ScreenSpace/AmbientOcclusionRenderer.java b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/ScreenSpace/AmbientOcclusionRenderer.java index 7baa556..cda2791 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/ScreenSpace/AmbientOcclusionRenderer.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/ScreenSpace/AmbientOcclusionRenderer.java @@ -65,9 +65,9 @@ public class AmbientOcclusionRenderer { private VkRenderingAttachmentInfo.Buffer ssaoAttachmentInfo; private Attachment ssaoBlurAttachment; private VkRenderingAttachmentInfo.Buffer ssaoBlurAttachmentInfo; - private Attachment ssaoViewPosAttachment; - private VkRenderingAttachmentInfo.Buffer ssaoViewPosAttachmentInfo; - private final Attachment[] SSAO_ATTACHMENTS = new Attachment[3]; + private Attachment viewPositionAttachment; + private boolean attachmentsInitialized; + private int sampleCount; private final Pipeline ssaoPipeline; private final Pipeline blurPipeline; @@ -89,17 +89,17 @@ public class AmbientOcclusionRenderer { private Attachment NoiseImageAttachment; public AmbientOcclusionRenderer(VulkanContext VkCtx, List MRTAttachments, Queue queue) { + viewPositionAttachment = MRTAttachments.get(7); List attachments = new ArrayList<>(); - attachments.add(MRTAttachments.get(7)); // position + attachments.add(viewPositionAttachment); attachments.add(MRTAttachments.get(2)); // normal CreateAttachments(VkCtx); clearColour = VkClearValue.calloc().color( - c -> c.float32(0, 0.0f).float32(1, 0.0f).float32(2, 0.0f).float32(3, 0.0f)); + c -> c.float32(0, 1.0f).float32(1, 0.0f).float32(2, 0.0f).float32(3, 0.0f)); ssaoAttachmentInfo = CreateColourAttachmentInfo(ssaoAttachment, clearColour); ssaoBlurAttachmentInfo = CreateColourAttachmentInfo(ssaoBlurAttachment, clearColour); - ssaoViewPosAttachmentInfo = CreateColourAttachmentInfo(ssaoViewPosAttachment, clearColour); var textureSamplerInfo = new TextureSamplerInfo(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, - VK_BORDER_COLOR_INT_OPAQUE_BLACK, 1, true); + VK_BORDER_COLOR_INT_OPAQUE_BLACK, 1, false); textureSampler = new TextureSampler(VkCtx, textureSamplerInfo); ByteBuffer NoiseTexturedata = SSAO_Utils.GenerateNoiseTexture(); @@ -173,9 +173,9 @@ public class AmbientOcclusionRenderer { var vtxBuffStruct = new EmptyVertexBufferStruct(); int[] formats; if(blur){ - formats = new int[]{SSAO_BLUR_ATTACHMENT, VIEW_POS_FORMAT}; + formats = new int[]{SSAO_BLUR_ATTACHMENT}; } else { - formats = new int[]{SSAO_RAW_ATTACHMENT, VIEW_POS_FORMAT}; + formats = new int[]{SSAO_RAW_ATTACHMENT}; } var buildInfo = new PipelineBuildInfo(shaderModules, vtxBuffStruct.GetVertexInput(),formats) .SetDescriptorSetLayouts(descSetLayouts) @@ -235,8 +235,11 @@ public class AmbientOcclusionRenderer { } private void CreateSSAOAttachmentDescriptorSet(VulkanContext vkCtx, List Attachments ) { - DescriptorSet descSet = vkCtx.GetDescriptorAllocator().AddDescriptorSet( - vkCtx.GetDevice(), DESC_ID_SSAO_ATTACHMENTS,ssaoAttachmentDescriptorLayout ); + DescriptorSet descSet = vkCtx.GetDescriptorAllocator().GetDescriptorSet(DESC_ID_SSAO_ATTACHMENTS); + if (descSet == null) { + descSet = vkCtx.GetDescriptorAllocator().AddDescriptorSet( + vkCtx.GetDevice(), DESC_ID_SSAO_ATTACHMENTS, ssaoAttachmentDescriptorLayout); + } List imageViews = new ArrayList<>(); Attachments.forEach(a -> imageViews.add(a.GetVkImageView())); @@ -246,12 +249,15 @@ public class AmbientOcclusionRenderer { private void CreateBlurDescriptorSet(VulkanContext vkCtx) { - DescriptorSet descSet = vkCtx.GetDescriptorAllocator().AddDescriptorSet( - vkCtx.GetDevice(),DESC_ID_SSAO_BLUR_INPUT, blurDescriptorLayout); + DescriptorSet descSet = vkCtx.GetDescriptorAllocator().GetDescriptorSet(DESC_ID_SSAO_BLUR_INPUT); + if (descSet == null) { + descSet = vkCtx.GetDescriptorAllocator().AddDescriptorSet( + vkCtx.GetDevice(), DESC_ID_SSAO_BLUR_INPUT, blurDescriptorLayout); + } List imageViews = new ArrayList<>(); imageViews.add(ssaoAttachment.GetVkImageView()); - imageViews.add(ssaoViewPosAttachment.GetVkImageView()); + imageViews.add(viewPositionAttachment.GetVkImageView()); descSet.SetImages(vkCtx.GetDevice(), imageViews,textureSampler,0); } @@ -259,21 +265,20 @@ public class AmbientOcclusionRenderer { private void CreateAttachments(VulkanContext VkCtx){ SwapChain swapChain = VkCtx.GetSwapChain(); VkExtent2D swapChainExtent = swapChain.GetSwapChainExtent(); - - float AspectRatio = (float)swapChainExtent.width()/(float)swapChainExtent.height(); - int Width = swapChainExtent.width(); - int Height = swapChainExtent.height(); - int RawWidth = (int)(Math.round(Math.min(swapChainExtent.width()/2.0f, 1920 * AspectRatio))); - int RawHeight = (int)(Math.round(Math.min(swapChainExtent.height()/2.0f, 1080))); + var config = EngineConfig.getInstance(); + float scale = config.GetSSAOScale(); + int Width = ScaledExtent(swapChainExtent.width(), scale); + int Height = ScaledExtent(swapChainExtent.height(), scale); + sampleCount = config.GetSSAOSamples(); + attachmentsInitialized = false; //SSAO Raw ssaoAttachment = new Attachment(VkCtx, Width, Height, SSAO_RAW_ATTACHMENT, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, 1); - SSAO_ATTACHMENTS[0] = ssaoAttachment; //SSAO Blur ssaoBlurAttachment = new Attachment(VkCtx, Width, Height, SSAO_BLUR_ATTACHMENT, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, 1); - SSAO_ATTACHMENTS[1] = ssaoBlurAttachment; + } - ssaoViewPosAttachment = new Attachment(VkCtx, Width, Height, VIEW_POS_FORMAT, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, 1); - SSAO_ATTACHMENTS[1] = ssaoViewPosAttachment; + static int ScaledExtent(int size, float scale) { + return Math.max(1, Math.round(size * scale)); } public void Render(VulkanContext VkCtx,EngineInstance engineInstance, CommandBuffer cmdBuffer, MultiRenderTargetAttachments MRT,int CurrentFrame) { @@ -285,20 +290,11 @@ public class AmbientOcclusionRenderer { UpdateSSAOInfo(VkCtx, engineInstance, CurrentFrame); VulkanUtils.ImageBarrier(stack, cmdHandle, ssaoAttachment.GetVkImage().getVulkanImage(), - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + attachmentsInitialized ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT, + attachmentsInitialized ? VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT : VK_PIPELINE_STAGE_2_NONE, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_ACCESS_2_SHADER_READ_BIT, - VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, - VK_IMAGE_ASPECT_COLOR_BIT); - - VulkanUtils.ImageBarrier(stack, cmdHandle, ssaoViewPosAttachment.GetVkImage().getVulkanImage(), - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, - VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT, - VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_ACCESS_2_SHADER_READ_BIT, + attachmentsInitialized ? VK_ACCESS_2_SHADER_READ_BIT : VK_ACCESS_2_NONE, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, VK_IMAGE_ASPECT_COLOR_BIT); @@ -335,11 +331,11 @@ public class AmbientOcclusionRenderer { VK_IMAGE_ASPECT_COLOR_BIT); VulkanUtils.ImageBarrier(stack, cmdHandle, ssaoBlurAttachment.GetVkImage().getVulkanImage(), - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + attachmentsInitialized ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT, + attachmentsInitialized ? VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT : VK_PIPELINE_STAGE_2_NONE, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_ACCESS_2_SHADER_READ_BIT, + attachmentsInitialized ? VK_ACCESS_2_SHADER_READ_BIT : VK_ACCESS_2_NONE, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, VK_IMAGE_ASPECT_COLOR_BIT); @@ -367,6 +363,7 @@ public class AmbientOcclusionRenderer { VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_2_SHADER_READ_BIT, VK_IMAGE_ASPECT_COLOR_BIT); + attachmentsInitialized = true; } } @@ -405,6 +402,8 @@ public class AmbientOcclusionRenderer { vkCmdSetScissor(cmdHandle, 0, scissor); } + // Retained for the legacy debug overlay; attachment quality comes from EngineConfig on resize. + @Deprecated public static float SSAO_RESOLUTION_SCALE = 0.5f; private void UpdateSSAOInfo(VulkanContext vkCtx, EngineInstance engineInstance, int currentFrame) { @@ -425,10 +424,7 @@ public class AmbientOcclusionRenderer { data.putFloat(offset + VulkanUtils.FLOAT_SIZE * 2, SSAO_RADIUS); data.putFloat(offset + VulkanUtils.FLOAT_SIZE * 3, SSAO_BIAS); offset += VulkanUtils.VEC4_SIZE; - data.putInt(offset, SSAO_KERNEL_SIZE); - offset +=VulkanUtils.INT_SIZE; - data.putFloat(offset, SSAO_RESOLUTION_SCALE); - offset += VulkanUtils.FLOAT_SIZE; + data.putInt(offset, sampleCount); buffer.UnMapMemory(vkCtx); } @@ -444,6 +440,7 @@ public class AmbientOcclusionRenderer { ssaoBlurAttachmentInfo.free(); ssaoBlurAttachment.CleanUp(VkCtx); + viewPositionAttachment = attachments.get(0); CreateAttachments(VkCtx); ssaoAttachmentInfo = CreateColourAttachmentInfo(ssaoAttachment, clearColour); @@ -451,9 +448,7 @@ public class AmbientOcclusionRenderer { ssaoRenderInfo = CreateRenderInfo(ssaoAttachment, ssaoAttachmentInfo,true); ssaoBlurRenderInfo = CreateRenderInfo(ssaoBlurAttachment, ssaoBlurAttachmentInfo,false); - attachments.add(NoiseImageAttachment); - - CreateSSAOAttachmentDescriptorSet(VkCtx,attachments); + CreateSSAOAttachmentDescriptorSet(VkCtx, List.of(attachments.get(0), attachments.get(1), NoiseImageAttachment)); CreateBlurDescriptorSet(VkCtx); } diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/ScreenSpace/ReflectionsRenderer.java b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/ScreenSpace/ReflectionsRenderer.java index d4f4d7b..692e0d8 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/ScreenSpace/ReflectionsRenderer.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/ScreenSpace/ReflectionsRenderer.java @@ -15,7 +15,6 @@ import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Structure.DisplayToS import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Structure.SwapChain.SwapChain; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanBuffer; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanUtils; -import org.joml.Matrix4f; import org.lwjgl.system.MemoryStack; import org.lwjgl.system.MemoryUtil; import org.lwjgl.util.shaderc.Shaderc; @@ -35,16 +34,15 @@ public class ReflectionsRenderer { private static final long SSR_INFO_BUFFER_SIZE = VulkanUtils.MATRIX4X4_SIZE + // projection - VulkanUtils.MATRIX4X4_SIZE + // projection inv VulkanUtils.MATRIX4X4_SIZE + // view - VulkanUtils.MATRIX4X4_SIZE + // view inv VulkanUtils.VEC4_SIZE + // steps, distance, max steps VulkanUtils.VEC4_SIZE; //Camera public static float STEP_SIZE = 0.25f; public static float MAX_DIST = 200.0f; - public static int MAX_STEPS = 64; private static final String DESC_ID_SSR_ATTACHMENTS = "SSR_DESC_ID_ATTACHMENTS"; private static final String DESC_ID_SSR_INFO = "SSR_DESC_ID_INFO"; + private static final String DESC_ID_SSR_COMPOSITE = "SSR_DESC_ID_COMPOSITE"; + private static final String COMPOSITE_FRAGMENT_SHADER_FILE_GLSL = "resources/EngineResources/shaders/ssr_composite_frag.glsl"; private static final String FRAGMENT_SHADER_FILE_GLSL = "resources/EngineResources/shaders/screen_space_reflection_frag.glsl"; private static final String FRAGMENT_SHADER_FILE_SPV = FRAGMENT_SHADER_FILE_GLSL + ".spv"; @@ -53,8 +51,14 @@ public class ReflectionsRenderer { private Attachment ssrAttachment; private Attachment renderedSceneAttachment; private VkRenderingAttachmentInfo.Buffer ssrAttachmentInfo; + private Attachment compositeAttachment; + private VkRenderingAttachmentInfo.Buffer compositeAttachmentInfo; + private VkRenderingInfo compositeRenderInfo; + private boolean attachmentsInitialized; + private int maxSteps; private final Pipeline ssrPipeline; + private final Pipeline compositePipeline; private final TextureSampler textureSampler; private DescriptorSetLayout ssrAttachmentDescriptorLayout; @@ -78,8 +82,9 @@ public class ReflectionsRenderer { clearColour = VkClearValue.calloc().color( c -> c.float32(0, 0.0f).float32(1, 0.0f).float32(2, 0.0f).float32(3, 0.0f)); ssrAttachmentInfo = CreateColourAttachmentInfo(ssrAttachment, clearColour); + compositeAttachmentInfo = CreateColourAttachmentInfo(compositeAttachment, clearColour); var textureSamplerInfo = new TextureSamplerInfo(VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE, - VK_BORDER_COLOR_INT_OPAQUE_BLACK, 1, true); + VK_BORDER_COLOR_INT_OPAQUE_BLACK, 1, false); textureSampler = new TextureSampler(VkCtx, textureSamplerInfo); int numAttachments = attachments.size(); @@ -97,6 +102,7 @@ public class ReflectionsRenderer { VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, DESC_ID_SSR_INFO,ssrInfoDescriptorLayout); ssrRenderInfo = CreateRenderInfo(ssrAttachment, ssrAttachmentInfo,true); + compositeRenderInfo = CreateRenderInfo(compositeAttachment, compositeAttachmentInfo, false); ShaderModule[] shaderModules = CreateRawShaderModules(VkCtx); ssrPipeline = CreatePipeline(VkCtx, shaderModules, @@ -105,6 +111,15 @@ public class ReflectionsRenderer { ssrInfoDescriptorLayout }); Arrays.stream(shaderModules).toList().forEach(shaderModule -> shaderModule.CleanUp(VkCtx)); + if (EngineConfig.getInstance().RecompileShaders()) { + ShaderCompiler.CompileGLSLShaderOnChange(COMPOSITE_FRAGMENT_SHADER_FILE_GLSL, Shaderc.shaderc_glsl_fragment_shader); + } + shaderModules = new ShaderModule[]{ + new ShaderModule(VkCtx, VK_SHADER_STAGE_VERTEX_BIT, VERTEX_SHADER_FILE_SPV, null), + new ShaderModule(VkCtx, VK_SHADER_STAGE_FRAGMENT_BIT, COMPOSITE_FRAGMENT_SHADER_FILE_GLSL + ".spv", null) + }; + compositePipeline = CreatePipeline(VkCtx, shaderModules, new DescriptorSetLayout[]{ssrAttachmentDescriptorLayout}); + Arrays.stream(shaderModules).toList().forEach(shaderModule -> shaderModule.CleanUp(VkCtx)); } @@ -162,13 +177,23 @@ public class ReflectionsRenderer { } private void CreateSSRAttachmentDescriptorSet(VulkanContext vkCtx, List Attachments ) { - DescriptorSet descSet = vkCtx.GetDescriptorAllocator().AddDescriptorSet( - vkCtx.GetDevice(), DESC_ID_SSR_ATTACHMENTS, ssrAttachmentDescriptorLayout); + DescriptorSet descSet = vkCtx.GetDescriptorAllocator().GetDescriptorSet(DESC_ID_SSR_ATTACHMENTS); + if (descSet == null) { + descSet = vkCtx.GetDescriptorAllocator().AddDescriptorSet( + vkCtx.GetDevice(), DESC_ID_SSR_ATTACHMENTS, ssrAttachmentDescriptorLayout); + } List imageViews = new ArrayList<>(); Attachments.forEach(a -> imageViews.add(a.GetVkImageView())); descSet.SetImages(vkCtx.GetDevice(), imageViews, textureSampler, 0); + DescriptorSet compositeSet = vkCtx.GetDescriptorAllocator().GetDescriptorSet(DESC_ID_SSR_COMPOSITE); + if (compositeSet == null) { + compositeSet = vkCtx.GetDescriptorAllocator().AddDescriptorSet( + vkCtx.GetDevice(), DESC_ID_SSR_COMPOSITE, ssrAttachmentDescriptorLayout); + } + compositeSet.SetImages(vkCtx.GetDevice(), List.of(renderedSceneAttachment.GetVkImageView(), + ssrAttachment.GetVkImageView(), Attachments.get(3).GetVkImageView(), Attachments.get(2).GetVkImageView()), textureSampler, 0); } @@ -178,13 +203,21 @@ public class ReflectionsRenderer { SwapChain swapChain = VkCtx.GetSwapChain(); VkExtent2D swapChainExtent = swapChain.GetSwapChainExtent(); - float AspectRatio = (float)swapChainExtent.width()/(float)swapChainExtent.height(); int Width = swapChainExtent.width(); int Height = swapChainExtent.height(); - int RawWidth = (int)(Math.round(Math.min(swapChainExtent.width()/2.0f, 1920 * AspectRatio))); - int RawHeight = (int)(Math.round(Math.min(swapChainExtent.height()/2.0f, 1080))); + var config = EngineConfig.getInstance(); + float scale = config.GetSSRScale(); + int RawWidth = ScaledExtent(Width, scale); + int RawHeight = ScaledExtent(Height, scale); + maxSteps = config.GetSSRMaxSteps(); + attachmentsInitialized = false; //SSR Raw - ssrAttachment = new Attachment(VkCtx, Width, Height, SSR_FORMAT, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, 1); + ssrAttachment = new Attachment(VkCtx, RawWidth, RawHeight, SSR_FORMAT, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, 1); + compositeAttachment = new Attachment(VkCtx, Width, Height, SSR_FORMAT, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, 1); + } + + static int ScaledExtent(int size, float scale) { + return Math.max(1, Math.round(size * scale)); } public void Render(VulkanContext VkCtx,EngineInstance engineInstance, CommandBuffer cmdBuffer, int CurrentFrame) { @@ -201,9 +234,11 @@ public class ReflectionsRenderer { VK_IMAGE_ASPECT_COLOR_BIT); VulkanUtils.ImageBarrier(stack, cmdHandle, ssrAttachment.GetVkImage().getVulkanImage(), - VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, - VK_PIPELINE_STAGE_2_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, - VK_ACCESS_2_NONE, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, + attachmentsInitialized ? VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL : VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + attachmentsInitialized ? VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT : VK_PIPELINE_STAGE_2_NONE, + VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, + attachmentsInitialized ? VK_ACCESS_2_SHADER_READ_BIT : VK_ACCESS_2_NONE, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, VK_IMAGE_ASPECT_COLOR_BIT); vkCmdBeginRendering(cmdHandle, ssrRenderInfo); @@ -223,6 +258,27 @@ public class ReflectionsRenderer { vkCmdDraw(cmdHandle, 3, 1, 0, 0); vkCmdEndRendering(cmdHandle); + + VulkanUtils.ImageBarrier(stack, cmdHandle, ssrAttachment.GetVkImage().getVulkanImage(), + VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, + VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT, + VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_2_SHADER_READ_BIT, VK_IMAGE_ASPECT_COLOR_BIT); + // Discard old contents, but wait for the previous frame's post-process reads. + VulkanUtils.ImageBarrier(stack, cmdHandle, compositeAttachment.GetVkImage().getVulkanImage(), + VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, + VK_PIPELINE_STAGE_2_FRAGMENT_SHADER_BIT | VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT, + VK_ACCESS_2_SHADER_READ_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, + VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, VK_IMAGE_ASPECT_COLOR_BIT); + vkCmdBeginRendering(cmdHandle, compositeRenderInfo); + vkCmdBindPipeline(cmdHandle, VK_PIPELINE_BIND_POINT_GRAPHICS, compositePipeline.GetVulkanPipeline()); + SetViewportAndScissor(stack, cmdHandle, compositeAttachment); + vkCmdBindDescriptorSets(cmdHandle, VK_PIPELINE_BIND_POINT_GRAPHICS, + compositePipeline.GetVulkanPipelineLayout(), 0, + stack.longs(descAllocator.GetDescriptorSet(DESC_ID_SSR_COMPOSITE).GetVkDescriptorSet()), null); + vkCmdDraw(cmdHandle, 3, 1, 0, 0); + vkCmdEndRendering(cmdHandle); + attachmentsInitialized = true; } } @@ -247,7 +303,7 @@ public class ReflectionsRenderer { public Attachment GetSSRAttachment() { - return ssrAttachment; + return compositeAttachment; } private void UpdateSSRInfo(VulkanContext vkCtx, EngineInstance engineInstance, int currentFrame) { @@ -259,16 +315,12 @@ public class ReflectionsRenderer { engineInstance.scene().GetProjection().GetProjectionMatrix().get(offset, data); offset += VulkanUtils.MATRIX4X4_SIZE; - engineInstance.scene().GetProjection().GetProjectionMatrix().invert(new Matrix4f()).get(offset, data); - offset += VulkanUtils.MATRIX4X4_SIZE; engineInstance.scene().GetCamera().GetViewMatrix().get(offset, data); offset += VulkanUtils.MATRIX4X4_SIZE; - engineInstance.scene().GetCamera().GetViewMatrix().invert(new Matrix4f()).get(offset, data); - offset += VulkanUtils.MATRIX4X4_SIZE; data.putFloat(offset, STEP_SIZE); data.putFloat(offset + VulkanUtils.FLOAT_SIZE, MAX_DIST); - data.putInt(offset + VulkanUtils.FLOAT_SIZE * 2, MAX_STEPS); + data.putInt(offset + VulkanUtils.FLOAT_SIZE * 2, maxSteps); data.putInt(offset + VulkanUtils.FLOAT_SIZE * 3, 0); offset += VulkanUtils.VEC4_SIZE; @@ -283,6 +335,9 @@ public class ReflectionsRenderer { ssrRenderInfo.free(); ssrAttachmentInfo.free(); ssrAttachment.CleanUp(VkCtx); + compositeRenderInfo.free(); + compositeAttachmentInfo.free(); + compositeAttachment.CleanUp(VkCtx); renderedSceneAttachment = attachments.get(0); @@ -290,6 +345,8 @@ public class ReflectionsRenderer { ssrAttachmentInfo = CreateColourAttachmentInfo(ssrAttachment, clearColour); ssrRenderInfo = CreateRenderInfo(ssrAttachment, ssrAttachmentInfo,true); + compositeAttachmentInfo = CreateColourAttachmentInfo(compositeAttachment, clearColour); + compositeRenderInfo = CreateRenderInfo(compositeAttachment, compositeAttachmentInfo, false); CreateSSRAttachmentDescriptorSet(VkCtx, attachments); } @@ -298,6 +355,10 @@ public class ReflectionsRenderer { ssrAttachmentInfo.free(); ssrAttachment.CleanUp(VkCtx); ssrPipeline.CleanUp(VkCtx); + compositeRenderInfo.free(); + compositeAttachmentInfo.free(); + compositeAttachment.CleanUp(VkCtx); + compositePipeline.CleanUp(VkCtx); clearColour.free(); Arrays.stream(ssrInfoBuffers).toList().forEach(b -> b.cleanup(VkCtx)); ssrAttachmentDescriptorLayout.CleanUp(VkCtx); diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/Shadows/ShadowRenderer.java b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/Shadows/ShadowRenderer.java index 880866a..9095758 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/Shadows/ShadowRenderer.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/Shadows/ShadowRenderer.java @@ -125,6 +125,12 @@ public class ShadowRenderer { newPipeline = createPipeline(VulkanContext, shaderModules, new DescriptorSetLayout[]{uniformGeomDescriptorSetLayout, textDescriptorSetLayout, descLayoutFrgStorage}); Arrays.asList(shaderModules).forEach(s -> s.CleanUp(VulkanContext)); + shaderModules = createPackedVertexShaderModules(VulkanContext); + Pipeline newPipelinePacked = CreateVoxelPipeline(VulkanContext, shaderModules, new DescriptorSetLayout[]{ + uniformGeomDescriptorSetLayout, textDescriptorSetLayout, descLayoutFrgStorage, + VoxelWorldManager.Resources().faceGraphicsLayout() + }); + Arrays.asList(shaderModules).forEach(s -> s.CleanUp(VulkanContext)); VkRenderingInfo OldRenderInfo = renderingInfo; Attachment OldColourAttachment = colorAttachment; @@ -132,6 +138,7 @@ public class ShadowRenderer { VkRenderingAttachmentInfo OldDepthAttachmentInfo = depthAttachmentInfo; VkRenderingAttachmentInfo.Buffer OldColourAttachmentInfo = colorAttachmentInfo; Pipeline OldPipeline = pipeline; + Pipeline oldPipelinePacked = pipelinePacked; renderingInfo = NewRenderInfo; colorAttachment = newColourAttachment; @@ -139,6 +146,7 @@ public class ShadowRenderer { depthAttachmentInfo = newDepthAttachmentInfo; colorAttachmentInfo = newColourAttachmentInfo; pipeline = newPipeline; + pipelinePacked = newPipelinePacked; OldRenderInfo.free(); OldDepthAttachmentInfo.free(); @@ -146,13 +154,14 @@ public class ShadowRenderer { OldColourAttachmentInfo.free(); OldColourAttachment.CleanUp(VulkanContext); OldPipeline.CleanUp(VulkanContext); + oldPipelinePacked.CleanUp(VulkanContext); } public ShadowRenderer(VulkanContext VulkanContext) { ClearValueColour = VkClearValue.calloc().color( - c -> c.float32(0, 0.0f).float32(1, 0.0f).float32(2, 0.0f).float32(3, 0.0f)); - ClearValueDepth = VkClearValue.calloc().color(c -> c.float32(0, 1.0f)); + c -> c.float32(0, 1.0f).float32(1, 1.0f).float32(2, 0.0f).float32(3, 0.0f)); + ClearValueDepth = VkClearValue.calloc().depthStencil(d -> d.depth(1.0f).stencil(0)); depthAttachment = createDepthAttachment(VulkanContext); depthAttachmentInfo = createDepthAttachmentInfo(depthAttachment, ClearValueDepth); @@ -241,7 +250,7 @@ public class ShadowRenderer { }) .SetDescriptorSetLayouts(DescriptorSetLayouts) .SetDescriptorSetLayouts(DescriptorSetLayouts) - .SetDepthClamp(VulkanContext.GetDevice().getDepthClamp()) + .SetDepthClamp(false) .CullMode(VK_CULL_MODE_FRONT_BIT) .DepthBuffer(VK_COMPARE_OP_LESS_OR_EQUAL); var pipeline = new DefaultPipeline(VulkanContext, buildInfo); @@ -267,7 +276,7 @@ public class ShadowRenderer { new PushConstantsRange(VK_SHADER_STAGE_VERTEX_BIT, 0, PUSH_CONSTANTS_SIZE) }) .SetDescriptorSetLayouts(DescriptorSetLayouts) - .SetDepthClamp(VkCtx.GetDevice().getDepthClamp()) + .SetDepthClamp(false) .CullMode(VK_CULL_MODE_FRONT_BIT) .DepthBuffer(VK_COMPARE_OP_LESS_OR_EQUAL); @@ -366,7 +375,7 @@ public class ShadowRenderer { try (var stack = MemoryStack.stackPush()) { IScene scene = engCtx.scene(); - ShadowUtils.updateCascadeShadows(cascadeShadows[currentFrame], scene); + boolean validCascades = ShadowUtils.updateCascadeShadows(cascadeShadows[currentFrame], scene); VkCommandBuffer cmdHandle = cmdBuffer.GetVulkanCommandBuffer(); @@ -384,6 +393,10 @@ public class ShadowRenderer { VK_IMAGE_ASPECT_DEPTH_BIT); vkCmdBeginRendering(cmdHandle, renderingInfo); + if (!validCascades) { + vkCmdEndRendering(cmdHandle); + return; + } vkCmdBindPipeline(cmdHandle, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline.GetVulkanPipeline()); @@ -451,7 +464,12 @@ public class ShadowRenderer { .put(3, descAllocator.GetDescriptorSet(VoxelWorldManager.Resources().faceGraphicsDescriptorId()).GetVkDescriptorSet()); vkCmdBindPipeline(cmdHandle, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelinePacked.GetVulkanPipeline()); vkCmdBindDescriptorSets(cmdHandle, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelinePacked.GetVulkanPipelineLayout(),0,descriptorSets,null); - VoxelWorldManager.RecordRender(cmdHandle,pipelinePacked,materialsCache,true); + List cascadeDataList = cascadeShadows[currentFrame].GetCascadeData(); + Matrix4f[] clipMatrices = new Matrix4f[cascadeDataList.size()]; + for (int i = 0; i < clipMatrices.length; i++) { + clipMatrices[i] = cascadeDataList.get(i).GetProjViewMatrix(); + } + VoxelWorldManager.RecordRender(cmdHandle, pipelinePacked, materialsCache, true, clipMatrices); vkCmdEndRendering(cmdHandle); } } diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/Shadows/ShadowUtils.java b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/Shadows/ShadowUtils.java index 0247eeb..334303c 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/Shadows/ShadowUtils.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/RenderPasses/Shadows/ShadowUtils.java @@ -4,11 +4,9 @@ import net.halbear.Terrain4J.EngineCore.Logic.EngineConfig; import net.halbear.Terrain4J.EngineCore.Main.Scene.Camera; import net.halbear.Terrain4J.EngineCore.Main.Scene.ILight; import net.halbear.Terrain4J.EngineCore.Main.Scene.IScene; -import net.halbear.Terrain4J.EngineCore.Main.Scene.SceneLightingManager; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Universal.Projection.Project3D; import org.joml.Matrix4f; import org.joml.Vector3f; -import org.joml.Vector4f; import java.util.List; @@ -21,11 +19,10 @@ public class ShadowUtils { private ShadowUtils() { } - public static void updateCascadeShadows(CascadeShadows cascadeShadows, IScene scene) { + public static boolean updateCascadeShadows(CascadeShadows cascadeShadows, IScene scene) { Camera camera = scene.GetCamera(); Matrix4f viewMatrix = camera.GetViewMatrix(); - Project3D projection = scene.GetProjection().GetNormalFarPlanes(); - Matrix4f projMatrix = projection.GetProjectionMatrix(); + Project3D projection = scene.GetProjection(); ILight[] lights = scene.GetLightingManager().GetLights(); int numLights = lights.length; ILight dirLight = null; @@ -36,114 +33,112 @@ public class ShadowUtils { } } if (dirLight == null) { - throw new RuntimeException("Could not find directional light"); + return disableCascades(cascadeShadows.GetCascadeData()); } Vector3f lightPos = dirLight.GetPosition(); - float[] cascadeSplits = new float[SceneLightingManager.SHADOW_MAP_CASCADE_COUNT]; - - float nearClip = projection.GetNearZ();//projection.GetNearZ(); - float farClip = EngineConfig.getInstance().MaxShadowDistance();//Math.min(projection.GetFarZ(), EngineConfig.getInstance().MaxShadowDistance()); - float clipRange = farClip - nearClip; - - float minZ = nearClip; - float maxZ = nearClip + clipRange; - - float range = maxZ - minZ; - float ratio = maxZ / minZ; + return updateCascadeShadows(cascadeShadows, viewMatrix, projection, lightPos, + EngineConfig.getInstance().MaxShadowDistance(), EngineConfig.getInstance().GetShadowMapSize()); + } + static boolean updateCascadeShadows(CascadeShadows cascadeShadows, Matrix4f viewMatrix, Project3D projection, + Vector3f lightPos, float maxShadowDistance, int shadowMapSize) { List cascadeDataList = cascadeShadows.GetCascadeData(); int numCascades = cascadeDataList.size(); - - // Calculate split depths based on view camera frustum - // Based on method presented in https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch10.html - for (int i = 0; i < numCascades; i++) { - float p = (i + 1) / (float) (SceneLightingManager.SHADOW_MAP_CASCADE_COUNT); - float log = (float) (minZ * java.lang.Math.pow(ratio, p)); - float uniform = minZ + range * p; - float d = LAMBDA * (log - uniform) + uniform; - cascadeSplits[i] = (d - nearClip) / clipRange; + float nearClip = Math.min(projection.GetNearZ(), projection.GetFarZ()); + float farClip = Math.min(Math.max(projection.GetNearZ(), projection.GetFarZ()), maxShadowDistance); + Matrix4f projMatrix = projection.GetProjectionMatrix(); + if (numCascades == 0 || !Float.isFinite(maxShadowDistance) || !Float.isFinite(nearClip) + || !Float.isFinite(farClip) || nearClip <= 0.0f || farClip <= nearClip || shadowMapSize <= 4 + || !viewMatrix.isFinite() || !projMatrix.isFinite() || !lightPos.isFinite()) { + return disableCascades(cascadeDataList); } - float lastSplitDist = 0.0f; + double lightLength = Math.sqrt((double) lightPos.x * lightPos.x + (double) lightPos.y * lightPos.y + + (double) lightPos.z * lightPos.z); + if (lightLength == 0.0) { + return disableCascades(cascadeDataList); + } + Vector3f lightDir = new Vector3f((float) (lightPos.x / lightLength), (float) (lightPos.y / lightLength), + (float) (lightPos.z / lightLength)); + Vector3f up = Math.abs(lightDir.dot(UP)) > 0.99f ? UP_ALT : UP; + Matrix4f lightViewMatrix = new Matrix4f().lookAt(new Vector3f(), lightDir, up); + Matrix4f invCam = new Matrix4f(viewMatrix).invert(); + Matrix4f viewToLight = new Matrix4f(lightViewMatrix).mul(invCam); + Matrix4f inverseProjection = new Matrix4f(projMatrix).invert(); + + // Unproject inside Vulkan's 0..1 depth range, not at a possibly infinite far plane. + // Rays scaled to view-space depth work with both normal and reversed camera projections. + Vector3f[] rays = new Vector3f[4]; + for (int j = 0; j < rays.length; j++) { + Vector3f ray = inverseProjection.transformProject(new Vector3f((j & 1) == 0 ? -1 : 1, + (j & 2) == 0 ? -1 : 1, 0.5f)); + if (!ray.isFinite() || ray.z >= 0.0f) { + return disableCascades(cascadeDataList); + } + rays[j] = ray.div(-ray.z); + } + + float lastSplitDist = nearClip; for (int i = 0; i < numCascades; i++) { - float splitDist = cascadeSplits[i]; + double p = (i + 1.0) / numCascades; + double log = nearClip * Math.pow((double) farClip / nearClip, p); + double uniform = nearClip + ((double) farClip - nearClip) * p; + float splitDist = i == numCascades - 1 ? farClip : (float) (LAMBDA * (log - uniform) + uniform); + if (splitDist <= lastSplitDist) { + return disableCascades(cascadeDataList); + } - Vector3f[] frustumCorners = new Vector3f[]{ - new Vector3f(-1.0f, 1.0f, 0.0f), - new Vector3f(1.0f, 1.0f, 0.0f), - new Vector3f(1.0f, -1.0f, 0.0f), - new Vector3f(-1.0f, -1.0f, 0.0f), - new Vector3f(-1.0f, 1.0f, 1.0f), - new Vector3f(1.0f, 1.0f, 1.0f), - new Vector3f(1.0f, -1.0f, 1.0f), - new Vector3f(-1.0f, -1.0f, 1.0f), - }; - - // Project frustum corners into world space - var invCam = (new Matrix4f(projMatrix).mul(viewMatrix)).invert(); + Vector3f minExtents = new Vector3f(Float.POSITIVE_INFINITY); + Vector3f maxExtents = new Vector3f(Float.NEGATIVE_INFINITY); + Vector3f corner = new Vector3f(); for (int j = 0; j < 8; j++) { - Vector4f invCorner = new Vector4f(frustumCorners[j], 1.0f).mul(invCam); - frustumCorners[j] = new Vector3f(invCorner.x, invCorner.y, invCorner.z).div(invCorner.w); + corner.set(rays[j & 3]).mul(j < 4 ? lastSplitDist : splitDist); + viewToLight.transformPosition(corner); + if (!corner.isFinite()) { + return disableCascades(cascadeDataList); + } + minExtents.min(corner); + maxExtents.max(corner); } - for (int j = 0; j < 4; j++) { - var dist = new Vector3f(frustumCorners[j + 4]).sub(frustumCorners[j]); - frustumCorners[j + 4] = new Vector3f(frustumCorners[j]).add(new Vector3f(dist).mul(splitDist)); - frustumCorners[j] = new Vector3f(frustumCorners[j]).add(new Vector3f(dist).mul(lastSplitDist)); + // Tight light-space XY bounds; reserve two texels per edge for snapping and the 3x3 filter. + float width = paddedExtent(maxExtents.x - minExtents.x, shadowMapSize); + float height = paddedExtent(maxExtents.y - minExtents.y, shadowMapSize); + float centerX = minExtents.x * 0.5f + maxExtents.x * 0.5f; + float centerY = minExtents.y * 0.5f + maxExtents.y * 0.5f; + float snappedX = (float) Math.rint((double) centerX / (width / shadowMapSize)); + float snappedY = (float) Math.rint((double) centerY / (height / shadowMapSize)); + + // Include off-camera casters up to the coverage distance toward the light. + // Shadow depth remains forward 0..1 regardless of the camera's depth convention. + Matrix4f lightOrthoMatrix = new Matrix4f().ortho(-width * 0.5f, width * 0.5f, + -height * 0.5f, height * 0.5f, -maxExtents.z - farClip, -minExtents.z + 1.0f, true); + lightOrthoMatrix.m30(-2.0f * snappedX / shadowMapSize); + lightOrthoMatrix.m31(-2.0f * snappedY / shadowMapSize); + lightOrthoMatrix.mul(lightViewMatrix); + if (!Float.isFinite(width) || !Float.isFinite(height) || !lightOrthoMatrix.isFinite()) { + return disableCascades(cascadeDataList); } - var frustumCenter = new Vector3f(0.0f); - for (int j = 0; j < 8; j++) { - frustumCenter.add(frustumCorners[j]); - } - frustumCenter.div(8.0f); - - var up = UP; - float sphereRadius = 0.0f; - for (int j = 0; j < 8; j++) { - float dist = new Vector3f(frustumCorners[j]).sub(frustumCenter).length(); - sphereRadius = java.lang.Math.max(sphereRadius, dist); - } - sphereRadius = (float) java.lang.Math.ceil(sphereRadius * 16.0f) / 16.0f; - - var maxExtents = new Vector3f(sphereRadius, sphereRadius, sphereRadius); - var minExtents = new Vector3f(maxExtents).mul(-1.0f); - - var lightDir = new Vector3f(lightPos.x, lightPos.y, lightPos.z); - var shadowCameraPos = new Vector3f(frustumCenter).add(lightDir.mul(minExtents.z)); - - float dot = java.lang.Math.abs(new Vector3f(lightPos.x, lightPos.y, lightPos.z).dot(up)); - if (dot == 1.0f) { - up = UP_ALT; - } - - var lightViewMatrix = new Matrix4f().lookAt(shadowCameraPos, frustumCenter, up); - var lightOrthoMatrix = new Matrix4f().ortho - (minExtents.x, maxExtents.x, minExtents.y, maxExtents.y, 0.0f, maxExtents.z - minExtents.z, true); - - int shadowMapSize = EngineConfig.getInstance().GetShadowMapSize(); - Vector4f shadowOrigin = new Vector4f(0.0f, 0.0f, 0.0f, 1.0f); - lightViewMatrix.transform(shadowOrigin); - shadowOrigin.mul(shadowMapSize / 2.0f); - - Vector4f roundedOrigin = new Vector4f(shadowOrigin).round(); - Vector4f roundOffset = roundedOrigin.sub(shadowOrigin); - roundOffset.mul(2.0f / shadowMapSize); - roundOffset.z = 0.0f; - roundOffset.w = 0.0f; - - lightOrthoMatrix.m30(lightOrthoMatrix.m30() + roundOffset.x); - lightOrthoMatrix.m31(lightOrthoMatrix.m31() + roundOffset.y); - lightOrthoMatrix.m32(lightOrthoMatrix.m32() + roundOffset.z); - lightOrthoMatrix.m33(lightOrthoMatrix.m33() + roundOffset.w); - - // Store split distance and matrix in cascade CascadeData cascadeData = cascadeDataList.get(i); - cascadeData.SetSplitDistance((nearClip + splitDist * clipRange) * -1.0f); - cascadeData.SetProjectionViewMatrix(lightOrthoMatrix.mul(lightViewMatrix)); - - lastSplitDist = cascadeSplits[i]; + cascadeData.SetSplitDistance(-splitDist); + cascadeData.SetProjectionViewMatrix(lightOrthoMatrix); + lastSplitDist = splitDist; } + return true; + } + + private static float paddedExtent(float extent, int shadowMapSize) { + double rounded = Math.max(1.0 / 16.0, Math.ceil((double) extent * 16.0) / 16.0); + return (float) (rounded * shadowMapSize / (shadowMapSize - 4.0)); + } + + private static boolean disableCascades(List cascadeDataList) { + for (CascadeData cascadeData : cascadeDataList) { + cascadeData.SetSplitDistance(0.0f); + cascadeData.GetProjViewMatrix().identity(); + } + return false; } } diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/Shader/ShaderCompiler.java b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/Shader/ShaderCompiler.java index 0a3b214..31afe02 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/Shader/ShaderCompiler.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/Shader/ShaderCompiler.java @@ -15,6 +15,7 @@ public class ShaderCompiler { public static byte[] CompileShader(String ShaderCode, int ShaderType, boolean HLSLshader){ long Compiler = 0; long Options = 0; + long CompilationResult = 0; byte[] CompiledShader; try{ Compiler = Shaderc.shaderc_compiler_initialize(); @@ -22,9 +23,11 @@ public class ShaderCompiler { if(EngineConfig.getInstance().DebugShaders()){ Shaderc.shaderc_compile_options_set_generate_debug_info(Options); Shaderc.shaderc_compile_options_set_optimization_level(Options,0); + } else { + Shaderc.shaderc_compile_options_set_optimization_level(Options, Shaderc.shaderc_optimization_level_performance); } Shaderc.shaderc_compile_options_set_source_language(Options, HLSLshader ? Shaderc.shaderc_source_language_hlsl : Shaderc.shaderc_source_language_glsl); - long CompilationResult = Shaderc.shaderc_compile_into_spv( + CompilationResult = Shaderc.shaderc_compile_into_spv( Compiler, ShaderCode, ShaderType, @@ -41,6 +44,7 @@ public class ShaderCompiler { CompiledShader = new byte[buffer.remaining()]; buffer.get(CompiledShader); } finally{ + if (CompilationResult != 0) Shaderc.shaderc_result_release(CompilationResult); Shaderc.shaderc_compile_options_release(Options); Shaderc.shaderc_compiler_release(Compiler); } @@ -52,11 +56,15 @@ public class ShaderCompiler { try{ var glslFile = new File(ShaderFile); var spvFile = new File(ShaderFile + ".spv"); - if(!spvFile.exists() || glslFile.lastModified() > spvFile.lastModified()){ + var optionsFile = new File(ShaderFile + ".spv.options"); + String options = "performance-v1:debug=" + EngineConfig.getInstance().DebugShaders(); + boolean optionsChanged = !optionsFile.exists() || !Files.readString(optionsFile.toPath()).equals(options); + if(!spvFile.exists() || glslFile.lastModified() > spvFile.lastModified() || optionsChanged){ Logger.debug("Compiling new [{}] shader from [{}] source",spvFile.getPath(),glslFile.getPath()); var ShaderCode = new String(Files.readAllBytes(glslFile.toPath())); CompiledShader = CompileShader(ShaderCode, ShaderType, false); Files.write(spvFile.toPath(), CompiledShader); + Files.writeString(optionsFile.toPath(), options); } else{ Logger.debug("Loading Compiled Shader [{}]",spvFile.getPath()); } diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/Shader/ShaderModule.java b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/Shader/ShaderModule.java index 47cab1f..7d0a962 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/Shader/ShaderModule.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Rendering/Shader/ShaderModule.java @@ -3,6 +3,7 @@ package net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Rendering.Shader; import net.halbear.Terrain4J.EngineCore.Logic.Rendering.VulkanContext; import net.halbear.Terrain4J.EngineCore.RenderingAPI.Vulkan.Util.VulkanUtils; import org.lwjgl.system.MemoryStack; +import org.lwjgl.system.MemoryUtil; import org.lwjgl.vulkan.VkShaderModuleCreateInfo; import static org.lwjgl.vulkan.VK13.vkCreateShaderModule; import static org.lwjgl.vulkan.VK13.vkDestroyShaderModule; @@ -35,17 +36,26 @@ public class ShaderModule { } private static long CreateShaderModule(VulkanContext VkCtx, byte[] Code){ + ByteBuffer CodePointer = CreateCodeBuffer(Code); try(var MemStack = MemoryStack.stackPush()){ - ByteBuffer CodePointer = MemStack.malloc(Code.length).put(0,Code); var ShaderModuleCreateInfo = VkShaderModuleCreateInfo.calloc(MemStack) .sType$Default() .pCode(CodePointer); LongBuffer LongPoiner = MemStack.mallocLong(1); VulkanUtils.vkCheck(vkCreateShaderModule(VkCtx.GetDevice().FetchVulkanDevice(), ShaderModuleCreateInfo, null, LongPoiner),"Failed to create a new Shader Module"); return LongPoiner.get(0); + } finally { + MemoryUtil.memFree(CodePointer); } } + static ByteBuffer CreateCodeBuffer(byte[] Code) { + if (Code.length == 0 || Code.length % Integer.BYTES != 0) { + throw new IllegalArgumentException("SPIR-V must contain complete 32-bit words"); + } + return MemoryUtil.memAlloc(Code.length).put(Code).flip(); + } + public VkSpecializationInfo GetSpecializationInfo(){return SpecializationInfo;} public void CleanUp(VulkanContext VkCtx){ diff --git a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Structure/DeviceLayers/Device.java b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Structure/DeviceLayers/Device.java index 91c9b2b..2634e0a 100644 --- a/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Structure/DeviceLayers/Device.java +++ b/src/main/java/net/halbear/Terrain4J/EngineCore/RenderingAPI/Vulkan/Structure/DeviceLayers/Device.java @@ -23,6 +23,9 @@ public class Device { private final boolean depthClamp; private final VkDevice VulkanDevice; private final boolean SamplesAnisotropy; + private final boolean multiDrawIndirect; + private final boolean drawIndirectFirstInstance; + private final int maxDrawIndirectCount; public static final int VENDOR_AMD = 0x1002; public static final int VENDOR_NVIDIA = 0x10DE; public static final int VENDOR_INTEL = 0x8086; @@ -66,6 +69,10 @@ public class Device { var features = features2.features(); VkPhysicalDeviceFeatures SupportedFeatures = PhysDevice.GetPhysicalDeviceFeatures(); + multiDrawIndirect = SupportedFeatures.multiDrawIndirect(); + drawIndirectFirstInstance = SupportedFeatures.drawIndirectFirstInstance(); + features.multiDrawIndirect(multiDrawIndirect); + features.drawIndirectFirstInstance(drawIndirectFirstInstance); SamplesAnisotropy = SupportedFeatures.samplerAnisotropy(); if(SamplesAnisotropy){ features.samplerAnisotropy(true); @@ -83,8 +90,9 @@ public class Device { .pQueueCreateInfos(QueueCreationInfoBuffer); PointerBuffer pp = MemStack.mallocPointer(1); - VkPhysicalDeviceProperties deviceProps = VkPhysicalDeviceProperties.malloc(); + VkPhysicalDeviceProperties deviceProps = VkPhysicalDeviceProperties.malloc(MemStack); vkGetPhysicalDeviceProperties(PhysDevice.GetPhysicalDevice(), deviceProps); + maxDrawIndirectCount = (int) Math.min(Integer.MAX_VALUE, Integer.toUnsignedLong(deviceProps.limits().maxDrawIndirectCount())); int VendorID = deviceProps.vendorID(); DeviceName = deviceProps.deviceNameString(); @@ -106,6 +114,9 @@ public class Device { } public GPU_VENDOR GetVendor(){return Vendor;} + public boolean multiDrawIndirect(){return multiDrawIndirect;} + public boolean drawIndirectFirstInstance(){return drawIndirectFirstInstance;} + public int maxDrawIndirectCount(){return maxDrawIndirectCount;} public String GetDeviceName(){return DeviceName;} diff --git a/terrain4j.properties b/terrain4j.properties index 32aa75f..4728b2d 100644 --- a/terrain4j.properties +++ b/terrain4j.properties @@ -11,7 +11,17 @@ Renderer=1 RenderingAPI=Vulkan RequestedImages=3 ShaderRecompiling=true -ShadowMapSize=512 +ShadowMapSize=1024 +max_shadow_distance=96 +ssao_scale=0.5 +ssao_samples=16 +ssr_scale=0.5 +ssr_steps=24 +bloom_scale=0.25 +bloom_passes=2 +voxel_chunk_radius=32 +voxel_generations_per_frame=1024 +voxel_face_pool_mib=2048 Software_Icon=ProgramIcon.png Software_Icon_Path=/WindowResources/Icon/ Software_Title=Terrain4J Game Engine @@ -20,8 +30,8 @@ cap_fast_to_tickrate=true cap_main_to_tickrate=true cap_master_to_tickrate=true compatibility_mode=false -display_height=480 -display_width=640 +display_height=1080 +display_width=1920 fast_thread_tickrate=240 field_of_view=60.0f frame_accuracy=0