From 031c1c35b5a6bdf14949b8939117459d1e267cd1 Mon Sep 17 00:00:00 2001 From: Stannic Date: Thu, 22 Jan 2026 19:58:14 +0500 Subject: [PATCH 1/5] (add): sdl_renderer and sdf rect rendering --- renderer/sdl_gpu/compile_shader.sh | 15 ++ renderer/sdl_gpu/compiled/main.frag.sprv | Bin 0 -> 7964 bytes renderer/sdl_gpu/compiled/main.vert.sprv | Bin 0 -> 5020 bytes renderer/sdl_gpu/compiled/stencil.frag.sprv | Bin 0 -> 2676 bytes renderer/sdl_gpu/compiled/stencil.vert.sprv | Bin 0 -> 2532 bytes renderer/sdl_gpu/main.odin | 282 ++++++++++++++++++++ renderer/sdl_gpu/source/main.frag | 82 ++++++ renderer/sdl_gpu/source/main.vert | 52 ++++ 8 files changed, 431 insertions(+) create mode 100644 renderer/sdl_gpu/compile_shader.sh create mode 100644 renderer/sdl_gpu/compiled/main.frag.sprv create mode 100644 renderer/sdl_gpu/compiled/main.vert.sprv create mode 100644 renderer/sdl_gpu/compiled/stencil.frag.sprv create mode 100644 renderer/sdl_gpu/compiled/stencil.vert.sprv create mode 100644 renderer/sdl_gpu/main.odin create mode 100644 renderer/sdl_gpu/source/main.frag create mode 100644 renderer/sdl_gpu/source/main.vert diff --git a/renderer/sdl_gpu/compile_shader.sh b/renderer/sdl_gpu/compile_shader.sh new file mode 100644 index 0000000..88878a2 --- /dev/null +++ b/renderer/sdl_gpu/compile_shader.sh @@ -0,0 +1,15 @@ +#!/bin/bash +set -e +SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" +SRC_DIR="$SCRIPT_DIR/source" +OUT_DIR="$SCRIPT_DIR/compiled" + +for file in "$SRC_DIR"/*; do + if [[ -f "$file" ]]; then + filename=$(basename -- "$file") + extension="${filename##*.}" + name="${filename%.*}" + glslc "$file" -o "$OUT_DIR/$name.$extension.sprv" + echo "Compiled $filename -> $name.$extension.sprv" + fi +done diff --git a/renderer/sdl_gpu/compiled/main.frag.sprv b/renderer/sdl_gpu/compiled/main.frag.sprv new file mode 100644 index 0000000000000000000000000000000000000000..030651519d13615e5dac4f477f484826d66de64a GIT binary patch literal 7964 zcmZXY37A%88HNwe3<%w<;vwNmNnLH*w7ehUOv#< z80a7DZS?dH_pWXBZ|b#f$68(g;M#$WJ@tYPD(Z9UC?+%0qR^l*(mPN#{XM<&HuWu7 zys2;DVsahKV=C>2p^*~r3?3=^v)2Hm)QS45;e)T|KW2FBIrcQopu{Z5g{F>|g*Iqo>J2En6R?bYn8Pt!?tD~6t z6u*vQFSN1l;12eulXj%1ui-djj(tbF)N+pd(#CT?@c22l7yF|PukPvJ$R@a-XTWNaP2h5WX*)QNo~{1DaXVj*y$!uSx0_17eQr0GeEZyPDf#%h-Hw*I-7T)>c8|E4 z+r8p)Zf(WG;7bYjrj4_^zIwoN&p-1lpSXN7e5B!SkJkHckl*O~e7?KE*=uk|`Oc8b z)|dXfli#p%T7hU6cTQL{TnJjA$9+zt`4r$?sweb1&Z4M7z%uD1RoCxphAK)~}Cl*V` z@}sZhsNPYWUe!f)kG+rBlN>FSj`DsQ+SnWu)x58$YxZ8&EFYHRcz^kPyaa|D&C541 zpXJQQv*~*@k&t@^G*<_r49c z-g~spy+_0C?>$=Qoi%R%IW63KGxd(=y%}!3cW0e@cZOT<-C5_}optWr8Sea+ws7xE z^Cz=^wiDr=mmAqt`Ho;U&&(<8xcjS?`kla@pYW+*`(Ib}-x+L;dTMq7>vL^Yvn$v- zb;t0Tsil4ou$noam)x9ZYC72SYR+dTU&LGEdou&x9_^D}Nd z>m1Mb(!72?({6BQ#hvS^VCUp}X#O;?HR|?XN~@OsPXjNjxc!%d9V7kE09&JOf8Tqx z^gkP{7QO=P81B8}p98i=-TvO;YEKjsiW3O$X2V`nvY>sy#jiY@HwHW{q0<91FIOzXzqyabWv67yDSFmOjUW-DCS?k52%r-&XDM zBCuNa_$06&_t@NGVhQ2ih~s@SIBSUaDQN1>#pf+|PxQ3EKIy*{ob{xCH=26-%j^C1 zN$sg%$B5r);LOMVa%+-5y_S#1GBouknVUFg-gm>h*pI&l=gd#5;oZTmX-`_uyl46h zqJHLAw(tvD_=PRJr-fhC!UtP8f8vgf=X>biqmG|7p9yx(p0lj^EU&e?B zfu^3C7lK>IUyY`o^{)ZjPd($W1!sKoj_-L=&$I3W`%JQq^ET&di^mAq^~IwJR;zn# zgzI5Wk6~K1Zl2>+v@a&EAg(0zolJW*p`LNJfZc<9$8H5%vxSJywP1a&A>#8AuzGy1 z1G|R!ycBGWx@&OkmzB6E%*Fc#u-?}b@qRg2J>IVXyT|cIVe>u)ao-T-#r!rusX{q~ti`zFE~_0+r>>^f5O7O-{d zsd+2d`K0D;VC&RZaof3%Zzt62;rTZA4#N3ba)&UtUb&vYW+ z0W)fNW`(EF?nU^0un%p%54M3P)%Z;<{N@&ZOAEifh2P!6?`h%pR=k_F&jP=TaK2qc z_VeA9o|^Z-T}QpRvH`TM~3Q_ubLesJy|^X~07TJ?B;0NgtM2hr41^C58S_#Z}7 z&;9oiu>I6C{zt(X-@N0yzv}V+7})U_5?TMp!Ro2`1laNGHJ^m5Z>`Smr@;18&-kAP zXMFRH@AFiT_h-PZ<9`-SJvFz2TgU$#ntIlM8`ys88UOR(jBnoYJwNJM>leV=h^*sI zu-Y9&?*F^My@WYEJacyu@%SRxeT~PLz-o1mFT?dPr^ns2YVr6A*u9CzSHWs^kFUY? zFsH{ov}*DAI@tBa;~QYLy2pKRJ^lbaP|0nAM6_9^8>Io>RH1N!TOtv_XA+P7ZUOM5m-I^$6)t3-ai3b zqaN>{g7r5S@1KG7evpXQ&%x^9zW}>$@qP$wje5L)3D*B1BK>{^cHhE(4R-zM^Bb@= z>Z$oH*mb1lcVO$(Q}cVU^GVGgz}Bhf4F3`AoXmNT%j@@eC*03$BKP>dHN0PiXVLCY zc#qGa&EK*QgC`Nr$@Wi#{e2hZeY7zTst4NjPp;hT0H&*o?FFK9Q61%;T-1?)?1UiZ literal 0 HcmV?d00001 diff --git a/renderer/sdl_gpu/compiled/main.vert.sprv b/renderer/sdl_gpu/compiled/main.vert.sprv new file mode 100644 index 0000000000000000000000000000000000000000..8c5817152c0b2d061840ca0878c707e226693e65 GIT binary patch literal 5020 zcmai$`F0dV6vjKu!X}#_i$a*NLV>VVafUdf>3tmHk(1<6Iphy`72ac60&1R_`Pt?LvEvkp5YSaoVaWoddC}qc$$B*$htCD;WRHO5;hL5clMOa#KC2CA zQe@6y^j5hVb(Ar$J>f%5v_up~je4nEua?@;4RTlvXRS19jh5r`E{1A$#+-LN(G2CA z@SGvO8da{>!#10?HFDC28?9>CD!EjpxX~0fZO_lPx1y1&+PTH+!XJ$vZp3k8blSdP zw!Ic!;o#I^qh>Bblfwng>AHX!>ADnz=c)_#*>!Vkc)x?rEHF4*U) z%O=^WLvl~K0L-9Pa5f6N}e75^gBEQn}0rH=R2O>aeP#d&n3@k+D6~VcBB?H zk48x?r)~5Pwoa`!ZQ~yJ?l9=*yP8MK?urzba~NB*)wsrt|2-6qqWt-`EtYJToRGDk znXx&UraATu$Jf#P*ehHi#brFlht>S(Z~XdF9K8)Ul;Y@TxXUSy9){zCVEO1KaqhJdLNc*JAs1Ewdjb3QP?GvGxIXIwv#o7zliX0ShoaqI<-**Ie0 zk~$@^=y1tiGOl-$+(}=_q*am}^cjQuSuP>3aWl2ex?bzSQG@-{dz&QeWu>Y@{vPdu zwb55g`uN21`suoM29~hJD7KB zc6yb4b2e(UUNh(7-nw;Cez)PCB-E2$A{Q~l7BoL84*Le(Q;%l+yQOod3C_B=$$i1G zfBJ!U1xI~3*H#I`&etowOTwPEX~wtU@lMU`k##NpCF%GZenomZ|6XBw%Iy24Qy;?z zq|?iW4@swnhWn+nrs1Q~={@#$S~I!X5BmoXdd@K40elSe9RL&mf!E289n5#Z{K0$| zz^u={E=hu5^048L9G>aBkA?Apw_YrHoaQcj4qx*tO9v-h(zC=twi$3QY2S@Bzn%NH=vEX~2!+ziE z_}jfbl&;aYPu)i>c^T~CUorF~x#%_ik8eG)R{FXW=G|^gVf>HJ!lzQ0_^QVTm7hAV zk?>tPq}9pIcdB1IAlI`JIDGJDAJ0kP@n>&f%k{kUUP-@dy-o8Ak}Z<05^VStG;fz+ zJEL`|>kiLpa%=O}UzBh*Vp$7}pT+EwPM$6a`z2<#gc!aH#DMX$n3ts!L(MJbRq4d= z?H~q>pT+EvP7HTrG5e$w!}o<4Fn$*Ensj2gSBrUFIx+ht#DMX$m^Y*o!`)lVo6?Cn zARz{fpT!)MPE1~6F^8oS!?%nWFn$)(C!HAX(qi6{PE5aq7;vA&Vva~BhMusPW73H^ zDj^1ppV^Djv2z#PJNJA-0zatLS^uQx;JFiOOfGsDo8@|2`s`c-;@~aUJJR7027N)U zQ<||^t|95<;=XNPXQad9&o>-w{^z9QPam59yVBwD=Nk_;|MSxE=bp{~ed+M{^BVxh zMh!lca9(T|JjS+4dPTC@+jzIb64v1!LJ1r`mg|b=$#qdW*!Fc*I{Tt$ZC{aec&p(x z>2L_cYB(Yto8@XqCl`Hbxth}9&F2&8_|S*u(~=HvK5gmv(0Aq&ONTd~j&ywJGxHgf z4nL#{)--=AVSVxwkDVIu+k%bn(M`>tNytSFZb-Vcc)h;kb8d%bWdOf-km9ULGn3wm&4C9Utm* VTJxWh+Y;(S{lNJBr>y%W{{!yJzW@LL literal 0 HcmV?d00001 diff --git a/renderer/sdl_gpu/compiled/stencil.frag.sprv b/renderer/sdl_gpu/compiled/stencil.frag.sprv new file mode 100644 index 0000000000000000000000000000000000000000..c6030d8fe7eaaf5ee6749daa5704bc6d3a255081 GIT binary patch literal 2676 zcmZ9N`EDCk6o+q`g)~Xily2-el%-N3(9$i0wkfnVP(uo3E1Ns@*onb$jGaV+zd#6y zN8u3=&%pbz2vEPznWK(8($D92)_d-`cgExUPCS&-Sei`-Qa|lIhtfVQ3ER|AR%^Fw zOM~rv>Fl{zjF?a3!$4yWrs*_+6q{MQtK%i`GPnU2h}Fj;uyHD<^aqI(X(m-`_3GMM zwRXMU==JJ@?e99xdZ*oO*7J71+1PIHHjSGu#kISQ&Q4wqm?WaS_9S)ZS!0m5>iw;q zZr;r67CX7yIf6ub`Czwb|{VZ?q4EEv|(7zq} zrqU_oAlvNO@>V0;Zf|vmHQoEp?q0q#D9YiRPM6f19lD{Zdt3dy*{?sSKQWxw1ARPw zX?)%q_L9MSSwGuE+FN5p&qwri^sLj{Ft19yeLV#{Gj)4Y_fhw()IFcW{3;iRC$?uh z2A{%K0mq5=Jk^b>pid*U3HAL2H)fH@8Km4eq5l+d+8zZyN70`|R#bTOt4E)qFJc=5 zuJdBSU3aC>-K+l4-K%=6_bQxc^$6WBL)_0bq`i2Tta}}4j&|#)-zaSQs|7bF`nm~s z9--fX`=-RY8}O8VxN@EoG|shrjQI=8B+|8!wU!ou5mVk2&-4B6t{h6-yp9aQgkC-!XV{R2OXW`CX{8HG?!Hv`RQ(-#~ zx1PSsOvhPhyNcglF92=c3)k`tE&}bp6XTh_R@l5-e^TRhq|ZOZ>+>V92AsP*YF~z1 zdl5wKD{$?<6}4C4+M@QGa35>0BHsY-0Bg&wT}8eHyhG*MZx?p=>6xnkO`bK)w@&2O z;O5^1(c8Ol?UAou&bLm)z6W;=W4%x6#yT6ny}Jvd7yoNK3w^$W>dUM=g=~R& zV7__!jq}~s7d6^&ZF77%%=;RQ0pB6d#_x_XzF+#kDQw1BZ@sWZy$-szc$YTe+N|{} zb6G}qfp*`m9@58i*S-a`d2aH5=|-;YKlnx9H_%?(pU(`qKrO#jvn4)P@cqaGz&Zz! z@ts_NdrmR;LvZuwsIL8C;4J*s>WjF;aBIgm=LpzI0^^Obf9LZA(C2r=xOhLGhFiD%4pq>#N6a&D zV-`W|=>%N6Yg_+W5NlW9`os5J;ZquC&+gm2=YhTHi`p;1ombp5_hX#)|2a2x&;CC7 C+0uOg literal 0 HcmV?d00001 diff --git a/renderer/sdl_gpu/compiled/stencil.vert.sprv b/renderer/sdl_gpu/compiled/stencil.vert.sprv new file mode 100644 index 0000000000000000000000000000000000000000..5f52b5b577c6077a9c30d54c8bb3461f8e754788 GIT binary patch literal 2532 zcmZ9NU2{`a6oyaRBoq-4Q9#9-v|3QGegUEgEmBO;Xo5w&JQJIYbDWrD^1+~2df|+> z{v!W?Kgb(re4ca8Dw%HP?Oy9$?^=8Bwf6}t%Nxg%WGOk3oJ_t?ig6}chDqRR`P^*n zx3;FU-qyA2@2fbKtmHy9r<3P$yTad}yV+3Z9B6=D&<6Wpg@4P;|BzH6r`bxI?RK+u zKRp* zejKEa`;%G!>4Raf|5U!3yprp1yFUzAD%ZYIXgmE$A;+acEs=Lnr!$&nvurekJ(lzK zAm_8;Y(M+eQin0mOYGht8}DY*S$BBQKaANyWAnk_Fjl2U_RZlQ-(=A0%l1*OB%5#!`?kwGn$ObtlVa@_ zerCH4`%}&DQcbxWdDZ-Gb*dsG?s<~g}xfUNnsd>hkR|lTUel>t2{B>;W>+dqJE%?5D zeTQ+s1AW&z%U|aPXYfqg=dfzbb#NZN`_VU6$6sZZ6EEZY<<)`XO6l7hb1vdP&#YF? zxco`L*uF}?jGY2!y4vjJFE2!FEQ7_ zQa)GU3S0gbaed!Y#4ljGZ_j3rE;4(r3Wy%Oj;-Dv*l*)02}I<|Z~{ zC;ZQH_qT2H?_#@O_4@Zp-+sBizHd}b+}V9>Ylx`%1-2tJO>8+e!R?itd~gp+O~DoS z|0SGjSi>A`XSK1XHnw}x_Y8g~`q86D*h}CBeYT(W+@a=Qm@2cmtIYoX$9a7#{?0G@ z{;uiYM(>@T2fnX6R8#AFy-P)Fy$a;is5kF5Ag|uF^wnQxej99pOF;bwv%i7zk$(l- zJafF?cY!&+Ej8ZV7LaeVqHDfaa`K+vbN@lUG5&ZZCy1{^E&p?nm6F&qvprhaw7KGDQkWL=i3#3mfWY<<`|2$Z(+N>v4`kt%(sEO zBe*-*a_+lBC+yocP;1P$D*mE|u^r%DsPRm`v(G^TtOM^;e+PIEzKyRyyba%tZ`oYm z#@9ejP2BA_C2uXiGyUja2m5IM9>U2-|GveR6U)GRwwB+Av8eSOwzVFCcr%Z&<<JD|Zi4>-@N%_` literal 0 HcmV?d00001 diff --git a/renderer/sdl_gpu/main.odin b/renderer/sdl_gpu/main.odin new file mode 100644 index 0000000..3c1f2f6 --- /dev/null +++ b/renderer/sdl_gpu/main.odin @@ -0,0 +1,282 @@ +package sdl_renderer + +import "core:fmt" +import "core:c" +import "core:mem" +import "core:os/os2" +import sdl "vendor:sdl3" + +import syl "../.." + +Rect :: struct #packed { + radius: [4]f32, + position_and_size: [4]f32, + color: [4]f32, + uv_and_type: [4]f32, // .z component as type, 0 for rect, 1 for text + border_thickness: [4]f32, + border_color: [4][4]f32, +} + +Command_Batch :: struct { + start, end: int, +} + +Renderer :: struct { + gpu: ^sdl.GPUDevice, + window: ^sdl.Window, + pipeline: ^sdl.GPUGraphicsPipeline, + rects: [dynamic]Rect, + rect_gpu_buf: Gpu_Dynamic_Buffer, + clear_color: [4]f32, +} + +Gpu_Dynamic_Buffer :: struct { + data: ^sdl.GPUBuffer, + tansfer: ^sdl.GPUTransferBuffer, + byte_size: int, + prev_byte_size: int, +} + +init_renderer :: proc( + window_title: cstring, + shader_paths: [sdl.GPUShaderStage]string, + window_size: [2]c.int = {800, 640}, + window_flags: sdl.WindowFlags = {.RESIZABLE}, + shader_format: sdl.GPUShaderFormat = {.SPIRV}, + shader_debug: bool = false, +) -> Renderer { + + r := Renderer{} + + ok := sdl.Init({.VIDEO}); assert(ok) + + r.window = sdl.CreateWindow(window_title, window_size.x, window_size.y, window_flags) + r.gpu = sdl.CreateGPUDevice(shader_format, shader_debug, nil) + + assert(r.window != nil) + assert(r.gpu != nil) + + ok = sdl.ClaimWindowForGPUDevice(r.gpu, r.window); assert(ok) + + vert_shader := load_shader(r.gpu, shader_paths[.VERTEX], .VERTEX, shader_format, 1, 0, 1) + frag_shader := load_shader(r.gpu, shader_paths[.FRAGMENT], .FRAGMENT, shader_format, 0, 0, 0) + + r.pipeline = sdl.CreateGPUGraphicsPipeline( + r.gpu, + { + fragment_shader = frag_shader, + vertex_shader = vert_shader, + target_info = { + color_target_descriptions = &sdl.GPUColorTargetDescription { + blend_state = { + src_alpha_blendfactor = .ONE, + dst_alpha_blendfactor = .ONE_MINUS_SRC_ALPHA, + alpha_blend_op = .ADD, + src_color_blendfactor = .SRC_ALPHA, + dst_color_blendfactor = .ONE_MINUS_SRC_ALPHA, + color_blend_op = .ADD, + enable_blend = true, + enable_color_write_mask = true, + color_write_mask = ~{}, + }, + format = sdl.GetGPUSwapchainTextureFormat(r.gpu, r.window), + }, + num_color_targets = 1, + }, + primitive_type = .TRIANGLELIST, + }, + ) + assert(r.pipeline != nil) + + sdl.ReleaseGPUShader(r.gpu, vert_shader) + sdl.ReleaseGPUShader(r.gpu, frag_shader) + + r.rect_gpu_buf = init_gpu_dynamic_buffer(&r) + + return r +} + +init_ui :: proc() { + syl.ctx.measure_text = measure_text +} + +init_gpu_dynamic_buffer :: proc(renderer: ^Renderer) -> Gpu_Dynamic_Buffer { + data_buffer := sdl.CreateGPUBuffer(renderer.gpu, {size = 64, usage = {.GRAPHICS_STORAGE_READ}}) + transfer_buffer := sdl.CreateGPUTransferBuffer(renderer.gpu, {size = 64, usage = .UPLOAD}) + + dyn_buf: Gpu_Dynamic_Buffer + dyn_buf.byte_size = 64 + dyn_buf.prev_byte_size = 64 + dyn_buf.data = data_buffer + dyn_buf.tansfer = transfer_buffer + + return dyn_buf +} + +destroy_renderer :: proc() { + +} + +destroy_gpu_dynamic_buffer :: proc(renderer: ^Renderer, buffer: ^Gpu_Dynamic_Buffer) { + sdl.ReleaseGPUBuffer(renderer.gpu, buffer.data) + sdl.ReleaseGPUTransferBuffer(renderer.gpu, buffer.tansfer) +} + +update_dynamic_buffer :: proc( + renderer: ^Renderer, + buf: ^Gpu_Dynamic_Buffer, + command_buffer: ^sdl.GPUCommandBuffer, + data: rawptr, +) { + if buf.byte_size > buf.prev_byte_size { + sdl.ReleaseGPUBuffer(renderer.gpu, buf.data) + buf.data = sdl.CreateGPUBuffer( + renderer.gpu, + {size = u32(buf.byte_size), usage = {.GRAPHICS_STORAGE_READ}}, + ) + + sdl.ReleaseGPUTransferBuffer(renderer.gpu, buf.tansfer) + buf.tansfer = sdl.CreateGPUTransferBuffer( + renderer.gpu, + {size = u32(buf.byte_size), usage = .UPLOAD}, + ) + } + + if buf.byte_size > 0 { + tmem := sdl.MapGPUTransferBuffer(renderer.gpu, buf.tansfer, false) + mem.copy(tmem, data, buf.byte_size) + sdl.UnmapGPUTransferBuffer(renderer.gpu, buf.tansfer) + + copy_pass := sdl.BeginGPUCopyPass(command_buffer) + sdl.UploadToGPUBuffer( + copy_pass, + {transfer_buffer = buf.tansfer}, + {size = u32(buf.byte_size), buffer = buf.data}, + false, + ) + sdl.EndGPUCopyPass(copy_pass) + } +} + +begin :: proc(renderer: ^Renderer) { + clear(&renderer.rects) +} + +feed_renderer :: proc( + renderer: ^Renderer, + elem: ^syl.Element, + start: int, + bound_texture: ^sdl.GPUTexture, +) { + start := start + bound_texture := bound_texture + + r := Rect{} + + switch elem.type { + case .Box, .Button: + box := cast(^syl.Box)elem + + r.position_and_size.xy = box.global_position + r.position_and_size.zw = box.size + r.border_thickness = 1 + r.radius = 5 + r.color = color_syl_sdl(box.background_color) + r.border_color = color_syl_sdl(box.border_color) + + case .Text: + text := cast(^syl.Text)elem + + r.position_and_size.xy = text.global_position + r.position_and_size.zw = text.size + r.border_thickness = 1 + } + + append(&renderer.rects, r) + + for child in elem.children { + feed_renderer(renderer, child, start, nil) + } +} + +render :: proc(renderer: ^Renderer) { + window_w, window_h: i32 + sdl.GetWindowSize(renderer.window, &window_w, &window_h) + + cmd_buf := sdl.AcquireGPUCommandBuffer(renderer.gpu) + + renderer.rect_gpu_buf.prev_byte_size = renderer.rect_gpu_buf.byte_size + renderer.rect_gpu_buf.byte_size = len(renderer.rects) * size_of(Rect) + update_dynamic_buffer(renderer, &renderer.rect_gpu_buf, cmd_buf, raw_data(renderer.rects)) + + swapchain_tex := &sdl.GPUTexture{} + ok := sdl.WaitAndAcquireGPUSwapchainTexture( + cmd_buf, + renderer.window, + &swapchain_tex, + nil, + nil, + ); assert(ok) + + swapchain_target := sdl.GPUColorTargetInfo { + clear_color = cast(sdl.FColor)renderer.clear_color, + load_op = .CLEAR, + store_op = .STORE, + texture = swapchain_tex, + } + + storage_bufs := []^sdl.GPUBuffer{renderer.rect_gpu_buf.data} + + projection_mat := matrix[4, 4]f32{ + 2.0 / f32(window_w), 0, 0, -1, + 0, -2.0 / f32(window_h), 0, 1, + 0, 0, 1, 0, + 0, 0, 0, 1, + } + + render_pass := sdl.BeginGPURenderPass(cmd_buf, &swapchain_target, 1, nil) + + sdl.BindGPUGraphicsPipeline(render_pass, renderer.pipeline) + sdl.PushGPUVertexUniformData(cmd_buf, 0, &projection_mat, u32(size_of(projection_mat))) + sdl.BindGPUVertexStorageBuffers(render_pass, 0, raw_data(storage_bufs), u32(len(storage_bufs))) + sdl.DrawGPUPrimitives(render_pass, 6, u32(len(renderer.rects)), 0, 0) + + sdl.EndGPURenderPass(render_pass) + + ok = sdl.SubmitGPUCommandBuffer(cmd_buf); assert(ok) +} + +load_shader :: proc( + gpu: ^sdl.GPUDevice, + path: string, + stage: sdl.GPUShaderStage, + format: sdl.GPUShaderFormat, + num_ubo, num_samplers, num_storage_buffers: u32, +) -> ^sdl.GPUShader { + source, read_err := os2.read_entire_file_from_path(path, context.allocator) + defer delete(source, context.allocator) + assert(read_err == nil) + shader := sdl.CreateGPUShader( + gpu, + { + code_size = len(source), + entrypoint = "main", + code = raw_data(source), + stage = stage, + format = format, + num_uniform_buffers = num_ubo, + num_samplers = num_samplers, + num_storage_buffers = num_storage_buffers, + }, + ) + assert(shader != nil) + return shader +} + +measure_text :: proc(s: string, size: int) -> int { + return 15 +} + +color_syl_sdl :: proc(c: [4]u8) -> [4]f32 { + return {f32(c.r), f32(c.g), f32(c.b), f32(c.a)} / 255.0 +} diff --git a/renderer/sdl_gpu/source/main.frag b/renderer/sdl_gpu/source/main.frag new file mode 100644 index 0000000..47d5c65 --- /dev/null +++ b/renderer/sdl_gpu/source/main.frag @@ -0,0 +1,82 @@ +#version 460 + +layout(location = 0) in vec4 in_color; +layout(location = 1) in vec4 in_radius; +layout(location = 2) in vec4 in_border_thickness; +layout(location = 3) in vec2 in_size; +layout(location = 4) in vec2 in_uv; +layout(location = 5) in vec2 in_text_uv; +layout(location = 6) in vec4 in_border_color[4]; +layout(location = 10) flat in int in_render_type; + +layout(location = 0) out vec4 out_color; + +// layout(set = 2, binding = 0) uniform sampler2D font_sampler; + +float rect_select_side(vec2 pos, vec4 s) { + s.xy = pos.x < 0 ? s.xw : s.yz; + s.x = pos.y > 0 ? s.x : s.y; + return s.x; +} + +vec4 rect_get_blended_border_color(vec2 p, vec2 half_size) { + float d_left = abs(p.x + half_size.x); + float d_right = abs(p.x - half_size.x); + float d_top = abs(p.y + half_size.y); + float d_bottom = abs(p.y - half_size.y); + + float wl = 1.0 / (d_left + 1e-9); + float wr = 1.0 / (d_right + 1e-9); + float wt = 1.0 / (d_top + 1e-9); + float wb = 1.0 / (d_bottom + 1e-9); + + vec4 color = (in_border_color[3] * wl + in_border_color[0] * wt + + in_border_color[1] * wr + in_border_color[2] * wb) / + (wl + wt + wr + wb); + + return color; +} + +float rect_get_blended_thickness(vec2 pos, vec2 half_size, vec4 thickness) { + float d_left = abs(pos.x + half_size.x); + float d_right = abs(pos.x - half_size.x); + float d_top = abs(pos.y + half_size.y); + float d_bottom = abs(pos.y - half_size.y); + + float wl = 1.0 / (d_left + 1e-2); + float wr = 1.0 / (d_right + 1e-2); + float wt = 1.0 / (d_top + 1e-2); + float wb = 1.0 / (d_bottom + 1e-2); + + return (thickness[3] * wl + thickness[0] * wt + thickness[1] * wr + + thickness[2] * wb) / + (wl + wt + wr + wb); +} + +float sdf_rect(vec2 pos, vec2 half_size, float radius) { + vec2 q = abs(pos) - half_size + radius; + return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - + radius; // NEGATIVE inside, POSITIVE outside +} + + +void main() { + + vec2 h_size = in_size * 0.5; + vec2 s_pos = in_uv * in_size - h_size; + + float rad = rect_select_side(s_pos, in_radius); + + float thickness_blended = rect_get_blended_thickness(s_pos, h_size, in_border_thickness); + vec4 border_color_blended = rect_get_blended_border_color(s_pos, h_size); + + float sdf = sdf_rect(s_pos, h_size, rad); + + float stroke = smoothstep(-1, 1, thickness_blended / 2 - abs(sdf + thickness_blended / 2)); + + float fill = smoothstep(-1, 1, -sdf - thickness_blended); + + vec4 stroke_color = vec4(border_color_blended) * stroke; + vec4 fill_color = vec4(in_color) * fill; + out_color = fill_color + stroke_color; +} diff --git a/renderer/sdl_gpu/source/main.vert b/renderer/sdl_gpu/source/main.vert new file mode 100644 index 0000000..78c37d0 --- /dev/null +++ b/renderer/sdl_gpu/source/main.vert @@ -0,0 +1,52 @@ +#version 460 + +struct Rect { + vec4 radius; + vec4 position_and_size; + vec4 color; + vec4 uv_and_type; + vec4 thickness; + vec4 border_color_top; + vec4 border_color_right; + vec4 border_color_bottom; + vec4 border_color_left; +}; + +layout(set = 0, binding = 0) readonly buffer Rects { Rect rects[]; }; +layout(set = 1, binding = 0) uniform mats { mat4 proj; }; + +layout(location = 0) out vec4 out_color; +layout(location = 1) out vec4 out_radius; +layout(location = 2) out vec4 out_thickness; +layout(location = 3) out vec2 out_size; +layout(location = 4) out vec2 out_uv; +layout(location = 5) out vec2 out_text_uv; +layout(location = 6) out vec4 out_border_color[4]; +layout(location = 10) out int out_render_type; + +const vec2 positions[6] = + vec2[](vec2(0.5, 0.5), vec2(-0.5, 0.5), vec2(-0.5, -0.5), vec2(0.5, 0.5), + vec2(-0.5, -0.5), vec2(0.5, -0.5)); + +const vec2 uvs[6] = vec2[](vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 1.0), + vec2(1.0, 0.0), vec2(0.0, 1.0), vec2(1.0, 1.0)); + +void main() { + vec2 vert_pos = positions[gl_VertexIndex]; + + Rect rect = rects[gl_InstanceIndex]; + vec2 size = rect.position_and_size.zw; + vec2 pos = rect.position_and_size.xy; + + out_color = rect.color; + out_radius = rect.radius; + out_thickness = rect.thickness; + out_size = size; + out_uv = uvs[gl_VertexIndex]; + out_text_uv = rect.uv_and_type.xy; + out_border_color = vec4[](rect.border_color_top, rect.border_color_right, + rect.border_color_bottom, rect.border_color_left); + + out_render_type = int(rect.uv_and_type.z); + gl_Position = proj * vec4(vert_pos * size + pos + size / 2, 1.0, 1.0); +} From 2ec89a55501cd669ce9f62cf619c829dc0f19e17 Mon Sep 17 00:00:00 2001 From: Stannic Date: Fri, 23 Jan 2026 22:11:43 +0500 Subject: [PATCH 2/5] (sdl renderer) : Add font rendering --- examples/assets/Lora.ttf | Bin 0 -> 210884 bytes examples/game_menu.odin | 2 +- examples/game_menu_sdl.odin | 272 ++++++++++++++++++++ renderer/sdl_gpu/compiled/main.frag.sprv | Bin 7964 -> 8352 bytes renderer/sdl_gpu/compiled/main.vert.sprv | Bin 5020 -> 5596 bytes renderer/sdl_gpu/compiled/stencil.frag.sprv | Bin 2676 -> 0 bytes renderer/sdl_gpu/compiled/stencil.vert.sprv | Bin 2532 -> 0 bytes renderer/sdl_gpu/main.odin | 216 ++++++++++++++-- renderer/sdl_gpu/source/main.frag | 41 +-- renderer/sdl_gpu/source/main.vert | 12 +- 10 files changed, 493 insertions(+), 50 deletions(-) create mode 100644 examples/assets/Lora.ttf create mode 100644 examples/game_menu_sdl.odin delete mode 100644 renderer/sdl_gpu/compiled/stencil.frag.sprv delete mode 100644 renderer/sdl_gpu/compiled/stencil.vert.sprv diff --git a/examples/assets/Lora.ttf b/examples/assets/Lora.ttf new file mode 100644 index 0000000000000000000000000000000000000000..f9eb7ceac9e08ecd27bcba18fab5d60dbb992b85 GIT binary patch literal 210884 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�&{03_u|kHN%qbI_Z{fT?!0T?-dFA|^vukAqEVez9opEN zod}NX8FGzyRJu;BAc*K?nU2_*xapqlW9QAp`?g&)7~8%h=I(ptn|$S*w<`qh6SMda z3)S=Q`2Wp)d6XPid2ioay-lyvU0v1tTD>pRGu_k6O!q83yJnF_Go#VSl8m(=$%__a zOO~8t%Mv~oe!<34SR4WYj+4N9K%Do;djTglIR=s#I5@ybLYxyE5`zs1U_)$duzK>l zRo%15l5LXw@ubsg-KyJn{qDEl@0%JrcYIQ#>An7BecPcbuhvRE{r=qxBU>u_XS2AO zKQU5wVSwSsNff| z>wQ>*WvoFfR}NM}VRvL?i%BZzE`d8X3AO@Essh`i1a!dMMVVa!|D!17p{!BZL$(su z?9ym*$CM}1Nsi1HXHuc5y0)YP-P!J;fof(6kPrr@yadS|=iJG+R5(RW;&t3- zXt^BXm$=t&1?0q6edI;J*mgOBYU@Su3rE4d7Sc)4**D#jL7lSX zs>7C968n6a!F)397W=%Jp?ng=f!)sd1~*E70&RbSWlm9`iOsxqqBiUMsluTvXKO9r zh)m;UBL+MM{@<>PN2;}(E-vr9cw_&-(TC<|PVel>uiQDk<;-$Ew|wXP_|f6u;GTMH z%%d(O^;>$OOwY~th8KMS|CXsj?dD6%ix-a%4IRI@xbV=i!9sQaUE||t5BBvPJUc#q z*Z%6E#__T2b<;VKkAA9APA?8tRtm|8l+4-W8{X@)Sq5c%%lRajbk7_%bTCrkc*utb z2PMEVhXV)z5ZXwB?RCW|E2M!$u+Pk!Ql4o4O&52rT)eSQp6HonO!Umm^y%fU+>SeM zjTs3kxJSp_`&Pu>yk0QJGd{~sYt&`&x(tRGbTr+Rx~fTpeu){(u0rjmOFI`Y9UmM# zera*>p`(Mv>b|?iC(a(|>pO6EV*IXs)e8}lm59dTGHBB96(MwMuUN{P)ed{LY>lYX z5l51#+xOet9W?nIab#lQw1ZP;qqh(sQRLVn8Sck8J^iXhkRo|*$jk`VjJf>)B@S{W za7Rpd0=h(4A+6SlLhEgU)#um^9mM#-611X$>Tq$^fhzQ|6BX z$(-9|5Y1YXLH*8?ehXo?fS%v?id+ZOm*ni_$Nje9fE#D#W_5rBvRTqaGOH%H*R1FD z&zJ>07XiZ++K9kF7YCfg*NnTb(bgI!vrDe-Sj{HpZHO|a9b{?9Fd1(i1*;Zm=5)$5#eRvnAKalyd zAE-WvZ*YUnmsB4H24cVAirfM>1HXT@?pLp2oJ)B3G5H;%_*~IQi`-6B7{iC0BX>0K zzkGt+-h40iarG5wEdDp@#u$GG+e9MHKv>qEu-z=RFu7KM?XzWDgl7jFpt`r(%)3X6 z{ZZRo{|6FYha{9z(y+_NuQb0R+(tV~U7YG|iZP20aXUZWg_jgT{bjibivXw)xWD$3DN}U99M=8cJ;t%qi7a z^trn$O3ljMIlTpMI(fT|z0p+`yepr7Y%fMEVZ;pg10yhLg&9UR5BX}-z_a!=Fl5Y9 zAr{)!G|&L!D%PjQ7z?+KF5QJbuqwJBhhI*Ub*X<#yI^VL{QYD}1ytD7y__;l39 z*20ELCc$73Ozxf@SNl<1i+Ey057FHfvBq}H2jr=)Ekb-|*zP-8)v@`@4>`3(ADG|K z?oR%J8d*cyYtwg&&v&d3M;6{(?T7LYtPvN(6{*#qQzv;H_agi#h8tvVw;YaMuTN$yV|z1O zi}_`jM>?D*_np!=j!h4qS~A{rlX|MFeec2L)EzrAdZb>AIx78|Q&^z^Rg(Mz`A5VR zfL~xGQIS~$95Wg6osc~kWayl_gv^j8Dpyd_L#bNS!4Ia&Vl^UF#d31c!AEP!qF9rp ze(|ar!jy=QTv zk{jq2UDa(hS3fw9lP5Xek$wj5<&knAUd;-QbnisIYoVmMpPfzcv)=$5C|>UI1Z0zV zFH?FcTn5x$EbR!3nxN5Xv<|06hm~z}8;>uYTj)Gs*@`=rmfifq*`@NX3@JOJy5yGr z@;kFomB#H!Pv#xhcaEK&BXuDw)F7g%h*4&4yW?u)pf=?oQ%!!^SLJ!bJCYYNC6h`Jy*&uH86FJr0LI z8}B)|1WTYCxr_#o-7F?YyO;k|rrF7N3Ylq4_rdmsEDInsPs9`;(I;cKTiOEmu^hgR^6j9{?5ftz`2kNKLy!OUlAtoT`haiC>v_OMF1Cl_p9H7kN)i%adWWg&T zmCosm-HYYg7^G{fO|Q{wlTxhcvFRRuAN-{KCa*a&Js^sH8luIXV6CqnQR~&3l-uU$ zi5sm3{WfyMKaw8ZUrv;rb~iEeIxXMf@F$03#T}{c>_A1LG!O`e>hu1%pTzr|7H^Ny z*5wP8z1_fO@WiG!0#;tPzIM92eVY1g;EO&V$D1K;kA={Mm0&6g025Fhx< zU8~>UB72*UkiCo7>X{5=4yK>S=PB0r>cxV15HtlwoZhu z(}6$gMU9p3;FFH8_Y6s=&YUOjez&q(VK#n}CqwvrKUwAhc41#n=-y7JTkNpgY&QKz z4PL(i7%Lm*28W-`pk{_J$7<%~1gf}i^-fU1W1xaB4KU5gDOHj>K!vpQXY%dfBddpL z1Xy27e_m$Lst2^5d)wF*li!G4-#vH7taXohlF%LIMcdrTt@(u{De*B)Y^1kmdC*Ks z-sZCuIT?I^FP_>~yCvpH?pjf>t26F?19o+lj2p2lMn8qojR3o9>B})T8@TY%VeF5V zzB0keTTvbWhS1Vq1X+0x>gO>VEqyh|%4|Q6%k^K=ZZ7{)E`y<<|3iNNtLNDJ>xf46 zG`SDD!_TTbL;xgCz!%_?Xp!9_mKdcZ&X&fkjnB1h{dVh^Yfq$UZ*IVJ81=8e+=E$oBa$n)+%@Ohh+u1Z(A}?SrXoJ;~ zYv4UvxK8y2@@LfBYJup82B~k(k($)}GTGhya)i{WxA~PQsdI7x>ylQTN1xlEv;C~< zLDePYn|zmQIoO%mnBa8r0{Q0pn1FIyJz`wsn}ve~yi2Q2qm>hKE&Q&6nIX4^{1>9@ zP_((9&4PVSyU0JeF0fx=9;zMWc^YE&ah?S%ipA38@#ZKw)4V`KPmcb@;2(SvwIZnX ztWql$W9ULdo<*g{n-|EL9j#hy?Nk^+4I?nH6CM-DdW&Q2R(sV|T5J78UT+opU;B+l zC@JkLqmVnOs(Vi;Kakgo^)X4~9QuG}5;JXn2JK{2A0p3VYgiT%6_K$#lf?i=OfUc< z1sb{PQ*t6!Ol1P8iZ@hACVJvAZ*;`%s3ouZlvD)2;I-FW9*0+?oLj63zK_505mqdt zdLH9B#Vkj$U#H*aoqng!G34n`1L#}y-Jn_-9 zNRapi29I^vqnWoO_L0Oi5?qPtBh6QuFE5bZU2sGBzyJOs;hR5XJk^Zq0vDz|IK4T> zoyH)+Ds+(vkT_)u3oYwnVzOhFy2YBp39u#G!atCn>DgEE?HtLCB68%`Qti4-;GM&L z(^6ulITP&b6}V1cvNsgyi_VZgj@2Ty{^a#pA=7`Mwou(3ADWHz?=DY{4K55HC=FlV zJL~L-2o%$RM?aoSkmsHyBL@9VGYh%V$Oo*V#IQoBRLH@0!Y(_-*)+WBe85czF&uIm6_( z4GbTA6e%qOuM*f9kqXtC4l`+`M;|&D@PyQ$a-F0S>ph} zozhn|ufNcGz}7H`F>J*c;%p2I#CQ%ek<0=(S&S$~1f9IV;mB%WEC72SH@ltsuRd)u zQlraAJ2m0xK>Yi9llF^$XLg(5nmq&%UCjzXGucPpBO;Fh$;wH%H3rexe2_NJiwPS? z0u!|_vHt1mWo|!rJ!lSmg3vRx^zD2~VIjtm;z!IDdiu+Ukgy)NO#cYBq+k3yNA3y6 zO`O#Hm9O6~jYsH8ihKPl3_Hi55Bdw*gy$#lA8v-ovN}`5ba6mHYl>X1)K*}NPv_>k zM{XYQ6&-4KY4_xwyXGRv@m;}iAtD&;#jrG%t92IkPwlxdO$lyi-!Pk{VSVlP5AS;C zdw0a+$*CKT*KtSp*!u|>WQrg}jB*@c)3#r0V zu|A(l&sU|S4bK8n8jc+anJ70uUEa~1U+E{byxdj1t{TmahFsM`C_fg4Q&+fo7s2!1 zU6s;uch|0dDL)<*D}_+D5pwqxL%{T^#OhDgKf=7Mphhg)=#*-O`CDqR$c+OqUB7oUg((WJk7Rvk#Dtlk#CXjvif7n?>q4O zZ=!5b+EblI`zKJg%J}iHY6r?2`H91A%HQTEFp9NWoNb)7dNU4E3S5Aevo~1zFv?N6 z%*%ZCA-O!M0@X(^jOy2$pONc*h)SUPT25$<=A#(h3d&A+hiDMB=|y@594#qF{xCUpNJP9qFPG!cahCgEwXfjdc zIf?thlYi-X{7F51=n{9yMsD?xyPNlWn(sMCLL||A23W6vyU5eXWEgBy(iSFY>2ucb z7PL=KTqA`bZM;PmzykdVImDe;;lT2WZnoWeOFPX}TQCqa9J!b0^%{*u@_3?VT!64U zoN1=(PdmADq*Krv#14zmXyHY@fXog7XfB-2tlts9D%9x#_&7WRFHGi_qi_XE^py?& zCol&FdC?d6)7CdV5OKRD$qmDD>zmPnF1im^f$uPn&K7?mw{$=Vg#~9YO!uu-22V%F zR8Mm%#@+JJ)IcR&hRF806Fj20a}J+niH_Z54>&m5p>tk9+d0(#Yr3k`mn(}f1aI*z zS$z&TwQ1~h%y~i7=_%*%^O)BZ?oQnTzP5u=CO&~*FqO7Wr~D5;eUd+8iGd+88-D7q znxcqRuQAhE&%GAT+5z!+ul*ihhe6};z(C7-pCkv+8sr#~kkClcNv6)ybMBAP575b@ zhx{LVM6NY}T5^sC^oT&IPTi9q@G8{?9tj;i?*EirYY!Qu-&0xSRceQjV_N7x_LMSa zzqKBbYH-LKr+eJJ39B!i^_l8$e_(qBj89)%w*a-Z`fO?mnV|&726f%XT{%A?zAl$5 z3paj$POv*&BJcLlem)fp$$9qT_TnRRqhb|&V zI_N!h7+HwwBrW6Q{2PlJz^-hsalx|jL3VeL8E3#Yo60k8v!TQ1w&QB1nm3sQS3X>- zI_)MqE!$m2z0=?b@kUOsFcQ)NomSwKYnR~#ODj5c?40;Lf>qNo z-kIg>e6rr_a|FZjd5r3Z zf8OA+jGJw|!x#8`P!xmg!|Gn&ysUa^^*ai@mT`Hc!lZ!H9?`=3&onQu^}h|ZbE{TF z`P8dmuz-Iv(#glM^3WHz*1|%IT2Cfy_eL!df7NS=*@Ds3(8x<+jfHnv9SCC6dCP*n zBN_;#y$ZZGFB1#6DD0(j62W<{D{{5k4|Vqs?=MON(U{vFw%GXL=Hs!(9L7(XvDv{8+vvftDM`=4ce|y z7n$PrVBCn-aOJq)8dRUy+)?A2kn7se?SpR$#cVB07<#&G${F#%;1 zH_+h&Vkj(vlK!sz9SCvyn8zE7c|EcAH_mJpJk878#}&-8Mp>V|D4H}JwfXtG>8rae z9zo3+sqHlChf)7W?fPq5ToLbf_-%yK7@hQ0|7jaFa%#b2VQayu!IykuowDLQZ=$SU zu^h8V1}!xiUhxc=B&%MdHF0XsAv3ibop#MIf3w4A(Fvk~ai>f4RdO%)2=sFYA#pK? z$&*vu(k6(hyb)sP28hYa5Hr6SVrJ0J^N1+;Ye2Bl&J_?mM!U%S;oJK5cfNl0o$r!I z`A=`$`FCNY-{L0F!#Zpy-Ue*%R$$w7P1siEGxg@M4cfz*$p7RXNB@jZV&>lOe_s65 z|8hLZnI4F&To<|s7>RKy;Pj7x^Vp1qEc8zT)tuGSITDYLbb75_6)xrK3Y&y*STKdV zWcz!8?g7re_ZswI4M4A;2VEOBcq1Ql%Z6=c`0)XZ>xnE=$T2_75AanG9q39u9W z8sWK5y*20g314p*As|XVZ?6Q-vgC3EBOa$eOur`Pqf)P5?21N9K8ZwwnG7X~P>kL5 z%)yK5QSN<=`+--#VpeQ%p735{oadvKL{{`?qanK(<|DR7Fp$QjBX7dWok{hGP#TAP ze72tu!6zm>ti|T*1QsLKBJKxVouA5vd3V$r_4j)%2~#|g9UX7J9@kg}w^cA}4Yqgf z=+GhRS1xErdt->2G^_Ccd=ZWi5COhM&-xD2gF(*li0_8Ia4jKiT*gjPi$;*6=gzu+ zhyE6Qb!o}}-~+&SZFvi^Rb^k0w#O-7V`UYcP~DAF!+hGGfUH7PBh;_@bIcaEQA{EF5GBG35$b zjW&~>u8_wNSPMb4cE|Phkh}Re?hvov`?;VS;SP=OAkqTye4SiVV*XpIU=#1tL?T-9Z+qzz_}B z6bL%ZGR;)CS3HzMmJ#HSd9D>4644@&3-#Rr{ENp3T83|WXRFwbx1$6p+njP?J^xC( z*7_6d8C)0$^3x)2H2cHu-@i!z2GSdD#4w+{ip;$ujyRT*sj)Yk(c#hp&q>+CMYeO$ zKC#MAqw_PNF%O{+Z#1mOWxKb~TlL@?OlGnZ85<2dk1o$zjLjcIudSHa6*ISN Rd;dj;NmtxEv~;BVKLGXUr+WYZ literal 0 HcmV?d00001 diff --git a/examples/game_menu.odin b/examples/game_menu.odin index 30b9086..0e365d7 100644 --- a/examples/game_menu.odin +++ b/examples/game_menu.odin @@ -210,4 +210,4 @@ game_ui_destroy :: proc(game_ui: ^Game_UI) { syl.element_destroy(game_ui.sub_menus.credits) syl.element_destroy(game_ui.sub_menus.exit) free(game_ui) -} \ No newline at end of file +} diff --git a/examples/game_menu_sdl.odin b/examples/game_menu_sdl.odin new file mode 100644 index 0000000..67eea8d --- /dev/null +++ b/examples/game_menu_sdl.odin @@ -0,0 +1,272 @@ +package main + +import syl "../" +import "core:math/ease" + +import renderer "../renderer/sdl_gpu" +import sdl "vendor:sdl3" + +SCREEN_W :: 800 +SCREEN_H :: 500 + +BACKGROUND_COLOR :: [4]u8{17, 45, 58, 255} +BLACK :: [4]u8{0, 0, 0, 255} +WHITE :: [4]u8{255, 255, 255, 255} +PRIMARY_COLOR :: [4]u8{236, 155, 92, 255} // orange +SECONDARY_COLOR :: [4]u8{160, 223, 227, 255} +PRIMARY_TEXT_COLOR :: [4]u8{25, 16, 0, 255} // black +MUTED :: [4]u8{46, 79, 90, 255} + +style_sheet := syl.Style_Sheet { + box = {padding = {0, 0, 0, 10}, transitions = {padding = {0.15, .Linear}}}, + button = { + normal = { + text_color = SECONDARY_COLOR, + font_size = 18, + background_color = BACKGROUND_COLOR, + padding = {10, 30, 10, 30}, + // border with different colors + border_color = { + PRIMARY_COLOR, // top + MUTED, // right + MUTED, // bottom + PRIMARY_COLOR, // left + }, + border_thickness = {2, 2, 2, 20}, + border_radius = {0, 0, 20, 0}, + transitions = {background_color = {0.2, .Cubic_Out}, padding = {0.1, .Cubic_Out}}, + }, + hover = { + background_color = PRIMARY_COLOR, + text_color = PRIMARY_TEXT_COLOR, + border_color = PRIMARY_COLOR, + }, + press = { + background_color = WHITE, + border_color = WHITE, + padding = [4]f32{13, 30, 7, 30}, + transitions = syl.Box_Transitions{background_color = {0, .Linear}}, + }, + }, + text = {color = SECONDARY_COLOR, font_size = 18}, +} + +Game_UI :: struct { + count: int, + using base: syl.Box, + container: ^syl.Box, + sub_menus: struct { + start: ^syl.Box, + settings: ^syl.Box, + network: ^syl.Box, + credits: ^syl.Box, + exit: ^syl.Box, + }, + current: ^syl.Box, +} + +Message :: enum { + Start, + Settings, + Network, + Credits, + Exit, +} + +game_ui_update :: proc(using game_ui: ^Game_UI, msg: ^Message) { + if current != nil { + syl.element_remove_child(container, current) + current.padding = 0 + } + + switch msg^ { + case .Start: + current = sub_menus.start + case .Network: + current = sub_menus.network + case .Settings: + current = sub_menus.settings + case .Credits: + current = sub_menus.credits + case .Exit: + current = sub_menus.exit + } + + syl.element_add_child(container, current, use_transitions = true) +} + +game_menu_ui :: proc() -> ^Game_UI { + game_ui := new(Game_UI) + // This allows the box to receive messages + handler := syl.make_handler(game_ui, syl.Box, Message, game_ui_update, game_ui_destroy) + game_ui.sub_menus.start = start() + game_ui.sub_menus.settings = settings() + game_ui.sub_menus.network = network() + game_ui.sub_menus.credits = credits() + game_ui.sub_menus.exit = exit() + + // With a handler syl.box will initialize Game_UI instead creating a new Box + syl.box( + size = {SCREEN_H, SCREEN_H}, + style_sheet = &style_sheet, + handler = handler, + children = { + syl.center( + syl.box( + syl.box( + gap = 10, + children = { + syl.button( + text_content = "START", + size = {200, 40}, + on_mouse_over = Message.Start, + ), + syl.button( + text_content = "SETTINGS", + size = {200, 40}, + on_mouse_over = Message.Settings, + ), + syl.button( + text_content = "NETWORK", + size = {200, 40}, + on_mouse_over = Message.Network, + ), + syl.button( + text_content = "CREDITS", + size = {200, 40}, + on_mouse_over = Message.Credits, + ), + syl.button( + text_content = "EXIT", + size = {200, 40}, + on_mouse_over = Message.Exit, + ), + }, + ), + syl.box(ref = &game_ui.container, width = 200, height_sizing = .Expand), + layout_direction = .Left_To_Right, + ), + ), + }, + ) + + return game_ui +} + +start :: proc() -> ^syl.Box { + return syl.box( + gap = 10, + children = { + syl.button(text_content = "CONTINUE", size = {200, 40}), + syl.button(text_content = "NEW GAME", size = {200, 40}), + }, + ) +} + +settings :: proc() -> ^syl.Box { + return syl.box( + gap = 10, + children = { + syl.button(text_content = "AUDIO", size = {200, 40}), + syl.button(text_content = "VIDEO", size = {200, 40}), + syl.button(text_content = "CONTROLS", size = {200, 40}), + syl.button(text_content = "GAMEPLAY", size = {200, 40}), + }, + ) +} + +network :: proc() -> ^syl.Box { + return syl.box( + gap = 10, + children = { + syl.button(text_content = "CONNECT TO SERVER", size = {240, 40}), + syl.button(text_content = "HOST GAME", size = {240, 40}), + syl.button(text_content = "DISCONNECT", size = {240, 40}), + }, + ) +} + +credits :: proc() -> ^syl.Box { + return syl.box( + gap = 10, + sizing = syl.Expand, + children = {syl.center(syl.text("Programmer: CRSOLVER", wrap = false))}, + ) +} + +exit :: proc() -> ^syl.Box { + return syl.box( + sizing = syl.Expand, + children = { + syl.center( + syl.box( + syl.text("ARE YOU SURE?", wrap = false), + syl.box( + syl.button(text_content = "YES", size = {100, 40}), + syl.button(text_content = "NO", size = {100, 40}), + layout_direction = .Left_To_Right, + padding = 0, + gap = 10, + ), + gap = 10, + padding = 0, + ), + ), + }, + ) +} + +main :: proc() { + r := renderer.init_renderer( + "Game Menu", + { + .VERTEX = "../renderer/sdl_gpu/compiled/main.vert.sprv", + .FRAGMENT = "../renderer/sdl_gpu/compiled/main.frag.sprv", + }, + ) + f := renderer.add_font(&r, "./assets/Lora.ttf", 16) + + renderer.default_font = f + r.clear_color = renderer.color_syl_sdl(BACKGROUND_COLOR) + + syl.create_context(renderer.measure_text) + + ui := game_menu_ui() + + run: for { + + event := sdl.Event{} + + for sdl.PollEvent(&event) { + #partial switch event.type { + case .QUIT: + break run + case .KEY_DOWN: + if event.key.scancode == .ESCAPE { + break run + } + } + } + + x, y: f32 + _ = sdl.GetMouseState(&x, &y) + + syl.input_mouse_move({x, y}) + + syl.update(ui) + syl.clear_context() + + renderer.begin(&r) + renderer.feed_validate(&r, renderer.feed_renderer(&r, ui, 0, nil)) + renderer.render(&r) + } +} + +game_ui_destroy :: proc(game_ui: ^Game_UI) { + syl.element_destroy(game_ui.sub_menus.start) + syl.element_destroy(game_ui.sub_menus.network) + syl.element_destroy(game_ui.sub_menus.settings) + syl.element_destroy(game_ui.sub_menus.credits) + syl.element_destroy(game_ui.sub_menus.exit) + free(game_ui) +} diff --git a/renderer/sdl_gpu/compiled/main.frag.sprv b/renderer/sdl_gpu/compiled/main.frag.sprv index 030651519d13615e5dac4f477f484826d66de64a..438672dea8a43f543499b8f5da5cd57da3d708a0 100644 GIT binary patch delta 2597 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 UF!Hb9O<;fQpSE%Trs_T5KOMmIm;e9( literal 5020 zcmai$`F0dV6vjKu!X}#_i$a*NLV>VVafUdf>3tmHk(1<6Iphy`72ac60&1R_`Pt?LvEvkp5YSaoVaWoddC}qc$$B*$htCD;WRHO5;hL5clMOa#KC2CA zQe@6y^j5hVb(Ar$J>f%5v_up~je4nEua?@;4RTlvXRS19jh5r`E{1A$#+-LN(G2CA z@SGvO8da{>!#10?HFDC28?9>CD!EjpxX~0fZO_lPx1y1&+PTH+!XJ$vZp3k8blSdP zw!Ic!;o#I^qh>Bblfwng>AHX!>ADnz=c)_#*>!Vkc)x?rEHF4*U) z%O=^WLvl~K0L-9Pa5f6N}e75^gBEQn}0rH=R2O>aeP#d&n3@k+D6~VcBB?H zk48x?r)~5Pwoa`!ZQ~yJ?l9=*yP8MK?urzba~NB*)wsrt|2-6qqWt-`EtYJToRGDk znXx&UraATu$Jf#P*ehHi#brFlht>S(Z~XdF9K8)Ul;Y@TxXUSy9){zCVEO1KaqhJdLNc*JAs1Ewdjb3QP?GvGxIXIwv#o7zliX0ShoaqI<-**Ie0 zk~$@^=y1tiGOl-$+(}=_q*am}^cjQuSuP>3aWl2ex?bzSQG@-{dz&QeWu>Y@{vPdu zwb55g`uN21`suoM29~hJD7KB zc6yb4b2e(UUNh(7-nw;Cez)PCB-E2$A{Q~l7BoL84*Le(Q;%l+yQOod3C_B=$$i1G zfBJ!U1xI~3*H#I`&etowOTwPEX~wtU@lMU`k##NpCF%GZenomZ|6XBw%Iy24Qy;?z zq|?iW4@swnhWn+nrs1Q~={@#$S~I!X5BmoXdd@K40elSe9RL&mf!E289n5#Z{K0$| zz^u={E=hu5^048L9G>aBkA?Apw_YrHoaQcj4qx*tO9v-h(zC=twi$3QY2S@Bzn%NH=vEX~2!+ziE z_}jfbl&;aYPu)i>c^T~CUorF~x#%_ik8eG)R{FXW=G|^gVf>HJ!lzQ0_^QVTm7hAV zk?>tPq}9pIcdB1IAlI`JIDGJDAJ0kP@n>&f%k{kUUP-@dy-o8Ak}Z<05^VStG;fz+ zJEL`|>kiLpa%=O}UzBh*Vp$7}pT+EwPM$6a`z2<#gc!aH#DMX$n3ts!L(MJbRq4d= z?H~q>pT+EvP7HTrG5e$w!}o<4Fn$*Ensj2gSBrUFIx+ht#DMX$m^Y*o!`)lVo6?Cn zARz{fpT!)MPE1~6F^8oS!?%nWFn$)(C!HAX(qi6{PE5aq7;vA&Vva~BhMusPW73H^ zDj^1ppV^Djv2z#PJNJA-0zatLS^uQx;JFiOOfGsDo8@|2`s`c-;@~aUJJR7027N)U zQ<||^t|95<;=XNPXQad9&o>-w{^z9QPam59yVBwD=Nk_;|MSxE=bp{~ed+M{^BVxh zMh!lca9(T|JjS+4dPTC@+jzIb64v1!LJ1r`mg|b=$#qdW*!Fc*I{Tt$ZC{aec&p(x z>2L_cYB(Yto8@XqCl`Hbxth}9&F2&8_|S*u(~=HvK5gmv(0Aq&ONTd~j&ywJGxHgf z4nL#{)--=AVSVxwkDVIu+k%bn(M`>tNytSFZb-Vcc)h;kb8d%bWdOf-km9ULGn3wm&4C9Utm* VTJxWh+Y;(S{lNJBr>y%W{{!yJzW@LL diff --git a/renderer/sdl_gpu/compiled/stencil.frag.sprv b/renderer/sdl_gpu/compiled/stencil.frag.sprv deleted file mode 100644 index c6030d8fe7eaaf5ee6749daa5704bc6d3a255081..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2676 zcmZ9N`EDCk6o+q`g)~Xily2-el%-N3(9$i0wkfnVP(uo3E1Ns@*onb$jGaV+zd#6y zN8u3=&%pbz2vEPznWK(8($D92)_d-`cgExUPCS&-Sei`-Qa|lIhtfVQ3ER|AR%^Fw zOM~rv>Fl{zjF?a3!$4yWrs*_+6q{MQtK%i`GPnU2h}Fj;uyHD<^aqI(X(m-`_3GMM zwRXMU==JJ@?e99xdZ*oO*7J71+1PIHHjSGu#kISQ&Q4wqm?WaS_9S)ZS!0m5>iw;q zZr;r67CX7yIf6ub`Czwb|{VZ?q4EEv|(7zq} zrqU_oAlvNO@>V0;Zf|vmHQoEp?q0q#D9YiRPM6f19lD{Zdt3dy*{?sSKQWxw1ARPw zX?)%q_L9MSSwGuE+FN5p&qwri^sLj{Ft19yeLV#{Gj)4Y_fhw()IFcW{3;iRC$?uh z2A{%K0mq5=Jk^b>pid*U3HAL2H)fH@8Km4eq5l+d+8zZyN70`|R#bTOt4E)qFJc=5 zuJdBSU3aC>-K+l4-K%=6_bQxc^$6WBL)_0bq`i2Tta}}4j&|#)-zaSQs|7bF`nm~s z9--fX`=-RY8}O8VxN@EoG|shrjQI=8B+|8!wU!ou5mVk2&-4B6t{h6-yp9aQgkC-!XV{R2OXW`CX{8HG?!Hv`RQ(-#~ zx1PSsOvhPhyNcglF92=c3)k`tE&}bp6XTh_R@l5-e^TRhq|ZOZ>+>V92AsP*YF~z1 zdl5wKD{$?<6}4C4+M@QGa35>0BHsY-0Bg&wT}8eHyhG*MZx?p=>6xnkO`bK)w@&2O z;O5^1(c8Ol?UAou&bLm)z6W;=W4%x6#yT6ny}Jvd7yoNK3w^$W>dUM=g=~R& zV7__!jq}~s7d6^&ZF77%%=;RQ0pB6d#_x_XzF+#kDQw1BZ@sWZy$-szc$YTe+N|{} zb6G}qfp*`m9@58i*S-a`d2aH5=|-;YKlnx9H_%?(pU(`qKrO#jvn4)P@cqaGz&Zz! z@ts_NdrmR;LvZuwsIL8C;4J*s>WjF;aBIgm=LpzI0^^Obf9LZA(C2r=xOhLGhFiD%4pq>#N6a&D zV-`W|=>%N6Yg_+W5NlW9`os5J;ZquC&+gm2=YhTHi`p;1ombp5_hX#)|2a2x&;CC7 C+0uOg diff --git a/renderer/sdl_gpu/compiled/stencil.vert.sprv b/renderer/sdl_gpu/compiled/stencil.vert.sprv deleted file mode 100644 index 5f52b5b577c6077a9c30d54c8bb3461f8e754788..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2532 zcmZ9NU2{`a6oyaRBoq-4Q9#9-v|3QGegUEgEmBO;Xo5w&JQJIYbDWrD^1+~2df|+> z{v!W?Kgb(re4ca8Dw%HP?Oy9$?^=8Bwf6}t%Nxg%WGOk3oJ_t?ig6}chDqRR`P^*n zx3;FU-qyA2@2fbKtmHy9r<3P$yTad}yV+3Z9B6=D&<6Wpg@4P;|BzH6r`bxI?RK+u zKRp* zejKEa`;%G!>4Raf|5U!3yprp1yFUzAD%ZYIXgmE$A;+acEs=Lnr!$&nvurekJ(lzK zAm_8;Y(M+eQin0mOYGht8}DY*S$BBQKaANyWAnk_Fjl2U_RZlQ-(=A0%l1*OB%5#!`?kwGn$ObtlVa@_ zerCH4`%}&DQcbxWdDZ-Gb*dsG?s<~g}xfUNnsd>hkR|lTUel>t2{B>;W>+dqJE%?5D zeTQ+s1AW&z%U|aPXYfqg=dfzbb#NZN`_VU6$6sZZ6EEZY<<)`XO6l7hb1vdP&#YF? zxco`L*uF}?jGY2!y4vjJFE2!FEQ7_ zQa)GU3S0gbaed!Y#4ljGZ_j3rE;4(r3Wy%Oj;-Dv*l*)02}I<|Z~{ zC;ZQH_qT2H?_#@O_4@Zp-+sBizHd}b+}V9>Ylx`%1-2tJO>8+e!R?itd~gp+O~DoS z|0SGjSi>A`XSK1XHnw}x_Y8g~`q86D*h}CBeYT(W+@a=Qm@2cmtIYoX$9a7#{?0G@ z{;uiYM(>@T2fnX6R8#AFy-P)Fy$a;is5kF5Ag|uF^wnQxej99pOF;bwv%i7zk$(l- zJafF?cY!&+Ej8ZV7LaeVqHDfaa`K+vbN@lUG5&ZZCy1{^E&p?nm6F&qvprhaw7KGDQkWL=i3#3mfWY<<`|2$Z(+N>v4`kt%(sEO zBe*-*a_+lBC+yocP;1P$D*mE|u^r%DsPRm`v(G^TtOM^;e+PIEzKyRyyba%tZ`oYm z#@9ejP2BA_C2uXiGyUja2m5IM9>U2-|GveR6U)GRwwB+Av8eSOwzVFCcr%Z&<<JD|Zi4>-@N%_` diff --git a/renderer/sdl_gpu/main.odin b/renderer/sdl_gpu/main.odin index 3c1f2f6..f394c37 100644 --- a/renderer/sdl_gpu/main.odin +++ b/renderer/sdl_gpu/main.odin @@ -1,10 +1,10 @@ package sdl_renderer -import "core:fmt" import "core:c" import "core:mem" import "core:os/os2" import sdl "vendor:sdl3" +import ttf "vendor:sdl3/ttf" import syl "../.." @@ -12,22 +12,32 @@ Rect :: struct #packed { radius: [4]f32, position_and_size: [4]f32, color: [4]f32, - uv_and_type: [4]f32, // .z component as type, 0 for rect, 1 for text + uv_x: [4]f32, + uv_y: [4]f32, border_thickness: [4]f32, border_color: [4][4]f32, + flags: [4]i32, } Command_Batch :: struct { - start, end: int, + start, end: int, + font_texture: ^sdl.GPUTexture, } +default_font: ^ttf.Font = nil + Renderer :: struct { - gpu: ^sdl.GPUDevice, - window: ^sdl.Window, - pipeline: ^sdl.GPUGraphicsPipeline, - rects: [dynamic]Rect, - rect_gpu_buf: Gpu_Dynamic_Buffer, - clear_color: [4]f32, + gpu: ^sdl.GPUDevice, + window: ^sdl.Window, + pipeline: ^sdl.GPUGraphicsPipeline, + font_engine: ^ttf.TextEngine, + font_sampler: ^sdl.GPUSampler, + dummy_texture: ^sdl.GPUTexture, + fonts: [dynamic]^ttf.Font, + rects: [dynamic]Rect, + batch: [dynamic]Command_Batch, + rect_gpu_buf: Gpu_Dynamic_Buffer, + clear_color: [4]f32, } Gpu_Dynamic_Buffer :: struct { @@ -59,7 +69,7 @@ init_renderer :: proc( ok = sdl.ClaimWindowForGPUDevice(r.gpu, r.window); assert(ok) vert_shader := load_shader(r.gpu, shader_paths[.VERTEX], .VERTEX, shader_format, 1, 0, 1) - frag_shader := load_shader(r.gpu, shader_paths[.FRAGMENT], .FRAGMENT, shader_format, 0, 0, 0) + frag_shader := load_shader(r.gpu, shader_paths[.FRAGMENT], .FRAGMENT, shader_format, 0, 1, 0) r.pipeline = sdl.CreateGPUGraphicsPipeline( r.gpu, @@ -93,11 +103,41 @@ init_renderer :: proc( r.rect_gpu_buf = init_gpu_dynamic_buffer(&r) + assert(ttf.Init()) + r.font_engine = ttf.CreateGPUTextEngine(r.gpu) + + r.font_sampler = sdl.CreateGPUSampler( + r.gpu, + { + address_mode_u = .REPEAT, + address_mode_v = .REPEAT, + address_mode_w = .REPEAT, + mag_filter = .LINEAR, + min_filter = .LINEAR, + mipmap_mode = .LINEAR, + }, + ) + + r.dummy_texture = sdl.CreateGPUTexture( + r.gpu, + { + height = 1, + width = 1, + format = .R8G8B8A8_UNORM, + usage = {.SAMPLER}, + layer_count_or_depth = 1, + num_levels = 1, + }, + ) + return r } -init_ui :: proc() { - syl.ctx.measure_text = measure_text +add_font :: proc(renderer: ^Renderer, path: cstring, size: f32) -> ^ttf.Font { + font := ttf.OpenFont(path, size) + assert(font != nil) + append(&renderer.fonts, font) + return font } init_gpu_dynamic_buffer :: proc(renderer: ^Renderer) -> Gpu_Dynamic_Buffer { @@ -160,6 +200,7 @@ update_dynamic_buffer :: proc( begin :: proc(renderer: ^Renderer) { clear(&renderer.rects) + clear(&renderer.batch) } feed_renderer :: proc( @@ -167,35 +208,125 @@ feed_renderer :: proc( elem: ^syl.Element, start: int, bound_texture: ^sdl.GPUTexture, +) -> ( + int, + ^sdl.GPUTexture, ) { start := start bound_texture := bound_texture - r := Rect{} - switch elem.type { case .Box, .Button: box := cast(^syl.Box)elem + r := Rect{} r.position_and_size.xy = box.global_position r.position_and_size.zw = box.size - r.border_thickness = 1 - r.radius = 5 + r.border_thickness = box.border_thickness + r.radius = box.border_radius r.color = color_syl_sdl(box.background_color) - r.border_color = color_syl_sdl(box.border_color) + r.flags = 0 + r.border_color = { + color_syl_sdl(box.border_color.x), + color_syl_sdl(box.border_color.y), + color_syl_sdl(box.border_color.z), + color_syl_sdl(box.border_color.w), + } + append(&renderer.rects, r) case .Text: text := cast(^syl.Text)elem + font := cast(^ttf.Font)text.font + + if font == nil { + font = default_font + if font == nil { + break + } + } + + for line in text.lines { + ttf.SetFontSize(font, f32(text.font_size)) + font_text := ttf.CreateText( + renderer.font_engine, + font, + cast(cstring)raw_data(line.content), + len(line.content), + ) + defer ttf.DestroyText(font_text) + draw_data := ttf.GetGPUTextDrawData(font_text) + + for seq := draw_data; seq != nil; seq = seq.next { + for idx: i32 = 0; idx < seq.num_indices; idx += 6 { + i0 := seq.indices[idx + 0] + i1 := seq.indices[idx + 1] + i2 := seq.indices[idx + 2] + i3 := seq.indices[idx + 5] + + v0 := seq.xy[i0] + v1 := seq.xy[i1] + v2 := seq.xy[i2] + v3 := seq.xy[i3] + + uv0 := seq.uv[i0] + uv1 := seq.uv[i1] + uv2 := seq.uv[i2] + uv3 := seq.uv[i3] + + x_min := min(min(v0.x, v1.x), min(v2.x, v3.x)) + y_min := min(min(v0.y, v1.y), min(v2.y, v3.y)) + x_max := max(max(v0.x, v1.x), max(v2.x, v3.x)) + y_max := max(max(v0.y, v1.y), max(v2.y, v3.y)) + + width := x_max - x_min + height := y_max - y_min + + r := Rect{} + r.position_and_size.xy = line.global_position + {x_min, 0} + r.position_and_size.zw = {width, height} + r.uv_x = {uv0.x, uv1.x, uv2.x, uv3.x} + r.uv_y = {uv2.y, uv3.y, uv0.y, uv1.y} + r.color = color_syl_sdl(text.color) + r.flags = {1, 0, 0, 0} + + index := len(renderer.rects) + append(&renderer.rects, r) + + if bound_texture == nil { + bound_texture = draw_data.atlas_texture + } + + if draw_data.next != nil && draw_data.next.atlas_texture != bound_texture { + append( + &renderer.batch, + Command_Batch { + start = start, + end = index, + font_texture = bound_texture, + }, + ) + bound_texture = draw_data.atlas_texture + start = index + } + } + } + } + } - r.position_and_size.xy = text.global_position - r.position_and_size.zw = text.size - r.border_thickness = 1 + for child in elem.children { + start, bound_texture = feed_renderer(renderer, child, start, bound_texture) } - append(&renderer.rects, r) + return start, bound_texture +} - for child in elem.children { - feed_renderer(renderer, child, start, nil) +feed_validate :: proc(renderer: ^Renderer, start: int, bound_texture : ^sdl.GPUTexture) { + if len(renderer.batch) == 0 || + renderer.batch[len(renderer.batch) - 1].end < len(renderer.rects) { + append( + &renderer.batch, + Command_Batch{start = start, end = len(renderer.rects), font_texture = bound_texture}, + ) } } @@ -239,7 +370,30 @@ render :: proc(renderer: ^Renderer) { sdl.BindGPUGraphicsPipeline(render_pass, renderer.pipeline) sdl.PushGPUVertexUniformData(cmd_buf, 0, &projection_mat, u32(size_of(projection_mat))) sdl.BindGPUVertexStorageBuffers(render_pass, 0, raw_data(storage_bufs), u32(len(storage_bufs))) - sdl.DrawGPUPrimitives(render_pass, 6, u32(len(renderer.rects)), 0, 0) + sdl.BindGPUFragmentSamplers( + render_pass, + 0, + &sdl.GPUTextureSamplerBinding { + sampler = renderer.font_sampler, + texture = renderer.dummy_texture, + }, + 1, + ) + + for b in renderer.batch { + if b.font_texture != nil { + sdl.BindGPUFragmentSamplers( + render_pass, + 0, + &sdl.GPUTextureSamplerBinding { + sampler = renderer.font_sampler, + texture = b.font_texture, + }, + 1, + ) + } + sdl.DrawGPUPrimitives(render_pass, 6, u32(b.end - b.start), 0, u32(b.start) ) + } sdl.EndGPURenderPass(render_pass) @@ -273,8 +427,18 @@ load_shader :: proc( return shader } -measure_text :: proc(s: string, size: int) -> int { - return 15 +measure_text :: proc(s: string, font: rawptr, size: int, spacing: f32) -> int { + font_ := cast(^ttf.Font)(font) + + if font == nil { + font_ = default_font + } + + ttf.SetFontSize(font_, f32(size)) + w, h: i32 + ttf.GetStringSize(font_, cast(cstring)raw_data(s), len(s), &w, &h) + + return int(w) } color_syl_sdl :: proc(c: [4]u8) -> [4]f32 { diff --git a/renderer/sdl_gpu/source/main.frag b/renderer/sdl_gpu/source/main.frag index 47d5c65..b27ceb4 100644 --- a/renderer/sdl_gpu/source/main.frag +++ b/renderer/sdl_gpu/source/main.frag @@ -7,16 +7,16 @@ layout(location = 3) in vec2 in_size; layout(location = 4) in vec2 in_uv; layout(location = 5) in vec2 in_text_uv; layout(location = 6) in vec4 in_border_color[4]; -layout(location = 10) flat in int in_render_type; +layout(location = 11) flat in ivec4 in_flags; layout(location = 0) out vec4 out_color; -// layout(set = 2, binding = 0) uniform sampler2D font_sampler; +layout(set = 2, binding = 0) uniform sampler2D font_sampler; float rect_select_side(vec2 pos, vec4 s) { - s.xy = pos.x < 0 ? s.xw : s.yz; - s.x = pos.y > 0 ? s.x : s.y; - return s.x; + s.xy = pos.x < 0 ? s.xw : s.yz; + s.x = pos.y > 0 ? s.x : s.y; + return s.x; } vec4 rect_get_blended_border_color(vec2 p, vec2 half_size) { @@ -59,24 +59,27 @@ float sdf_rect(vec2 pos, vec2 half_size, float radius) { radius; // NEGATIVE inside, POSITIVE outside } - void main() { - - vec2 h_size = in_size * 0.5; - vec2 s_pos = in_uv * in_size - h_size; - - float rad = rect_select_side(s_pos, in_radius); + if (in_flags.x == 0) { + vec2 h_size = in_size * 0.5; + vec2 s_pos = in_uv * in_size - h_size; - float thickness_blended = rect_get_blended_thickness(s_pos, h_size, in_border_thickness); - vec4 border_color_blended = rect_get_blended_border_color(s_pos, h_size); + float rad = rect_select_side(s_pos, in_radius); - float sdf = sdf_rect(s_pos, h_size, rad); + float thickness_blended = + rect_get_blended_thickness(s_pos, h_size, in_border_thickness); + vec4 border_color_blended = rect_get_blended_border_color(s_pos, h_size); - float stroke = smoothstep(-1, 1, thickness_blended / 2 - abs(sdf + thickness_blended / 2)); + float sdf = sdf_rect(s_pos, h_size, rad); - float fill = smoothstep(-1, 1, -sdf - thickness_blended); + float stroke = smoothstep( + -1, 1, thickness_blended / 2 - abs(sdf + thickness_blended / 2)); + float fill = smoothstep(-1, 1, -sdf - thickness_blended); - vec4 stroke_color = vec4(border_color_blended) * stroke; - vec4 fill_color = vec4(in_color) * fill; - out_color = fill_color + stroke_color; + vec4 stroke_color = vec4(border_color_blended) * stroke; + vec4 fill_color = vec4(in_color) * fill; + out_color = fill_color + stroke_color; + } else { + out_color = texture(font_sampler, in_text_uv) * in_color; + } } diff --git a/renderer/sdl_gpu/source/main.vert b/renderer/sdl_gpu/source/main.vert index 78c37d0..65dfd65 100644 --- a/renderer/sdl_gpu/source/main.vert +++ b/renderer/sdl_gpu/source/main.vert @@ -4,12 +4,14 @@ struct Rect { vec4 radius; vec4 position_and_size; vec4 color; - vec4 uv_and_type; + vec4 uv_x; + vec4 uv_y; vec4 thickness; vec4 border_color_top; vec4 border_color_right; vec4 border_color_bottom; vec4 border_color_left; + ivec4 flags; }; layout(set = 0, binding = 0) readonly buffer Rects { Rect rects[]; }; @@ -22,7 +24,7 @@ layout(location = 3) out vec2 out_size; layout(location = 4) out vec2 out_uv; layout(location = 5) out vec2 out_text_uv; layout(location = 6) out vec4 out_border_color[4]; -layout(location = 10) out int out_render_type; +layout(location = 11) out ivec4 out_flags; const vec2 positions[6] = vec2[](vec2(0.5, 0.5), vec2(-0.5, 0.5), vec2(-0.5, -0.5), vec2(0.5, 0.5), @@ -31,6 +33,8 @@ const vec2 positions[6] = const vec2 uvs[6] = vec2[](vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(0.0, 1.0), vec2(1.0, 0.0), vec2(0.0, 1.0), vec2(1.0, 1.0)); +const int uv_map[6] = int[](1, 0, 3, 1, 3, 2); + void main() { vec2 vert_pos = positions[gl_VertexIndex]; @@ -43,10 +47,10 @@ void main() { out_thickness = rect.thickness; out_size = size; out_uv = uvs[gl_VertexIndex]; - out_text_uv = rect.uv_and_type.xy; + out_text_uv = vec2(rect.uv_x[uv_map[gl_VertexIndex]], rect.uv_y[uv_map[gl_VertexIndex]]); out_border_color = vec4[](rect.border_color_top, rect.border_color_right, rect.border_color_bottom, rect.border_color_left); + out_flags = rect.flags; - out_render_type = int(rect.uv_and_type.z); gl_Position = proj * vec4(vert_pos * size + pos + size / 2, 1.0, 1.0); } From 700916b76cecd07b17df8712e726c610a8248f5f Mon Sep 17 00:00:00 2001 From: Stannic Date: Fri, 23 Jan 2026 23:42:08 +0500 Subject: [PATCH 3/5] (renderer) : Refactors for both raylib and sdl renderer --- box.odin | 2 +- examples/box_sdf.fs | 96 -------- examples/counters.odin | 76 +------ examples/game_menu.odin | 30 +-- examples/game_menu_sdl.odin | 272 ----------------------- examples/vertex.vs | 26 --- layout_box.odin | 2 +- renderer/raylib/raylib.odin | 3 +- renderer/sdl_gpu/compiled/main.frag.sprv | Bin 8352 -> 9024 bytes renderer/sdl_gpu/main.odin | 66 ++++-- renderer/sdl_gpu/source/main.frag | 56 ++--- 11 files changed, 102 insertions(+), 527 deletions(-) delete mode 100644 examples/box_sdf.fs delete mode 100644 examples/game_menu_sdl.odin delete mode 100644 examples/vertex.vs diff --git a/box.odin b/box.odin index d2d7f85..db83cec 100644 --- a/box.odin +++ b/box.odin @@ -61,4 +61,4 @@ box_set_style :: proc(box: ^Box, style: ^Box_Style_Override, use_transitions: bo box_apply_style :: proc(box: ^Box, style: Box_Style_Override, use_transitions: bool = true) { layout_box_apply_style(box, style, use_transitions) -} \ No newline at end of file +} diff --git a/examples/box_sdf.fs b/examples/box_sdf.fs deleted file mode 100644 index d565faa..0000000 --- a/examples/box_sdf.fs +++ /dev/null @@ -1,96 +0,0 @@ -#version 330 - -in vec2 fragTexCoord; -in vec4 fragColor; - -out vec4 finalColor; - -uniform vec2 position; -uniform vec2 size; -uniform vec4 backgroundColor; -uniform vec4 borderColor[4]; // 0:Top, 1:Right, 2:Bottom, 3:Left -uniform vec4 borderThickness; // x:Top, y:Right, z:Bottom, w:Left -uniform vec4 borderRadius; // TL, TR, BR, BL -uniform float screenHeight; - -float sdf_rect(vec2 pos, vec2 half_size, float radius) { - vec2 q = abs(pos) - half_size + radius; - return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - radius; -} - -// Helper to calculate the inner SDF for a specific thickness pair -float get_inner_sdf(vec2 s_pos, vec2 h_size, vec4 thickness, float rad_outer) { - // Calculate the hole size and offset for the whole rectangle - vec2 inner_h_size = h_size - vec2(thickness.w + thickness.y, thickness.x + thickness.z) * 0.5; - vec2 inner_offset = vec2(thickness.w - thickness.y, thickness.x - thickness.z) * 0.5; - - // Use the maximum thickness meeting at the corner to determine the local inner radius - // (This prevents the inner corner from "over-rounding") - vec2 sign_pos = sign(s_pos); - float t_corner = (sign_pos.x < 0.0) ? - ((sign_pos.y < 0.0) ? max(thickness.w, thickness.x) : max(thickness.w, thickness.z)) : - ((sign_pos.y < 0.0) ? max(thickness.y, thickness.x) : max(thickness.y, thickness.z)); - - float rad_inner = max(0.0, rad_outer - t_corner); - return sdf_rect(s_pos - inner_offset, inner_h_size, rad_inner); -} - -void main() { - vec2 pixelPos = vec2(gl_FragCoord.x, screenHeight - gl_FragCoord.y) - position; - vec2 h_size = size * 0.5; - vec2 s_pos = pixelPos - h_size; - - // 1. Identify Corner & Sides - float t_horiz, t_vert; - vec4 c_horiz, c_vert; - float rad; - - if (s_pos.x < 0.0) { // Left - t_horiz = borderThickness.w; c_horiz = borderColor[3]; - if (s_pos.y < 0.0) { t_vert = borderThickness.x; c_vert = borderColor[0]; rad = borderRadius.x; } - else { t_vert = borderThickness.z; c_vert = borderColor[2]; rad = borderRadius.w; } - } else { // Right - t_horiz = borderThickness.y; c_horiz = borderColor[1]; - if (s_pos.y < 0.0) { t_vert = borderThickness.x; c_vert = borderColor[0]; rad = borderRadius.y; } - else { t_vert = borderThickness.z; c_vert = borderColor[2]; rad = borderRadius.z; } - } - - // 2. Miter Blending Logic - vec2 dist_to_edges = h_size - abs(s_pos); - float miter_val = (dist_to_edges.x / max(t_horiz, 0.01)) - (dist_to_edges.y / max(t_vert, 0.01)); - - // We blend over a slightly wider range to ensure G1 continuity (smoothness) - float blend = smoothstep(-0.1, 0.1, miter_val); - vec4 side_color = mix(c_horiz, c_vert, blend); - - // 3. Geometry Blending (The Fix for the Bump) - // Clamp radius to the rectangle's half-size to avoid over-rounding - // (prevents "pinched" corners when width != height) - float rad_outer = min(rad, min(h_size.x, h_size.y)); - - // Instead of blending radius, we blend the actual Distance Field results - float d_outer = sdf_rect(s_pos, h_size, rad_outer); - - // We calculate two "candidate" inner SDFs - // Candidate A: The inner hole if the whole corner used t_horiz - // Candidate B: The inner hole if the whole corner used t_vert - vec4 thick_h = vec4(t_horiz); // Symmetric assumption for the candidate - vec4 thick_v = vec4(t_vert); - - // However, to keep the rectangle's overall shape, we use the actual thickness vector - // but interpolate the weight of the "active" sides. - float d_inner = get_inner_sdf(s_pos, h_size, borderThickness, rad_outer); - - // 4. Final Masks - float edge_softness = 1.0; - float outer_mask = smoothstep(edge_softness, -edge_softness, d_outer); - float inner_mask = smoothstep(-edge_softness, edge_softness, d_inner); - float fill_mask = smoothstep(edge_softness, -edge_softness, d_inner); - - vec4 border_part = side_color * outer_mask * inner_mask; - vec4 fill_part = backgroundColor * fill_mask; - - if (d_outer > edge_softness) discard; - - finalColor = fill_part + border_part; -} \ No newline at end of file diff --git a/examples/counters.odin b/examples/counters.odin index a8cb085..4c7e5ee 100644 --- a/examples/counters.odin +++ b/examples/counters.odin @@ -1,54 +1,7 @@ package main import syl "../" -import renderer "../renderer/raylib" import "core:math/ease" -import "core:fmt" -import rl "vendor:raylib" - -GREEN :: [4]u8{175,194,30, 255} -BLUE :: [4]u8{0, 0, 255, 255} -YELLOW :: [4]u8{255,255,40, 255} -WHITE :: [4]u8{255,255,255, 255} -BLACK :: [4]u8{0,0,0, 255} -BLANK :: [4]u8{0, 0, 0, 0} -RED :: [4]u8{255, 0, 0, 255} -DARK :: [4]u8{31,31,33, 255} -CELESTE :: [4]u8{80,99,132, 255} - -SCREEN_W :: 800 -SCREEN_H :: 500 - -style_sheet := syl.Style_Sheet { - box = { - padding = 0 - }, - button = { - normal = { - text_color = BLACK, - font_size = 18, - background_color = GREEN, - padding = {10,15,10,15}, - border_radius = 8, - transitions = { - background_color = {0.05, .Linear}, - }, - }, - hover = { - background_color = YELLOW, - }, - press = { - background_color = GREEN, - transitions = syl.Box_Transitions{ - background_color = {0, .Linear} - } - } - }, - text = { - color = WHITE, - font_size = 18 - } -} primary_button := syl.Button_Styles_Override { normal = { @@ -72,7 +25,7 @@ primary_button := syl.Button_Styles_Override { } } -Message :: enum { +Message_Counter :: enum { Increment, Decrement } @@ -83,7 +36,7 @@ Counter :: struct { counter_label: ^syl.Text, } -counter_update:: proc(using counter: ^Counter, message: ^Message) { +counter_update:: proc(using counter: ^Counter, message: ^Message_Counter) { switch message^ { case .Increment: count += 1 case .Decrement: count -= 1 @@ -99,26 +52,22 @@ counter_destroy :: proc(counter: ^Counter) { counter :: proc() -> ^Counter { c := new(Counter) syl.box( - handler = syl.make_handler(c, syl.Box, Message, counter_update, counter_destroy), + handler = syl.make_handler(c, syl.Box, Message_Counter, counter_update, counter_destroy), layout_direction = .Left_To_Right, gap = 10, children = { - syl.button(text_content = "-", on_click = Message.Decrement, style=&primary_button), + syl.button(text_content = "-", on_click = Message_Counter.Decrement, style=&primary_button), syl.box( syl.text("0", ref = &c.counter_label), sizing=syl.Expand, ), - syl.button(text_content = "+", on_click = Message.Increment), + syl.button(text_content = "+", on_click = Message_Counter.Increment), } ) return c } -main :: proc() { - rl.SetConfigFlags({.MSAA_4X_HINT}) - rl.InitWindow(SCREEN_W, SCREEN_H, "Syl in Raylib") - rl.SetTargetFPS(60) - +counter_app :: proc () -> ^syl.Box{ app := syl.box( counter(), counter(), @@ -131,15 +80,6 @@ main :: proc() { gap=4, ) - renderer.init() - for !rl.WindowShouldClose() { - renderer.update(app) - - rl.BeginDrawing() - rl.ClearBackground(cast(rl.Color)BLACK) - renderer.render(app) - rl.EndDrawing() - } - - rl.CloseWindow() + return app } + diff --git a/examples/game_menu.odin b/examples/game_menu.odin index 0e365d7..0c404de 100644 --- a/examples/game_menu.odin +++ b/examples/game_menu.odin @@ -1,9 +1,7 @@ package main import syl "../" -import renderer "../renderer/raylib" import "core:math/ease" -import rl "vendor:raylib" SCREEN_W :: 800 SCREEN_H :: 500 @@ -16,6 +14,14 @@ SECONDARY_COLOR :: [4]u8{160,223,227, 255} PRIMARY_TEXT_COLOR :: [4]u8{25,16,0, 255} // black MUTED :: [4]u8{46,79,90, 255} +GREEN :: [4]u8{175,194,30, 255} +BLUE :: [4]u8{0, 0, 255, 255} +YELLOW :: [4]u8{255,255,40, 255} +BLANK :: [4]u8{0, 0, 0, 0} +RED :: [4]u8{255, 0, 0, 255} +DARK :: [4]u8{31,31,33, 255} +CELESTE :: [4]u8{80,99,132, 255} + style_sheet := syl.Style_Sheet { box = { padding = {0,0,0,10}, @@ -183,26 +189,6 @@ exit :: proc() -> ^syl.Box { }), } -main :: proc() { - //rl.SetConfigFlags({.VSYNC_HINT}) - rl.InitWindow(SCREEN_W, SCREEN_H, "Game Settings") - ui := game_menu_ui() - - renderer.init() - defer renderer.deinit() - - for !rl.WindowShouldClose() { - renderer.update(ui) - - rl.BeginDrawing() - rl.ClearBackground(cast(rl.Color)BACKGROUND_COLOR) - renderer.render(ui) - rl.EndDrawing() - } - - rl.CloseWindow() -} - game_ui_destroy :: proc(game_ui: ^Game_UI) { syl.element_destroy(game_ui.sub_menus.start) syl.element_destroy(game_ui.sub_menus.network) diff --git a/examples/game_menu_sdl.odin b/examples/game_menu_sdl.odin deleted file mode 100644 index 67eea8d..0000000 --- a/examples/game_menu_sdl.odin +++ /dev/null @@ -1,272 +0,0 @@ -package main - -import syl "../" -import "core:math/ease" - -import renderer "../renderer/sdl_gpu" -import sdl "vendor:sdl3" - -SCREEN_W :: 800 -SCREEN_H :: 500 - -BACKGROUND_COLOR :: [4]u8{17, 45, 58, 255} -BLACK :: [4]u8{0, 0, 0, 255} -WHITE :: [4]u8{255, 255, 255, 255} -PRIMARY_COLOR :: [4]u8{236, 155, 92, 255} // orange -SECONDARY_COLOR :: [4]u8{160, 223, 227, 255} -PRIMARY_TEXT_COLOR :: [4]u8{25, 16, 0, 255} // black -MUTED :: [4]u8{46, 79, 90, 255} - -style_sheet := syl.Style_Sheet { - box = {padding = {0, 0, 0, 10}, transitions = {padding = {0.15, .Linear}}}, - button = { - normal = { - text_color = SECONDARY_COLOR, - font_size = 18, - background_color = BACKGROUND_COLOR, - padding = {10, 30, 10, 30}, - // border with different colors - border_color = { - PRIMARY_COLOR, // top - MUTED, // right - MUTED, // bottom - PRIMARY_COLOR, // left - }, - border_thickness = {2, 2, 2, 20}, - border_radius = {0, 0, 20, 0}, - transitions = {background_color = {0.2, .Cubic_Out}, padding = {0.1, .Cubic_Out}}, - }, - hover = { - background_color = PRIMARY_COLOR, - text_color = PRIMARY_TEXT_COLOR, - border_color = PRIMARY_COLOR, - }, - press = { - background_color = WHITE, - border_color = WHITE, - padding = [4]f32{13, 30, 7, 30}, - transitions = syl.Box_Transitions{background_color = {0, .Linear}}, - }, - }, - text = {color = SECONDARY_COLOR, font_size = 18}, -} - -Game_UI :: struct { - count: int, - using base: syl.Box, - container: ^syl.Box, - sub_menus: struct { - start: ^syl.Box, - settings: ^syl.Box, - network: ^syl.Box, - credits: ^syl.Box, - exit: ^syl.Box, - }, - current: ^syl.Box, -} - -Message :: enum { - Start, - Settings, - Network, - Credits, - Exit, -} - -game_ui_update :: proc(using game_ui: ^Game_UI, msg: ^Message) { - if current != nil { - syl.element_remove_child(container, current) - current.padding = 0 - } - - switch msg^ { - case .Start: - current = sub_menus.start - case .Network: - current = sub_menus.network - case .Settings: - current = sub_menus.settings - case .Credits: - current = sub_menus.credits - case .Exit: - current = sub_menus.exit - } - - syl.element_add_child(container, current, use_transitions = true) -} - -game_menu_ui :: proc() -> ^Game_UI { - game_ui := new(Game_UI) - // This allows the box to receive messages - handler := syl.make_handler(game_ui, syl.Box, Message, game_ui_update, game_ui_destroy) - game_ui.sub_menus.start = start() - game_ui.sub_menus.settings = settings() - game_ui.sub_menus.network = network() - game_ui.sub_menus.credits = credits() - game_ui.sub_menus.exit = exit() - - // With a handler syl.box will initialize Game_UI instead creating a new Box - syl.box( - size = {SCREEN_H, SCREEN_H}, - style_sheet = &style_sheet, - handler = handler, - children = { - syl.center( - syl.box( - syl.box( - gap = 10, - children = { - syl.button( - text_content = "START", - size = {200, 40}, - on_mouse_over = Message.Start, - ), - syl.button( - text_content = "SETTINGS", - size = {200, 40}, - on_mouse_over = Message.Settings, - ), - syl.button( - text_content = "NETWORK", - size = {200, 40}, - on_mouse_over = Message.Network, - ), - syl.button( - text_content = "CREDITS", - size = {200, 40}, - on_mouse_over = Message.Credits, - ), - syl.button( - text_content = "EXIT", - size = {200, 40}, - on_mouse_over = Message.Exit, - ), - }, - ), - syl.box(ref = &game_ui.container, width = 200, height_sizing = .Expand), - layout_direction = .Left_To_Right, - ), - ), - }, - ) - - return game_ui -} - -start :: proc() -> ^syl.Box { - return syl.box( - gap = 10, - children = { - syl.button(text_content = "CONTINUE", size = {200, 40}), - syl.button(text_content = "NEW GAME", size = {200, 40}), - }, - ) -} - -settings :: proc() -> ^syl.Box { - return syl.box( - gap = 10, - children = { - syl.button(text_content = "AUDIO", size = {200, 40}), - syl.button(text_content = "VIDEO", size = {200, 40}), - syl.button(text_content = "CONTROLS", size = {200, 40}), - syl.button(text_content = "GAMEPLAY", size = {200, 40}), - }, - ) -} - -network :: proc() -> ^syl.Box { - return syl.box( - gap = 10, - children = { - syl.button(text_content = "CONNECT TO SERVER", size = {240, 40}), - syl.button(text_content = "HOST GAME", size = {240, 40}), - syl.button(text_content = "DISCONNECT", size = {240, 40}), - }, - ) -} - -credits :: proc() -> ^syl.Box { - return syl.box( - gap = 10, - sizing = syl.Expand, - children = {syl.center(syl.text("Programmer: CRSOLVER", wrap = false))}, - ) -} - -exit :: proc() -> ^syl.Box { - return syl.box( - sizing = syl.Expand, - children = { - syl.center( - syl.box( - syl.text("ARE YOU SURE?", wrap = false), - syl.box( - syl.button(text_content = "YES", size = {100, 40}), - syl.button(text_content = "NO", size = {100, 40}), - layout_direction = .Left_To_Right, - padding = 0, - gap = 10, - ), - gap = 10, - padding = 0, - ), - ), - }, - ) -} - -main :: proc() { - r := renderer.init_renderer( - "Game Menu", - { - .VERTEX = "../renderer/sdl_gpu/compiled/main.vert.sprv", - .FRAGMENT = "../renderer/sdl_gpu/compiled/main.frag.sprv", - }, - ) - f := renderer.add_font(&r, "./assets/Lora.ttf", 16) - - renderer.default_font = f - r.clear_color = renderer.color_syl_sdl(BACKGROUND_COLOR) - - syl.create_context(renderer.measure_text) - - ui := game_menu_ui() - - run: for { - - event := sdl.Event{} - - for sdl.PollEvent(&event) { - #partial switch event.type { - case .QUIT: - break run - case .KEY_DOWN: - if event.key.scancode == .ESCAPE { - break run - } - } - } - - x, y: f32 - _ = sdl.GetMouseState(&x, &y) - - syl.input_mouse_move({x, y}) - - syl.update(ui) - syl.clear_context() - - renderer.begin(&r) - renderer.feed_validate(&r, renderer.feed_renderer(&r, ui, 0, nil)) - renderer.render(&r) - } -} - -game_ui_destroy :: proc(game_ui: ^Game_UI) { - syl.element_destroy(game_ui.sub_menus.start) - syl.element_destroy(game_ui.sub_menus.network) - syl.element_destroy(game_ui.sub_menus.settings) - syl.element_destroy(game_ui.sub_menus.credits) - syl.element_destroy(game_ui.sub_menus.exit) - free(game_ui) -} diff --git a/examples/vertex.vs b/examples/vertex.vs deleted file mode 100644 index 7366e8a..0000000 --- a/examples/vertex.vs +++ /dev/null @@ -1,26 +0,0 @@ -#version 330 - -// Input vertex attributes -in vec3 vertexPosition; -in vec2 vertexTexCoord; -in vec3 vertexNormal; -in vec4 vertexColor; - -// Input uniform values -uniform mat4 mvp; - -// Output to fragment shader -out vec2 fragTexCoord; -out vec4 fragColor; - -void main() -{ - // Send texture coordinates to fragment shader - fragTexCoord = vertexTexCoord; - - // Send vertex color to fragment shader - fragColor = vertexColor; - - // Calculate final vertex position - gl_Position = mvp * vec4(vertexPosition, 1.0); -} \ No newline at end of file diff --git a/layout_box.odin b/layout_box.odin index a6cc6f0..ed61f28 100644 --- a/layout_box.odin +++ b/layout_box.odin @@ -547,4 +547,4 @@ layout_box_apply_style:: proc(box: ^Layout_Box, new: Box_Style_Override, use_tra if bc, ok := new.border_color.?; ok && !(.Border_Color in box.overrides) { box.border_color = bc } -} \ No newline at end of file +} diff --git a/renderer/raylib/raylib.odin b/renderer/raylib/raylib.odin index 89156b0..3b6f6f2 100644 --- a/renderer/raylib/raylib.odin +++ b/renderer/raylib/raylib.odin @@ -3,7 +3,6 @@ package raylib_renderer import rl "vendor:raylib" import syl "../.." import "core:strings" -import "core:fmt" mouse_buttons_map := [syl.Mouse]rl.MouseButton{ .LEFT = .LEFT, @@ -37,7 +36,7 @@ deinit :: proc() { init_layout_box_shader :: proc() { // Load shaders from renderer/raylib relative to the process working directory // Use the alternative fragment shader `alt.fs` (converted for Raylib). - shader := rl.LoadShader("vertex.vs", "box_sdf.fs") + shader := rl.LoadShader("assets/vertex.vs", "assets/box_sdf.fs") box_shader = Box_Shader{ shader = shader, loc_position = rl.GetShaderLocation(shader, "position"), diff --git a/renderer/sdl_gpu/compiled/main.frag.sprv b/renderer/sdl_gpu/compiled/main.frag.sprv index 438672dea8a43f543499b8f5da5cd57da3d708a0..9faeebf0c504020bae91654ae3dccd51102b2c18 100644 GIT binary patch literal 9024 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 OMvbjg_u0#R_WuWI=ke13 diff --git a/renderer/sdl_gpu/main.odin b/renderer/sdl_gpu/main.odin index f394c37..a520f53 100644 --- a/renderer/sdl_gpu/main.odin +++ b/renderer/sdl_gpu/main.odin @@ -1,6 +1,8 @@ package sdl_renderer +import "base:runtime" import "core:c" +import "core:fmt" import "core:mem" import "core:os/os2" import sdl "vendor:sdl3" @@ -27,6 +29,7 @@ Command_Batch :: struct { default_font: ^ttf.Font = nil Renderer :: struct { + allocator: runtime.Allocator, gpu: ^sdl.GPUDevice, window: ^sdl.Window, pipeline: ^sdl.GPUGraphicsPipeline, @@ -47,7 +50,8 @@ Gpu_Dynamic_Buffer :: struct { prev_byte_size: int, } -init_renderer :: proc( +init :: proc( + allocator: runtime.Allocator, window_title: cstring, shader_paths: [sdl.GPUShaderStage]string, window_size: [2]c.int = {800, 640}, @@ -103,8 +107,10 @@ init_renderer :: proc( r.rect_gpu_buf = init_gpu_dynamic_buffer(&r) - assert(ttf.Init()) + ok = ttf.Init(); assert(ok) + r.font_engine = ttf.CreateGPUTextEngine(r.gpu) + assert(r.font_engine != nil) r.font_sampler = sdl.CreateGPUSampler( r.gpu, @@ -117,6 +123,7 @@ init_renderer :: proc( mipmap_mode = .LINEAR, }, ) + assert(r.font_sampler != nil) r.dummy_texture = sdl.CreateGPUTexture( r.gpu, @@ -129,6 +136,13 @@ init_renderer :: proc( num_levels = 1, }, ) + assert(r.dummy_texture != nil) + + r.allocator = allocator + + r.batch = make([dynamic]Command_Batch, r.allocator) + r.fonts = make([dynamic]^ttf.Font, r.allocator) + r.rects = make([dynamic]Rect, r.allocator) return r } @@ -144,8 +158,11 @@ init_gpu_dynamic_buffer :: proc(renderer: ^Renderer) -> Gpu_Dynamic_Buffer { data_buffer := sdl.CreateGPUBuffer(renderer.gpu, {size = 64, usage = {.GRAPHICS_STORAGE_READ}}) transfer_buffer := sdl.CreateGPUTransferBuffer(renderer.gpu, {size = 64, usage = .UPLOAD}) + assert(data_buffer != nil) + assert(transfer_buffer != nil) + dyn_buf: Gpu_Dynamic_Buffer - dyn_buf.byte_size = 64 + dyn_buf.byte_size = 64 // can't initialize a buffer with zero size, so reserve 64 bytes in advance dyn_buf.prev_byte_size = 64 dyn_buf.data = data_buffer dyn_buf.tansfer = transfer_buffer @@ -153,8 +170,29 @@ init_gpu_dynamic_buffer :: proc(renderer: ^Renderer) -> Gpu_Dynamic_Buffer { return dyn_buf } -destroy_renderer :: proc() { +destroy :: proc(renderer: ^Renderer) { + + destroy_gpu_dynamic_buffer(renderer, &renderer.rect_gpu_buf) + + sdl.ReleaseGPUGraphicsPipeline(renderer.gpu, renderer.pipeline) + + sdl.ReleaseGPUTexture(renderer.gpu, renderer.dummy_texture) + sdl.ReleaseGPUSampler(renderer.gpu, renderer.font_sampler) + for f in renderer.fonts { + ttf.CloseFont(f) + } + + ttf.DestroyGPUTextEngine(renderer.font_engine) + + sdl.ReleaseWindowFromGPUDevice(renderer.gpu, renderer.window) + + sdl.DestroyWindow(renderer.window) + sdl.DestroyGPUDevice(renderer.gpu) + + delete(renderer.batch) + delete(renderer.rects) + delete(renderer.fonts) } destroy_gpu_dynamic_buffer :: proc(renderer: ^Renderer, buffer: ^Gpu_Dynamic_Buffer) { @@ -224,13 +262,13 @@ feed_renderer :: proc( r.position_and_size.zw = box.size r.border_thickness = box.border_thickness r.radius = box.border_radius - r.color = color_syl_sdl(box.background_color) + r.color = color_syl_to_sdl(box.background_color) r.flags = 0 r.border_color = { - color_syl_sdl(box.border_color.x), - color_syl_sdl(box.border_color.y), - color_syl_sdl(box.border_color.z), - color_syl_sdl(box.border_color.w), + color_syl_to_sdl(box.border_color.x), + color_syl_to_sdl(box.border_color.y), + color_syl_to_sdl(box.border_color.z), + color_syl_to_sdl(box.border_color.w), } append(&renderer.rects, r) @@ -286,7 +324,7 @@ feed_renderer :: proc( r.position_and_size.zw = {width, height} r.uv_x = {uv0.x, uv1.x, uv2.x, uv3.x} r.uv_y = {uv2.y, uv3.y, uv0.y, uv1.y} - r.color = color_syl_sdl(text.color) + r.color = color_syl_to_sdl(text.color) r.flags = {1, 0, 0, 0} index := len(renderer.rects) @@ -320,7 +358,7 @@ feed_renderer :: proc( return start, bound_texture } -feed_validate :: proc(renderer: ^Renderer, start: int, bound_texture : ^sdl.GPUTexture) { +feed_validate :: proc(renderer: ^Renderer, start: int, bound_texture: ^sdl.GPUTexture) { if len(renderer.batch) == 0 || renderer.batch[len(renderer.batch) - 1].end < len(renderer.rects) { append( @@ -379,7 +417,7 @@ render :: proc(renderer: ^Renderer) { }, 1, ) - + for b in renderer.batch { if b.font_texture != nil { sdl.BindGPUFragmentSamplers( @@ -392,7 +430,7 @@ render :: proc(renderer: ^Renderer) { 1, ) } - sdl.DrawGPUPrimitives(render_pass, 6, u32(b.end - b.start), 0, u32(b.start) ) + sdl.DrawGPUPrimitives(render_pass, 6, u32(b.end - b.start), 0, u32(b.start)) } sdl.EndGPURenderPass(render_pass) @@ -441,6 +479,6 @@ measure_text :: proc(s: string, font: rawptr, size: int, spacing: f32) -> int { return int(w) } -color_syl_sdl :: proc(c: [4]u8) -> [4]f32 { +color_syl_to_sdl :: proc(c: [4]u8) -> [4]f32 { return {f32(c.r), f32(c.g), f32(c.b), f32(c.a)} / 255.0 } diff --git a/renderer/sdl_gpu/source/main.frag b/renderer/sdl_gpu/source/main.frag index b27ceb4..55bfc5b 100644 --- a/renderer/sdl_gpu/source/main.frag +++ b/renderer/sdl_gpu/source/main.frag @@ -37,28 +37,32 @@ vec4 rect_get_blended_border_color(vec2 p, vec2 half_size) { return color; } -float rect_get_blended_thickness(vec2 pos, vec2 half_size, vec4 thickness) { - float d_left = abs(pos.x + half_size.x); - float d_right = abs(pos.x - half_size.x); - float d_top = abs(pos.y + half_size.y); - float d_bottom = abs(pos.y - half_size.y); - - float wl = 1.0 / (d_left + 1e-2); - float wr = 1.0 / (d_right + 1e-2); - float wt = 1.0 / (d_top + 1e-2); - float wb = 1.0 / (d_bottom + 1e-2); - - return (thickness[3] * wl + thickness[0] * wt + thickness[1] * wr + - thickness[2] * wb) / - (wl + wt + wr + wb); -} - float sdf_rect(vec2 pos, vec2 half_size, float radius) { vec2 q = abs(pos) - half_size + radius; return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - radius; // NEGATIVE inside, POSITIVE outside } +float sdf_inner_rect(vec2 pos, vec2 half_size, vec4 thickness, + float rad_outer) { + vec2 inner_half = + half_size - + vec2(thickness.w + thickness.y, thickness.x + thickness.z) * 0.5; + + vec2 offset = + vec2(thickness.w - thickness.y, thickness.x - thickness.z) * 0.5; + + vec2 s = sign(pos); + float t_corner = (s.x < 0.0) ? ((s.y < 0.0) ? max(thickness.w, thickness.x) + : max(thickness.w, thickness.z)) + : ((s.y < 0.0) ? max(thickness.y, thickness.x) + : max(thickness.y, thickness.z)); + + float rad_inner = max(0.0, rad_outer - t_corner); + + return sdf_rect(pos - offset, inner_half, rad_inner); +} + void main() { if (in_flags.x == 0) { vec2 h_size = in_size * 0.5; @@ -66,19 +70,21 @@ void main() { float rad = rect_select_side(s_pos, in_radius); - float thickness_blended = - rect_get_blended_thickness(s_pos, h_size, in_border_thickness); vec4 border_color_blended = rect_get_blended_border_color(s_pos, h_size); - float sdf = sdf_rect(s_pos, h_size, rad); + float sdf_outer = sdf_rect(s_pos, h_size, rad); + float sdf_inner = sdf_inner_rect(s_pos, h_size, in_border_thickness, rad); + + float aa = 1.0; + float outer_mask = smoothstep(aa, -aa, sdf_outer); + float inner_mask = smoothstep(-aa, aa, sdf_inner); + + float fill = smoothstep(aa, -aa, sdf_inner); - float stroke = smoothstep( - -1, 1, thickness_blended / 2 - abs(sdf + thickness_blended / 2)); - float fill = smoothstep(-1, 1, -sdf - thickness_blended); + vec4 fill_color = in_color * fill; + vec4 border_color = border_color_blended * outer_mask * inner_mask; - vec4 stroke_color = vec4(border_color_blended) * stroke; - vec4 fill_color = vec4(in_color) * fill; - out_color = fill_color + stroke_color; + out_color = fill_color + border_color; } else { out_color = texture(font_sampler, in_text_uv) * in_color; } From 73211af0c483f3cd74e25a043260bf058da048dc Mon Sep 17 00:00:00 2001 From: Stannic Date: Fri, 23 Jan 2026 23:45:23 +0500 Subject: [PATCH 4/5] (renderer) : Forgot to git add . --- examples/assets/box_sdf.fs | 96 +++++++++++++++++++++++++++++++++ examples/assets/vertex.vs | 26 +++++++++ examples/main.odin | 105 +++++++++++++++++++++++++++++++++++++ 3 files changed, 227 insertions(+) create mode 100644 examples/assets/box_sdf.fs create mode 100644 examples/assets/vertex.vs create mode 100644 examples/main.odin diff --git a/examples/assets/box_sdf.fs b/examples/assets/box_sdf.fs new file mode 100644 index 0000000..d565faa --- /dev/null +++ b/examples/assets/box_sdf.fs @@ -0,0 +1,96 @@ +#version 330 + +in vec2 fragTexCoord; +in vec4 fragColor; + +out vec4 finalColor; + +uniform vec2 position; +uniform vec2 size; +uniform vec4 backgroundColor; +uniform vec4 borderColor[4]; // 0:Top, 1:Right, 2:Bottom, 3:Left +uniform vec4 borderThickness; // x:Top, y:Right, z:Bottom, w:Left +uniform vec4 borderRadius; // TL, TR, BR, BL +uniform float screenHeight; + +float sdf_rect(vec2 pos, vec2 half_size, float radius) { + vec2 q = abs(pos) - half_size + radius; + return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - radius; +} + +// Helper to calculate the inner SDF for a specific thickness pair +float get_inner_sdf(vec2 s_pos, vec2 h_size, vec4 thickness, float rad_outer) { + // Calculate the hole size and offset for the whole rectangle + vec2 inner_h_size = h_size - vec2(thickness.w + thickness.y, thickness.x + thickness.z) * 0.5; + vec2 inner_offset = vec2(thickness.w - thickness.y, thickness.x - thickness.z) * 0.5; + + // Use the maximum thickness meeting at the corner to determine the local inner radius + // (This prevents the inner corner from "over-rounding") + vec2 sign_pos = sign(s_pos); + float t_corner = (sign_pos.x < 0.0) ? + ((sign_pos.y < 0.0) ? max(thickness.w, thickness.x) : max(thickness.w, thickness.z)) : + ((sign_pos.y < 0.0) ? max(thickness.y, thickness.x) : max(thickness.y, thickness.z)); + + float rad_inner = max(0.0, rad_outer - t_corner); + return sdf_rect(s_pos - inner_offset, inner_h_size, rad_inner); +} + +void main() { + vec2 pixelPos = vec2(gl_FragCoord.x, screenHeight - gl_FragCoord.y) - position; + vec2 h_size = size * 0.5; + vec2 s_pos = pixelPos - h_size; + + // 1. Identify Corner & Sides + float t_horiz, t_vert; + vec4 c_horiz, c_vert; + float rad; + + if (s_pos.x < 0.0) { // Left + t_horiz = borderThickness.w; c_horiz = borderColor[3]; + if (s_pos.y < 0.0) { t_vert = borderThickness.x; c_vert = borderColor[0]; rad = borderRadius.x; } + else { t_vert = borderThickness.z; c_vert = borderColor[2]; rad = borderRadius.w; } + } else { // Right + t_horiz = borderThickness.y; c_horiz = borderColor[1]; + if (s_pos.y < 0.0) { t_vert = borderThickness.x; c_vert = borderColor[0]; rad = borderRadius.y; } + else { t_vert = borderThickness.z; c_vert = borderColor[2]; rad = borderRadius.z; } + } + + // 2. Miter Blending Logic + vec2 dist_to_edges = h_size - abs(s_pos); + float miter_val = (dist_to_edges.x / max(t_horiz, 0.01)) - (dist_to_edges.y / max(t_vert, 0.01)); + + // We blend over a slightly wider range to ensure G1 continuity (smoothness) + float blend = smoothstep(-0.1, 0.1, miter_val); + vec4 side_color = mix(c_horiz, c_vert, blend); + + // 3. Geometry Blending (The Fix for the Bump) + // Clamp radius to the rectangle's half-size to avoid over-rounding + // (prevents "pinched" corners when width != height) + float rad_outer = min(rad, min(h_size.x, h_size.y)); + + // Instead of blending radius, we blend the actual Distance Field results + float d_outer = sdf_rect(s_pos, h_size, rad_outer); + + // We calculate two "candidate" inner SDFs + // Candidate A: The inner hole if the whole corner used t_horiz + // Candidate B: The inner hole if the whole corner used t_vert + vec4 thick_h = vec4(t_horiz); // Symmetric assumption for the candidate + vec4 thick_v = vec4(t_vert); + + // However, to keep the rectangle's overall shape, we use the actual thickness vector + // but interpolate the weight of the "active" sides. + float d_inner = get_inner_sdf(s_pos, h_size, borderThickness, rad_outer); + + // 4. Final Masks + float edge_softness = 1.0; + float outer_mask = smoothstep(edge_softness, -edge_softness, d_outer); + float inner_mask = smoothstep(-edge_softness, edge_softness, d_inner); + float fill_mask = smoothstep(edge_softness, -edge_softness, d_inner); + + vec4 border_part = side_color * outer_mask * inner_mask; + vec4 fill_part = backgroundColor * fill_mask; + + if (d_outer > edge_softness) discard; + + finalColor = fill_part + border_part; +} \ No newline at end of file diff --git a/examples/assets/vertex.vs b/examples/assets/vertex.vs new file mode 100644 index 0000000..7366e8a --- /dev/null +++ b/examples/assets/vertex.vs @@ -0,0 +1,26 @@ +#version 330 + +// Input vertex attributes +in vec3 vertexPosition; +in vec2 vertexTexCoord; +in vec3 vertexNormal; +in vec4 vertexColor; + +// Input uniform values +uniform mat4 mvp; + +// Output to fragment shader +out vec2 fragTexCoord; +out vec4 fragColor; + +void main() +{ + // Send texture coordinates to fragment shader + fragTexCoord = vertexTexCoord; + + // Send vertex color to fragment shader + fragColor = vertexColor; + + // Calculate final vertex position + gl_Position = mvp * vec4(vertexPosition, 1.0); +} \ No newline at end of file diff --git a/examples/main.odin b/examples/main.odin new file mode 100644 index 0000000..afab009 --- /dev/null +++ b/examples/main.odin @@ -0,0 +1,105 @@ +package main + +import "core:fmt" +import "core:mem" + +import syl "../" + +import sdl_renderer "../renderer/sdl_gpu/" +import sdl "vendor:sdl3" + +import rl_renderer "../renderer/raylib/" +import rl "vendor:raylib" + +main :: proc() { + when ODIN_DEBUG { + track: mem.Tracking_Allocator + mem.tracking_allocator_init(&track, context.allocator) + context.allocator = mem.tracking_allocator(&track) + + defer { + if len(track.allocation_map) > 0 { + fmt.eprintf("=== %v allocations not freed: ===\n", len(track.allocation_map)) + for _, entry in track.allocation_map { + fmt.eprintf("- %v bytes @ %v\n", entry.size, entry.location) + } + } + mem.tracking_allocator_destroy(&track) + } + } + + run_sdl_backend() +} + +run_raylib_backend :: proc() { + rl.InitWindow(SCREEN_W, SCREEN_H, "Game Settings") + ui := game_menu_ui() + + rl_renderer.init() + defer rl_renderer.deinit() + + for !rl.WindowShouldClose() { + rl_renderer.update(ui) + + rl.BeginDrawing() + rl.ClearBackground(cast(rl.Color)BACKGROUND_COLOR) + rl_renderer.render(ui) + rl.EndDrawing() + } + + rl.CloseWindow() + +} + +run_sdl_backend :: proc() { + r := sdl_renderer.init( + context.allocator, + "Game Menu", + { + .VERTEX = "../renderer/sdl_gpu/compiled/main.vert.sprv", + .FRAGMENT = "../renderer/sdl_gpu/compiled/main.frag.sprv", + }, + ) + + defer sdl_renderer.destroy(&r) + + f := sdl_renderer.add_font(&r, "./assets/Lora.ttf", 16) + + sdl_renderer.default_font = f + r.clear_color = sdl_renderer.color_syl_to_sdl(BACKGROUND_COLOR) + + syl.create_context(sdl_renderer.measure_text) + + ui := game_menu_ui() + + defer syl.destroy_context() + defer syl.box_destroy(ui) + + run: for { + + event := sdl.Event{} + + for sdl.PollEvent(&event) { + #partial switch event.type { + case .QUIT: + break run + case .KEY_DOWN: + if event.key.scancode == .ESCAPE { + break run + } + } + } + + x, y: f32 + _ = sdl.GetMouseState(&x, &y) + + syl.input_mouse_move({x, y}) + + syl.update(ui) + syl.clear_context() + + sdl_renderer.begin(&r) + sdl_renderer.feed_validate(&r, sdl_renderer.feed_renderer(&r, ui, 0, nil)) + sdl_renderer.render(&r) + } +} From 1a9ad2d3905f7448453d497191184f87d4d221f4 Mon Sep 17 00:00:00 2001 From: Stannic Date: Sat, 24 Jan 2026 13:53:59 +0500 Subject: [PATCH 5/5] (refactor): Make data more compact --- examples/game_menu.odin | 222 +++++++++++++---------- examples/main.odin | 1 - renderer/sdl_gpu/compiled/main.frag.sprv | Bin 9024 -> 8968 bytes renderer/sdl_gpu/compiled/main.vert.sprv | Bin 5596 -> 5016 bytes renderer/sdl_gpu/main.odin | 14 +- renderer/sdl_gpu/source/main.frag | 7 +- renderer/sdl_gpu/source/main.vert | 31 ++-- 7 files changed, 150 insertions(+), 125 deletions(-) diff --git a/examples/game_menu.odin b/examples/game_menu.odin index 0c404de..bb86dac 100644 --- a/examples/game_menu.odin +++ b/examples/game_menu.odin @@ -6,48 +6,40 @@ import "core:math/ease" SCREEN_W :: 800 SCREEN_H :: 500 -BACKGROUND_COLOR :: [4]u8{17,45,58, 255} -BLACK :: [4]u8{0,0,0, 255} +BACKGROUND_COLOR :: [4]u8{17, 45, 58, 255} +BLACK :: [4]u8{0, 0, 0, 255} WHITE :: [4]u8{255, 255, 255, 255} -PRIMARY_COLOR :: [4]u8{236,155,92, 255} // orange -SECONDARY_COLOR :: [4]u8{160,223,227, 255} -PRIMARY_TEXT_COLOR :: [4]u8{25,16,0, 255} // black -MUTED :: [4]u8{46,79,90, 255} +PRIMARY_COLOR :: [4]u8{236, 155, 92, 255} // orange +SECONDARY_COLOR :: [4]u8{160, 223, 227, 255} +PRIMARY_TEXT_COLOR :: [4]u8{25, 16, 0, 255} // black +MUTED :: [4]u8{46, 79, 90, 255} -GREEN :: [4]u8{175,194,30, 255} +GREEN :: [4]u8{175, 194, 30, 255} BLUE :: [4]u8{0, 0, 255, 255} -YELLOW :: [4]u8{255,255,40, 255} +YELLOW :: [4]u8{255, 255, 40, 255} BLANK :: [4]u8{0, 0, 0, 0} RED :: [4]u8{255, 0, 0, 255} -DARK :: [4]u8{31,31,33, 255} -CELESTE :: [4]u8{80,99,132, 255} +DARK :: [4]u8{31, 31, 33, 255} +CELESTE :: [4]u8{80, 99, 132, 255} style_sheet := syl.Style_Sheet { - box = { - padding = {0,0,0,10}, - transitions = { - padding = {0.15, .Linear}, - }, - }, + box = {padding = {0, 0, 0, 10}, transitions = {padding = {0.15, .Linear}}}, button = { normal = { text_color = SECONDARY_COLOR, font_size = 18, background_color = BACKGROUND_COLOR, - padding = {10,30,10,30}, + padding = {10, 30, 10, 30}, // border with different colors border_color = { PRIMARY_COLOR, // top - MUTED, // right - MUTED, // bottom + MUTED, // right + MUTED, // bottom PRIMARY_COLOR, // left }, - border_thickness = {2,2,2,12}, - border_radius = {0,0,20,0}, - transitions = { - background_color = {0.2, .Cubic_Out}, - padding = {0.1, .Cubic_Out}, - }, + border_thickness = {2, 2, 2, 12}, + border_radius = {20, 20, 20, 20}, + transitions = {background_color = {0.2, .Cubic_Out}, padding = {0.1, .Cubic_Out}}, }, hover = { background_color = PRIMARY_COLOR, @@ -57,30 +49,25 @@ style_sheet := syl.Style_Sheet { press = { background_color = WHITE, border_color = WHITE, - padding = [4]f32{13,30,7,30}, - transitions = syl.Box_Transitions{ - background_color = {0, .Linear} - } - } + padding = [4]f32{13, 30, 7, 30}, + transitions = syl.Box_Transitions{background_color = {0, .Linear}}, + }, }, - text = { - color = SECONDARY_COLOR, - font_size = 18 - } + text = {color = SECONDARY_COLOR, font_size = 18}, } Game_UI :: struct { - count: int, + count: int, using base: syl.Box, - container: ^syl.Box, - sub_menus: struct { - start: ^syl.Box, + container: ^syl.Box, + sub_menus: struct { + start: ^syl.Box, settings: ^syl.Box, - network: ^syl.Box, - credits: ^syl.Box, - exit: ^syl.Box, + network: ^syl.Box, + credits: ^syl.Box, + exit: ^syl.Box, }, - current: ^syl.Box, + current: ^syl.Box, } Message :: enum { @@ -98,11 +85,16 @@ game_ui_update :: proc(using game_ui: ^Game_UI, msg: ^Message) { } switch msg^ { - case .Start: current = sub_menus.start - case .Network: current = sub_menus.network - case .Settings: current = sub_menus.settings - case .Credits: current = sub_menus.credits - case .Exit: current = sub_menus.exit + case .Start: + current = sub_menus.start + case .Network: + current = sub_menus.network + case .Settings: + current = sub_menus.settings + case .Credits: + current = sub_menus.credits + case .Exit: + current = sub_menus.exit } syl.element_add_child(container, current, use_transitions = true) @@ -119,74 +111,114 @@ game_menu_ui :: proc() -> ^Game_UI { game_ui.sub_menus.exit = exit() // With a handler syl.box will initialize Game_UI instead creating a new Box - syl.box(size = {SCREEN_H, SCREEN_H}, style_sheet = &style_sheet, handler = handler, children = { - syl.center( - syl.box( - syl.box(gap=10, children = { - syl.button(text_content="START", size={200,40}, on_mouse_over = Message.Start), - syl.button(text_content="SETTINGS", size={200,40}, on_mouse_over = Message.Settings), - syl.button(text_content="NETWORK", size={200,40}, on_mouse_over = Message.Network), - syl.button(text_content="CREDITS", size={200,40}, on_mouse_over = Message.Credits), - syl.button(text_content="EXIT", size={200,40}, on_mouse_over = Message.Exit), - }), - syl.box(ref=&game_ui.container, width=200, height_sizing=.Expand), - layout_direction = .Left_To_Right - ) - ) - }) + syl.box( + size = {SCREEN_H, SCREEN_H}, + style_sheet = &style_sheet, + handler = handler, + children = { + syl.center( + syl.box( + syl.box( + gap = 10, + children = { + syl.button( + text_content = "START", + size = {200, 40}, + on_mouse_over = Message.Start, + ), + syl.button( + text_content = "SETTINGS", + size = {200, 40}, + on_mouse_over = Message.Settings, + ), + syl.button( + text_content = "NETWORK", + size = {200, 40}, + on_mouse_over = Message.Network, + ), + syl.button( + text_content = "CREDITS", + size = {200, 40}, + on_mouse_over = Message.Credits, + ), + syl.button( + text_content = "EXIT", + size = {200, 40}, + on_mouse_over = Message.Exit, + ), + }, + ), + syl.box(ref = &game_ui.container, width = 200, height_sizing = .Expand), + layout_direction = .Left_To_Right, + ), + ), + }, + ) return game_ui } start :: proc() -> ^syl.Box { - return syl.box(gap=10, children = { - syl.button(text_content="CONTINUE", size={200,40}), - syl.button(text_content="NEW GAME", size={200,40}), - }), + return syl.box( + gap = 10, + children = { + syl.button(text_content = "CONTINUE", size = {200, 40}), + syl.button(text_content = "NEW GAME", size = {200, 40}), + }, + ) } settings :: proc() -> ^syl.Box { - return syl.box(gap=10, children = { - syl.button(text_content="AUDIO", size={200,40}), - syl.button(text_content="VIDEO", size={200,40}), - syl.button(text_content="CONTROLS", size={200,40}), - syl.button(text_content="GAMEPLAY", size={200,40}), - }), + return syl.box( + gap = 10, + children = { + syl.button(text_content = "AUDIO", size = {200, 40}), + syl.button(text_content = "VIDEO", size = {200, 40}), + syl.button(text_content = "CONTROLS", size = {200, 40}), + syl.button(text_content = "GAMEPLAY", size = {200, 40}), + }, + ) } network :: proc() -> ^syl.Box { - return syl.box(gap=10, children = { - syl.button(text_content="CONNECT TO SERVER", size={240,40}), - syl.button(text_content="HOST GAME", size={240,40}), - syl.button(text_content="DISCONNECT", size={240,40}), - }), + return syl.box( + gap = 10, + children = { + syl.button(text_content = "CONNECT TO SERVER", size = {240, 40}), + syl.button(text_content = "HOST GAME", size = {240, 40}), + syl.button(text_content = "DISCONNECT", size = {240, 40}), + }, + ) } credits :: proc() -> ^syl.Box { - return syl.box(gap=10, sizing=syl.Expand, children = { - syl.center( - syl.text("Programmer: CRSOLVER", wrap = false), - ), - }), + return syl.box( + gap = 10, + sizing = syl.Expand, + children = {syl.center(syl.text("Programmer: CRSOLVER", wrap = false))}, + ) } exit :: proc() -> ^syl.Box { - return syl.box(sizing=syl.Expand, children = { - syl.center( - syl.box( - syl.text("ARE YOU SURE?", wrap = false), + return syl.box( + sizing = syl.Expand, + children = { + syl.center( syl.box( - syl.button(text_content="YES", size={100,40}), - syl.button(text_content="NO", size={100,40}), - layout_direction = .Left_To_Right, + syl.text("ARE YOU SURE?", wrap = false), + syl.box( + syl.button(text_content = "YES", size = {100, 40}), + syl.button(text_content = "NO", size = {100, 40}), + layout_direction = .Left_To_Right, + padding = 0, + gap = 10, + ), + gap = 10, padding = 0, - gap = 10 ), - gap=10, - padding=0, - ) - ) - }), + ), + }, + ) } game_ui_destroy :: proc(game_ui: ^Game_UI) { diff --git a/examples/main.odin b/examples/main.odin index afab009..f411ec5 100644 --- a/examples/main.odin +++ b/examples/main.odin @@ -73,7 +73,6 @@ run_sdl_backend :: proc() { ui := game_menu_ui() defer syl.destroy_context() - defer syl.box_destroy(ui) run: for { diff --git a/renderer/sdl_gpu/compiled/main.frag.sprv b/renderer/sdl_gpu/compiled/main.frag.sprv index 9faeebf0c504020bae91654ae3dccd51102b2c18..410eef5e07cbef7381b3e37506e79358877034ae 100644 GIT binary patch delta 143 zcmX@$*5Ss>%%sfDz`)4B&A`i0Ig!_tk$0lA;l>ITR$g}o7KUmD1_n7GW}Up3Rhp4= z@=ey}%{go$%$p~0u3+;lWn^Gb1LAN7Rt8R>sxlzE1c*W6wm{t=v2q|g0f<|nYAS$i VkUEgK9Ro8%Hju8^+$gYL2msIF7J2{x delta 215 zcmeBhJK)C4%%sfDz`)4B&A`i0HIdhpk#C~2A!F&rLUmSKRt8pvQbq=b%)I!L)QXb$ z(lQ1HcLo-QY6b=dIUr^OVs{2+21%eAJ}{dF%m&GEPd>=n%nMQtG65vQy4jIUoSD}e zD5MA!0tqv24&_|U=2-^ih6C;61gb0t@=JgiByI~-4ic*XvJ-%~6{@BZ$OfqciQ56) OkPW0OH+u@~7Xkn-o*d@@ diff --git a/renderer/sdl_gpu/compiled/main.vert.sprv b/renderer/sdl_gpu/compiled/main.vert.sprv index f0ebd54a264fc60de2f4ca782578501489463614..95739ee3faf8623a45af37b22bc56c7eeb7ebc95 100644 GIT binary patch literal 5016 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 UF!Hb9O<;fQpSE%Trs_T5KOMmIm;e9( diff --git a/renderer/sdl_gpu/main.odin b/renderer/sdl_gpu/main.odin index a520f53..fb9f03e 100644 --- a/renderer/sdl_gpu/main.odin +++ b/renderer/sdl_gpu/main.odin @@ -11,12 +11,10 @@ import ttf "vendor:sdl3/ttf" import syl "../.." Rect :: struct #packed { - radius: [4]f32, position_and_size: [4]f32, color: [4]f32, - uv_x: [4]f32, - uv_y: [4]f32, - border_thickness: [4]f32, + f1: [4]f32, // uv_x for text, border radius for rects + f2: [4]f32, // uv_y for text, border thickness for rects border_color: [4][4]f32, flags: [4]i32, } @@ -260,8 +258,8 @@ feed_renderer :: proc( r := Rect{} r.position_and_size.xy = box.global_position r.position_and_size.zw = box.size - r.border_thickness = box.border_thickness - r.radius = box.border_radius + r.f1 = box.border_radius + r.f2 = box.border_thickness r.color = color_syl_to_sdl(box.background_color) r.flags = 0 r.border_color = { @@ -322,8 +320,8 @@ feed_renderer :: proc( r := Rect{} r.position_and_size.xy = line.global_position + {x_min, 0} r.position_and_size.zw = {width, height} - r.uv_x = {uv0.x, uv1.x, uv2.x, uv3.x} - r.uv_y = {uv2.y, uv3.y, uv0.y, uv1.y} + r.f1 = {uv0.x, uv1.x, uv2.x, uv3.x} + r.f2 = {uv2.y, uv3.y, uv0.y, uv1.y} r.color = color_syl_to_sdl(text.color) r.flags = {1, 0, 0, 0} diff --git a/renderer/sdl_gpu/source/main.frag b/renderer/sdl_gpu/source/main.frag index 55bfc5b..4020cab 100644 --- a/renderer/sdl_gpu/source/main.frag +++ b/renderer/sdl_gpu/source/main.frag @@ -5,9 +5,8 @@ layout(location = 1) in vec4 in_radius; layout(location = 2) in vec4 in_border_thickness; layout(location = 3) in vec2 in_size; layout(location = 4) in vec2 in_uv; -layout(location = 5) in vec2 in_text_uv; -layout(location = 6) in vec4 in_border_color[4]; -layout(location = 11) flat in ivec4 in_flags; +layout(location = 5) in vec4 in_border_color[4]; +layout(location = 9) flat in ivec4 in_flags; layout(location = 0) out vec4 out_color; @@ -86,6 +85,6 @@ void main() { out_color = fill_color + border_color; } else { - out_color = texture(font_sampler, in_text_uv) * in_color; + out_color = texture(font_sampler, in_uv) * in_color; } } diff --git a/renderer/sdl_gpu/source/main.vert b/renderer/sdl_gpu/source/main.vert index 65dfd65..72642ee 100644 --- a/renderer/sdl_gpu/source/main.vert +++ b/renderer/sdl_gpu/source/main.vert @@ -1,16 +1,11 @@ #version 460 struct Rect { - vec4 radius; vec4 position_and_size; vec4 color; - vec4 uv_x; - vec4 uv_y; - vec4 thickness; - vec4 border_color_top; - vec4 border_color_right; - vec4 border_color_bottom; - vec4 border_color_left; + vec4 f1; + vec4 f2; + vec4 border_color[4]; ivec4 flags; }; @@ -22,9 +17,8 @@ layout(location = 1) out vec4 out_radius; layout(location = 2) out vec4 out_thickness; layout(location = 3) out vec2 out_size; layout(location = 4) out vec2 out_uv; -layout(location = 5) out vec2 out_text_uv; -layout(location = 6) out vec4 out_border_color[4]; -layout(location = 11) out ivec4 out_flags; +layout(location = 5) out vec4 out_border_color[4]; +layout(location = 9) out ivec4 out_flags; const vec2 positions[6] = vec2[](vec2(0.5, 0.5), vec2(-0.5, 0.5), vec2(-0.5, -0.5), vec2(0.5, 0.5), @@ -43,14 +37,17 @@ void main() { vec2 pos = rect.position_and_size.xy; out_color = rect.color; - out_radius = rect.radius; - out_thickness = rect.thickness; + out_radius = rect.f1; + out_thickness = rect.f2; out_size = size; - out_uv = uvs[gl_VertexIndex]; - out_text_uv = vec2(rect.uv_x[uv_map[gl_VertexIndex]], rect.uv_y[uv_map[gl_VertexIndex]]); - out_border_color = vec4[](rect.border_color_top, rect.border_color_right, - rect.border_color_bottom, rect.border_color_left); + out_border_color = rect.border_color; out_flags = rect.flags; + if (rect.flags.x == 1) { + out_uv = vec2(rect.f1[uv_map[gl_VertexIndex]], rect.f2[uv_map[gl_VertexIndex]]); + } else { + out_uv = uvs[gl_VertexIndex]; + } + gl_Position = proj * vec4(vert_pos * size + pos + size / 2, 1.0, 1.0); }