From 3da43d5a695d5d3c2c630c433c87f1c6f729434c Mon Sep 17 00:00:00 2001 From: Avery Date: Wed, 16 Apr 2025 16:43:43 +0200 Subject: [PATCH 1/7] Seperate shader for skybox, shader and cube in one scene --- .gitignore | 1 + Makefile | 6 +- flake.nix | 1 + shaders/{frag.frag => cube.frag} | 6 +- shaders/{vert.vert => cube.vert} | 3 +- shaders/frag.spv | Bin 892 -> 0 bytes shaders/skybox.frag | 10 + shaders/skybox.vert | 157 ++++++++++++++++ shaders/vert.spv | Bin 4760 -> 0 bytes src/main.rs | 313 ++++++++++++++++++------------- 10 files changed, 358 insertions(+), 139 deletions(-) rename shaders/{frag.frag => cube.frag} (63%) rename shaders/{vert.vert => cube.vert} (98%) delete mode 100644 shaders/frag.spv create mode 100644 shaders/skybox.frag create mode 100644 shaders/skybox.vert delete mode 100644 shaders/vert.spv diff --git a/.gitignore b/.gitignore index ea8c4bf..e065db9 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/shaders/*.spv diff --git a/Makefile b/Makefile index 239f7f3..2260bea 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ BIN = target/debug/mineclone -SHADERS = shaders/vert.spv shaders/frag.spv +SHADERS = $(shell find shaders -name '*.frag' -or -name '*.vert' | sed -e 's/\.frag/_frag.spv/' -e 's/\.vert/_vert.spv/') SOURCES = $(shell find src -name '*.rs') all: $(SHADERS) $(BIN) @@ -11,8 +11,8 @@ run: all $(BIN): $(SOURCES) cargo build -%.spv: %.vert +%_vert.spv: %.vert glslc $< -o $@ -%.spv: %.frag +%_frag.spv: %.frag glslc $< -o $@ diff --git a/flake.nix b/flake.nix index 24242cb..29a02a7 100644 --- a/flake.nix +++ b/flake.nix @@ -26,6 +26,7 @@ vulkan-headers vulkan-loader vulkan-validation-layers # maybe? + shaderc # glm and whatnot … ]; }; diff --git a/shaders/frag.frag b/shaders/cube.frag similarity index 63% rename from shaders/frag.frag rename to shaders/cube.frag index 3b185db..d980196 100644 --- a/shaders/frag.frag +++ b/shaders/cube.frag @@ -1,7 +1,6 @@ #version 450 // vim: ft=c -// -layout(binding = 1) uniform samplerCube combined_image; +// clang-format off layout(location = 0) out vec4 outColor; @@ -13,8 +12,7 @@ layout(push_constant, std430) uniform pc { }; void main() { - outColor = texture(combined_image, vec3(pos_pre)); - // outColor = vec4(data.rgb*(1.0+dot(normal.xyz, normalize(vec3(-0.7, -0.5, -0.1))))/2.0, 1.0); + outColor = vec4(data.rgb*(1.0+dot(normal.xyz, normalize(vec3(-0.7, -0.5, -0.1))))/2.0, 1.0); //if(pos_post.z <= 0.0) { // outColor = vec4(1.0); //} diff --git a/shaders/vert.vert b/shaders/cube.vert similarity index 98% rename from shaders/vert.vert rename to shaders/cube.vert index b8b3a04..6a0402f 100644 --- a/shaders/vert.vert +++ b/shaders/cube.vert @@ -1,5 +1,6 @@ #version 450 // vim: ft=c +// clang-format off struct PosNorm { vec4 pos; @@ -91,7 +92,7 @@ void main() { // apply view's origin transformation //gl_Position.xyz += cam_orig.xyz; - // gl_Position *= view_orig; + gl_Position *= view_orig; // apply view's xz rotation //mat2 xz_rot; diff --git a/shaders/frag.spv b/shaders/frag.spv deleted file mode 100644 index 94a38d774cb7746b2374f6adf3ba6ab8ae55b6ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 892 zcmYk3%Syvw5QRr$YQ0vi_lj7%7Z)mm=td+i3MwvKhL{8iq)lm~;OqHRZUoQwx77?x zCv)a<&Oh~LJM(5Uwqz@IU}IaiSs^B@=3>x&?w*Xw^wG4fCv@hDkO|<1+h5>8n@z zvVNSuC)EaD9aV1Me8*qBm$yY;3`HlTrq`LpMekMXlQ8Q=Z`#c)ItRrl91K&Jqq$!U zdr?l^vpwpuX@l>+m0A*&5s`X}4_TmHy!+5);~dVmoI6n`KbB+UaMM4n!~H)$=H_1a z74D95{)DD3Nv-X!YVLbm-!)-J@*VBx=H%SxNWXPO{yFfgNseZ)J*i(*q@TQM=)+p7 zEGg2*zGXle82iY1BSyWU$n2DloHv7^Z$oj)$2@gzK>bi$+@4u>?JJ@cj9zkX4|bvZ za0}jvq5oTmuXrsKeXDY`k+XAMj+Q5Np`kg!6#lTy35K3&-_`_!?@^PyVO!2T{nTHy U6OJ7@ywq=`3awy2df}z~7c8_u5dZ)H diff --git a/shaders/skybox.frag b/shaders/skybox.frag new file mode 100644 index 0000000..63315d9 --- /dev/null +++ b/shaders/skybox.frag @@ -0,0 +1,10 @@ +#version 450 +// vim: ft=c + +layout(binding = 1) uniform samplerCube combined_image; + +layout(location = 0) out vec4 outColor; + +layout(location = 0) in vec4 pos_pre; + +void main() { outColor = texture(combined_image, vec3(pos_pre)); } diff --git a/shaders/skybox.vert b/shaders/skybox.vert new file mode 100644 index 0000000..4616c0c --- /dev/null +++ b/shaders/skybox.vert @@ -0,0 +1,157 @@ +#version 450 +// vim: ft=c +// clang-format off + +const vec4 positions[36] = vec4[36]( + // BOTTOM + vec4(-0.5, 0.5, -0.5, 1.0), + vec4(0.5, 0.5, 0.5, 1.0), + vec4(-0.5, 0.5, 0.5, 1.0), + vec4(0.5, 0.5, -0.5, 1.0), + vec4(0.5, 0.5, 0.5, 1.0), + vec4(-0.5, 0.5, -0.5, 1.0), + + // TOP + vec4(0.5, -0.5, 0.5, 1.0), + vec4(-0.5, -0.5, -0.5, 1.0), + vec4(-0.5, -0.5, 0.5, 1.0), + vec4(0.5, -0.5, 0.5, 1.0), + vec4(0.5, -0.5, -0.5, 1.0), + vec4(-0.5, -0.5, -0.5, 1.0), + + // FRONT + vec4(-0.5, -0.5, -0.5, 1.0), + vec4(0.5, 0.5, -0.5, 1.0), + vec4(-0.5, 0.5, -0.5, 1.0), + vec4(0.5, -0.5, -0.5, 1.0), + vec4(0.5, 0.5, -0.5, 1.0), + vec4(-0.5, -0.5, -0.5, 1.0), + + // BACK + vec4(0.5, 0.5, 0.5, 1.0), + vec4(-0.5, -0.5, 0.5, 1.0), + vec4(-0.5, 0.5, 0.5, 1.0), + vec4(0.5, 0.5, 0.5, 1.0), + vec4(0.5, -0.5, 0.5, 1.0), + vec4(-0.5, -0.5, 0.5, 1.0), + + // LEFT + vec4(-0.5, -0.5, -0.5, 1.0), + vec4(-0.5, 0.5, 0.5, 1.0), + vec4(-0.5, -0.5, 0.5, 1.0), + vec4(-0.5, 0.5, -0.5, 1.0), + vec4(-0.5, 0.5, 0.5, 1.0), + vec4(-0.5, -0.5, -0.5, 1.0), + + // RIGHT + vec4(0.5, 0.5, 0.5, 1.0), + vec4(0.5, -0.5, -0.5, 1.0), + vec4(0.5, -0.5, 0.5, 1.0), + vec4(0.5, 0.5, 0.5, 1.0), + vec4(0.5, 0.5, -0.5, 1.0), + vec4(0.5, -0.5, -0.5, 1.0) +); + +layout(push_constant, std430) uniform pc { + layout(offset=0) vec4 cam_orig; + layout(offset=16) vec4 cam_rot; + layout(offset=32) uvec2 screen_res; +}; + +layout (location = 0) out vec4 pos_pre; + +const float PI = 3.14159; +// Forgive me for I have sinned +const float TAU = PI*2.0; + +void main() { + // assign outs + pos_pre = positions[gl_VertexIndex]; + + // define constants + const float zFar = 100.0; + const float zNear = 0.1; + + // assign the transformee + gl_Position = positions[gl_VertexIndex]; + + mat4 fix_coordinates = mat4( + -1.0, 0.0, 0.0, 0.0, + 0.0, -1.0, 0.0, 0.0, + 0.0, 0.0, -1.0, 0.0, + 0.0, 0.0, 0.0, 1.0 + ); + mat4 view_rot_xz = mat4( + cos(cam_rot.y), 0.0, sin(cam_rot.y), 0.0, + 0.0, 1.0, 0.0, 0.0, + -sin(cam_rot.y), 0.0, cos(cam_rot.y), 0.0, + 0.0, 0.0, 0.0, 1.0 + ); + mat4 view_rot_yz = mat4( + 1.0, 0.0, 0.0, 0.0, + 0.0, cos(cam_rot.x), sin(cam_rot.x), 0.0, + 0.0, -sin(cam_rot.x), cos(cam_rot.x), 0.0, + 0.0, 0.0, 0.0, 1.0 + ); + mat4 project_aspect = mat4( + (float(screen_res.y)/float(screen_res.x)), 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, 0.0, 1.0 + ); + mat4 project_znear = mat4( + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, -zNear, + 0.0, 0.0, 0.0, 1.0 + ); + mat4 project_div = mat4( + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0, 0.0, + 0.0, 0.0, tan((70.0/2.0)/360.0*TAU), 0.0 + ); + mat4 project_normal = mat4( + 1.0, 0.0, 0.0, 0.0, + 0.0, 1.0, 0.0, 0.0, + 0.0, 0.0, 1.0/zFar, 0.0, + 0.0, 0.0, 0.0, 1.0 + ); + + // vulkan has inverted screen coordinates + // but we want regular mesh coordinates + // gl_Position.xyz *= -1.0 + gl_Position *= fix_coordinates; + + // apply view's xz rotation + //mat2 xz_rot; + //xz_rot[0] = vec2(cos(cam_rot.y), -sin(cam_rot.y)); + //xz_rot[1] = vec2(sin(cam_rot.y), cos(cam_rot.y)); + //gl_Position.xz *= inverse(xz_rot); + gl_Position *= view_rot_xz; + + // apply view's yz rotation + //mat2 yz_rot; + //yz_rot[0] = vec2(cos(cam_rot.x), -sin(cam_rot.x)); + //yz_rot[1] = vec2(sin(cam_rot.x), cos(cam_rot.x)); + //gl_Position.yz *= inverse(yz_rot); + gl_Position *= view_rot_yz; + + // aspect correction + //gl_Position.x *= (1080.0/1920.0); + gl_Position *= project_aspect; + + // z near correction + //gl_Position.z -= zNear; + gl_Position *= project_znear; + + // division by z + // has to be assigned by w so that the hardware performs the division AFTER clipping. + //gl_Position.w = (gl_Position.z*sin(140.0/360.0*TAU)); + gl_Position *= project_div; + + // z normalization + //gl_Position.z /= zFar; + gl_Position *= project_normal; + +} diff --git a/shaders/vert.spv b/shaders/vert.spv deleted file mode 100644 index 8518aa38d2a9452cf921366311c0931a394ec1cf..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4760 zcmZveX>$~16onhYB0rmn%s4X%NmPP@f;;Z} zzHdLl((;>CSmihWieIeqJUxBlTU465dCxuPF7MVov`p`6i=t`KoM>M3bJTnmM$^Sa z;#$+Ox9@P@`g)^({ie+uh?pO>q=CdNh-RgDhW-u}k}?j=#j<6xPFaioP1pFl5@tlR zqu#!J@4@DHlt_{b4{(5n)e!`j#uT zp+YH&@Mp_ERH+{jrIj>&QBk#0m!C^@)<6z?E^3SBHua>DRLb@I(cyuCxQ0eN8jteJ zWaPB2`AuKAHC^+uQvOI>Ys91b%l+|aa~|x2a`M$$oW@-tUvY=xS~FCxvRfl~9<)k5 zor2i5lxLnPpOhPilX2}3IZchdmA1Q-RQDwHMxk7cCpAtAZMamL)R;Ht!o$4RXdFmJ z^TkS~)}NFMjkqp87hNxYB#DpbE4AcU8ru?W)L1Px=L_a37KR+6YPmFq`NG#K4RNh0 z$2{xBS{#@2HMLM=OSG8+dg!-DW7e@bI&z(AO)P!UG=E#BTkS$UL{7%)QK6r_c_h_lQ>BE!y^zD#8QzNnP+cKYb z8m?VFXMu*T5X+q5+J)I;eCo{8NIy90;IrRwi{&rS*dd!HTbuc;gNOWu8qtU&AD{CG z<1fj4;ui~3JDfM?#k}daTw{lfhkp1gWYk+OpL1nj;M#>a6KcVsVf5+HNKJI+h2JTI zTPA;%M)JI$t1};-zSO%~7DXq2wa@+HS*J{L*UE^2w^>`4aiMPQ+92M#d#snHp1T}- zT@XtTbTsD44-9jl7d46HuH2xJePf^7vpdLqF*NpPlg3#x?kD?$&-Vq+`@Th(hxi*c zqOmX5w+qt)pYwk_^NBAB6VE)nmT_U`@4aHQ_GHF~y7%NMX@8zS|BGXv4r&mO?mamn zOdskI$DTOvq_BItk9#~L?YHiYll%T`5R2xyvhSy4?r~C>Sl4+@c=q32+uied`R<7p z);S~Jb%;eEf5hZ8a*nGt@*Blj+vm3_^!Hrg z^Scr1gBSSxR)qS*1^(&G-;v#eMe_Lu^eBt>eTi(ojPC{--veT)xm1R}Q!C-V;JePH z!eO1u)W#MVln=qQ=b_8*}YmVEQV*P(jJIEgHlKDP! zzvySJTNutdJ*@R)nsw@0yCu-9qjCRt%jnrHqXx0;2louk^WQ7XIyvufowv$Io2%Vn ze|lxpWSj|_^Y;l)mA_v+n)7cHCZFFfH0R$gJXL<5cr@qVA?$p#iR|1C2&1{)LE)+D z9TJb`dUpzYJ!sz3yM$RQ`^lUctsTyMpHryaEseRrQ9IP`$$U7Be4o=1VR}-V8pLu= zy#Huk*L}iXS9ovl7mwz3Js|9Lp*jCS;i>W;5|8HmhlRb*XwH8`c&hxocr@ofD(rkT zpHo2?&Gn87PgSod9?kXog}oj$pG7Pjo3)T~A$1*9CT@6&SOvJ9=7-P=mWIj<_;c|KqAo=9{4&#PtJS$+F> zFY7XD!Fz8S8Siz3YaEv5wOM!GNT!9lwc~+i9c`8PQ5ij{O$}n%n?a3e;4zISGCZO2 zREAG$JR{?~Nv*T8H8OwOUl8t;@x3OdP2-C)_;cC={FgG0T<5(k>^%D86X!m!2=k6P z{#9YeyU%Ol;N9n(FdVtgdtKOh^uZ_2eclk}jyV2JVaL1ATjJo|=WSs)a-H{%u=D7H zPn`R_E6jd7{ykyGyU+XL;N9l~VK{P~_o1-!=z~w3`+OwKSvvk>VaL1AC*t7U=Tl)g za-H{?u=D7HPn`RFF3de~{1?KGcb_lC!Mo2_!f@m|?`vV_(FdP6_xVPcJLvdtg&prc c--&~FpYMg?$aUTi!p@@)K5_r*>$O$(ANiSaRsaA1 diff --git a/src/main.rs b/src/main.rs index 610e245..ae2e881 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,120 +36,118 @@ const MESH_SIZE: u64 = 36 * 2 * 4 * 4; #[rustfmt::skip] const POSITIONS: [f32; 36 * 2 * 4] = [ // BOTTOM - - -0.5, 0.5, -0.5, 1.0, + -0.5, 0.5, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, - 0.5, 0.5, 0.5, 1.0, + 0.5, 0.5, 2.0, 1.0, 0.0, 1.0, 0.0, 0.0, - -0.5, 0.5, 0.5, 1.0, + -0.5, 0.5, 2.0, 1.0, 0.0, 1.0, 0.0, 0.0, - 0.5, 0.5, -0.5, 1.0, + 0.5, 0.5, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, - 0.5, 0.5, 0.5, 1.0, + 0.5, 0.5, 2.0, 1.0, 0.0, 1.0, 0.0, 0.0, - -0.5, 0.5, -0.5, 1.0, + -0.5, 0.5, 1.0, 1.0, 0.0, 1.0, 0.0, 0.0, // TOP - 0.5, -0.5, 0.5, 1.0, + 0.5, -0.5, 2.0, 1.0, 0.0, -1.0, 0.0, 0.0, - -0.5, -0.5, -0.5, 1.0, + -0.5, -0.5, 1.0, 1.0, 0.0, -1.0, 0.0, 0.0, - -0.5, -0.5, 0.5, 1.0, + -0.5, -0.5, 2.0, 1.0, 0.0, -1.0, 0.0, 0.0, - 0.5, -0.5, 0.5, 1.0, + 0.5, -0.5, 2.0, 1.0, 0.0, -1.0, 0.0, 0.0, - 0.5, -0.5, -0.5, 1.0, + 0.5, -0.5, 1.0, 1.0, 0.0, -1.0, 0.0, 0.0, - -0.5, -0.5, -0.5, 1.0, + -0.5, -0.5, 1.0, 1.0, 0.0, -1.0, 0.0, 0.0, // FRONT - -0.5, -0.5, -0.5, 1.0, + -0.5, -0.5, 1.0, 1.0, 0.0, 0.0, -1.0, 0.0, - 0.5, 0.5, -0.5, 1.0, + 0.5, 0.5, 1.0, 1.0, 0.0, 0.0, -1.0, 0.0, - -0.5, 0.5, -0.5, 1.0, + -0.5, 0.5, 1.0, 1.0, 0.0, 0.0, -1.0, 0.0, - 0.5, -0.5, -0.5, 1.0, + 0.5, -0.5, 1.0, 1.0, 0.0, 0.0, -1.0, 0.0, - 0.5, 0.5, -0.5, 1.0, + 0.5, 0.5, 1.0, 1.0, 0.0, 0.0, -1.0, 0.0, - -0.5, -0.5, -0.5, 1.0, + -0.5, -0.5, 1.0, 1.0, 0.0, 0.0, -1.0, 0.0, // BACK - 0.5, 0.5, 0.5, 1.0, + 0.5, 0.5, 2.0, 1.0, 0.0, 0.0, 1.0, 0.0, - -0.5, -0.5, 0.5, 1.0, + -0.5, -0.5, 2.0, 1.0, 0.0, 0.0, 1.0, 0.0, - -0.5, 0.5, 0.5, 1.0, + -0.5, 0.5, 2.0, 1.0, 0.0, 0.0, 1.0, 0.0, - 0.5, 0.5, 0.5, 1.0, + 0.5, 0.5, 2.0, 1.0, 0.0, 0.0, 1.0, 0.0, - 0.5, -0.5, 0.5, 1.0, + 0.5, -0.5, 2.0, 1.0, 0.0, 0.0, 1.0, 0.0, - -0.5, -0.5, 0.5, 1.0, + -0.5, -0.5, 2.0, 1.0, 0.0, 0.0, 1.0, 0.0, // LEFT - -0.5, -0.5, -0.5, 1.0, + -0.5, -0.5, 1.0, 1.0, -1.0, 0.0, 0.0, 0.0, - -0.5, 0.5, 0.5, 1.0, + -0.5, 0.5, 2.0, 1.0, -1.0, 0.0, 0.0, 0.0, - -0.5, -0.5, 0.5, 1.0, + -0.5, -0.5, 2.0, 1.0, -1.0, 0.0, 0.0, 0.0, - -0.5, 0.5, -0.5, 1.0, + -0.5, 0.5, 1.0, 1.0, -1.0, 0.0, 0.0, 0.0, - -0.5, 0.5, 0.5, 1.0, + -0.5, 0.5, 2.0, 1.0, -1.0, 0.0, 0.0, 0.0, - -0.5, -0.5, -0.5, 1.0, + -0.5, -0.5, 1.0, 1.0, -1.0, 0.0, 0.0, 0.0, // RIGHT - 0.5, 0.5, 0.5, 1.0, - 1.0, 0.0, 0.0, 0.0, - 0.5, -0.5, -0.5, 1.0, + 0.5, 0.5, 2.0, 1.0, 1.0, 0.0, 0.0, 0.0, - 0.5, -0.5, 0.5, 1.0, + 0.5, -0.5, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, - 0.5, 0.5, 0.5, 1.0, + 0.5, -0.5, 2.0, 1.0, 1.0, 0.0, 0.0, 0.0, - 0.5, 0.5, -0.5, 1.0, + 0.5, 0.5, 2.0, 1.0, 1.0, 0.0, 0.0, 0.0, - 0.5, -0.5, -0.5, 1.0, + 0.5, 0.5, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, - + 0.5, -0.5, 1.0, 1.0, + 1.0, 0.0, 0.0, 0.0, ]; fn create_instance(window: &Window, entry: &Entry) -> Instance { @@ -568,6 +566,7 @@ fn setup_pipeline( frag_shader: &vk::ShaderModule, pass: &vk::RenderPass, pipe_layout: &vk::PipelineLayout, + cull_mode: vk::CullModeFlags, ) -> vk::Pipeline { let shader_stages = setup_shader_stage(&dev, *vert_shader, *frag_shader); let vert_input = vk::PipelineVertexInputStateCreateInfo::default(); @@ -575,7 +574,7 @@ fn setup_pipeline( .topology(vk::PrimitiveTopology::TRIANGLE_LIST); let rasterization = vk::PipelineRasterizationStateCreateInfo::default() .polygon_mode(vk::PolygonMode::FILL) - .cull_mode(vk::CullModeFlags::NONE) + .cull_mode(cull_mode) .front_face(vk::FrontFace::CLOCKWISE) .line_width(1.0); let multisample = vk::PipelineMultisampleStateCreateInfo::default() @@ -828,7 +827,7 @@ fn write_desc_sets( desc_sets: &Vec, mesh_buf: &vk::Buffer, image_view: vk::ImageView, - skybox_sampler: vk::Sampler + skybox_sampler: vk::Sampler, ) { let buf_info = vk::DescriptorBufferInfo::default() .buffer(*mesh_buf) @@ -912,11 +911,9 @@ fn record_commands( cmd_buf: &vk::CommandBuffer, framebuf: &vk::Framebuffer, pass: &vk::RenderPass, - layout: &vk::PipelineLayout, width: u32, height: u32, - pipe: &vk::Pipeline, - desc_sets: &Vec, + pipes: &[(vk::Pipeline, vk::PipelineLayout, &[vk::DescriptorSet])], camera: &Camera, skybox_buffer: &vk::Buffer, skybox_image: &vk::Image, @@ -947,22 +944,22 @@ fn record_commands( unsafe { let regions = (0..6) - .map(|i| { - vk::BufferImageCopy::default() - .buffer_offset(3 * 2048 * 2048 * i) - .buffer_row_length(0) - .buffer_image_height(0) - .image_offset(vk::Offset3D::default()) - .image_extent(vk::Extent3D::default().width(2048).height(2048).depth(1)) - .image_subresource( - vk::ImageSubresourceLayers::default() - .aspect_mask(vk::ImageAspectFlags::COLOR) - .mip_level(0) - .layer_count(1) - .base_array_layer(i as u32), - ) - }) - .collect::>(); + .map(|i| { + vk::BufferImageCopy::default() + .buffer_offset(3 * 2048 * 2048 * i) + .buffer_row_length(0) + .buffer_image_height(0) + .image_offset(vk::Offset3D::default()) + .image_extent(vk::Extent3D::default().width(2048).height(2048).depth(1)) + .image_subresource( + vk::ImageSubresourceLayers::default() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .mip_level(0) + .layer_count(1) + .base_array_layer(i as u32), + ) + }) + .collect::>(); dev.cmd_copy_buffer_to_image( *cmd_buf, *skybox_buffer, @@ -971,25 +968,17 @@ fn record_commands( ®ions, ); dev.cmd_begin_render_pass(*cmd_buf, &render_pass_info, vk::SubpassContents::INLINE); - dev.cmd_bind_pipeline(*cmd_buf, vk::PipelineBindPoint::GRAPHICS, *pipe); - dev.cmd_bind_descriptor_sets( - *cmd_buf, - vk::PipelineBindPoint::GRAPHICS, - *layout, - 0, - desc_sets.as_ref(), - &[], - ); - dev.cmd_set_viewport_with_count( - *cmd_buf, - &[vk::Viewport::default() - .width(width as f32) - .height(height as f32)], - ); - dev.cmd_set_scissor_with_count( - *cmd_buf, - &[vk::Rect2D::default().extent(vk::Extent2D { width, height })], - ); + + let screen_res: Vec = (Vec::::from([width, height])) + .iter() + .map(|f| f.to_ne_bytes()) + .collect::>() + .into_flattened(); + let base_color: Vec = (Vec::::from([0.0, 1.0, 1.0, 1.0])) + .iter() + .map(|f| f.to_ne_bytes()) + .collect::>() + .into_flattened(); let cam_data: Vec = (Vec::::from([ camera.origin.x, camera.origin.y, @@ -1004,42 +993,54 @@ fn record_commands( .collect::>() .into_flattened(); - let screen_res: Vec = (Vec::::from([width, height])) - .iter() - .map(|f| f.to_ne_bytes()) - .collect::>() - .into_flattened(); - let base_color: Vec = (Vec::::from([0.0, 1.0, 1.0, 1.0])) - .iter() - .map(|f| f.to_ne_bytes()) - .collect::>() - .into_flattened(); + for (pipe, layout, desc_sets) in pipes { + dev.cmd_bind_pipeline(*cmd_buf, vk::PipelineBindPoint::GRAPHICS, *pipe); - dev.cmd_push_constants( - *cmd_buf, - *layout, - vk::ShaderStageFlags::VERTEX, - 0, - cam_data.as_ref(), - ); - dev.cmd_push_constants( - *cmd_buf, - *layout, - vk::ShaderStageFlags::VERTEX, - 32, - screen_res.as_ref(), - ); - dev.cmd_push_constants( - *cmd_buf, - *layout, - vk::ShaderStageFlags::FRAGMENT, - 48, - base_color.as_ref(), - ); + dev.cmd_bind_descriptor_sets( + *cmd_buf, + vk::PipelineBindPoint::GRAPHICS, + *layout, + 0, + desc_sets.as_ref(), + &[], + ); + dev.cmd_set_viewport_with_count( + *cmd_buf, + &[vk::Viewport::default() + .width(width as f32) + .height(height as f32)], + ); + dev.cmd_set_scissor_with_count( + *cmd_buf, + &[vk::Rect2D::default().extent(vk::Extent2D { width, height })], + ); + dev.cmd_push_constants( + *cmd_buf, + *layout, + vk::ShaderStageFlags::VERTEX, + 0, + cam_data.as_ref(), + ); + dev.cmd_push_constants( + *cmd_buf, + *layout, + vk::ShaderStageFlags::VERTEX, + 32, + screen_res.as_ref(), + ); + dev.cmd_push_constants( + *cmd_buf, + *layout, + vk::ShaderStageFlags::FRAGMENT, + 48, + base_color.as_ref(), + ); + + dev.cmd_draw(*cmd_buf, 36, 1, 0, 0); + } - dev.cmd_draw(*cmd_buf, 36, 1, 0, 0); dev.cmd_end_render_pass(*cmd_buf); } } @@ -1225,7 +1226,7 @@ fn main() { unsafe { std::ptr::copy(POSITIONS.as_ptr(), mesh_mem as *mut f32, POSITIONS.len()); } - + let (skybox_image, skybox_image_view, skybox_sampler) = { let qf_idxs = [rcs_queue_inx]; let create_info = vk::ImageCreateInfo::default() @@ -1243,11 +1244,13 @@ fn main() { .queue_family_indices(&qf_idxs); let image = unsafe { dev.create_image(&create_info, None) }.expect("Failed to create image"); - - let skybox_mem_size: vk::MemoryRequirements = unsafe {dev.get_image_memory_requirements(image)}; + + let skybox_mem_size: vk::MemoryRequirements = + unsafe { dev.get_image_memory_requirements(image) }; let skybox_dev_mem = mem_alloc(&dev, host_invisible_inx, skybox_mem_size.size); unsafe { - dev.bind_image_memory(image, skybox_dev_mem, 0).expect("Failed to bind image memory"); + dev.bind_image_memory(image, skybox_dev_mem, 0) + .expect("Failed to bind image memory"); } let create_info = vk::ImageViewCreateInfo::default() .image(image) @@ -1277,7 +1280,6 @@ fn main() { let sampler = unsafe { dev.create_sampler(&create_info, None) }.expect("Failed to create sampler"); - (image, image_view, sampler) }; @@ -1295,14 +1297,14 @@ fn main() { std::ptr::copy(data.as_ptr(), skybox_ptr as *mut u8, data.len()); } - let vert_shader_bin = std::fs::read("shaders/vert.spv").unwrap(); - let frag_shader_bin = std::fs::read("shaders/frag.spv").unwrap(); + let skybox_vert_shader_bin = std::fs::read("shaders/skybox_vert.spv").unwrap(); + let skybox_frag_shader_bin = std::fs::read("shaders/skybox_frag.spv").unwrap(); - let vert_shader = unsafe { + let skybox_vert_shader = unsafe { dev.create_shader_module( &vk::ShaderModuleCreateInfo { - code_size: vert_shader_bin.len(), - p_code: vert_shader_bin.as_ptr() as *const u32, + code_size: skybox_vert_shader_bin.len(), + p_code: skybox_vert_shader_bin.as_ptr() as *const u32, ..Default::default() }, None, @@ -1310,11 +1312,38 @@ fn main() { .expect("Failed to create vertex shader module") }; - let frag_shader = unsafe { + let skybox_frag_shader = unsafe { dev.create_shader_module( &vk::ShaderModuleCreateInfo { - code_size: frag_shader_bin.len(), - p_code: frag_shader_bin.as_ptr() as *const u32, + code_size: skybox_frag_shader_bin.len(), + p_code: skybox_frag_shader_bin.as_ptr() as *const u32, + ..Default::default() + }, + None, + ) + .expect("Failed to create fragment shader module") + }; + + let cube_vert_shader_bin = std::fs::read("shaders/cube_vert.spv").unwrap(); + let cube_frag_shader_bin = std::fs::read("shaders/cube_frag.spv").unwrap(); + + let cube_vert_shader = unsafe { + dev.create_shader_module( + &vk::ShaderModuleCreateInfo { + code_size: cube_vert_shader_bin.len(), + p_code: cube_vert_shader_bin.as_ptr() as *const u32, + ..Default::default() + }, + None, + ) + .expect("Failed to create vertex shader module") + }; + + let cube_frag_shader = unsafe { + dev.create_shader_module( + &vk::ShaderModuleCreateInfo { + code_size: cube_frag_shader_bin.len(), + p_code: cube_frag_shader_bin.as_ptr() as *const u32, ..Default::default() }, None, @@ -1325,7 +1354,22 @@ fn main() { let pass = setup_render_pass(&dev); let pipe_layout = setup_pipe_layout(&dev); - let pipe = setup_pipeline(&dev, &vert_shader, &frag_shader, &pass, &pipe_layout); + let skybox_pipe = setup_pipeline( + &dev, + &skybox_vert_shader, + &skybox_frag_shader, + &pass, + &pipe_layout, + vk::CullModeFlags::NONE, + ); + let cube_pipe = setup_pipeline( + &dev, + &cube_vert_shader, + &cube_frag_shader, + &pass, + &pipe_layout, + vk::CullModeFlags::FRONT, + ); let surface_loader = surface::Instance::new(&entry, &instance); let swapchain_loader = swapchain::Device::new(&instance, &dev); @@ -1341,8 +1385,6 @@ fn main() { height, ); - let desc_pool = setup_desc_pool(&dev); - let mut swap_images = make_swap_images(&dev, &swapchain, &swapchain_loader); let mut swap_views = make_swap_views(&dev, &swap_images, vk::Format::B8G8R8A8_UNORM); @@ -1351,9 +1393,17 @@ fn main() { let mut framebufs = make_framebufs(&dev, &swap_views, &depth_view, &pass, width, height); + let desc_pool = setup_desc_pool(&dev); + let desc_sets = make_desc_sets(&dev, &desc_pool); - write_desc_sets(&dev, &desc_sets, &mesh_buf, skybox_image_view, skybox_sampler); + write_desc_sets( + &dev, + &desc_sets, + &mesh_buf, + skybox_image_view, + skybox_sampler, + ); let sem_avail = make_sem(&dev); let sem_finish = make_sem(&dev); @@ -1463,11 +1513,12 @@ fn main() { &cmd_buf, &framebufs[img_inx as usize], &pass, - &pipe_layout, width, height, - &pipe, - &desc_sets, + &[ + (cube_pipe, pipe_layout, &desc_sets), + (skybox_pipe, pipe_layout, &desc_sets), + ], &camera, &skybox_buf, &skybox_image, -- 2.39.1 From 8168eebf7b5e257cba0ba63006f5678a55b68901 Mon Sep 17 00:00:00 2001 From: Avery Date: Wed, 16 Apr 2025 17:05:27 +0200 Subject: [PATCH 2/7] Reflective cube --- shaders/cube.frag | 7 ++++++- shaders/cube.vert | 2 ++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/shaders/cube.frag b/shaders/cube.frag index d980196..95894e0 100644 --- a/shaders/cube.frag +++ b/shaders/cube.frag @@ -2,17 +2,22 @@ // vim: ft=c // clang-format off +layout(binding = 1) uniform samplerCube combined_image; + layout(location = 0) out vec4 outColor; layout(location = 0) in vec4 normal; layout(location = 1) in vec4 pos_pre; +layout(location = 2) in vec4 view_orig; layout(push_constant, std430) uniform pc { layout(offset=48) vec4 data; }; void main() { - outColor = vec4(data.rgb*(1.0+dot(normal.xyz, normalize(vec3(-0.7, -0.5, -0.1))))/2.0, 1.0); + vec4 ray = pos_pre - view_orig; + outColor = texture(combined_image, vec3(reflect(ray, normal))); + // outColor = vec4(data.rgb*(1.0+dot(normal.xyz, normalize(vec3(-0.7, -0.5, -0.1))))/2.0, 1.0); //if(pos_post.z <= 0.0) { // outColor = vec4(1.0); //} diff --git a/shaders/cube.vert b/shaders/cube.vert index 6a0402f..a8375c7 100644 --- a/shaders/cube.vert +++ b/shaders/cube.vert @@ -19,6 +19,7 @@ layout(push_constant, std430) uniform pc { layout (location = 0) out vec4 normal; layout (location = 1) out vec4 pos_pre; +layout (location = 2) out vec4 view_orig; const float PI = 3.14159; // Forgive me for I have sinned @@ -28,6 +29,7 @@ void main() { // assign outs normal = pos.posnrm[gl_VertexIndex].norm; pos_pre = pos.posnrm[gl_VertexIndex].pos; + view_orig = cam_orig; // define constants const float zFar = 100.0; -- 2.39.1 From 2f15842ca45d29316a35b1fe5ee1e7bf8b5ddb3c Mon Sep 17 00:00:00 2001 From: Avery Date: Thu, 17 Apr 2025 00:47:02 +0200 Subject: [PATCH 3/7] Reorganise and split `main.rs` - The state of the game is now tracked using a simple state machine, where the main struct representing the game is `Game` and its state a generic implementing `GameState` on that struct, the following states exist: - `Unstarted`: Nothing has been done yet - `RenderAvailable`: The very basic things for rendering are available (sdl context, entry, instance, device, cmdbuf, surface, etc) - `LoadingWorld`: We are loading into a world, the only thing that can be done here currently is add things to be rendered (Components) of which only the `Cube` is available, with the skybox being automaticall added. - `InWorld`: We have loaded a world and can (theoretically) render things and move about - The render logic now lives in `render/` and is split both by phase, as well as components and the swapchain having their own modules - The input logic now lives in input and constits of one trait that should be implemented for states that should handle input, and is currently capable of handling mouse movenent, key presses and keys that should be held down (ex, movement keys) - The game logic lives in `game/` and is/will be responsible for driving both the input and render modules Note: the current commit is broken, as our surface is lost when returning from `Game::init_render` --- shaders/skybox.frag | 2 +- src/game/mod.rs | 96 +++ src/input/ingame_input.rs | 67 ++ src/input/mod.rs | 19 + src/main.rs | 1556 ++--------------------------------- src/render/cube.rs | 413 ++++++++++ src/render/in_world.rs | 284 +++++++ src/render/init.rs | 250 ++++++ src/render/loading_world.rs | 322 ++++++++ src/render/mod.rs | 210 +++++ src/render/skybox.rs | 318 +++++++ src/render/swapchain.rs | 203 +++++ 12 files changed, 2251 insertions(+), 1489 deletions(-) create mode 100644 src/game/mod.rs create mode 100644 src/input/ingame_input.rs create mode 100644 src/input/mod.rs create mode 100644 src/render/cube.rs create mode 100644 src/render/in_world.rs create mode 100644 src/render/init.rs create mode 100644 src/render/loading_world.rs create mode 100644 src/render/mod.rs create mode 100644 src/render/skybox.rs create mode 100644 src/render/swapchain.rs diff --git a/shaders/skybox.frag b/shaders/skybox.frag index 63315d9..f7c6ea4 100644 --- a/shaders/skybox.frag +++ b/shaders/skybox.frag @@ -1,7 +1,7 @@ #version 450 // vim: ft=c -layout(binding = 1) uniform samplerCube combined_image; +layout(binding = 0) uniform samplerCube combined_image; layout(location = 0) out vec4 outColor; diff --git a/src/game/mod.rs b/src/game/mod.rs new file mode 100644 index 0000000..125e218 --- /dev/null +++ b/src/game/mod.rs @@ -0,0 +1,96 @@ +use std::{ + process, thread, + time::{Duration, Instant}, +}; + +use sdl2::{ + event::{Event, WindowEvent}, + keyboard::Keycode, +}; + +use crate::{Game, InWorld, RenderAvailable, input::InputHandler}; + +#[derive(Debug, Default)] +pub struct Vector3 { + pub x: f32, + pub y: f32, + pub z: f32, +} + +#[derive(Debug, Default)] +pub struct Camera { + pub origin: Vector3, + pub rotation: Vector3, +} + +impl Game { + pub fn main_loop(mut self) -> Game { + let mut event_pump = self + .state + .sdl_context + .event_pump() + .expect("Failed to get event pump"); + let mouse = self.state.sdl_context.mouse(); + + mouse.set_relative_mouse_mode(true); + + let mut running = true; + while running { + let now = Instant::now(); + for event in event_pump.poll_iter() { + match event { + // Setting running to false just returns to + // RenderAvailable state (and then main menu) + Event::Quit { .. } => process::exit(0), + Event::Window { + win_event: WindowEvent::Resized(w, h), + .. + } => { + self.window_resize((w as u32, h as u32)); + } + Event::KeyDown { + keycode: Some(keycode), + keymod, + repeat, + .. + } => self.handle_keydown(keycode, keymod, repeat), + + Event::KeyUp { + keycode: Some(keycode), + keymod, + repeat, + .. + } => self.handle_keyup(keycode, keymod, repeat), + Event::MouseMotion { + mousestate, + x, + y, + xrel, + yrel, + .. + } => self.handle_mouse_motion(mousestate, (x, y), (xrel, yrel)), + _ => {} + } + } + event_pump + .keyboard_state() + .pressed_scancodes() + .filter_map(Keycode::from_scancode) + .for_each(|k| self.handle_cont_key(k)); + + self.render_frame(); + + let elapsed = now.elapsed(); + let delay = (1_000_000u128 / 60).saturating_sub(elapsed.as_micros()); + thread::sleep(Duration::from_micros(delay as u64)); + } + + Game { + state: RenderAvailable { + sdl_context: self.state.sdl_context, + window: self.state.window, + ctx: self.state.ctx, + }, + } + } +} diff --git a/src/input/ingame_input.rs b/src/input/ingame_input.rs new file mode 100644 index 0000000..86bfa0e --- /dev/null +++ b/src/input/ingame_input.rs @@ -0,0 +1,67 @@ +use sdl2::{ + keyboard::{Keycode, Mod}, + mouse::MouseState, +}; + +use crate::{Game, InWorld}; + +use super::InputHandler; + +const MOV_STEP: f32 = 0.05; +const ROT_MOUSE_SCALE: f32 = 0.001; + +impl InputHandler for Game { + fn handle_cont_key(&mut self, keycode: Keycode) { + let Self { + state: InWorld { camera, .. }, + } = self; + + match keycode { + Keycode::W => { + camera.origin.x -= -camera.rotation.y.sin() * MOV_STEP; + camera.origin.z -= camera.rotation.y.cos() * MOV_STEP; + } + Keycode::S => { + camera.origin.x += -camera.rotation.y.sin() * MOV_STEP; + camera.origin.z += camera.rotation.y.cos() * MOV_STEP; + } + Keycode::A => { + camera.origin.x += camera.rotation.y.cos() * MOV_STEP; + camera.origin.z += camera.rotation.y.sin() * MOV_STEP; + } + Keycode::D => { + camera.origin.z -= camera.rotation.y.sin() * MOV_STEP; + camera.origin.x -= camera.rotation.y.cos() * MOV_STEP; + } + Keycode::Space => { + camera.origin.y += MOV_STEP; + } + Keycode::LShift => { + camera.origin.y -= MOV_STEP; + } + _ => {} + } + } + + fn handle_keydown(&mut self, keycode: Keycode, modifier: Mod, repeat: bool) { + match keycode { + Keycode::Escape => { + let mouse = self.state.sdl_context.mouse(); + mouse.set_relative_mouse_mode(!mouse.relative_mouse_mode()); + } + _ => {} + } + } + + fn handle_mouse_motion( + &mut self, + state: MouseState, + abs: (i32, i32), + (xrel, yrel): (i32, i32), + ) { + if self.state.sdl_context.mouse().relative_mouse_mode() { + self.state.camera.rotation.y -= xrel as f32 * ROT_MOUSE_SCALE; + self.state.camera.rotation.x -= yrel as f32 * ROT_MOUSE_SCALE; + } + } +} diff --git a/src/input/mod.rs b/src/input/mod.rs new file mode 100644 index 0000000..6abb388 --- /dev/null +++ b/src/input/mod.rs @@ -0,0 +1,19 @@ +mod ingame_input; + +use sdl2::{ + keyboard::{Keycode, Mod}, + mouse::MouseState, +}; + +// More methods to be added for other forms of input +pub trait InputHandler { + // Handle keys which are meant to be held down and repeated every frame, ex movement keys + fn handle_cont_key(&mut self, keycode: Keycode) {} + + // Handle single keypresses or keys held down after the repear delay + fn handle_keydown(&mut self, keycode: Keycode, modifier: Mod, repeat: bool) {} + fn handle_keyup(&mut self, keycode: Keycode, modifier: Mod, repeat: bool) {} + + // Handle mouse movements + fn handle_mouse_motion(&mut self, state: MouseState, abs: (i32, i32), rel: (i32, i32)) {} +} diff --git a/src/main.rs b/src/main.rs index ae2e881..41cc3d9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,1149 +1,101 @@ #![allow(unused_variables, unused_mut)] +mod game; +mod input; +mod render; + use ash::Device; use ash::Entry; use ash::Instance; -use ash::khr::{surface, swapchain}; +use ash::khr::surface; use ash::vk; -use ash::vk::ImageCreateFlags; -use sdl2::mouse::MouseUtil; - -use std::cmp; -use std::ffi::CStr; -use std::ffi::CString; -use std::thread; -use std::time::Duration; -use std::time::Instant; +use game::Camera; +use render::SwapchainCtx; +use render::loading_world::Component; +use render::loading_world::WorldComponent; +use render::skybox::Skybox; +use sdl2::Sdl; -use raw_window_handle::HasDisplayHandle; -use raw_window_handle::HasWindowHandle; - -use sdl2::event::Event; -use sdl2::event::WindowEvent; -use sdl2::keyboard::Keycode; -use sdl2::video::Window; +use std::collections::HashMap; const APP_NAME: &str = "MineClone"; -const MOV_STEP: f32 = 0.05; -const ROT_MOUSE_SCALE: f32 = 0.001; - -const MAX_WIDTH: u32 = 3440; -const MAX_HEIGHT: u32 = 1440; - -const MESH_SIZE: u64 = 36 * 2 * 4 * 4; - -#[rustfmt::skip] -const POSITIONS: [f32; 36 * 2 * 4] = [ - // BOTTOM - -0.5, 0.5, 1.0, 1.0, - 0.0, 1.0, 0.0, 0.0, - - 0.5, 0.5, 2.0, 1.0, - 0.0, 1.0, 0.0, 0.0, - - -0.5, 0.5, 2.0, 1.0, - 0.0, 1.0, 0.0, 0.0, - - 0.5, 0.5, 1.0, 1.0, - 0.0, 1.0, 0.0, 0.0, - - 0.5, 0.5, 2.0, 1.0, - 0.0, 1.0, 0.0, 0.0, - - -0.5, 0.5, 1.0, 1.0, - 0.0, 1.0, 0.0, 0.0, - - // TOP - 0.5, -0.5, 2.0, 1.0, - 0.0, -1.0, 0.0, 0.0, - - -0.5, -0.5, 1.0, 1.0, - 0.0, -1.0, 0.0, 0.0, - - -0.5, -0.5, 2.0, 1.0, - 0.0, -1.0, 0.0, 0.0, - - 0.5, -0.5, 2.0, 1.0, - 0.0, -1.0, 0.0, 0.0, - - 0.5, -0.5, 1.0, 1.0, - 0.0, -1.0, 0.0, 0.0, - - -0.5, -0.5, 1.0, 1.0, - 0.0, -1.0, 0.0, 0.0, - - // FRONT - -0.5, -0.5, 1.0, 1.0, - 0.0, 0.0, -1.0, 0.0, - - 0.5, 0.5, 1.0, 1.0, - 0.0, 0.0, -1.0, 0.0, - - -0.5, 0.5, 1.0, 1.0, - 0.0, 0.0, -1.0, 0.0, - - 0.5, -0.5, 1.0, 1.0, - 0.0, 0.0, -1.0, 0.0, - - 0.5, 0.5, 1.0, 1.0, - 0.0, 0.0, -1.0, 0.0, - - -0.5, -0.5, 1.0, 1.0, - 0.0, 0.0, -1.0, 0.0, - - // BACK - 0.5, 0.5, 2.0, 1.0, - 0.0, 0.0, 1.0, 0.0, - - -0.5, -0.5, 2.0, 1.0, - 0.0, 0.0, 1.0, 0.0, - - -0.5, 0.5, 2.0, 1.0, - 0.0, 0.0, 1.0, 0.0, - - 0.5, 0.5, 2.0, 1.0, - 0.0, 0.0, 1.0, 0.0, - - 0.5, -0.5, 2.0, 1.0, - 0.0, 0.0, 1.0, 0.0, - - -0.5, -0.5, 2.0, 1.0, - 0.0, 0.0, 1.0, 0.0, +pub trait GameState {} - // LEFT - -0.5, -0.5, 1.0, 1.0, - -1.0, 0.0, 0.0, 0.0, +pub struct Unstarted; - -0.5, 0.5, 2.0, 1.0, - -1.0, 0.0, 0.0, 0.0, +impl GameState for Unstarted {} - -0.5, -0.5, 2.0, 1.0, - -1.0, 0.0, 0.0, 0.0, - - -0.5, 0.5, 1.0, 1.0, - -1.0, 0.0, 0.0, 0.0, - - -0.5, 0.5, 2.0, 1.0, - -1.0, 0.0, 0.0, 0.0, - - -0.5, -0.5, 1.0, 1.0, - -1.0, 0.0, 0.0, 0.0, - - // RIGHT - 0.5, 0.5, 2.0, 1.0, - 1.0, 0.0, 0.0, 0.0, - - 0.5, -0.5, 1.0, 1.0, - 1.0, 0.0, 0.0, 0.0, - - 0.5, -0.5, 2.0, 1.0, - 1.0, 0.0, 0.0, 0.0, - - 0.5, 0.5, 2.0, 1.0, - 1.0, 0.0, 0.0, 0.0, - - 0.5, 0.5, 1.0, 1.0, - 1.0, 0.0, 0.0, 0.0, - - 0.5, -0.5, 1.0, 1.0, - 1.0, 0.0, 0.0, 0.0, -]; - -fn create_instance(window: &Window, entry: &Entry) -> Instance { - unsafe { - let n_app_name = CString::new(APP_NAME).unwrap(); - let n_engine_name = CString::new(format!("{} Engine", APP_NAME)).unwrap(); - - let appinfo = vk::ApplicationInfo::default() - .application_name(n_app_name.as_c_str()) - .application_version(0) - .engine_name(n_engine_name.as_c_str()) - .engine_version(0) - .api_version(vk::make_api_version(0, 1, 3, 0)); - - let default_exts = window - .vulkan_instance_extensions() - .expect("Failed to get list of required extensions from SDL2"); - - let mut exts = vec![]; - exts.extend(default_exts); - - let exts: Vec<*const i8> = exts - .iter() - .map(|s| s.as_ptr() as *const i8) - .collect::>(); - let exts: &[*const i8] = exts.as_slice(); - - let insinfo = vk::InstanceCreateInfo::default() - .application_info(&appinfo) - .enabled_extension_names(exts); - - let instance = entry - .create_instance(&insinfo, None) - .expect("Failed to create instance"); - println!("Created instance"); - instance - } -} - -fn find_pdev_dgpu(instance: &Instance) -> vk::PhysicalDevice { - unsafe { - let mut pdevs = instance - .enumerate_physical_devices() - .expect("Failed to enumerate devices"); - for (i, d) in pdevs.iter().enumerate() { - let props = instance.get_physical_device_properties(*d); - if props.device_type == vk::PhysicalDeviceType::DISCRETE_GPU { - // TODO this assumes that the first discrete GPU will have an RCS queue family - // This is not guaranteed by the spec (example, a compute-only GPU) - // Fix. - println!( - "Found discrete GPU: {}", - std::str::from_utf8(std::mem::transmute(props.device_name.as_slice())).unwrap() - ); - return pdevs.remove(i); - } - } - println!("No discrete GPU found"); - assert!(pdevs.len() > 0, "No GPU found"); - return pdevs.remove(0); - } -} - -fn make_surface(entry: &Entry, instance: &Instance, window: &Window) -> vk::SurfaceKHR { - unsafe { - let surface = ash_window::create_surface( - &entry, - &instance, - (&window).display_handle().unwrap().as_raw(), - (&window).window_handle().unwrap().as_raw(), - None, - ) - .expect("Failed to create surface"); - println!("Created surface"); - surface - } +pub struct RenderCtx { + entry: Entry, + instance: Instance, + pdev: vk::PhysicalDevice, + dev: Device, + surface: vk::SurfaceKHR, + surface_loader: surface::Instance, + queue: vk::Queue, + queue_idx: u32, + cmd_buf: vk::CommandBuffer, + host_vis_idx: u32, + host_invis_idx: u32, } -// Using Intel GPU hardware terminology 🥴 -fn find_rcs_queue_inx(instance: &Instance, pdev: &vk::PhysicalDevice) -> Option { - unsafe { - let qfams = instance.get_physical_device_queue_family_properties(*pdev); - for (inx, qfam) in qfams.iter().enumerate() { - if qfam - .queue_flags - .contains(vk::QueueFlags::GRAPHICS | vk::QueueFlags::COMPUTE) - { - println!("Found RCS queue at index {}", inx); - return Some(inx as u32); - } - } - println!("No RCS queue found"); - return None; - } -} - -fn make_dev( - instance: &Instance, - pdev: &vk::PhysicalDevice, - rcs_queue_inx: u32, - exts: Vec<&CStr>, -) -> Device { - unsafe { - let dev_queue = vec![ - vk::DeviceQueueCreateInfo::default() - .queue_family_index(rcs_queue_inx) - .queue_priorities(&[1.0]), - ]; - - let exts = exts.iter().map(|&s| s.as_ptr()).collect::>(); - let exts = exts.as_slice(); - - // Enable all features - let features = instance.get_physical_device_features(*pdev); - - let dev_info = vk::DeviceCreateInfo::default() - .queue_create_infos(&dev_queue) - .enabled_extension_names(exts) - .enabled_features(&features); - - let dev = instance - .create_device(*pdev, &dev_info, None) - .expect("Failed to create device"); - println!("Created device"); - dev - } -} - -fn make_command_pool(dev: &Device, rcs_queue_inx: u32) -> vk::CommandPool { - unsafe { - let pool_info = vk::CommandPoolCreateInfo::default() - .queue_family_index(rcs_queue_inx) - .flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER); - let pool = dev - .create_command_pool(&pool_info, None) - .expect("Failed to create command pool"); - println!("Created command pool"); - pool - } -} - -fn alloc_cmd_buf(dev: &Device, pool: vk::CommandPool) -> vk::CommandBuffer { - unsafe { - let alloc_info = vk::CommandBufferAllocateInfo::default() - .command_pool(pool) - .level(vk::CommandBufferLevel::PRIMARY) - .command_buffer_count(1); - let cmd_buf = dev - .allocate_command_buffers(&alloc_info) - .expect("Failed to allocate command buffer") - .remove(0); - println!("Allocated command buffer"); - cmd_buf - } -} - -fn find_host_visible(instance: &Instance, pdev: &vk::PhysicalDevice) -> Option { - unsafe { - let mem_props = instance.get_physical_device_memory_properties(*pdev); - for (i, mem_type) in mem_props.memory_types.iter().enumerate() { - if mem_type.property_flags.contains( - vk::MemoryPropertyFlags::DEVICE_LOCAL - | vk::MemoryPropertyFlags::HOST_VISIBLE - | vk::MemoryPropertyFlags::HOST_COHERENT, - ) { - println!("Found host visible memory at index {}", i); - return Some(i as u32); - } - } - println!("No host visible memory found"); - return None; - } -} - -fn find_host_invisible(instance: &Instance, pdev: &vk::PhysicalDevice) -> Option { - unsafe { - let mem_props = instance.get_physical_device_memory_properties(*pdev); - for (i, mem_type) in mem_props.memory_types.iter().enumerate() { - if mem_type - .property_flags - .contains(vk::MemoryPropertyFlags::DEVICE_LOCAL) - && !mem_type.property_flags.contains( - vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT, - ) - { - println!("Found host invisible memory at index {}", i); - return Some(i as u32); - } - } - println!("No host invisible memory found"); - return None; - } -} - -fn mem_alloc(dev: &Device, mem_type_inx: u32, size: u64) -> vk::DeviceMemory { - unsafe { - let mem_info = vk::MemoryAllocateInfo::default() - .allocation_size(size) - .memory_type_index(mem_type_inx); - let mem = dev - .allocate_memory(&mem_info, None) - .expect("Failed to allocate memory"); - println!("Allocated memory"); - mem - } -} - -fn make_depth_img( - dev: &Device, - host_invisible_inx: u32, +pub struct Window { width: u32, height: u32, - mem: vk::DeviceMemory, - qfam: u32, -) -> vk::Image { - unsafe { - let queue_fams = [qfam]; - let img_info = vk::ImageCreateInfo::default() - .flags(vk::ImageCreateFlags::empty()) - .image_type(vk::ImageType::TYPE_2D) - .format(vk::Format::D32_SFLOAT) - .extent(vk::Extent3D { - width, - height, - depth: 1, - }) - .mip_levels(1) - .array_layers(1) - .samples(vk::SampleCountFlags::TYPE_1) - .tiling(vk::ImageTiling::OPTIMAL) - .usage(vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT) - .sharing_mode(vk::SharingMode::EXCLUSIVE) - .queue_family_indices(&queue_fams) - .initial_layout(vk::ImageLayout::UNDEFINED); - - let img = dev - .create_image(&img_info, None) - .expect("Failed to create image"); - println!("Created image"); - - dev.bind_image_memory(img, mem, 0) - .expect("Failed to bind image memory"); - - img - } -} - -fn alloc_buf( - dev: &Device, - mem: &vk::DeviceMemory, - size: u64, - usage: vk::BufferUsageFlags, -) -> vk::Buffer { - unsafe { - let buf_info = vk::BufferCreateInfo::default() - .size(size) - .usage(usage) - .sharing_mode(vk::SharingMode::EXCLUSIVE); - let buf = dev - .create_buffer(&buf_info, None) - .expect("Failed to create buffer"); - - dev.bind_buffer_memory(buf, *mem, 0) - .expect("Failed to bind buffer memory"); - println!("Created buffer"); - buf - } } -fn buf_to_ptr(dev: &Device, mem: &vk::DeviceMemory, buf: &vk::Buffer) -> *mut std::ffi::c_void { - unsafe { - let ptr = dev - .map_memory(*mem, 0, vk::WHOLE_SIZE, vk::MemoryMapFlags::empty()) - .expect("Failed to map memory"); - println!("Mapped memory"); - ptr - } -} - -fn setup_desc_layout(dev: &Device) -> vk::DescriptorSetLayout { - let storage_binding = vk::DescriptorSetLayoutBinding::default() - .binding(0) - .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) - .descriptor_count(1) - .stage_flags(vk::ShaderStageFlags::VERTEX); - let image_binding = vk::DescriptorSetLayoutBinding::default() - .binding(1) - .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) - .descriptor_count(1) - .stage_flags(vk::ShaderStageFlags::FRAGMENT); - let layouts = [storage_binding, image_binding]; - - let layout_info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&layouts); - let layout = unsafe { - dev.create_descriptor_set_layout(&layout_info, None) - .expect("Failed to create descriptor set layout") - }; - layout -} - -fn setup_render_pass(dev: &Device) -> vk::RenderPass { - let color_attach = vk::AttachmentDescription::default() - .format(vk::Format::B8G8R8A8_UNORM) - .samples(vk::SampleCountFlags::TYPE_1) - .load_op(vk::AttachmentLoadOp::CLEAR) - .store_op(vk::AttachmentStoreOp::STORE) - .stencil_load_op(vk::AttachmentLoadOp::DONT_CARE) - .stencil_store_op(vk::AttachmentStoreOp::DONT_CARE) - .initial_layout(vk::ImageLayout::UNDEFINED) - .final_layout(vk::ImageLayout::PRESENT_SRC_KHR); - let depth_attach = vk::AttachmentDescription::default() - .format(vk::Format::D32_SFLOAT) - .samples(vk::SampleCountFlags::TYPE_1) - .load_op(vk::AttachmentLoadOp::CLEAR) - .store_op(vk::AttachmentStoreOp::DONT_CARE) - .stencil_load_op(vk::AttachmentLoadOp::DONT_CARE) - .stencil_store_op(vk::AttachmentStoreOp::DONT_CARE) - .initial_layout(vk::ImageLayout::UNDEFINED) - .final_layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL); - - let color_ref = vk::AttachmentReference::default() - .attachment(0) - .layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL); - - let depth_ref = vk::AttachmentReference::default() - .attachment(1) - .layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL); - - let color_refs = [color_ref]; - let depth_refs = [depth_ref]; - - let subpass = vk::SubpassDescription::default() - .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS) - .color_attachments(color_refs.as_ref()) - .depth_stencil_attachment(&depth_ref); - - let subpass_dep = vk::SubpassDependency::default() - .src_subpass(vk::SUBPASS_EXTERNAL) - .dst_subpass(0) - .src_stage_mask( - vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT - | vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS, - ) - .dst_stage_mask( - vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT - | vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS, - ) - .src_access_mask(vk::AccessFlags::empty()) - .dst_access_mask( - vk::AccessFlags::COLOR_ATTACHMENT_WRITE - | vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE, - ); - - let attach_desc = [color_attach, depth_attach]; - - let subpasses = [subpass]; - - let deps = [subpass_dep]; - - let pass_info = vk::RenderPassCreateInfo::default() - .attachments(&attach_desc) - .subpasses(subpasses.as_ref()) - .dependencies(deps.as_ref()); - - let pass = unsafe { - dev.create_render_pass(&pass_info, None) - .expect("Failed to create render pass") - }; - pass -} - -fn setup_pipe_layout(dev: &Device) -> vk::PipelineLayout { - let layout = setup_desc_layout(&dev); - let push_range: [vk::PushConstantRange; 2] = [ - vk::PushConstantRange::default() - .stage_flags(vk::ShaderStageFlags::VERTEX) - .offset(0) - .size(48), // vec4 camera_orig, camera_rot - vk::PushConstantRange::default() - .stage_flags(vk::ShaderStageFlags::FRAGMENT) - .offset(48) - .size(16), // vec4 base_color - ]; - - let layouts = &[layout]; - - let pipe_layout = vk::PipelineLayoutCreateInfo::default() - .set_layouts(layouts.as_ref()) - .push_constant_ranges(&push_range); - let pipe_layout = unsafe { - dev.create_pipeline_layout(&pipe_layout, None) - .expect("Failed to create pipeline layout") - }; - pipe_layout -} - -fn setup_shader_stage( - dev: &Device, - vert_shader: vk::ShaderModule, - frag_shader: vk::ShaderModule, -) -> [vk::PipelineShaderStageCreateInfo; 2] { - let vert_stage = vk::PipelineShaderStageCreateInfo::default() - .stage(vk::ShaderStageFlags::VERTEX) - .module(vert_shader) - .name(c"main"); - let frag_stage = vk::PipelineShaderStageCreateInfo::default() - .stage(vk::ShaderStageFlags::FRAGMENT) - .module(frag_shader) - .name(c"main"); - [vert_stage, frag_stage] +pub struct RenderAvailable { + sdl_context: Sdl, + window: Window, + ctx: RenderCtx, } -fn setup_pipeline( - dev: &Device, - vert_shader: &vk::ShaderModule, - frag_shader: &vk::ShaderModule, - pass: &vk::RenderPass, - pipe_layout: &vk::PipelineLayout, - cull_mode: vk::CullModeFlags, -) -> vk::Pipeline { - let shader_stages = setup_shader_stage(&dev, *vert_shader, *frag_shader); - let vert_input = vk::PipelineVertexInputStateCreateInfo::default(); - let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default() - .topology(vk::PrimitiveTopology::TRIANGLE_LIST); - let rasterization = vk::PipelineRasterizationStateCreateInfo::default() - .polygon_mode(vk::PolygonMode::FILL) - .cull_mode(cull_mode) - .front_face(vk::FrontFace::CLOCKWISE) - .line_width(1.0); - let multisample = vk::PipelineMultisampleStateCreateInfo::default() - .rasterization_samples(vk::SampleCountFlags::TYPE_1); - let depth_stencil = vk::PipelineDepthStencilStateCreateInfo::default() - .depth_test_enable(true) - .depth_write_enable(true) - .depth_compare_op(vk::CompareOp::LESS) - .depth_bounds_test_enable(true) - .stencil_test_enable(false) - .min_depth_bounds(0.0) - .max_depth_bounds(1.0); - - let blend = vk::PipelineColorBlendAttachmentState::default() - .src_color_blend_factor(vk::BlendFactor::SRC_COLOR) - .dst_color_blend_factor(vk::BlendFactor::SRC_COLOR) - .color_blend_op(vk::BlendOp::ADD) - .src_alpha_blend_factor(vk::BlendFactor::SRC_COLOR) - .dst_alpha_blend_factor(vk::BlendFactor::SRC_COLOR) - .alpha_blend_op(vk::BlendOp::ADD) - .color_write_mask( - vk::ColorComponentFlags::R - | vk::ColorComponentFlags::G - | vk::ColorComponentFlags::B - | vk::ColorComponentFlags::A, - ); +impl GameState for RenderAvailable {} - let blend_attachments = [blend]; +pub struct LoadingWorld { + sdl_context: Sdl, + window: Window, + ctx: RenderCtx, - let color_blend = vk::PipelineColorBlendStateCreateInfo::default() - .attachments(blend_attachments.as_ref()) - .logic_op_enable(false) - .logic_op(vk::LogicOp::COPY) - .blend_constants([1.0, 1.0, 1.0, 1.0]); + requested_descriptors: HashMap, - let dynamic = vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&[ - vk::DynamicState::VIEWPORT_WITH_COUNT, - vk::DynamicState::SCISSOR_WITH_COUNT, - ]); + swapchain: SwapchainCtx, + pass: vk::RenderPass, - let pipe_info = vk::GraphicsPipelineCreateInfo::default() - .stages(&shader_stages) - .vertex_input_state(&vert_input) - .input_assembly_state(&input_assembly) - .rasterization_state(&rasterization) - .multisample_state(&multisample) - .depth_stencil_state(&depth_stencil) - .color_blend_state(&color_blend) - .layout(*pipe_layout) - .dynamic_state(&dynamic) - .render_pass(*pass) - .subpass(0); - - let pipe = unsafe { - dev.create_graphics_pipelines(vk::PipelineCache::null(), &[pipe_info], None) - .expect("Failed to create graphics pipeline") - .remove(0) - }; - println!("Created pipeline"); - pipe + skybox: Skybox, + components: Vec>, } -// TODO dynamic state for resizing -fn setup_swapchain( - pdev: &vk::PhysicalDevice, - dev: &Device, - surface: &vk::SurfaceKHR, - surface_loader: &surface::Instance, - swapchain_loader: &ash::khr::swapchain::Device, - instance: &Instance, - width: u32, - height: u32, -) -> (vk::SwapchainKHR, vk::Extent2D) { - let caps = unsafe { - surface_loader - .get_physical_device_surface_capabilities(*pdev, *surface) - .expect("Failed to get surface capabilities") - }; +impl GameState for LoadingWorld {} - let formats = unsafe { - surface_loader - .get_physical_device_surface_formats(*pdev, *surface) - .expect("Failed to get surface formats") - }; +pub struct InWorld { + sdl_context: Sdl, + window: Window, + ctx: RenderCtx, - let format = (formats) - .iter() - .filter(|f| { - f.format == vk::Format::B8G8R8A8_UNORM - && f.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR - }) - .next() - .expect("No suitable format found"); + skybox: (Skybox, vk::DescriptorSet), + components: Vec, - // When the window is resized fast, then the caps goes out of sync of the target extents - // Thus we are using the passed-in (target) extents instead of the cap extents. - // assert_eq!(caps.current_extent.width, width); - // assert_eq!(caps.current_extent.height, height); + sem_avail: vk::Semaphore, + sem_finish: vk::Semaphore, + fence_flight: vk::Fence, - let swap_create = vk::SwapchainCreateInfoKHR::default() - .surface(*surface) - .min_image_count(caps.min_image_count) - .image_format(format.format) - .image_color_space(format.color_space) - .image_extent(vk::Extent2D { - width: cmp::min(cmp::max(caps.current_extent.width, width), MAX_WIDTH), - height: cmp::min(cmp::max(caps.current_extent.height, height), MAX_HEIGHT), - }) - .image_array_layers(1) - .image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT) - .image_sharing_mode(vk::SharingMode::EXCLUSIVE) - .pre_transform(vk::SurfaceTransformFlagsKHR::IDENTITY) - .composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE) - .present_mode(vk::PresentModeKHR::MAILBOX) - .clipped(true); + swapchain: SwapchainCtx, + pass: vk::RenderPass, - let swapchain = unsafe { - swapchain_loader - .create_swapchain(&swap_create, None) - .expect("Failed to create swapchain") - }; - println!("Created swapchain"); - (swapchain, caps.current_extent) + camera: Camera, } -fn setup_desc_pool(dev: &Device) -> vk::DescriptorPool { - let pool_size_storage = vk::DescriptorPoolSize::default() - .ty(vk::DescriptorType::STORAGE_BUFFER) - .descriptor_count(1); - let pool_size_image = vk::DescriptorPoolSize::default() - .ty(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) - .descriptor_count(1); - let pool_sizes = [pool_size_storage, pool_size_image]; +impl GameState for InWorld {} - let pool_info = vk::DescriptorPoolCreateInfo::default() - .max_sets(1) - .pool_sizes(&pool_sizes); - let pool = unsafe { - dev.create_descriptor_pool(&pool_info, None) - .expect("Failed to create descriptor pool") - }; - pool +pub struct Game { + state: S, } -fn make_swap_images( - dev: &Device, - swapchain: &vk::SwapchainKHR, - swapchain_loader: &swapchain::Device, -) -> Vec { - unsafe { - let images = swapchain_loader - .get_swapchain_images(*swapchain) - .expect("Failed to get swapchain images"); - println!("Fetched swapchain images"); - images - } -} - -fn make_swap_views( - dev: &Device, - swap_images: &Vec, - format: vk::Format, -) -> Vec { - let mut views = Vec::new(); - for img in swap_images.iter() { - let view_info = vk::ImageViewCreateInfo::default() - .image(*img) - .view_type(vk::ImageViewType::TYPE_2D) - .format(format) - .components(vk::ComponentMapping::default()) - .subresource_range( - vk::ImageSubresourceRange::default() - .aspect_mask(vk::ImageAspectFlags::COLOR) - .base_mip_level(0) - .level_count(1) - .base_array_layer(0) - .layer_count(1), - ); - let view = unsafe { - dev.create_image_view(&view_info, None) - .expect("Failed to create image view") - }; - views.push(view); - } - views -} - -fn make_depth_view(dev: &Device, depth_img: &vk::Image, format: vk::Format) -> vk::ImageView { - let view_info = vk::ImageViewCreateInfo::default() - .image(*depth_img) - .view_type(vk::ImageViewType::TYPE_2D) - .format(format) - .components(vk::ComponentMapping::default()) - .subresource_range( - vk::ImageSubresourceRange::default() - .aspect_mask(vk::ImageAspectFlags::DEPTH) - .base_mip_level(0) - .level_count(1) - .base_array_layer(0) - .layer_count(1), - ); - let view = unsafe { - dev.create_image_view(&view_info, None) - .expect("Failed to create image view") - }; - view -} - -fn make_framebufs( - dev: &Device, - swap_views: &Vec, - depth_view: &vk::ImageView, - pass: &vk::RenderPass, - width: u32, - height: u32, -) -> Vec { - let mut framebufs = Vec::new(); - for view in swap_views.iter() { - let attachments = [*view, *depth_view]; - let framebuf_info = vk::FramebufferCreateInfo::default() - .render_pass(*pass) - .attachments(attachments.as_ref()) - .width(width) - .height(height) - .layers(1); - let framebuf = unsafe { - dev.create_framebuffer(&framebuf_info, None) - .expect("Failed to create framebuffer") - }; - framebufs.push(framebuf); - } - framebufs -} - -fn make_desc_sets(dev: &Device, desc_pool: &vk::DescriptorPool) -> Vec { - let layout = setup_desc_layout(&dev); - let layouts = [layout]; - let alloc_info = vk::DescriptorSetAllocateInfo::default() - .descriptor_pool(*desc_pool) - .set_layouts(&layouts); - let sets = unsafe { - dev.allocate_descriptor_sets(&alloc_info) - .expect("Failed to allocate descriptor sets") - }; - sets -} - -fn write_desc_sets( - dev: &Device, - desc_sets: &Vec, - mesh_buf: &vk::Buffer, - image_view: vk::ImageView, - skybox_sampler: vk::Sampler, -) { - let buf_info = vk::DescriptorBufferInfo::default() - .buffer(*mesh_buf) - .offset(0) - .range(vk::WHOLE_SIZE); - let buf_infos = [buf_info]; - let buf_desc = vk::WriteDescriptorSet::default() - .dst_set(desc_sets[0]) - .dst_binding(0) - .dst_array_element(0) - .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) - .buffer_info(&buf_infos); - - let img_info = vk::DescriptorImageInfo::default() - .image_layout(vk::ImageLayout::GENERAL) - .image_view(image_view) - .sampler(skybox_sampler); - let img_infos = &[img_info]; - let img_desc = vk::WriteDescriptorSet::default() - .dst_set(desc_sets[0]) - .dst_binding(1) - .dst_array_element(0) - .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) - .image_info(img_infos); - - unsafe { - dev.update_descriptor_sets(&[buf_desc, img_desc], &[]); - } -} - -fn make_sem(dev: &Device) -> vk::Semaphore { - let sem_info = vk::SemaphoreCreateInfo::default(); - let sem = unsafe { - dev.create_semaphore(&sem_info, None) - .expect("Failed to create semaphore") - }; - sem -} - -fn make_fence(dev: &Device) -> vk::Fence { - let fence_info = vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED); - let fence = unsafe { - dev.create_fence(&fence_info, None) - .expect("Failed to create fence") - }; - fence -} - -fn wait_for_fence(dev: &Device, fence: &vk::Fence) { - unsafe { - let fence = [*fence]; - dev.wait_for_fences(&fence, true, u64::MAX) - .expect("Failed to wait for fence"); - } -} - -fn reset_fence(dev: &Device, fence: &vk::Fence) { - unsafe { - let fence = [*fence]; - dev.reset_fences(&fence).expect("Failed to reset fence"); - } -} - -fn ack_next_img( - dev: &Device, - swapchain_loader: &swapchain::Device, - swapchain: &vk::SwapchainKHR, - sem_avail: &vk::Semaphore, - fence: &vk::Fence, -) -> u32 { - unsafe { - let (img_inx, _) = swapchain_loader - .acquire_next_image(*swapchain, u64::MAX, *sem_avail, vk::Fence::null()) - .expect("Failed to acquire next image"); - img_inx - } -} - -fn record_commands( - dev: &Device, - cmd_buf: &vk::CommandBuffer, - framebuf: &vk::Framebuffer, - pass: &vk::RenderPass, - width: u32, - height: u32, - pipes: &[(vk::Pipeline, vk::PipelineLayout, &[vk::DescriptorSet])], - camera: &Camera, - skybox_buffer: &vk::Buffer, - skybox_image: &vk::Image, -) { - let clear_vals = [ - vk::ClearValue { - color: vk::ClearColorValue { - float32: [0.0, 0.0, 0.0, 1.0], - }, - }, - vk::ClearValue { - depth_stencil: vk::ClearDepthStencilValue { - depth: 1.0, - stencil: 0, - }, - }, - ]; - - let render_pass_info = vk::RenderPassBeginInfo::default() - .render_pass(*pass) - .framebuffer(*framebuf) - .render_area( - vk::Rect2D::default() - .offset(vk::Offset2D::default()) - .extent(vk::Extent2D { width, height }), - ) - .clear_values(&clear_vals); - - unsafe { - let regions = (0..6) - .map(|i| { - vk::BufferImageCopy::default() - .buffer_offset(3 * 2048 * 2048 * i) - .buffer_row_length(0) - .buffer_image_height(0) - .image_offset(vk::Offset3D::default()) - .image_extent(vk::Extent3D::default().width(2048).height(2048).depth(1)) - .image_subresource( - vk::ImageSubresourceLayers::default() - .aspect_mask(vk::ImageAspectFlags::COLOR) - .mip_level(0) - .layer_count(1) - .base_array_layer(i as u32), - ) - }) - .collect::>(); - dev.cmd_copy_buffer_to_image( - *cmd_buf, - *skybox_buffer, - *skybox_image, - vk::ImageLayout::GENERAL, - ®ions, - ); - dev.cmd_begin_render_pass(*cmd_buf, &render_pass_info, vk::SubpassContents::INLINE); - - let screen_res: Vec = (Vec::::from([width, height])) - .iter() - .map(|f| f.to_ne_bytes()) - .collect::>() - .into_flattened(); - let base_color: Vec = (Vec::::from([0.0, 1.0, 1.0, 1.0])) - .iter() - .map(|f| f.to_ne_bytes()) - .collect::>() - .into_flattened(); - let cam_data: Vec = (Vec::::from([ - camera.origin.x, - camera.origin.y, - camera.origin.z, - 0.0, - camera.rotation.x, - camera.rotation.y, - camera.rotation.z, - ])) - .iter() - .map(|f| f.to_ne_bytes()) - .collect::>() - .into_flattened(); - - for (pipe, layout, desc_sets) in pipes { - dev.cmd_bind_pipeline(*cmd_buf, vk::PipelineBindPoint::GRAPHICS, *pipe); - - dev.cmd_bind_descriptor_sets( - *cmd_buf, - vk::PipelineBindPoint::GRAPHICS, - *layout, - 0, - desc_sets.as_ref(), - &[], - ); - - dev.cmd_set_viewport_with_count( - *cmd_buf, - &[vk::Viewport::default() - .width(width as f32) - .height(height as f32)], - ); - dev.cmd_set_scissor_with_count( - *cmd_buf, - &[vk::Rect2D::default().extent(vk::Extent2D { width, height })], - ); - - dev.cmd_push_constants( - *cmd_buf, - *layout, - vk::ShaderStageFlags::VERTEX, - 0, - cam_data.as_ref(), - ); - dev.cmd_push_constants( - *cmd_buf, - *layout, - vk::ShaderStageFlags::VERTEX, - 32, - screen_res.as_ref(), - ); - dev.cmd_push_constants( - *cmd_buf, - *layout, - vk::ShaderStageFlags::FRAGMENT, - 48, - base_color.as_ref(), - ); - - dev.cmd_draw(*cmd_buf, 36, 1, 0, 0); - } - - dev.cmd_end_render_pass(*cmd_buf); - } -} - -fn start_cmd_buf(dev: &Device, cmd_buf: &vk::CommandBuffer) { - unsafe { - dev.reset_command_buffer(*cmd_buf, vk::CommandBufferResetFlags::empty()) - .expect("Failed to reset command buffer"); - let begin_info = - vk::CommandBufferBeginInfo::default().flags(vk::CommandBufferUsageFlags::empty()); - dev.begin_command_buffer(*cmd_buf, &begin_info) - .expect("Failed to begin command buffer"); - } -} - -fn end_cmd_buf(dev: &Device, cmd_buf: &vk::CommandBuffer) { - unsafe { - dev.end_command_buffer(*cmd_buf) - .expect("Failed to end command buffer"); - } -} - -fn submit_queue( - dev: &Device, - queue: &vk::Queue, - cmd_buf: &vk::CommandBuffer, - sem_avail: &vk::Semaphore, - sem_finish: &vk::Semaphore, - fence: &vk::Fence, -) { - let cmd_bufs = [*cmd_buf]; - let wait_sems = [*sem_avail]; - let signal_sems = [*sem_finish]; - let submit_info = vk::SubmitInfo::default() - .command_buffers(&cmd_bufs) - .wait_semaphores(&wait_sems) - .wait_dst_stage_mask(&[vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT]) - .signal_semaphores(&signal_sems); - unsafe { - dev.queue_submit(*queue, &[submit_info], *fence) - .expect("Failed to submit queue"); - } -} - -fn queue_present( - swapchain_loader: &swapchain::Device, - queue: &vk::Queue, - swapchain: &vk::SwapchainKHR, - img_inx: u32, - sem_finish: &vk::Semaphore, -) { - let swapchains = [*swapchain]; - let img_inxs = [img_inx]; - let wait_sems = [*sem_finish]; - let present_info = vk::PresentInfoKHR::default() - .swapchains(&swapchains) - .image_indices(&img_inxs) - .wait_semaphores(&wait_sems); - unsafe { - swapchain_loader - .queue_present(*queue, &present_info) - .expect("Failed to present queue"); - } -} - -#[derive(Debug)] -struct Vector3 { - x: f32, - y: f32, - z: f32, -} - -#[derive(Debug)] -struct Camera { - origin: Vector3, - rotation: Vector3, -} - -fn handle_cont_key(keycode: Keycode, camera: &mut Camera, mouse: &MouseUtil) { - match keycode { - Keycode::W => { - camera.origin.x -= -camera.rotation.y.sin() * MOV_STEP; - camera.origin.z -= camera.rotation.y.cos() * MOV_STEP; - } - Keycode::S => { - camera.origin.x += -camera.rotation.y.sin() * MOV_STEP; - camera.origin.z += camera.rotation.y.cos() * MOV_STEP; - } - Keycode::A => { - camera.origin.x += camera.rotation.y.cos() * MOV_STEP; - camera.origin.z += camera.rotation.y.sin() * MOV_STEP; - } - Keycode::D => { - camera.origin.z -= camera.rotation.y.sin() * MOV_STEP; - camera.origin.x -= camera.rotation.y.cos() * MOV_STEP; - } - Keycode::Space => { - camera.origin.y += MOV_STEP; - } - Keycode::LShift => { - camera.origin.y -= MOV_STEP; - } - _ => {} +impl Game { + pub fn new() -> Self { + Self { state: Unstarted } } } @@ -1154,390 +106,18 @@ fn decode_rif(raw: &[u8]) -> &[u8] { fn main() { let sdl_context = sdl2::init().unwrap(); let video_subsystem = sdl_context.video().unwrap(); - let mouse = sdl_context.mouse(); - - let mut width = 1024; - let mut height = 768; let mut window = video_subsystem - .window(APP_NAME, width, height) + .window(APP_NAME, 1024, 768) .vulkan() .resizable() .build() .unwrap(); - // We start in relative move - mouse.set_relative_mouse_mode(true); - - let entry = Entry::linked(); - - let instance = create_instance(&window, &entry); - - let surface = make_surface(&entry, &instance, &window); - - let pdev = find_pdev_dgpu(&instance); - - let rcs_queue_inx = find_rcs_queue_inx(&instance, &pdev).expect("No RCS queue found"); - - let dev = make_dev( - &instance, - &pdev, - rcs_queue_inx, - vec![c"VK_KHR_swapchain", c"VK_EXT_extended_dynamic_state3"], - ); - - let queue = unsafe { dev.get_device_queue(rcs_queue_inx, 0) }; - - let cmd_pool = make_command_pool(&dev, rcs_queue_inx); - - let cmd_buf = alloc_cmd_buf(&dev, cmd_pool); - - let host_visible_inx = - find_host_visible(&instance, &pdev).expect("No host visible memory found"); - - let host_invisible_inx = - find_host_invisible(&instance, &pdev).expect("No host invisible memory found"); - - let mesh_mem = mem_alloc(&dev, host_visible_inx, MESH_SIZE); - // Max viewport size is 3440x1440 - let depth_mem = mem_alloc( - &dev, - host_invisible_inx, - (MAX_WIDTH * MAX_HEIGHT * 8) as u64, - ); - - let mut depth_img = make_depth_img( - &dev, - host_invisible_inx, - width, - height, - depth_mem, - rcs_queue_inx, - ); - - let mesh_buf = alloc_buf( - &dev, - &mesh_mem, - MESH_SIZE, - vk::BufferUsageFlags::TRANSFER_SRC | vk::BufferUsageFlags::STORAGE_BUFFER, - ); - - let mesh_mem = buf_to_ptr(&dev, &mesh_mem, &mesh_buf); - unsafe { - std::ptr::copy(POSITIONS.as_ptr(), mesh_mem as *mut f32, POSITIONS.len()); - } - - let (skybox_image, skybox_image_view, skybox_sampler) = { - let qf_idxs = [rcs_queue_inx]; - let create_info = vk::ImageCreateInfo::default() - .flags(ImageCreateFlags::CUBE_COMPATIBLE) - .image_type(vk::ImageType::TYPE_2D) - .format(vk::Format::R8G8B8_UNORM) - .extent(vk::Extent3D::default().width(2048).height(2048).depth(1)) - .mip_levels(1) - .array_layers(6) - .samples(vk::SampleCountFlags::TYPE_1) - .tiling(vk::ImageTiling::OPTIMAL) - .usage(vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::SAMPLED) - .sharing_mode(vk::SharingMode::EXCLUSIVE) - .initial_layout(vk::ImageLayout::UNDEFINED) - .queue_family_indices(&qf_idxs); - let image = - unsafe { dev.create_image(&create_info, None) }.expect("Failed to create image"); - - let skybox_mem_size: vk::MemoryRequirements = - unsafe { dev.get_image_memory_requirements(image) }; - let skybox_dev_mem = mem_alloc(&dev, host_invisible_inx, skybox_mem_size.size); - unsafe { - dev.bind_image_memory(image, skybox_dev_mem, 0) - .expect("Failed to bind image memory"); - } - let create_info = vk::ImageViewCreateInfo::default() - .image(image) - .view_type(vk::ImageViewType::CUBE) - .format(vk::Format::R8G8B8_UNORM) - .components(vk::ComponentMapping::default()) - .subresource_range( - vk::ImageSubresourceRange::default() - .aspect_mask(vk::ImageAspectFlags::COLOR) - .base_mip_level(0) - .level_count(1) - .base_array_layer(0) - .layer_count(6), - ); - let image_view = unsafe { dev.create_image_view(&create_info, None) } - .expect("Failed to create image view"); - let create_info = vk::SamplerCreateInfo::default() - .mag_filter(vk::Filter::LINEAR) - .min_filter(vk::Filter::LINEAR) - .mipmap_mode(vk::SamplerMipmapMode::LINEAR) - .address_mode_u(vk::SamplerAddressMode::CLAMP_TO_EDGE) - .address_mode_v(vk::SamplerAddressMode::CLAMP_TO_EDGE) - .address_mode_w(vk::SamplerAddressMode::CLAMP_TO_EDGE) - .max_anisotropy(1.0) - .max_lod(1.0) - .border_color(vk::BorderColor::FLOAT_OPAQUE_WHITE); - let sampler = - unsafe { dev.create_sampler(&create_info, None) }.expect("Failed to create sampler"); - - (image, image_view, sampler) - }; - - let skybox_mem = mem_alloc(&dev, host_visible_inx, 3 * 2048 * 2048 * 6); - let skybox_buf = alloc_buf( - &dev, - &skybox_mem, - 3 * 2048 * 2048 * 6, - vk::BufferUsageFlags::TRANSFER_SRC, - ); - let skybox_ptr = buf_to_ptr(&dev, &skybox_mem, &skybox_buf); - let img = std::fs::read("resources/skybox.rif").unwrap(); - let data = decode_rif(&img); - unsafe { - std::ptr::copy(data.as_ptr(), skybox_ptr as *mut u8, data.len()); - } - - let skybox_vert_shader_bin = std::fs::read("shaders/skybox_vert.spv").unwrap(); - let skybox_frag_shader_bin = std::fs::read("shaders/skybox_frag.spv").unwrap(); - - let skybox_vert_shader = unsafe { - dev.create_shader_module( - &vk::ShaderModuleCreateInfo { - code_size: skybox_vert_shader_bin.len(), - p_code: skybox_vert_shader_bin.as_ptr() as *const u32, - ..Default::default() - }, - None, - ) - .expect("Failed to create vertex shader module") - }; - - let skybox_frag_shader = unsafe { - dev.create_shader_module( - &vk::ShaderModuleCreateInfo { - code_size: skybox_frag_shader_bin.len(), - p_code: skybox_frag_shader_bin.as_ptr() as *const u32, - ..Default::default() - }, - None, - ) - .expect("Failed to create fragment shader module") - }; - - let cube_vert_shader_bin = std::fs::read("shaders/cube_vert.spv").unwrap(); - let cube_frag_shader_bin = std::fs::read("shaders/cube_frag.spv").unwrap(); - - let cube_vert_shader = unsafe { - dev.create_shader_module( - &vk::ShaderModuleCreateInfo { - code_size: cube_vert_shader_bin.len(), - p_code: cube_vert_shader_bin.as_ptr() as *const u32, - ..Default::default() - }, - None, - ) - .expect("Failed to create vertex shader module") - }; - - let cube_frag_shader = unsafe { - dev.create_shader_module( - &vk::ShaderModuleCreateInfo { - code_size: cube_frag_shader_bin.len(), - p_code: cube_frag_shader_bin.as_ptr() as *const u32, - ..Default::default() - }, - None, - ) - .expect("Failed to create fragment shader module") - }; - - let pass = setup_render_pass(&dev); - - let pipe_layout = setup_pipe_layout(&dev); - let skybox_pipe = setup_pipeline( - &dev, - &skybox_vert_shader, - &skybox_frag_shader, - &pass, - &pipe_layout, - vk::CullModeFlags::NONE, - ); - let cube_pipe = setup_pipeline( - &dev, - &cube_vert_shader, - &cube_frag_shader, - &pass, - &pipe_layout, - vk::CullModeFlags::FRONT, - ); - - let surface_loader = surface::Instance::new(&entry, &instance); - let swapchain_loader = swapchain::Device::new(&instance, &dev); - - let (mut swapchain, mut extents) = setup_swapchain( - &pdev, - &dev, - &surface, - &surface_loader, - &swapchain_loader, - &instance, - width, - height, - ); - - let mut swap_images = make_swap_images(&dev, &swapchain, &swapchain_loader); - - let mut swap_views = make_swap_views(&dev, &swap_images, vk::Format::B8G8R8A8_UNORM); - - let mut depth_view = make_depth_view(&dev, &depth_img, vk::Format::D32_SFLOAT); - - let mut framebufs = make_framebufs(&dev, &swap_views, &depth_view, &pass, width, height); - - let desc_pool = setup_desc_pool(&dev); - - let desc_sets = make_desc_sets(&dev, &desc_pool); - - write_desc_sets( - &dev, - &desc_sets, - &mesh_buf, - skybox_image_view, - skybox_sampler, - ); - - let sem_avail = make_sem(&dev); - let sem_finish = make_sem(&dev); - let fence_flight = make_fence(&dev); - - let mut event_pump = sdl_context.event_pump().expect("Failed to get event pump"); - let mut camera = Camera { - origin: Vector3 { - x: 0.0, - y: 0.0, - z: 0.0, - }, - rotation: Vector3 { - x: 0.0, - y: 0.0, - z: 0.0, - }, - }; - - let mut running = true; - while running { - let now = Instant::now(); - for event in event_pump.poll_iter() { - match event { - Event::Quit { .. } => running = false, - Event::Window { - win_event: WindowEvent::Resized(w, h), - .. - } => { - width = w as u32; - height = h as u32; - unsafe { - dev.device_wait_idle() - .expect("Failed to wait for device idle"); - swapchain_loader.destroy_swapchain(swapchain, None); - } - (swapchain, _) = setup_swapchain( - &pdev, - &dev, - &surface, - &surface_loader, - &swapchain_loader, - &instance, - width, - height, - ); - swap_images = make_swap_images(&dev, &swapchain, &swapchain_loader); - swap_views = make_swap_views(&dev, &swap_images, vk::Format::B8G8R8A8_UNORM); - depth_img = make_depth_img( - &dev, - host_invisible_inx, - width, - height, - depth_mem, - rcs_queue_inx, - ); - depth_view = make_depth_view(&dev, &depth_img, vk::Format::D32_SFLOAT); - framebufs = - make_framebufs(&dev, &swap_views, &depth_view, &pass, width, height); - } - // We do this key here and not in `handle_cont_key()` because we don't want key - // repeat, just a single press - Event::KeyDown { - keycode: Some(Keycode::ESCAPE), - repeat: false, - .. - } => { - mouse.set_relative_mouse_mode(!mouse.relative_mouse_mode()); - } - Event::MouseMotion { - window_id, - xrel, - yrel, - .. - } if mouse.relative_mouse_mode() => { - // We only wanna do movement if the mouse is in relative mode (ie. its pinned to - // the center of the window and hidden) - camera.rotation.y -= xrel as f32 * ROT_MOUSE_SCALE; - camera.rotation.x -= yrel as f32 * ROT_MOUSE_SCALE; - mouse.warp_mouse_in_window(&window, width as i32 / 2, height as i32 / 2); - } - _ => {} - } - } - - event_pump - .keyboard_state() - .pressed_scancodes() - .filter_map(Keycode::from_scancode) - .for_each(|k| handle_cont_key(k, &mut camera, &mouse)); - - wait_for_fence(&dev, &fence_flight); - reset_fence(&dev, &fence_flight); - - let img_inx = ack_next_img( - &dev, - &swapchain_loader, - &swapchain, - &sem_avail, - &fence_flight, - ); - - start_cmd_buf(&dev, &cmd_buf); - - record_commands( - &dev, - &cmd_buf, - &framebufs[img_inx as usize], - &pass, - width, - height, - &[ - (cube_pipe, pipe_layout, &desc_sets), - (skybox_pipe, pipe_layout, &desc_sets), - ], - &camera, - &skybox_buf, - &skybox_image, - ); - end_cmd_buf(&dev, &cmd_buf); - - submit_queue( - &dev, - &queue, - &cmd_buf, - &sem_avail, - &sem_finish, - &fence_flight, - ); - - queue_present(&swapchain_loader, &queue, &swapchain, img_inx, &sem_finish); - - let elapsed = now.elapsed(); - let delay = (1_000_000u128 / 60).saturating_sub(elapsed.as_micros()); - thread::sleep(Duration::from_micros(delay as u64)); - } + let game = Game::new().init_render(sdl_context, window); + let mut game = game.start_load_world(); + let cube = game.create_cube(); + game.add_component(cube); + let game = game.start_world(); + let game = game.main_loop(); } diff --git a/src/render/cube.rs b/src/render/cube.rs new file mode 100644 index 0000000..937a534 --- /dev/null +++ b/src/render/cube.rs @@ -0,0 +1,413 @@ +use std::collections::HashMap; + +use ash::vk; + +use crate::{Game, LoadingWorld, RenderCtx, render::MemType}; + +use super::loading_world::WorldComponent; + +pub struct Cube { + mesh_buf: vk::Buffer, + pipeline: vk::Pipeline, + pipe_layout: vk::PipelineLayout, +} + +const MESH_SIZE: u64 = 36 * 2 * 4 * 4; + +#[rustfmt::skip] +const POSITIONS: [f32; 36 * 2 * 4] = [ + // BOTTOM + -0.5, 0.5, 1.0, 1.0, + 0.0, 1.0, 0.0, 0.0, + + 0.5, 0.5, 2.0, 1.0, + 0.0, 1.0, 0.0, 0.0, + + -0.5, 0.5, 2.0, 1.0, + 0.0, 1.0, 0.0, 0.0, + + 0.5, 0.5, 1.0, 1.0, + 0.0, 1.0, 0.0, 0.0, + + 0.5, 0.5, 2.0, 1.0, + 0.0, 1.0, 0.0, 0.0, + + -0.5, 0.5, 1.0, 1.0, + 0.0, 1.0, 0.0, 0.0, + + // TOP + 0.5, -0.5, 2.0, 1.0, + 0.0, -1.0, 0.0, 0.0, + + -0.5, -0.5, 1.0, 1.0, + 0.0, -1.0, 0.0, 0.0, + + -0.5, -0.5, 2.0, 1.0, + 0.0, -1.0, 0.0, 0.0, + + 0.5, -0.5, 2.0, 1.0, + 0.0, -1.0, 0.0, 0.0, + + 0.5, -0.5, 1.0, 1.0, + 0.0, -1.0, 0.0, 0.0, + + -0.5, -0.5, 1.0, 1.0, + 0.0, -1.0, 0.0, 0.0, + + // FRONT + -0.5, -0.5, 1.0, 1.0, + 0.0, 0.0, -1.0, 0.0, + + 0.5, 0.5, 1.0, 1.0, + 0.0, 0.0, -1.0, 0.0, + + -0.5, 0.5, 1.0, 1.0, + 0.0, 0.0, -1.0, 0.0, + + 0.5, -0.5, 1.0, 1.0, + 0.0, 0.0, -1.0, 0.0, + + 0.5, 0.5, 1.0, 1.0, + 0.0, 0.0, -1.0, 0.0, + + -0.5, -0.5, 1.0, 1.0, + 0.0, 0.0, -1.0, 0.0, + + // BACK + 0.5, 0.5, 2.0, 1.0, + 0.0, 0.0, 1.0, 0.0, + + -0.5, -0.5, 2.0, 1.0, + 0.0, 0.0, 1.0, 0.0, + + -0.5, 0.5, 2.0, 1.0, + 0.0, 0.0, 1.0, 0.0, + + 0.5, 0.5, 2.0, 1.0, + 0.0, 0.0, 1.0, 0.0, + + 0.5, -0.5, 2.0, 1.0, + 0.0, 0.0, 1.0, 0.0, + + -0.5, -0.5, 2.0, 1.0, + 0.0, 0.0, 1.0, 0.0, + + // LEFT + -0.5, -0.5, 1.0, 1.0, + -1.0, 0.0, 0.0, 0.0, + + -0.5, 0.5, 2.0, 1.0, + -1.0, 0.0, 0.0, 0.0, + + -0.5, -0.5, 2.0, 1.0, + -1.0, 0.0, 0.0, 0.0, + + -0.5, 0.5, 1.0, 1.0, + -1.0, 0.0, 0.0, 0.0, + + -0.5, 0.5, 2.0, 1.0, + -1.0, 0.0, 0.0, 0.0, + + -0.5, -0.5, 1.0, 1.0, + -1.0, 0.0, 0.0, 0.0, + + // RIGHT + 0.5, 0.5, 2.0, 1.0, + 1.0, 0.0, 0.0, 0.0, + + 0.5, -0.5, 1.0, 1.0, + 1.0, 0.0, 0.0, 0.0, + + 0.5, -0.5, 2.0, 1.0, + 1.0, 0.0, 0.0, 0.0, + + 0.5, 0.5, 2.0, 1.0, + 1.0, 0.0, 0.0, 0.0, + + 0.5, 0.5, 1.0, 1.0, + 1.0, 0.0, 0.0, 0.0, + + 0.5, -0.5, 1.0, 1.0, + 1.0, 0.0, 0.0, 0.0, +]; + +impl WorldComponent for Cube { + fn descriptors(&self) -> std::collections::HashMap { + let mut map = HashMap::new(); + map.insert(vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 1); + map.insert(vk::DescriptorType::STORAGE_BUFFER, 1); + map + } + + fn desc_layout(&self, ctx: &RenderCtx) -> vk::DescriptorSetLayout { + let storage_binding = vk::DescriptorSetLayoutBinding::default() + .binding(0) + .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) + .descriptor_count(1) + .stage_flags(vk::ShaderStageFlags::VERTEX); + let image_binding = vk::DescriptorSetLayoutBinding::default() + .binding(1) + .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) + .descriptor_count(1) + .stage_flags(vk::ShaderStageFlags::FRAGMENT); + let layouts = [storage_binding, image_binding]; + + let layout_info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&layouts); + let layout = unsafe { + ctx.dev + .create_descriptor_set_layout(&layout_info, None) + .expect("Failed to create descriptor set layout") + }; + layout + } + + fn write_desc_set( + &self, + ctx: &RenderCtx, + desc_set: vk::DescriptorSet, + skybox: (vk::ImageView, vk::Sampler), + ) { + let buf_info = vk::DescriptorBufferInfo::default() + .buffer(self.mesh_buf) + .offset(0) + .range(vk::WHOLE_SIZE); + let buf_infos = [buf_info]; + let buf_desc = vk::WriteDescriptorSet::default() + .dst_set(desc_set) + .dst_binding(0) + .dst_array_element(0) + .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) + .buffer_info(&buf_infos); + + let img_info = vk::DescriptorImageInfo::default() + .image_layout(vk::ImageLayout::GENERAL) + .image_view(skybox.0) + .sampler(skybox.1); + let img_infos = &[img_info]; + let img_desc = vk::WriteDescriptorSet::default() + .dst_set(desc_set) + .dst_binding(1) + .dst_array_element(0) + .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) + .image_info(img_infos); + + unsafe { + ctx.dev.update_descriptor_sets(&[buf_desc, img_desc], &[]); + } + } + + fn pipeline(&self) -> (vk::Pipeline, vk::PipelineLayout) { + (self.pipeline, self.pipe_layout) + } + + fn push_constants( + &self, + camera: &crate::game::Camera, + res: [u32; 2], + base_color: [f32; 4], + ) -> Vec<(vk::ShaderStageFlags, u32, Vec)> { + let mut constants = Vec::new(); + let cam_data: Vec = (Vec::::from([ + camera.origin.x, + camera.origin.y, + camera.origin.z, + 0.0, + camera.rotation.x, + camera.rotation.y, + camera.rotation.z, + ])) + .iter() + .map(|f| f.to_ne_bytes()) + .collect::>() + .into_flattened(); + constants.push((vk::ShaderStageFlags::VERTEX, 0, cam_data)); + + let screen_res: Vec = res + .iter() + .map(|f| f.to_ne_bytes()) + .collect::>() + .into_flattened(); + constants.push((vk::ShaderStageFlags::VERTEX, 32, screen_res)); + + let base_color: Vec = (Vec::::from([0.0, 1.0, 1.0, 1.0])) + .iter() + .map(|f| f.to_ne_bytes()) + .collect::>() + .into_flattened(); + constants.push((vk::ShaderStageFlags::FRAGMENT, 48, base_color)); + + constants + } +} + +impl RenderCtx { + fn create_cube(&self, pass: vk::RenderPass) -> Cube { + let mesh_mem = self.mem_alloc(MemType::HostVisibile, MESH_SIZE); + + let mesh_buf = self.alloc_buf( + mesh_mem, + MESH_SIZE, + vk::BufferUsageFlags::TRANSFER_SRC | vk::BufferUsageFlags::STORAGE_BUFFER, + ); + + let mesh_mem = self.buf_to_ptr(mesh_mem, mesh_buf); + unsafe { + std::ptr::copy(POSITIONS.as_ptr(), mesh_mem as *mut f32, POSITIONS.len()); + } + + let pipe_layout = setup_pipe_layout(self); + let pipeline = setup_pipeline(self, &pass, pipe_layout); + + Cube { + mesh_buf, + pipeline, + pipe_layout, + } + } +} + +impl Game { + pub fn create_cube(&self) -> Cube { + let Self { + state: LoadingWorld { ctx, pass, .. }, + } = self; + ctx.create_cube(*pass) + } +} + +fn load_cube_shaders(ctx: &RenderCtx) -> (vk::ShaderModule, vk::ShaderModule) { + let shader_vert_shader = ctx.shader_mod_from_file( + "shaders/shader_vert.spv", + vk::ShaderModuleCreateFlags::empty(), + ); + let shader_frag_shader = ctx.shader_mod_from_file( + "shaders/shader_rag.spv", + vk::ShaderModuleCreateFlags::empty(), + ); + + (shader_vert_shader, shader_frag_shader) +} + +fn setup_desc_layout(ctx: &RenderCtx) -> vk::DescriptorSetLayout { + let storage_binding = vk::DescriptorSetLayoutBinding::default() + .binding(0) + .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) + .descriptor_count(1) + .stage_flags(vk::ShaderStageFlags::VERTEX); + let image_binding = vk::DescriptorSetLayoutBinding::default() + .binding(1) + .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) + .descriptor_count(1) + .stage_flags(vk::ShaderStageFlags::FRAGMENT); + let layouts = [storage_binding, image_binding]; + + let layout_info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&layouts); + let layout = unsafe { + ctx.dev + .create_descriptor_set_layout(&layout_info, None) + .expect("Failed to create descriptor set layout") + }; + layout +} + +fn setup_pipe_layout(ctx: &RenderCtx) -> vk::PipelineLayout { + let layout = setup_desc_layout(ctx); + let push_range: [vk::PushConstantRange; 2] = [ + vk::PushConstantRange::default() + .stage_flags(vk::ShaderStageFlags::VERTEX) + .offset(0) + .size(48), // vec4 camera_orig, camera_rot; uvec2 screen_res + vk::PushConstantRange::default() + .stage_flags(vk::ShaderStageFlags::FRAGMENT) + .offset(48) + .size(16), // vec4 base_color + ]; + + let layouts = &[layout]; + + let pipe_layout = vk::PipelineLayoutCreateInfo::default() + .set_layouts(layouts.as_ref()) + .push_constant_ranges(&push_range); + let pipe_layout = unsafe { + ctx.dev + .create_pipeline_layout(&pipe_layout, None) + .expect("Failed to create pipeline layout") + }; + pipe_layout +} + +fn setup_pipeline( + ctx: &RenderCtx, + pass: &vk::RenderPass, + layout: vk::PipelineLayout, +) -> vk::Pipeline { + let (vert_shader, frag_shader) = load_cube_shaders(ctx); + let shader_stages = ctx.setup_simple_shader_stage(vert_shader, frag_shader); + let vert_input = vk::PipelineVertexInputStateCreateInfo::default(); + let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default() + .topology(vk::PrimitiveTopology::TRIANGLE_LIST); + let rasterization = vk::PipelineRasterizationStateCreateInfo::default() + .polygon_mode(vk::PolygonMode::FILL) + .cull_mode(vk::CullModeFlags::BACK) + .front_face(vk::FrontFace::COUNTER_CLOCKWISE) + .line_width(1.0); + let multisample = vk::PipelineMultisampleStateCreateInfo::default() + .rasterization_samples(vk::SampleCountFlags::TYPE_1); + let depth_stencil = vk::PipelineDepthStencilStateCreateInfo::default() + .depth_test_enable(true) + .depth_write_enable(true) + .depth_compare_op(vk::CompareOp::LESS) + .depth_bounds_test_enable(true) + .stencil_test_enable(false) + .min_depth_bounds(0.0) + .max_depth_bounds(1.0); + + let blend = vk::PipelineColorBlendAttachmentState::default() + .src_color_blend_factor(vk::BlendFactor::SRC_COLOR) + .dst_color_blend_factor(vk::BlendFactor::SRC_COLOR) + .color_blend_op(vk::BlendOp::ADD) + .src_alpha_blend_factor(vk::BlendFactor::SRC_COLOR) + .dst_alpha_blend_factor(vk::BlendFactor::SRC_COLOR) + .alpha_blend_op(vk::BlendOp::ADD) + .color_write_mask( + vk::ColorComponentFlags::R + | vk::ColorComponentFlags::G + | vk::ColorComponentFlags::B + | vk::ColorComponentFlags::A, + ); + + let blend_attachments = [blend]; + + let color_blend = vk::PipelineColorBlendStateCreateInfo::default() + .attachments(blend_attachments.as_ref()) + .logic_op_enable(false) + .logic_op(vk::LogicOp::COPY) + .blend_constants([1.0, 1.0, 1.0, 1.0]); + + let dynamic = vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&[ + vk::DynamicState::VIEWPORT_WITH_COUNT, + vk::DynamicState::SCISSOR_WITH_COUNT, + ]); + + let pipe_info = vk::GraphicsPipelineCreateInfo::default() + .stages(&shader_stages) + .vertex_input_state(&vert_input) + .input_assembly_state(&input_assembly) + .rasterization_state(&rasterization) + .multisample_state(&multisample) + .depth_stencil_state(&depth_stencil) + .color_blend_state(&color_blend) + .layout(layout) + .dynamic_state(&dynamic) + .render_pass(*pass) + .subpass(0); + + let pipe = unsafe { + ctx.dev + .create_graphics_pipelines(vk::PipelineCache::null(), &[pipe_info], None) + .expect("Failed to create graphics pipeline") + .remove(0) + }; + println!("Created pipeline"); + pipe +} diff --git a/src/render/in_world.rs b/src/render/in_world.rs new file mode 100644 index 0000000..02ce576 --- /dev/null +++ b/src/render/in_world.rs @@ -0,0 +1,284 @@ +use ash::vk::{self}; + +use crate::{Game, InWorld, RenderCtx, Window}; + +use super::{ + SwapchainCtx, + loading_world::{Component, WorldComponent as _}, + swapchain::{ + make_depth_img, make_depth_view, make_framebufs, make_swap_images, make_swap_views, + setup_swapchain, + }, +}; + +impl Game { + pub fn window_resize(&mut self, size: (u32, u32)) { + let Self { + state: + InWorld { + sdl_context, + window, + ctx, + skybox, + components, + sem_avail, + sem_finish, + fence_flight, + swapchain: + SwapchainCtx { + surface_loader, + swapchain_loader, + swap_images, + swap_views, + depth_mem, + depth_image, + depth_view, + framebufs, + swapchain, + }, + pass, + camera, + }, + } = self; + + window.width = size.0; + window.height = size.1; + + unsafe { + ctx.dev + .device_wait_idle() + .expect("Failed to wait for device idle"); + swapchain_loader.destroy_swapchain(*swapchain, None); + } + (*swapchain, _) = setup_swapchain( + &ctx, + &surface_loader, + &swapchain_loader, + window.width, + window.height, + ); + *swap_images = make_swap_images(*swapchain, swapchain_loader); + *swap_views = make_swap_views(&ctx, &swap_images, vk::Format::B8G8R8A8_UNORM); + *depth_image = make_depth_img(&ctx, window.width, window.height, *depth_mem); + *depth_view = make_depth_view(&ctx, *depth_image, vk::Format::D32_SFLOAT); + *framebufs = make_framebufs( + &ctx, + &swap_views, + &depth_view, + &pass, + window.width, + window.height, + ); + } + + pub fn render_frame(&self) { + let Self { + state: + InWorld { + sdl_context, + window, + ctx, + skybox, + components, + sem_avail, + sem_finish, + fence_flight, + swapchain, + pass, + camera, + }, + } = self; + + ctx.wait_for_fence(*fence_flight); + ctx.reset_fence(*fence_flight); + + let img_idx = ack_next_img(swapchain, sem_avail); + + ctx.start_cmd_buf(); + + self.record_commands(img_idx as usize); + + ctx.end_cmd_buf(); + + ctx.submit_queue(*sem_avail, *sem_finish, *fence_flight); + + ctx.queue_present(swapchain, img_idx, *sem_finish); + } + + fn record_commands(&self, img_idx: usize) { + let Self { + state: + InWorld { + sdl_context, + window: Window { width, height }, + ctx: RenderCtx { dev, cmd_buf, .. }, + skybox, + components, + swapchain, + pass, + camera, + .. + }, + } = self; + + let clear_vals = [ + vk::ClearValue { + color: vk::ClearColorValue { + float32: [0.0, 0.0, 0.0, 1.0], + }, + }, + vk::ClearValue { + depth_stencil: vk::ClearDepthStencilValue { + depth: 1.0, + stencil: 0, + }, + }, + ]; + + let render_pass_info = vk::RenderPassBeginInfo::default() + .render_pass(*pass) + .framebuffer(swapchain.framebufs[img_idx]) + .render_area( + vk::Rect2D::default() + .offset(vk::Offset2D::default()) + .extent(vk::Extent2D { + width: *width, + height: *height, + }), + ) + .clear_values(&clear_vals); + + unsafe { + let regions = (0..6) + .map(|i| { + vk::BufferImageCopy::default() + .buffer_offset(3 * 2048 * 2048 * i) + .buffer_row_length(0) + .buffer_image_height(0) + .image_offset(vk::Offset3D::default()) + .image_extent(vk::Extent3D::default().width(2048).height(2048).depth(1)) + .image_subresource( + vk::ImageSubresourceLayers::default() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .mip_level(0) + .layer_count(1) + .base_array_layer(i as u32), + ) + }) + .collect::>(); + dev.cmd_copy_buffer_to_image( + *cmd_buf, + skybox.0.buf, + skybox.0.image, + vk::ImageLayout::GENERAL, + ®ions, + ); + dev.cmd_begin_render_pass(*cmd_buf, &render_pass_info, vk::SubpassContents::INLINE); + + let base_color = [0.0, 1.0, 1.0, 1.0]; + + for Component { inner, desc_set } in components { + let (pipe, layout) = inner.pipeline(); + dev.cmd_bind_pipeline(*cmd_buf, vk::PipelineBindPoint::GRAPHICS, pipe); + + let desc_sets = [*desc_set]; + + dev.cmd_bind_descriptor_sets( + *cmd_buf, + vk::PipelineBindPoint::GRAPHICS, + layout, + 0, + desc_sets.as_ref(), + &[], + ); + + dev.cmd_set_viewport_with_count( + *cmd_buf, + &[vk::Viewport::default() + .width(*width as f32) + .height(*height as f32)], + ); + dev.cmd_set_scissor_with_count( + *cmd_buf, + &[vk::Rect2D::default().extent(vk::Extent2D { + width: *width, + height: *height, + })], + ); + + inner + .push_constants(camera, [*width, *height], base_color) + .into_iter() + .for_each(|(f, o, d)| { + dev.cmd_push_constants( + *cmd_buf, + layout, + vk::ShaderStageFlags::VERTEX, + 0, + d.as_ref(), + ); + }); + + dev.cmd_draw(*cmd_buf, 36, 1, 0, 0); + } + + { + let (pipe, layout) = skybox.0.pipeline(); + dev.cmd_bind_pipeline(*cmd_buf, vk::PipelineBindPoint::GRAPHICS, pipe); + + let desc_sets = [skybox.1]; + + dev.cmd_bind_descriptor_sets( + *cmd_buf, + vk::PipelineBindPoint::GRAPHICS, + layout, + 0, + desc_sets.as_ref(), + &[], + ); + + dev.cmd_set_viewport_with_count( + *cmd_buf, + &[vk::Viewport::default() + .width(*width as f32) + .height(*height as f32)], + ); + dev.cmd_set_scissor_with_count( + *cmd_buf, + &[vk::Rect2D::default().extent(vk::Extent2D { + width: *width, + height: *height, + })], + ); + + skybox + .0 + .push_constants(camera, [*width, *height], base_color) + .into_iter() + .for_each(|(f, o, d)| { + dev.cmd_push_constants( + *cmd_buf, + layout, + vk::ShaderStageFlags::VERTEX, + 0, + d.as_ref(), + ); + }); + + dev.cmd_draw(*cmd_buf, 36, 1, 0, 0); + } + + dev.cmd_end_render_pass(*cmd_buf); + } + } +} + +fn ack_next_img(swapchain: &SwapchainCtx, sem_avail: &vk::Semaphore) -> u32 { + unsafe { + let (img_inx, _) = swapchain + .swapchain_loader + .acquire_next_image(swapchain.swapchain, u64::MAX, *sem_avail, vk::Fence::null()) + .expect("Failed to acquire next image"); + img_inx + } +} diff --git a/src/render/init.rs b/src/render/init.rs new file mode 100644 index 0000000..34efea4 --- /dev/null +++ b/src/render/init.rs @@ -0,0 +1,250 @@ +use std::ffi::{CStr, CString}; + +use ash::{ + Device, Entry, Instance, + khr::surface, + vk::{self}, +}; +use raw_window_handle::{HasDisplayHandle as _, HasWindowHandle as _}; +use sdl2::{Sdl, video::Window}; + +use crate::{APP_NAME, Game, RenderAvailable, RenderCtx, Unstarted}; + +impl Game { + pub fn init_render(self, sdl_context: Sdl, window: Window) -> Game { + let (width, height) = window.size(); + + let entry = Entry::linked(); + + let instance = create_instance(&window, &entry); + + let surface = make_surface(&entry, &instance, &window); + + let pdev = find_pdev_dgpu(&instance); + + let queue_idx = find_rcs_queue_inx(&instance, &pdev).expect("No RCS queue found"); + + let dev = make_dev( + &instance, + &pdev, + queue_idx, + vec![c"VK_KHR_swapchain", c"VK_EXT_extended_dynamic_state3"], + ); + + let queue = unsafe { dev.get_device_queue(queue_idx, 0) }; + + let cmd_pool = make_command_pool(&dev, queue_idx); + + let cmd_buf = alloc_cmd_buf(&dev, cmd_pool); + + let host_vis_idx = + find_host_visible(&instance, &pdev).expect("No host visible memory found"); + + let host_invis_idx = + find_host_invisible(&instance, &pdev).expect("No host invisible memory found"); + + let surface_loader = surface::Instance::new(&entry, &instance); + + Game { + state: RenderAvailable { + sdl_context, + window: crate::Window { width, height }, + ctx: RenderCtx { + entry, + instance, + pdev, + dev, + surface, + surface_loader, + queue, + queue_idx, + cmd_buf, + host_vis_idx, + host_invis_idx, + }, + }, + } + } +} + +fn create_instance(window: &Window, entry: &Entry) -> Instance { + let n_app_name = CString::new(APP_NAME).unwrap(); + let n_engine_name = CString::new(format!("{} Engine", APP_NAME)).unwrap(); + + let appinfo = vk::ApplicationInfo::default() + .application_name(n_app_name.as_c_str()) + .application_version(0) + .engine_name(n_engine_name.as_c_str()) + .engine_version(0) + .api_version(vk::make_api_version(0, 1, 3, 0)); + + let default_exts = window + .vulkan_instance_extensions() + .expect("Failed to get list of required extensions from SDL2"); + + let mut exts = vec![]; + exts.extend(default_exts); + + let exts: Vec<*const i8> = exts + .iter() + .map(|s| s.as_ptr() as *const i8) + .collect::>(); + let exts: &[*const i8] = exts.as_slice(); + + let insinfo = vk::InstanceCreateInfo::default() + .application_info(&appinfo) + .enabled_extension_names(exts); + + let instance = + unsafe { entry.create_instance(&insinfo, None) }.expect("Failed to create instance"); + println!("Created instance"); + instance +} + +fn make_surface(entry: &Entry, instance: &Instance, window: &Window) -> vk::SurfaceKHR { + let surface = unsafe { + ash_window::create_surface( + &entry, + &instance, + window.display_handle().unwrap().as_raw(), + window.window_handle().unwrap().as_raw(), + None, + ) + } + .expect("Failed to create surface"); + println!("Created surface"); + surface +} + +fn find_pdev_dgpu(instance: &Instance) -> vk::PhysicalDevice { + let mut pdevs = + unsafe { instance.enumerate_physical_devices() }.expect("Failed to enumerate devices"); + for (i, d) in pdevs.iter().enumerate() { + let props = unsafe { instance.get_physical_device_properties(*d) }; + if props.device_type == vk::PhysicalDeviceType::DISCRETE_GPU { + // TODO this assumes that the first discrete GPU will have an RCS queue family + // This is not guaranteed by the spec (example, a compute-only GPU) + // Fix. + println!( + "Found discrete GPU: {}", + unsafe { std::str::from_utf8(std::mem::transmute(props.device_name.as_slice())) } + .unwrap() + ); + return pdevs.remove(i); + } + } + println!("No discrete GPU found"); + assert!(pdevs.len() > 0, "No GPU found"); + return pdevs.remove(0); +} + +// Using Intel GPU hardware terminology 🥴 +fn find_rcs_queue_inx(instance: &Instance, pdev: &vk::PhysicalDevice) -> Option { + let qfams = unsafe { instance.get_physical_device_queue_family_properties(*pdev) }; + for (inx, qfam) in qfams.iter().enumerate() { + if qfam + .queue_flags + .contains(vk::QueueFlags::GRAPHICS | vk::QueueFlags::COMPUTE) + { + println!("Found RCS queue at index {}", inx); + return Some(inx as u32); + } + } + println!("No RCS queue found"); + return None; +} + +fn make_dev( + instance: &Instance, + pdev: &vk::PhysicalDevice, + rcs_queue_inx: u32, + exts: Vec<&CStr>, +) -> Device { + unsafe { + let dev_queue = vec![ + vk::DeviceQueueCreateInfo::default() + .queue_family_index(rcs_queue_inx) + .queue_priorities(&[1.0]), + ]; + + let exts = exts.iter().map(|&s| s.as_ptr()).collect::>(); + let exts = exts.as_slice(); + + // Enable all features + let features = instance.get_physical_device_features(*pdev); + + let dev_info = vk::DeviceCreateInfo::default() + .queue_create_infos(&dev_queue) + .enabled_extension_names(exts) + .enabled_features(&features); + + let dev = instance + .create_device(*pdev, &dev_info, None) + .expect("Failed to create device"); + println!("Created device"); + dev + } +} + +fn make_command_pool(dev: &Device, rcs_queue_inx: u32) -> vk::CommandPool { + unsafe { + let pool_info = vk::CommandPoolCreateInfo::default() + .queue_family_index(rcs_queue_inx) + .flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER); + let pool = dev + .create_command_pool(&pool_info, None) + .expect("Failed to create command pool"); + println!("Created command pool"); + pool + } +} + +fn alloc_cmd_buf(dev: &Device, pool: vk::CommandPool) -> vk::CommandBuffer { + unsafe { + let alloc_info = vk::CommandBufferAllocateInfo::default() + .command_pool(pool) + .level(vk::CommandBufferLevel::PRIMARY) + .command_buffer_count(1); + let cmd_buf = dev + .allocate_command_buffers(&alloc_info) + .expect("Failed to allocate command buffer") + .remove(0); + println!("Allocated command buffer"); + cmd_buf + } +} + +fn find_host_visible(instance: &Instance, pdev: &vk::PhysicalDevice) -> Option { + let mem_props = unsafe { instance.get_physical_device_memory_properties(*pdev) }; + + for (i, mem_type) in mem_props.memory_types.iter().enumerate() { + if mem_type.property_flags.contains( + vk::MemoryPropertyFlags::DEVICE_LOCAL + | vk::MemoryPropertyFlags::HOST_VISIBLE + | vk::MemoryPropertyFlags::HOST_COHERENT, + ) { + println!("Found host visible memory at index {}", i); + return Some(i as u32); + } + } + println!("No host visible memory found"); + return None; +} + +fn find_host_invisible(instance: &Instance, pdev: &vk::PhysicalDevice) -> Option { + let mem_props = unsafe { instance.get_physical_device_memory_properties(*pdev) }; + for (i, mem_type) in mem_props.memory_types.iter().enumerate() { + if mem_type + .property_flags + .contains(vk::MemoryPropertyFlags::DEVICE_LOCAL) + && !mem_type.property_flags.contains( + vk::MemoryPropertyFlags::HOST_VISIBLE | vk::MemoryPropertyFlags::HOST_COHERENT, + ) + { + println!("Found host invisible memory at index {}", i); + return Some(i as u32); + } + } + println!("No host invisible memory found"); + return None; +} diff --git a/src/render/loading_world.rs b/src/render/loading_world.rs new file mode 100644 index 0000000..8d04ff6 --- /dev/null +++ b/src/render/loading_world.rs @@ -0,0 +1,322 @@ +use std::{collections::HashMap, iter}; + +use ash::{ + khr::{surface, swapchain}, + vk::{self, DescriptorSet}, +}; + +use crate::{ + Game, InWorld, LoadingWorld, RenderAvailable, RenderCtx, game::Camera, render::MemType, +}; + +use super::{ + MAX_HEIGHT, MAX_WIDTH, SwapchainCtx, + swapchain::{ + make_depth_img, make_depth_view, make_framebufs, make_swap_images, make_swap_views, + setup_swapchain, + }, +}; + +/// Anything can be rendered in world +pub trait WorldComponent { + fn descriptors(&self) -> HashMap; + + fn desc_layout(&self, ctx: &RenderCtx) -> vk::DescriptorSetLayout; + + /// Skybox is passed in for reflections + fn write_desc_set( + &self, + ctx: &RenderCtx, + desc_set: vk::DescriptorSet, + skybox: (vk::ImageView, vk::Sampler), + ); + + fn pipeline(&self) -> (vk::Pipeline, vk::PipelineLayout); + + fn push_constants( + &self, + camera: &Camera, + res: [u32; 2], + base_color: [f32; 4], + ) -> Vec<(vk::ShaderStageFlags, u32, Vec)>; +} + +pub struct Component { + pub inner: Box, + pub desc_set: DescriptorSet, +} + +impl Game { + pub fn start_load_world(self) -> Game { + let Self { + state: + RenderAvailable { + sdl_context, + ctx, + window, + }, + } = self; + + let depth_mem = ctx.mem_alloc(MemType::HostInvisible, (MAX_WIDTH * MAX_HEIGHT * 8) as u64); + + let pass = setup_render_pass(&ctx); + + let surface_loader = surface::Instance::new(&ctx.entry, &ctx.instance); + let swapchain_loader = swapchain::Device::new(&ctx.instance, &ctx.dev); + + let (swapchain, extents) = setup_swapchain( + &ctx, + &surface_loader, + &swapchain_loader, + window.width, + window.height, + ); + + let depth_img = make_depth_img(&ctx, window.width, window.height, depth_mem); + let swap_images = make_swap_images(swapchain, &swapchain_loader); + let swap_views = make_swap_views(&ctx, &swap_images, vk::Format::B8G8R8A8_UNORM); + let depth_view = make_depth_view(&ctx, depth_img, vk::Format::D32_SFLOAT); + let framebufs = make_framebufs( + &ctx, + &swap_views, + &depth_view, + &pass, + window.width, + window.height, + ); + + let skybox = ctx.create_skybox(pass); + + let requested_descriptors = skybox.descriptors(); + + Game { + state: LoadingWorld { + sdl_context, + ctx, + window, + + requested_descriptors, + + swapchain: SwapchainCtx { + surface_loader, + swapchain_loader, + swap_images, + swap_views, + depth_mem, + depth_image: depth_img, + depth_view, + framebufs, + swapchain, + }, + pass, + + skybox, + components: Vec::new(), + }, + } + } +} + +impl Game { + pub fn add_component(&mut self, component: C) { + let Self { + state: + LoadingWorld { + requested_descriptors, + components, + .. + }, + } = self; + for (desc, n) in component.descriptors() { + match requested_descriptors.remove(&desc) { + Some(m) => requested_descriptors.insert(desc, n + m), + None => requested_descriptors.insert(desc, n), + }; + } + components.push(Box::new(component)); + } + + pub fn start_world(self) -> Game { + let Self { + state: + LoadingWorld { + sdl_context, + window, + ctx, + + requested_descriptors, + + swapchain, + pass, + + skybox, + components, + }, + } = self; + let desc_pool = setup_desc_pool(&ctx, requested_descriptors); + + let mut desc_sets = make_desc_sets( + &ctx, + &desc_pool, + &components + .iter() + .map(|c| c.desc_layout(&ctx)) + .chain(iter::once(skybox.desc_layout(&ctx))) + .collect::>(), + ); + + let skybox_descriptor = desc_sets.pop().unwrap(); + + let components = components + .into_iter() + .zip(desc_sets.into_iter()) + .map(|(c, d)| Component { + inner: c, + desc_set: d, + }) + .collect::>(); + + for comp in &components { + let Component { inner, desc_set } = comp; + inner.write_desc_set(&ctx, *desc_set, (skybox.imageview, skybox.sampler)); + } + + skybox.write_desc_set(&ctx, skybox_descriptor, (skybox.imageview, skybox.sampler)); + + let sem_avail = ctx.make_sem(); + let sem_finish = ctx.make_sem(); + let fence_flight = ctx.make_fence(); + + Game { + state: InWorld { + sdl_context, + window, + ctx, + + skybox: (skybox, skybox_descriptor), + components, + + sem_avail, + sem_finish, + fence_flight, + + swapchain, + pass, + + camera: Camera::default(), + }, + } + } +} + +fn setup_render_pass(ctx: &RenderCtx) -> vk::RenderPass { + let color_attach = vk::AttachmentDescription::default() + .format(vk::Format::B8G8R8A8_UNORM) + .samples(vk::SampleCountFlags::TYPE_1) + .load_op(vk::AttachmentLoadOp::CLEAR) + .store_op(vk::AttachmentStoreOp::STORE) + .stencil_load_op(vk::AttachmentLoadOp::DONT_CARE) + .stencil_store_op(vk::AttachmentStoreOp::DONT_CARE) + .initial_layout(vk::ImageLayout::UNDEFINED) + .final_layout(vk::ImageLayout::PRESENT_SRC_KHR); + let depth_attach = vk::AttachmentDescription::default() + .format(vk::Format::D32_SFLOAT) + .samples(vk::SampleCountFlags::TYPE_1) + .load_op(vk::AttachmentLoadOp::CLEAR) + .store_op(vk::AttachmentStoreOp::DONT_CARE) + .stencil_load_op(vk::AttachmentLoadOp::DONT_CARE) + .stencil_store_op(vk::AttachmentStoreOp::DONT_CARE) + .initial_layout(vk::ImageLayout::UNDEFINED) + .final_layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL); + + let color_ref = vk::AttachmentReference::default() + .attachment(0) + .layout(vk::ImageLayout::COLOR_ATTACHMENT_OPTIMAL); + + let depth_ref = vk::AttachmentReference::default() + .attachment(1) + .layout(vk::ImageLayout::DEPTH_STENCIL_ATTACHMENT_OPTIMAL); + + let color_refs = [color_ref]; + let depth_refs = [depth_ref]; + + let subpass = vk::SubpassDescription::default() + .pipeline_bind_point(vk::PipelineBindPoint::GRAPHICS) + .color_attachments(color_refs.as_ref()) + .depth_stencil_attachment(&depth_ref); + + let subpass_dep = vk::SubpassDependency::default() + .src_subpass(vk::SUBPASS_EXTERNAL) + .dst_subpass(0) + .src_stage_mask( + vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT + | vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS, + ) + .dst_stage_mask( + vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT + | vk::PipelineStageFlags::EARLY_FRAGMENT_TESTS, + ) + .src_access_mask(vk::AccessFlags::empty()) + .dst_access_mask( + vk::AccessFlags::COLOR_ATTACHMENT_WRITE + | vk::AccessFlags::DEPTH_STENCIL_ATTACHMENT_WRITE, + ); + + let attach_desc = [color_attach, depth_attach]; + + let subpasses = [subpass]; + + let deps = [subpass_dep]; + + let pass_info = vk::RenderPassCreateInfo::default() + .attachments(&attach_desc) + .subpasses(subpasses.as_ref()) + .dependencies(deps.as_ref()); + + let pass = unsafe { + ctx.dev + .create_render_pass(&pass_info, None) + .expect("Failed to create render pass") + }; + pass +} + +fn setup_desc_pool( + ctx: &RenderCtx, + requested_descriptors: HashMap, +) -> vk::DescriptorPool { + let pool_sizes = requested_descriptors + .into_iter() + .map(|(desc, count)| { + vk::DescriptorPoolSize::default() + .ty(desc) + .descriptor_count(count) + }) + .collect::>(); + + let pool_info = vk::DescriptorPoolCreateInfo::default() + .max_sets(1) + .pool_sizes(&pool_sizes); + let pool = unsafe { + ctx.dev + .create_descriptor_pool(&pool_info, None) + .expect("Failed to create descriptor pool") + }; + pool +} + +fn make_desc_sets( + ctx: &RenderCtx, + desc_pool: &vk::DescriptorPool, + layouts: &[vk::DescriptorSetLayout], +) -> Vec { + let alloc_info = vk::DescriptorSetAllocateInfo::default() + .descriptor_pool(*desc_pool) + .set_layouts(&layouts); + let sets = unsafe { + ctx.dev + .allocate_descriptor_sets(&alloc_info) + .expect("Failed to allocate descriptor sets") + }; + sets +} diff --git a/src/render/mod.rs b/src/render/mod.rs new file mode 100644 index 0000000..838f979 --- /dev/null +++ b/src/render/mod.rs @@ -0,0 +1,210 @@ +use ash::{ + khr::{surface, swapchain as khr_swapchain}, + vk, +}; + +use crate::RenderCtx; + +mod cube; +pub mod in_world; +mod init; +pub mod loading_world; +pub mod skybox; +mod swapchain; + +const MAX_WIDTH: u32 = 3440; +const MAX_HEIGHT: u32 = 1440; + +pub struct SwapchainCtx { + surface_loader: surface::Instance, + swapchain_loader: khr_swapchain::Device, + swap_images: Vec, + swap_views: Vec, + depth_mem: vk::DeviceMemory, + depth_image: vk::Image, + depth_view: vk::ImageView, + framebufs: Vec, + swapchain: vk::SwapchainKHR, +} + +pub enum MemType { + HostVisibile, + HostInvisible, +} + +impl RenderCtx { + pub fn mem_alloc(&self, typ: MemType, size: u64) -> vk::DeviceMemory { + let mem_info = vk::MemoryAllocateInfo::default() + .allocation_size(size) + .memory_type_index(match typ { + MemType::HostVisibile => self.host_vis_idx, + MemType::HostInvisible => self.host_invis_idx, + }); + let mem = unsafe { self.dev.allocate_memory(&mem_info, None) } + .expect("Failed to allocate memory"); + println!("Allocated memory"); + mem + } + + pub fn alloc_buf( + &self, + mem: vk::DeviceMemory, + size: vk::DeviceSize, + usage: vk::BufferUsageFlags, + ) -> vk::Buffer { + unsafe { + let buf_info = vk::BufferCreateInfo::default() + .size(size) + .usage(usage) + .sharing_mode(vk::SharingMode::EXCLUSIVE); + let buf = self + .dev + .create_buffer(&buf_info, None) + .expect("Failed to create buffer"); + + self.dev + .bind_buffer_memory(buf, mem, 0) + .expect("Failed to bind buffer memory"); + println!("Created buffer"); + buf + } + } + + pub fn buf_to_ptr(&self, mem: vk::DeviceMemory, buf: vk::Buffer) -> *mut std::ffi::c_void { + let ptr = unsafe { + self.dev + .map_memory(mem, 0, vk::WHOLE_SIZE, vk::MemoryMapFlags::empty()) + } + .expect("Failed to map memory"); + println!("Mapped memory"); + ptr + } + + pub fn shader_mod_from_file( + &self, + file: &str, + flags: vk::ShaderModuleCreateFlags, + ) -> vk::ShaderModule { + let shader_bin = std::fs::read(file).unwrap(); + + unsafe { + self.dev.create_shader_module( + &vk::ShaderModuleCreateInfo { + code_size: shader_bin.len(), + p_code: shader_bin.as_ptr() as *const u32, + flags, + ..Default::default() + }, + None, + ) + } + .expect(&format!("Failed to create shader module form file {file}")) + } + + fn setup_simple_shader_stage( + &self, + vert_shader: vk::ShaderModule, + frag_shader: vk::ShaderModule, + ) -> [vk::PipelineShaderStageCreateInfo; 2] { + let vert_stage = vk::PipelineShaderStageCreateInfo::default() + .stage(vk::ShaderStageFlags::VERTEX) + .module(vert_shader) + .name(c"main"); + let frag_stage = vk::PipelineShaderStageCreateInfo::default() + .stage(vk::ShaderStageFlags::FRAGMENT) + .module(frag_shader) + .name(c"main"); + [vert_stage, frag_stage] + } + + fn make_sem(&self) -> vk::Semaphore { + let sem_info = vk::SemaphoreCreateInfo::default(); + let sem = unsafe { + self.dev + .create_semaphore(&sem_info, None) + .expect("Failed to create semaphore") + }; + sem + } + + fn make_fence(&self) -> vk::Fence { + let fence_info = vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED); + let fence = unsafe { + self.dev + .create_fence(&fence_info, None) + .expect("Failed to create fence") + }; + fence + } + + fn wait_for_fence(&self, fence: vk::Fence) { + unsafe { + let fence = [fence]; + self.dev + .wait_for_fences(&fence, true, u64::MAX) + .expect("Failed to wait for fence"); + } + } + + fn reset_fence(&self, fence: vk::Fence) { + unsafe { + let fence = [fence]; + self.dev + .reset_fences(&fence) + .expect("Failed to reset fence"); + } + } + + fn start_cmd_buf(&self) { + unsafe { + self.dev + .reset_command_buffer(self.cmd_buf, vk::CommandBufferResetFlags::empty()) + .expect("Failed to reset command buffer"); + let begin_info = + vk::CommandBufferBeginInfo::default().flags(vk::CommandBufferUsageFlags::empty()); + self.dev + .begin_command_buffer(self.cmd_buf, &begin_info) + .expect("Failed to begin command buffer"); + } + } + + fn end_cmd_buf(&self) { + unsafe { + self.dev + .end_command_buffer(self.cmd_buf) + .expect("Failed to end command buffer"); + } + } + + fn submit_queue(&self, sem_avail: vk::Semaphore, sem_finish: vk::Semaphore, fence: vk::Fence) { + let cmd_bufs = [self.cmd_buf]; + let wait_sems = [sem_avail]; + let signal_sems = [sem_finish]; + let submit_info = vk::SubmitInfo::default() + .command_buffers(&cmd_bufs) + .wait_semaphores(&wait_sems) + .wait_dst_stage_mask(&[vk::PipelineStageFlags::COLOR_ATTACHMENT_OUTPUT]) + .signal_semaphores(&signal_sems); + unsafe { + self.dev + .queue_submit(self.queue, &[submit_info], fence) + .expect("Failed to submit queue"); + } + } + + fn queue_present(&self, swapchain: &SwapchainCtx, img_inx: u32, sem_finish: vk::Semaphore) { + let swapchains = [swapchain.swapchain]; + let img_inxs = [img_inx]; + let wait_sems = [sem_finish]; + let present_info = vk::PresentInfoKHR::default() + .swapchains(&swapchains) + .image_indices(&img_inxs) + .wait_semaphores(&wait_sems); + unsafe { + swapchain + .swapchain_loader + .queue_present(self.queue, &present_info) + .expect("Failed to present queue"); + } + } +} diff --git a/src/render/skybox.rs b/src/render/skybox.rs new file mode 100644 index 0000000..0dddfd9 --- /dev/null +++ b/src/render/skybox.rs @@ -0,0 +1,318 @@ +use std::collections::HashMap; + +use ash::vk::{self}; + +use crate::{RenderCtx, decode_rif, game::Camera}; + +use super::{MemType, loading_world::WorldComponent}; + +pub struct Skybox { + pub buf: vk::Buffer, + pub image: vk::Image, + pub imageview: vk::ImageView, + pub sampler: vk::Sampler, + pipeline: vk::Pipeline, + pipe_layout: vk::PipelineLayout, +} + +impl WorldComponent for Skybox { + fn descriptors(&self) -> HashMap { + let mut map = HashMap::new(); + map.insert(vk::DescriptorType::COMBINED_IMAGE_SAMPLER, 1); + map + } + + fn desc_layout(&self, ctx: &RenderCtx) -> vk::DescriptorSetLayout { + let image_binding = vk::DescriptorSetLayoutBinding::default() + .binding(0) + .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) + .descriptor_count(1) + .stage_flags(vk::ShaderStageFlags::FRAGMENT); + let layouts = [image_binding]; + + let layout_info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&layouts); + let layout = unsafe { + ctx.dev + .create_descriptor_set_layout(&layout_info, None) + .expect("Failed to create descriptor set layout") + }; + layout + } + + fn write_desc_set( + &self, + ctx: &RenderCtx, + desc_set: vk::DescriptorSet, + _: (vk::ImageView, vk::Sampler), + ) { + let img_info = vk::DescriptorImageInfo::default() + .image_layout(vk::ImageLayout::GENERAL) + .image_view(self.imageview) + .sampler(self.sampler); + let img_infos = &[img_info]; + let img_desc = vk::WriteDescriptorSet::default() + .dst_set(desc_set) + .dst_binding(0) + .dst_array_element(0) + .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) + .image_info(img_infos); + + unsafe { + ctx.dev.update_descriptor_sets(&[img_desc], &[]); + } + } + + fn pipeline(&self) -> (vk::Pipeline, vk::PipelineLayout) { + (self.pipeline, self.pipe_layout) + } + + fn push_constants( + &self, + camera: &Camera, + res: [u32; 2], + base_color: [f32; 4], + ) -> Vec<(vk::ShaderStageFlags, u32, Vec)> { + let mut constants = Vec::new(); + let cam_data: Vec = (Vec::::from([ + camera.origin.x, + camera.origin.y, + camera.origin.z, + 0.0, + camera.rotation.x, + camera.rotation.y, + camera.rotation.z, + ])) + .iter() + .map(|f| f.to_ne_bytes()) + .collect::>() + .into_flattened(); + constants.push((vk::ShaderStageFlags::VERTEX, 0, cam_data)); + + let screen_res: Vec = res + .iter() + .map(|f| f.to_ne_bytes()) + .collect::>() + .into_flattened(); + constants.push((vk::ShaderStageFlags::VERTEX, 32, screen_res)); + + constants + } +} + +impl Skybox {} + +impl RenderCtx { + pub(super) fn create_skybox(&self, pass: vk::RenderPass) -> Skybox { + let skybox_mem = self.mem_alloc(MemType::HostVisibile, 3 * 2048 * 2048 * 6); + let skybox_buf = self.alloc_buf( + skybox_mem, + 3 * 2048 * 2048 * 6, + vk::BufferUsageFlags::TRANSFER_SRC, + ); + let skybox_ptr = self.buf_to_ptr(skybox_mem, skybox_buf); + let img = std::fs::read("resources/skybox.rif").unwrap(); + let data = decode_rif(&img); + unsafe { + std::ptr::copy(data.as_ptr(), skybox_ptr as *mut u8, data.len()); + } + + let image = make_skybox_image(self); + let imageview = make_skybox_image_view(self, image); + let sampler = make_skybox_sampler(self); + + let pipe_layout = setup_pipe_layout(self); + let pipeline = setup_pipeline(self, pass, pipe_layout); + + Skybox { + buf: skybox_buf, + image, + imageview, + sampler, + pipeline, + pipe_layout, + } + } +} + +fn make_skybox_image(ctx: &RenderCtx) -> vk::Image { + let qf_idxs = [ctx.queue_idx]; + let create_info = vk::ImageCreateInfo::default() + .flags(vk::ImageCreateFlags::CUBE_COMPATIBLE) + .image_type(vk::ImageType::TYPE_2D) + .format(vk::Format::R8G8B8_UNORM) + .extent(vk::Extent3D::default().width(2048).height(2048).depth(1)) + .mip_levels(1) + .array_layers(6) + .samples(vk::SampleCountFlags::TYPE_1) + .tiling(vk::ImageTiling::OPTIMAL) + .usage(vk::ImageUsageFlags::TRANSFER_DST | vk::ImageUsageFlags::SAMPLED) + .sharing_mode(vk::SharingMode::EXCLUSIVE) + .initial_layout(vk::ImageLayout::UNDEFINED) + .queue_family_indices(&qf_idxs); + let image = + unsafe { ctx.dev.create_image(&create_info, None) }.expect("Failed to create image"); + + let skybox_mem_size: vk::MemoryRequirements = + unsafe { ctx.dev.get_image_memory_requirements(image) }; + let skybox_dev_mem = ctx.mem_alloc(MemType::HostInvisible, skybox_mem_size.size); + unsafe { ctx.dev.bind_image_memory(image, skybox_dev_mem, 0) } + .expect("Failed to bind image memory"); + + image +} + +fn make_skybox_image_view(ctx: &RenderCtx, image: vk::Image) -> vk::ImageView { + let create_info = vk::ImageViewCreateInfo::default() + .image(image) + .view_type(vk::ImageViewType::CUBE) + .format(vk::Format::R8G8B8_UNORM) + .components(vk::ComponentMapping::default()) + .subresource_range( + vk::ImageSubresourceRange::default() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .base_mip_level(0) + .level_count(1) + .base_array_layer(0) + .layer_count(6), + ); + unsafe { ctx.dev.create_image_view(&create_info, None) }.expect("Failed to create image view") +} + +fn make_skybox_sampler(ctx: &RenderCtx) -> vk::Sampler { + let create_info = vk::SamplerCreateInfo::default() + .mag_filter(vk::Filter::LINEAR) + .min_filter(vk::Filter::LINEAR) + .mipmap_mode(vk::SamplerMipmapMode::LINEAR) + .address_mode_u(vk::SamplerAddressMode::CLAMP_TO_EDGE) + .address_mode_v(vk::SamplerAddressMode::CLAMP_TO_EDGE) + .address_mode_w(vk::SamplerAddressMode::CLAMP_TO_EDGE) + .max_anisotropy(1.0) + .max_lod(1.0) + .border_color(vk::BorderColor::FLOAT_OPAQUE_WHITE); + unsafe { ctx.dev.create_sampler(&create_info, None) }.expect("Failed to create sampler") +} + +fn load_skybox_shaders(ctx: &RenderCtx) -> (vk::ShaderModule, vk::ShaderModule) { + let skybox_vert_shader = ctx.shader_mod_from_file( + "shaders/skybox_vert.spv", + vk::ShaderModuleCreateFlags::empty(), + ); + let skybox_frag_shader = ctx.shader_mod_from_file( + "shaders/skybox_rag.spv", + vk::ShaderModuleCreateFlags::empty(), + ); + + (skybox_vert_shader, skybox_frag_shader) +} + +fn setup_desc_layout(ctx: &RenderCtx) -> vk::DescriptorSetLayout { + let image_binding = vk::DescriptorSetLayoutBinding::default() + .binding(0) + .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) + .descriptor_count(1) + .stage_flags(vk::ShaderStageFlags::FRAGMENT); + let layouts = [image_binding]; + + let layout_info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&layouts); + let layout = unsafe { ctx.dev.create_descriptor_set_layout(&layout_info, None) } + .expect("Failed to create descriptor set layout"); + layout +} + +fn setup_pipe_layout(ctx: &RenderCtx) -> vk::PipelineLayout { + let layout = setup_desc_layout(ctx); + let push_range: [vk::PushConstantRange; 1] = [ + vk::PushConstantRange::default() + .stage_flags(vk::ShaderStageFlags::VERTEX) + .offset(0) + .size(48), // vec4 camera_orig, camera_rot; uvec2 screen_res + ]; + + let layouts = &[layout]; + + let pipe_layout = vk::PipelineLayoutCreateInfo::default() + .set_layouts(layouts.as_ref()) + .push_constant_ranges(&push_range); + let pipe_layout = unsafe { + ctx.dev + .create_pipeline_layout(&pipe_layout, None) + .expect("Failed to create pipeline layout") + }; + pipe_layout +} + +fn setup_pipeline( + ctx: &RenderCtx, + pass: vk::RenderPass, + layout: vk::PipelineLayout, +) -> vk::Pipeline { + let (vert_shader, frag_shader) = load_skybox_shaders(ctx); + let shader_stages = ctx.setup_simple_shader_stage(vert_shader, frag_shader); + let vert_input = vk::PipelineVertexInputStateCreateInfo::default(); + let input_assembly = vk::PipelineInputAssemblyStateCreateInfo::default() + .topology(vk::PrimitiveTopology::TRIANGLE_LIST); + let rasterization = vk::PipelineRasterizationStateCreateInfo::default() + .polygon_mode(vk::PolygonMode::FILL) + .cull_mode(vk::CullModeFlags::NONE) + .front_face(vk::FrontFace::CLOCKWISE) + .line_width(1.0); + let multisample = vk::PipelineMultisampleStateCreateInfo::default() + .rasterization_samples(vk::SampleCountFlags::TYPE_1); + let depth_stencil = vk::PipelineDepthStencilStateCreateInfo::default() + .depth_test_enable(true) + .depth_write_enable(true) + .depth_compare_op(vk::CompareOp::LESS) + .depth_bounds_test_enable(true) + .stencil_test_enable(false) + .min_depth_bounds(0.0) + .max_depth_bounds(1.0); + + let blend = vk::PipelineColorBlendAttachmentState::default() + .src_color_blend_factor(vk::BlendFactor::SRC_COLOR) + .dst_color_blend_factor(vk::BlendFactor::SRC_COLOR) + .color_blend_op(vk::BlendOp::ADD) + .src_alpha_blend_factor(vk::BlendFactor::SRC_COLOR) + .dst_alpha_blend_factor(vk::BlendFactor::SRC_COLOR) + .alpha_blend_op(vk::BlendOp::ADD) + .color_write_mask( + vk::ColorComponentFlags::R + | vk::ColorComponentFlags::G + | vk::ColorComponentFlags::B + | vk::ColorComponentFlags::A, + ); + + let blend_attachments = [blend]; + + let color_blend = vk::PipelineColorBlendStateCreateInfo::default() + .attachments(blend_attachments.as_ref()) + .logic_op_enable(false) + .logic_op(vk::LogicOp::COPY) + .blend_constants([1.0, 1.0, 1.0, 1.0]); + + let dynamic = vk::PipelineDynamicStateCreateInfo::default().dynamic_states(&[ + vk::DynamicState::VIEWPORT_WITH_COUNT, + vk::DynamicState::SCISSOR_WITH_COUNT, + ]); + + let pipe_info = vk::GraphicsPipelineCreateInfo::default() + .stages(&shader_stages) + .vertex_input_state(&vert_input) + .input_assembly_state(&input_assembly) + .rasterization_state(&rasterization) + .multisample_state(&multisample) + .depth_stencil_state(&depth_stencil) + .color_blend_state(&color_blend) + .layout(layout) + .dynamic_state(&dynamic) + .render_pass(pass) + .subpass(0); + + let pipe = unsafe { + ctx.dev + .create_graphics_pipelines(vk::PipelineCache::null(), &[pipe_info], None) + .expect("Failed to create graphics pipeline") + .remove(0) + }; + println!("Created pipeline"); + pipe +} diff --git a/src/render/swapchain.rs b/src/render/swapchain.rs new file mode 100644 index 0000000..494450e --- /dev/null +++ b/src/render/swapchain.rs @@ -0,0 +1,203 @@ +use std::cmp; + +use ash::{ + khr::{surface, swapchain as khr_swapchain}, + vk, +}; + +use crate::{ + RenderCtx, + render::{MAX_HEIGHT, MAX_WIDTH}, +}; + +pub fn setup_swapchain( + ctx: &RenderCtx, + surface_loader: &surface::Instance, + swapchain_loader: &khr_swapchain::Device, + width: u32, + height: u32, +) -> (vk::SwapchainKHR, vk::Extent2D) { + let caps = unsafe { + surface_loader + .get_physical_device_surface_capabilities(ctx.pdev, ctx.surface) + .expect("Failed to get surface capabilities") + }; + + let formats = unsafe { + surface_loader + .get_physical_device_surface_formats(ctx.pdev, ctx.surface) + .expect("Failed to get surface formats") + }; + + let format = (formats) + .iter() + .filter(|f| { + f.format == vk::Format::B8G8R8A8_UNORM + && f.color_space == vk::ColorSpaceKHR::SRGB_NONLINEAR + }) + .next() + .expect("No suitable format found"); + + // When the window is resized fast, then the caps goes out of sync of the target extents + // Thus we are using the passed-in (target) extents instead of the cap extents. + // assert_eq!(caps.current_extent.width, width); + // assert_eq!(caps.current_extent.height, height); + + let swap_create = vk::SwapchainCreateInfoKHR::default() + .surface(ctx.surface) + .min_image_count(caps.min_image_count) + .image_format(format.format) + .image_color_space(format.color_space) + .image_extent(vk::Extent2D { + width: cmp::min(cmp::max(caps.current_extent.width, width), MAX_WIDTH), + height: cmp::min(cmp::max(caps.current_extent.height, height), MAX_HEIGHT), + }) + .image_array_layers(1) + .image_usage(vk::ImageUsageFlags::COLOR_ATTACHMENT) + .image_sharing_mode(vk::SharingMode::EXCLUSIVE) + .pre_transform(vk::SurfaceTransformFlagsKHR::IDENTITY) + .composite_alpha(vk::CompositeAlphaFlagsKHR::OPAQUE) + .present_mode(vk::PresentModeKHR::MAILBOX) + .clipped(true); + + let swapchain = unsafe { + swapchain_loader + .create_swapchain(&swap_create, None) + .expect("Failed to create swapchain") + }; + println!("Created swapchain"); + (swapchain, caps.current_extent) +} + +pub fn make_swap_images( + swapchain: vk::SwapchainKHR, + swapchain_loader: &khr_swapchain::Device, +) -> Vec { + unsafe { + let images = swapchain_loader + .get_swapchain_images(swapchain) + .expect("Failed to get swapchain images"); + println!("Fetched swapchain images"); + images + } +} + +pub fn make_swap_views( + ctx: &RenderCtx, + swap_images: &[vk::Image], + format: vk::Format, +) -> Vec { + let mut views = Vec::new(); + for img in swap_images.iter() { + let view_info = vk::ImageViewCreateInfo::default() + .image(*img) + .view_type(vk::ImageViewType::TYPE_2D) + .format(format) + .components(vk::ComponentMapping::default()) + .subresource_range( + vk::ImageSubresourceRange::default() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .base_mip_level(0) + .level_count(1) + .base_array_layer(0) + .layer_count(1), + ); + let view = unsafe { + ctx.dev + .create_image_view(&view_info, None) + .expect("Failed to create image view") + }; + views.push(view); + } + views +} + +pub fn make_depth_img( + ctx: &RenderCtx, + width: u32, + height: u32, + mem: vk::DeviceMemory, +) -> vk::Image { + let queue_fams = [ctx.queue_idx]; + let img_info = vk::ImageCreateInfo::default() + .flags(vk::ImageCreateFlags::empty()) + .image_type(vk::ImageType::TYPE_2D) + .format(vk::Format::D32_SFLOAT) + .extent(vk::Extent3D { + width, + height, + depth: 1, + }) + .mip_levels(1) + .array_layers(1) + .samples(vk::SampleCountFlags::TYPE_1) + .tiling(vk::ImageTiling::OPTIMAL) + .usage(vk::ImageUsageFlags::DEPTH_STENCIL_ATTACHMENT) + .sharing_mode(vk::SharingMode::EXCLUSIVE) + .queue_family_indices(&queue_fams) + .initial_layout(vk::ImageLayout::UNDEFINED); + + let img = unsafe { + ctx.dev + .create_image(&img_info, None) + .expect("Failed to create image") + }; + println!("Created image"); + + unsafe { + ctx.dev + .bind_image_memory(img, mem, 0) + .expect("Failed to bind image memory") + }; + + img +} + +pub fn make_depth_view(ctx: &RenderCtx, depth_img: vk::Image, format: vk::Format) -> vk::ImageView { + let view_info = vk::ImageViewCreateInfo::default() + .image(depth_img) + .view_type(vk::ImageViewType::TYPE_2D) + .format(format) + .components(vk::ComponentMapping::default()) + .subresource_range( + vk::ImageSubresourceRange::default() + .aspect_mask(vk::ImageAspectFlags::DEPTH) + .base_mip_level(0) + .level_count(1) + .base_array_layer(0) + .layer_count(1), + ); + let view = unsafe { + ctx.dev + .create_image_view(&view_info, None) + .expect("Failed to create image view") + }; + view +} + +pub fn make_framebufs( + ctx: &RenderCtx, + swap_views: &Vec, + depth_view: &vk::ImageView, + pass: &vk::RenderPass, + width: u32, + height: u32, +) -> Vec { + let mut framebufs = Vec::new(); + for view in swap_views.iter() { + let attachments = [*view, *depth_view]; + let framebuf_info = vk::FramebufferCreateInfo::default() + .render_pass(*pass) + .attachments(attachments.as_ref()) + .width(width) + .height(height) + .layers(1); + let framebuf = unsafe { + ctx.dev + .create_framebuffer(&framebuf_info, None) + .expect("Failed to create framebuffer") + }; + framebufs.push(framebuf); + } + framebufs +} -- 2.39.1 From dbce32f625d08fd5aaf160f6e35fc1dea9ae62c8 Mon Sep 17 00:00:00 2001 From: Avery Date: Thu, 17 Apr 2025 11:39:16 +0200 Subject: [PATCH 4/7] Fix `VK_ERROR_SURFACE_LOST_KHR`, `VK_ERROR_OUT_OF_POOL_MEMORY`, typos in shader names - `VK_ERROR_SURFACE_LOST_KHR` was caused by the sdl window being dropped, it is now kept in gamestate to prevent that - `VK_ERROR_OUT_OF_POOL_MEMORY` was caused by the descriptor pool having a hardcodes size for the number of descriptor sets instead of that number depending on the required number of sets Note: We are still broken, a window opens and controls seem to work fine, but nothing is shown in the window --- src/game/mod.rs | 1 + src/main.rs | 5 ++--- src/render/cube.rs | 4 ++-- src/render/in_world.rs | 2 +- src/render/init.rs | 10 +++++----- src/render/loading_world.rs | 9 +++++++-- src/render/skybox.rs | 2 +- 7 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/game/mod.rs b/src/game/mod.rs index 125e218..f284bc3 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -25,6 +25,7 @@ pub struct Camera { impl Game { pub fn main_loop(mut self) -> Game { + println!("Starting main loop"); let mut event_pump = self .state .sdl_context diff --git a/src/main.rs b/src/main.rs index 41cc3d9..a6e0f90 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,14 +7,13 @@ mod render; use ash::Device; use ash::Entry; use ash::Instance; -use ash::khr::surface; use ash::vk; use game::Camera; use render::SwapchainCtx; use render::loading_world::Component; use render::loading_world::WorldComponent; use render::skybox::Skybox; -use sdl2::Sdl; +use sdl2::{Sdl, video::Window as SdlWindow}; use std::collections::HashMap; @@ -32,7 +31,6 @@ pub struct RenderCtx { pdev: vk::PhysicalDevice, dev: Device, surface: vk::SurfaceKHR, - surface_loader: surface::Instance, queue: vk::Queue, queue_idx: u32, cmd_buf: vk::CommandBuffer, @@ -43,6 +41,7 @@ pub struct RenderCtx { pub struct Window { width: u32, height: u32, + _sdl: SdlWindow, } pub struct RenderAvailable { diff --git a/src/render/cube.rs b/src/render/cube.rs index 937a534..563577a 100644 --- a/src/render/cube.rs +++ b/src/render/cube.rs @@ -277,11 +277,11 @@ impl Game { fn load_cube_shaders(ctx: &RenderCtx) -> (vk::ShaderModule, vk::ShaderModule) { let shader_vert_shader = ctx.shader_mod_from_file( - "shaders/shader_vert.spv", + "shaders/cube_vert.spv", vk::ShaderModuleCreateFlags::empty(), ); let shader_frag_shader = ctx.shader_mod_from_file( - "shaders/shader_rag.spv", + "shaders/cube_frag.spv", vk::ShaderModuleCreateFlags::empty(), ); diff --git a/src/render/in_world.rs b/src/render/in_world.rs index 02ce576..5e5a0bd 100644 --- a/src/render/in_world.rs +++ b/src/render/in_world.rs @@ -110,7 +110,7 @@ impl Game { state: InWorld { sdl_context, - window: Window { width, height }, + window: Window { width, height, .. }, ctx: RenderCtx { dev, cmd_buf, .. }, skybox, components, diff --git a/src/render/init.rs b/src/render/init.rs index 34efea4..42094a0 100644 --- a/src/render/init.rs +++ b/src/render/init.rs @@ -2,7 +2,6 @@ use std::ffi::{CStr, CString}; use ash::{ Device, Entry, Instance, - khr::surface, vk::{self}, }; use raw_window_handle::{HasDisplayHandle as _, HasWindowHandle as _}; @@ -43,19 +42,20 @@ impl Game { let host_invis_idx = find_host_invisible(&instance, &pdev).expect("No host invisible memory found"); - let surface_loader = surface::Instance::new(&entry, &instance); - Game { state: RenderAvailable { sdl_context, - window: crate::Window { width, height }, + window: crate::Window { + width, + height, + _sdl: window, + }, ctx: RenderCtx { entry, instance, pdev, dev, surface, - surface_loader, queue, queue_idx, cmd_buf, diff --git a/src/render/loading_world.rs b/src/render/loading_world.rs index 8d04ff6..5ae25eb 100644 --- a/src/render/loading_world.rs +++ b/src/render/loading_world.rs @@ -137,6 +137,7 @@ impl Game { } pub fn start_world(self) -> Game { + println!("Starting world"); let Self { state: LoadingWorld { @@ -153,7 +154,8 @@ impl Game { components, }, } = self; - let desc_pool = setup_desc_pool(&ctx, requested_descriptors); + + let desc_pool = setup_desc_pool(&ctx, requested_descriptors, (components.len() + 1) as u32); let mut desc_sets = make_desc_sets( &ctx, @@ -187,6 +189,8 @@ impl Game { let sem_finish = ctx.make_sem(); let fence_flight = ctx.make_fence(); + println!("Starting world"); + Game { state: InWorld { sdl_context, @@ -284,6 +288,7 @@ fn setup_render_pass(ctx: &RenderCtx) -> vk::RenderPass { fn setup_desc_pool( ctx: &RenderCtx, requested_descriptors: HashMap, + num_sets: u32, ) -> vk::DescriptorPool { let pool_sizes = requested_descriptors .into_iter() @@ -295,7 +300,7 @@ fn setup_desc_pool( .collect::>(); let pool_info = vk::DescriptorPoolCreateInfo::default() - .max_sets(1) + .max_sets(num_sets) .pool_sizes(&pool_sizes); let pool = unsafe { ctx.dev diff --git a/src/render/skybox.rs b/src/render/skybox.rs index 0dddfd9..4bfa721 100644 --- a/src/render/skybox.rs +++ b/src/render/skybox.rs @@ -198,7 +198,7 @@ fn load_skybox_shaders(ctx: &RenderCtx) -> (vk::ShaderModule, vk::ShaderModule) vk::ShaderModuleCreateFlags::empty(), ); let skybox_frag_shader = ctx.shader_mod_from_file( - "shaders/skybox_rag.spv", + "shaders/skybox_frag.spv", vk::ShaderModuleCreateFlags::empty(), ); -- 2.39.1 From b09eedafabfbf51fd8c59b045ffce8729aca86fe Mon Sep 17 00:00:00 2001 From: Avery Date: Thu, 17 Apr 2025 12:20:34 +0200 Subject: [PATCH 5/7] Fix pushing constants not using specified flags and offset, rendering now works --- src/render/in_world.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/render/in_world.rs b/src/render/in_world.rs index 5e5a0bd..ef08b88 100644 --- a/src/render/in_world.rs +++ b/src/render/in_world.rs @@ -210,13 +210,7 @@ impl Game { .push_constants(camera, [*width, *height], base_color) .into_iter() .for_each(|(f, o, d)| { - dev.cmd_push_constants( - *cmd_buf, - layout, - vk::ShaderStageFlags::VERTEX, - 0, - d.as_ref(), - ); + dev.cmd_push_constants(*cmd_buf, layout, f, o, d.as_ref()); }); dev.cmd_draw(*cmd_buf, 36, 1, 0, 0); -- 2.39.1 From 32aa330b33026252839bf6900da54c11dc32ac2b Mon Sep 17 00:00:00 2001 From: Avery Date: Thu, 17 Apr 2025 12:42:53 +0200 Subject: [PATCH 6/7] Add doc comments to various things Doc comments were added to: - States and their components, expaining their meaning - Traits - Functions transitioning game state --- src/game/mod.rs | 3 +++ src/input/mod.rs | 11 ++++++---- src/main.rs | 16 +++++++++++++++ src/render/cube.rs | 2 ++ src/render/in_world.rs | 4 ++++ src/render/loading_world.rs | 40 ++++++++++++++++++++++++++++++++++--- src/render/mod.rs | 8 ++++---- 7 files changed, 73 insertions(+), 11 deletions(-) diff --git a/src/game/mod.rs b/src/game/mod.rs index f284bc3..de1f55d 100644 --- a/src/game/mod.rs +++ b/src/game/mod.rs @@ -24,6 +24,9 @@ pub struct Camera { } impl Game { + /// The main gameloop, this will exit the program is the game is requested to close, + /// or return to the [`RenderAvailable`] state if the world is closed and we want to + /// return to a menu. pub fn main_loop(mut self) -> Game { println!("Starting main loop"); let mut event_pump = self diff --git a/src/input/mod.rs b/src/input/mod.rs index 6abb388..cf7c778 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -5,15 +5,18 @@ use sdl2::{ mouse::MouseState, }; -// More methods to be added for other forms of input +/// The generic interface for input handling, this is supposed to be implemented for +/// the game in a specific state, see [`ingame_input`] for an example. +/// More methods to be added for other forms of input pub trait InputHandler { - // Handle keys which are meant to be held down and repeated every frame, ex movement keys + /// Handle keys which are meant to be held down and repeated every frame, ex movement keys fn handle_cont_key(&mut self, keycode: Keycode) {} - // Handle single keypresses or keys held down after the repear delay + /// Handle single key presses or keys held down after the repear delay fn handle_keydown(&mut self, keycode: Keycode, modifier: Mod, repeat: bool) {} + /// Handle single key releases fn handle_keyup(&mut self, keycode: Keycode, modifier: Mod, repeat: bool) {} - // Handle mouse movements + /// Handle mouse movements fn handle_mouse_motion(&mut self, state: MouseState, abs: (i32, i32), rel: (i32, i32)) {} } diff --git a/src/main.rs b/src/main.rs index a6e0f90..15a7ac4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,6 +25,8 @@ pub struct Unstarted; impl GameState for Unstarted {} +/// The render context, this is what is used in most of the rendering, +/// contains everything we create in and need after [`RenderAvailable`] pub struct RenderCtx { entry: Entry, instance: Instance, @@ -38,12 +40,17 @@ pub struct RenderCtx { host_invis_idx: u32, } +/// Our window, holds the width and height, since we track those independent of Sdl and +/// the window Sdl gave us, since if we lose that, everything explodes pub struct Window { width: u32, height: u32, _sdl: SdlWindow, } +/// The render avalable state, this is the bare minimun state that is not +/// [`Unstarted`], from here we can transition into [`LoadingWorld`] or in +/// the future Menus and such pub struct RenderAvailable { sdl_context: Sdl, window: Window, @@ -52,6 +59,9 @@ pub struct RenderAvailable { impl GameState for RenderAvailable {} +/// The state in which we can load a world, this means loading the actual +/// world data, as well as adding components to be rendered, or potentially +/// loading mods. pub struct LoadingWorld { sdl_context: Sdl, window: Window, @@ -68,6 +78,8 @@ pub struct LoadingWorld { impl GameState for LoadingWorld {} +/// We are now in a running world and can render this world and do +/// things like movenent or (in the future) physics pub struct InWorld { sdl_context: Sdl, window: Window, @@ -114,9 +126,13 @@ fn main() { .unwrap(); let game = Game::new().init_render(sdl_context, window); + + // Hardcoded loading of a world, skybox + 1 cube let mut game = game.start_load_world(); let cube = game.create_cube(); game.add_component(cube); let game = game.start_world(); + + // Run the actual game let game = game.main_loop(); } diff --git a/src/render/cube.rs b/src/render/cube.rs index 563577a..7aab60b 100644 --- a/src/render/cube.rs +++ b/src/render/cube.rs @@ -267,6 +267,8 @@ impl RenderCtx { } impl Game { + /// Create a cube to be rendered in world, this can then be + /// added using [`Game::add_component`] pub fn create_cube(&self) -> Cube { let Self { state: LoadingWorld { ctx, pass, .. }, diff --git a/src/render/in_world.rs b/src/render/in_world.rs index ef08b88..586c965 100644 --- a/src/render/in_world.rs +++ b/src/render/in_world.rs @@ -12,6 +12,7 @@ use super::{ }; impl Game { + /// Rebuild the swapchain in response to a resize of the window pub fn window_resize(&mut self, size: (u32, u32)) { let Self { state: @@ -71,6 +72,7 @@ impl Game { ); } + /// Render a single frame pub fn render_frame(&self) { let Self { state: @@ -259,6 +261,8 @@ impl Game { ); }); + // FIXME: We currently get away with hardcoding these number + // because all we draw are cubes, but not for long I suspect dev.cmd_draw(*cmd_buf, 36, 1, 0, 0); } diff --git a/src/render/loading_world.rs b/src/render/loading_world.rs index 5ae25eb..2dcc893 100644 --- a/src/render/loading_world.rs +++ b/src/render/loading_world.rs @@ -2,7 +2,7 @@ use std::{collections::HashMap, iter}; use ash::{ khr::{surface, swapchain}, - vk::{self, DescriptorSet}, + vk::{self}, }; use crate::{ @@ -19,11 +19,22 @@ use super::{ /// Anything can be rendered in world pub trait WorldComponent { + /// Return the types and number of descriptors this component wants to use fn descriptors(&self) -> HashMap; + /// Return the layout of the descriptor set this component wants to used, + /// the used descriptors should match [`WorldComponent::descriptors`] fn desc_layout(&self, ctx: &RenderCtx) -> vk::DescriptorSetLayout; - /// Skybox is passed in for reflections + /// Write to this components descriptor set, the passed descriptor set + /// is layed out like was requested in [`WorldComponent::desc_layout`] + /// + /// Skybox is currently passed in for reflections + /// + /// Note: + /// This api might be changed to having this functions return the + /// [`vk::WriteDescriptorSet`]s it wants, so that the render context + /// does not have to be passed in fn write_desc_set( &self, ctx: &RenderCtx, @@ -31,8 +42,16 @@ pub trait WorldComponent { skybox: (vk::ImageView, vk::Sampler), ); + /// Return the pipeline and pipeline layout this component wants to use, + /// this function is called every frame, so the component should not + /// contruct the pipeline in this function, but merely return an earlier + /// contructed pipeline fn pipeline(&self) -> (vk::Pipeline, vk::PipelineLayout); + /// Return the constants that must be pushed to the shader, + /// camera, screen resolution and base color are provided + /// as they are relevant for most shaders. + /// Return in `(stage, offset, data)` fn push_constants( &self, camera: &Camera, @@ -41,12 +60,19 @@ pub trait WorldComponent { ) -> Vec<(vk::ShaderStageFlags, u32, Vec)>; } +/// A [`WorldComponent`] and its associated [`vk::DescriptorSet`] pub struct Component { pub inner: Box, - pub desc_set: DescriptorSet, + pub desc_set: vk::DescriptorSet, } impl Game { + /// Start the loading of a world + /// + /// FIXME: This should not remain in [`crate::render`], + /// the function here should be replaced with one for render + /// specifically and this should be moved to [`crate::game`] + /// and call the render specific function from there pub fn start_load_world(self) -> Game { let Self { state: @@ -118,6 +144,8 @@ impl Game { } impl Game { + /// Add a component to be rendered to the world, see [`crate::render::cube`] + /// for an example for such a component pub fn add_component(&mut self, component: C) { let Self { state: @@ -136,6 +164,12 @@ impl Game { components.push(Box::new(component)); } + /// Finish loading of a world, it is now ready for play + /// + /// FIXME: This should not remain in [`crate::render`], + /// the function here should be replaced with one for render + /// specifically and this should be moved to [`crate::game`] + /// and call the render specific function from there pub fn start_world(self) -> Game { println!("Starting world"); let Self { diff --git a/src/render/mod.rs b/src/render/mod.rs index 838f979..4e3f76f 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -33,7 +33,7 @@ pub enum MemType { } impl RenderCtx { - pub fn mem_alloc(&self, typ: MemType, size: u64) -> vk::DeviceMemory { + fn mem_alloc(&self, typ: MemType, size: u64) -> vk::DeviceMemory { let mem_info = vk::MemoryAllocateInfo::default() .allocation_size(size) .memory_type_index(match typ { @@ -46,7 +46,7 @@ impl RenderCtx { mem } - pub fn alloc_buf( + fn alloc_buf( &self, mem: vk::DeviceMemory, size: vk::DeviceSize, @@ -70,7 +70,7 @@ impl RenderCtx { } } - pub fn buf_to_ptr(&self, mem: vk::DeviceMemory, buf: vk::Buffer) -> *mut std::ffi::c_void { + fn buf_to_ptr(&self, mem: vk::DeviceMemory, buf: vk::Buffer) -> *mut std::ffi::c_void { let ptr = unsafe { self.dev .map_memory(mem, 0, vk::WHOLE_SIZE, vk::MemoryMapFlags::empty()) @@ -80,7 +80,7 @@ impl RenderCtx { ptr } - pub fn shader_mod_from_file( + fn shader_mod_from_file( &self, file: &str, flags: vk::ShaderModuleCreateFlags, -- 2.39.1 From c9fdcfe008352f0e77ba97f3bd54a443b3aaa77c Mon Sep 17 00:00:00 2001 From: Avery Date: Thu, 17 Apr 2025 15:13:58 +0200 Subject: [PATCH 7/7] Deduplicate setup_dest_layout The vube and skybox need the layout of their desc_set twice, once for creating their pipeline, once for creating their desc set, two different functions were used for that (one of which belonging to the `WorldComponent` trait), now since they can't be fully merged, one just calls the other. --- src/render/cube.rs | 20 +------------------- src/render/skybox.rs | 15 +-------------- 2 files changed, 2 insertions(+), 33 deletions(-) diff --git a/src/render/cube.rs b/src/render/cube.rs index 7aab60b..adc79be 100644 --- a/src/render/cube.rs +++ b/src/render/cube.rs @@ -140,25 +140,7 @@ impl WorldComponent for Cube { } fn desc_layout(&self, ctx: &RenderCtx) -> vk::DescriptorSetLayout { - let storage_binding = vk::DescriptorSetLayoutBinding::default() - .binding(0) - .descriptor_type(vk::DescriptorType::STORAGE_BUFFER) - .descriptor_count(1) - .stage_flags(vk::ShaderStageFlags::VERTEX); - let image_binding = vk::DescriptorSetLayoutBinding::default() - .binding(1) - .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) - .descriptor_count(1) - .stage_flags(vk::ShaderStageFlags::FRAGMENT); - let layouts = [storage_binding, image_binding]; - - let layout_info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&layouts); - let layout = unsafe { - ctx.dev - .create_descriptor_set_layout(&layout_info, None) - .expect("Failed to create descriptor set layout") - }; - layout + setup_desc_layout(ctx) } fn write_desc_set( diff --git a/src/render/skybox.rs b/src/render/skybox.rs index 4bfa721..4be5777 100644 --- a/src/render/skybox.rs +++ b/src/render/skybox.rs @@ -23,20 +23,7 @@ impl WorldComponent for Skybox { } fn desc_layout(&self, ctx: &RenderCtx) -> vk::DescriptorSetLayout { - let image_binding = vk::DescriptorSetLayoutBinding::default() - .binding(0) - .descriptor_type(vk::DescriptorType::COMBINED_IMAGE_SAMPLER) - .descriptor_count(1) - .stage_flags(vk::ShaderStageFlags::FRAGMENT); - let layouts = [image_binding]; - - let layout_info = vk::DescriptorSetLayoutCreateInfo::default().bindings(&layouts); - let layout = unsafe { - ctx.dev - .create_descriptor_set_layout(&layout_info, None) - .expect("Failed to create descriptor set layout") - }; - layout + setup_desc_layout(ctx) } fn write_desc_set( -- 2.39.1