| 1 | // vtest build: misc-tooling // needs .h files that are produced by `v shader` |
| 2 | /********************************************************************** |
| 3 | * Sokol 3d cube multishader demo |
| 4 | * Copyright (c) 2024 Dario Deledda. All rights reserved. |
| 5 | * Use of this source code is governed by an MIT license |
| 6 | * that can be found in the LICENSE file. |
| 7 | * |
| 8 | * HOW TO COMPILE SHADERS: |
| 9 | * Run `v shader .` in this directory to compile the shaders. |
| 10 | * For more info and help with shader compilation see `docs.md` and `v help shader`. |
| 11 | **********************************************************************/ |
| 12 | import gg |
| 13 | import gg.m4 |
| 14 | import sokol.sapp |
| 15 | import sokol.gfx |
| 16 | import sokol.sgl |
| 17 | import time |
| 18 | |
| 19 | // GLSL Include and functions |
| 20 | #include "@VMODROOT/rt_glsl_march.h" # It should be generated with `v shader .` (see the instructions at the top of this file) |
| 21 | #include "@VMODROOT/rt_glsl_puppy.h" # It should be generated with `v shader .` (see the instructions at the top of this file) |
| 22 | |
| 23 | fn C.rt_march_shader_desc(gfx.Backend) &gfx.ShaderDesc |
| 24 | fn C.rt_puppy_shader_desc(gfx.Backend) &gfx.ShaderDesc |
| 25 | |
| 26 | const start_ticks = time.ticks() |
| 27 | |
| 28 | @[heap] |
| 29 | struct App { |
| 30 | mut: |
| 31 | gg &gg.Context = unsafe { nil } |
| 32 | texture gfx.Image |
| 33 | sampler gfx.Sampler |
| 34 | init_flag bool |
| 35 | mouse_x int = 502 |
| 36 | mouse_y int = 394 |
| 37 | mouse_down bool |
| 38 | // glsl |
| 39 | cube_pip_glsl gfx.Pipeline |
| 40 | cube_bind gfx.Bindings |
| 41 | pipe map[string]gfx.Pipeline |
| 42 | bind map[string]gfx.Bindings |
| 43 | } |
| 44 | |
| 45 | /****************************************************************************** |
| 46 | * Texture functions |
| 47 | ******************************************************************************/ |
| 48 | fn create_texture(w int, h int, buf byteptr) (gfx.Image, gfx.Sampler) { |
| 49 | sz := w * h * 4 |
| 50 | mut img_desc := gfx.ImageDesc{ |
| 51 | width: w |
| 52 | height: h |
| 53 | } |
| 54 | img_desc.data.subimage[0][0] = gfx.Range{ |
| 55 | ptr: buf |
| 56 | size: usize(sz) |
| 57 | } |
| 58 | sg_img := gfx.make_image(&img_desc) |
| 59 | mut smp_desc := gfx.SamplerDesc{ |
| 60 | min_filter: .linear |
| 61 | mag_filter: .linear |
| 62 | wrap_u: .clamp_to_edge |
| 63 | wrap_v: .clamp_to_edge |
| 64 | } |
| 65 | sg_smp := gfx.make_sampler(&smp_desc) |
| 66 | return sg_img, sg_smp |
| 67 | } |
| 68 | |
| 69 | /****************************************************************************** |
| 70 | * Draw functions |
| 71 | ****************************************************************************** |
| 72 | Cube vertex buffer with packed vertex formats for color and texture coords. |
| 73 | Note that a vertex format which must be portable across all |
| 74 | backends must only use the normalized integer formats |
| 75 | (BYTE4N, UBYTE4N, SHORT2N, SHORT4N), which can be converted |
| 76 | to floating point formats in the vertex shader inputs. |
| 77 | The reason is that D3D11 cannot convert from non-normalized |
| 78 | formats to floating point inputs (only to integer inputs), |
| 79 | and WebGL2 / GLES2 does not support integer vertex shader inputs. |
| 80 | */ |
| 81 | struct Vertex_t { |
| 82 | x f32 |
| 83 | y f32 |
| 84 | z f32 |
| 85 | color u32 |
| 86 | u f32 |
| 87 | v f32 |
| 88 | } |
| 89 | |
| 90 | const vertices = cube_vertices() |
| 91 | |
| 92 | fn cube_vertices() []Vertex_t { |
| 93 | // cube vertex buffer |
| 94 | d := f32(1.0) |
| 95 | c := u32(0xFF_FF_FF_FF) // white color RGBA8 |
| 96 | // vfmt off |
| 97 | // 6 faces, each defined by 4 vertices: |
| 98 | cube := [ |
| 99 | Vertex_t{-1.0, -1.0, -1.0, c, 0, 0}, Vertex_t{ 1.0, -1.0, -1.0, c, d, 0}, Vertex_t{ 1.0, 1.0, -1.0, c, d, d}, Vertex_t{-1.0, 1.0, -1.0, c, 0, d}, |
| 100 | Vertex_t{-1.0, -1.0, 1.0, c, 0, 0}, Vertex_t{ 1.0, -1.0, 1.0, c, d, 0}, Vertex_t{ 1.0, 1.0, 1.0, c, d, d}, Vertex_t{-1.0, 1.0, 1.0, c, 0, d}, |
| 101 | Vertex_t{-1.0, -1.0, -1.0, c, 0, 0}, Vertex_t{-1.0, 1.0, -1.0, c, d, 0}, Vertex_t{-1.0, 1.0, 1.0, c, d, d}, Vertex_t{-1.0, -1.0, 1.0, c, 0, d}, |
| 102 | Vertex_t{ 1.0, -1.0, -1.0, c, 0, 0}, Vertex_t{ 1.0, 1.0, -1.0, c, d, 0}, Vertex_t{ 1.0, 1.0, 1.0, c, d, d}, Vertex_t{ 1.0, -1.0, 1.0, c, 0, d}, |
| 103 | Vertex_t{-1.0, -1.0, -1.0, c, 0, 0}, Vertex_t{-1.0, -1.0, 1.0, c, d, 0}, Vertex_t{ 1.0, -1.0, 1.0, c, d, d}, Vertex_t{ 1.0, -1.0, -1.0, c, 0, d}, |
| 104 | Vertex_t{-1.0, 1.0, -1.0, c, 0, 0}, Vertex_t{-1.0, 1.0, 1.0, c, d, 0}, Vertex_t{ 1.0, 1.0, 1.0, c, d, d}, Vertex_t{ 1.0, 1.0, -1.0, c, 0, d}, |
| 105 | ] |
| 106 | // vfmt off |
| 107 | return cube |
| 108 | } |
| 109 | |
| 110 | fn (mut app App) init_glsl_shader(shader_name string, shader_desc &gfx.ShaderDesc, indices []u16) { |
| 111 | mut vert_buffer_desc := gfx.BufferDesc{} |
| 112 | unsafe { vmemset(&vert_buffer_desc, 0, sizeof(vert_buffer_desc)) } |
| 113 | vert_buffer_desc.label = c'cube-vertices' |
| 114 | vert_buffer_desc.size = usize(vertices.len) * sizeof(Vertex_t) |
| 115 | vert_buffer_desc.data = gfx.Range{ |
| 116 | ptr: vertices.data |
| 117 | size: vert_buffer_desc.size |
| 118 | } |
| 119 | vert_buffer_desc.type = .vertexbuffer |
| 120 | vbuf := gfx.make_buffer(&vert_buffer_desc) |
| 121 | |
| 122 | mut index_buffer_desc := gfx.BufferDesc{} |
| 123 | unsafe { vmemset(&index_buffer_desc, 0, sizeof(index_buffer_desc)) } |
| 124 | index_buffer_desc.label = c'cube-indices' |
| 125 | index_buffer_desc.size = usize(indices.len) * sizeof(u16) |
| 126 | index_buffer_desc.data = gfx.Range{ |
| 127 | ptr: indices.data |
| 128 | size: index_buffer_desc.size |
| 129 | } |
| 130 | index_buffer_desc.type = .indexbuffer |
| 131 | ibuf := gfx.make_buffer(&index_buffer_desc) |
| 132 | |
| 133 | // create shader |
| 134 | shader := gfx.make_shader(shader_desc) |
| 135 | |
| 136 | mut pipdesc := gfx.PipelineDesc{} |
| 137 | unsafe { vmemset(&pipdesc, 0, sizeof(pipdesc)) } |
| 138 | pipdesc.label = c'glsl_shader pipeline' |
| 139 | pipdesc.layout.buffers[0].stride = int(sizeof(Vertex_t)) |
| 140 | |
| 141 | // the constants [C.ATTR_vs_m_pos, C.ATTR_vs_m_color0, C.ATTR_vs_m_texcoord0] are generated by sokol-shdc |
| 142 | pipdesc.layout.attrs[C.ATTR_vs_m_pos].format = .float3 // x,y,z as f32 |
| 143 | pipdesc.layout.attrs[C.ATTR_vs_m_color0].format = .ubyte4n // color as u32 |
| 144 | pipdesc.layout.attrs[C.ATTR_vs_m_texcoord0].format = .float2 // u,v as f32 |
| 145 | // pipdesc.layout.attrs[C.ATTR_vs_m_texcoord0].format = .short2n // u,v as u16 |
| 146 | pipdesc.shader = shader |
| 147 | pipdesc.index_type = .uint16 |
| 148 | pipdesc.depth = gfx.DepthState{ |
| 149 | write_enabled: true |
| 150 | compare: .less_equal |
| 151 | } |
| 152 | pipdesc.cull_mode = .back |
| 153 | |
| 154 | mut bind := gfx.Bindings{} |
| 155 | unsafe { vmemset(&bind, 0, sizeof(bind)) } |
| 156 | bind.vertex_buffers[0] = vbuf |
| 157 | bind.index_buffer = ibuf |
| 158 | bind.fs.images[C.SLOT_tex] = app.texture |
| 159 | bind.fs.samplers[C.SLOT_smp] = app.sampler |
| 160 | app.bind[shader_name] = bind |
| 161 | app.pipe[shader_name] = gfx.make_pipeline(&pipdesc) |
| 162 | println('${@FN} for shader `${shader_name}` done.') |
| 163 | } |
| 164 | |
| 165 | fn calc_tr_matrices(w f32, h f32, rx f32, ry f32, in_scale f32) m4.Mat4 { |
| 166 | proj := m4.perspective(60, w / h, 0.01, 10.0) |
| 167 | view := m4.look_at(m4.vec4(0.0, 0, 6, 0), m4.vec4(0, 0, 0, 0), m4.vec4(0, 1, 0, 0)) |
| 168 | view_proj := view * proj |
| 169 | |
| 170 | rxm := m4.rotate(m4.rad(rx), m4.vec4(1, 0, 0, 0)) |
| 171 | rym := m4.rotate(m4.rad(ry), m4.vec4(0, 1, 0, 0)) |
| 172 | |
| 173 | model := rym * rxm |
| 174 | scale_m := m4.scale(m4.vec4(in_scale, in_scale, in_scale, 1)) |
| 175 | |
| 176 | res := (scale_m * model) * view_proj |
| 177 | return res |
| 178 | } |
| 179 | |
| 180 | fn (app &App) draw_glsl_shader(shader_name string) { |
| 181 | ws := gg.window_size_real_pixels() |
| 182 | ratio := f32(ws.width) / ws.height |
| 183 | dw := f32(ws.width / 2) |
| 184 | dh := f32(ws.height / 2) |
| 185 | |
| 186 | rot := [f32(app.mouse_y), f32(app.mouse_x)] |
| 187 | tr_matrix := calc_tr_matrices(dw, dh, rot[0], rot[1], 2.3) |
| 188 | |
| 189 | // apply the pipeline and bindings |
| 190 | gfx.apply_pipeline(app.pipe[shader_name]) |
| 191 | gfx.apply_bindings(app.bind[shader_name]) |
| 192 | |
| 193 | // Uniforms |
| 194 | // *** vertex shadeer uniforms *** |
| 195 | // passing the view matrix as uniform |
| 196 | // res is a 4x4 matrix of f32 thus: 4*16 byte of size |
| 197 | vs_uniforms_range := gfx.Range{ |
| 198 | ptr: &tr_matrix |
| 199 | size: usize(4 * 16) |
| 200 | } |
| 201 | gfx.apply_uniforms(.vs, C.SLOT_vs_params_m, &vs_uniforms_range) |
| 202 | |
| 203 | // *** fragment shader uniforms *** |
| 204 | time_ticks := f32(time.ticks() - start_ticks) / 1000 |
| 205 | mut tmp_fs_params := [ |
| 206 | f32(ws.width), |
| 207 | ws.height * ratio, // x,y resolution to pass to FS |
| 208 | 0, |
| 209 | 0, // dont send mouse position |
| 210 | // app.mouse_x, // mouse x |
| 211 | // ws.height - app.mouse_y*2, // mouse y scaled |
| 212 | time_ticks, // time as f32 |
| 213 | f32(app.gg.frame), // frame count |
| 214 | 0, |
| 215 | 0, // padding bytes , see "fs_params" struct paddings in rt_glsl.h |
| 216 | ]! |
| 217 | fs_uniforms_range := gfx.Range{ |
| 218 | ptr: unsafe { &tmp_fs_params } |
| 219 | size: usize(sizeof(tmp_fs_params)) |
| 220 | } |
| 221 | gfx.apply_uniforms(.fs, C.SLOT_fs_params_p, &fs_uniforms_range) |
| 222 | |
| 223 | // 3 vertices for triangle * 2 triangles per face * 6 faces = 36 vertices to draw |
| 224 | gfx.draw(0, (3 * 2) * 3, 1) |
| 225 | } |
| 226 | |
| 227 | fn (mut app App) frame() { |
| 228 | mut pass_action := gfx.PassAction{} |
| 229 | pass_action.colors[0] = gfx.ColorAttachmentAction{ |
| 230 | load_action: .clear |
| 231 | clear_value: gfx.Color{b: 0.9} |
| 232 | } |
| 233 | gfx.begin_pass(sapp.create_default_pass(pass_action)) |
| 234 | if !app.init_flag { |
| 235 | return |
| 236 | } |
| 237 | ws := gg.window_size_real_pixels() |
| 238 | gfx.apply_viewport(0, 0, ws.width, ws.height, true) |
| 239 | app.draw_glsl_shader('march') |
| 240 | app.draw_glsl_shader('puppy') |
| 241 | gfx.end_pass() |
| 242 | gfx.commit() |
| 243 | } |
| 244 | |
| 245 | fn (mut app App) on_init() { |
| 246 | // set max vertices, but note, that for a large number of the same type of object it is better use the instances!! |
| 247 | gfx.setup(sapp.create_desc()) |
| 248 | sgl.setup(sgl.Desc{max_vertices: 50 * 65536}) |
| 249 | |
| 250 | // create chessboard texture 256*256 RGBA |
| 251 | w := 256 |
| 252 | h := 256 |
| 253 | sz := w * h * 4 |
| 254 | tmp_txt := unsafe { malloc(sz) } |
| 255 | defer { |
| 256 | unsafe { free(tmp_txt) } |
| 257 | } |
| 258 | mut i := 0 |
| 259 | for i < sz { |
| 260 | unsafe { |
| 261 | y := (i >> 0x8) >> 5 // 8 cell |
| 262 | x := (i & 0xFF) >> 5 // 8 cell |
| 263 | // upper left corner |
| 264 | if x == 0 && y == 0 { |
| 265 | tmp_txt[i + 0] = 0xFF |
| 266 | tmp_txt[i + 1] = 0 |
| 267 | tmp_txt[i + 2] = 0 |
| 268 | tmp_txt[i + 3] = 0xFF |
| 269 | } |
| 270 | // low right corner |
| 271 | else if x == 7 && y == 7 { |
| 272 | tmp_txt[i + 0] = 0 |
| 273 | tmp_txt[i + 1] = 0xFF |
| 274 | tmp_txt[i + 2] = 0 |
| 275 | tmp_txt[i + 3] = 0xFF |
| 276 | } else { |
| 277 | col := if ((x + y) & 1) == 1 { u8(0xFF) } else { 128 } |
| 278 | tmp_txt[i + 0] = col // red |
| 279 | tmp_txt[i + 1] = col // green |
| 280 | tmp_txt[i + 2] = col // blue |
| 281 | tmp_txt[i + 3] = 0xFF // alpha |
| 282 | } |
| 283 | i += 4 |
| 284 | } |
| 285 | } |
| 286 | app.texture, app.sampler = create_texture(w, h, tmp_txt) |
| 287 | |
| 288 | // vfmt off |
| 289 | app.init_glsl_shader('march', voidptr(C.rt_march_shader_desc(C.sg_query_backend())), [ |
| 290 | u16(0), 1, 2, 0, 2, 3, |
| 291 | 6, 5, 4, 7, 6, 4, |
| 292 | 8, 9, 10, 8, 10, 11, |
| 293 | ]) |
| 294 | app.init_glsl_shader('puppy', voidptr(C.rt_puppy_shader_desc(C.sg_query_backend())), [ |
| 295 | u16(14), 13, 12, 15, 14, 12, |
| 296 | 16, 17, 18, 16, 18, 19, |
| 297 | 22, 21, 20, 23, 22, 20, |
| 298 | ]) |
| 299 | // vfmt on |
| 300 | app.init_flag = true |
| 301 | } |
| 302 | |
| 303 | /****************************************************************************** |
| 304 | * events handling |
| 305 | ******************************************************************************/ |
| 306 | fn (mut app App) on_event(ev &gg.Event, x voidptr) { |
| 307 | if ev.typ == .mouse_down { |
| 308 | app.mouse_down = true |
| 309 | } |
| 310 | if ev.typ == .mouse_up { |
| 311 | app.mouse_down = false |
| 312 | } |
| 313 | if app.mouse_down == true && ev.typ == .mouse_move { |
| 314 | app.mouse_x = int(ev.mouse_x) |
| 315 | app.mouse_y = int(ev.mouse_y) |
| 316 | } |
| 317 | if ev.typ == .touches_began || ev.typ == .touches_moved { |
| 318 | if ev.num_touches > 0 { |
| 319 | touch_point := ev.touches[0] |
| 320 | app.mouse_x = int(touch_point.pos_x) |
| 321 | app.mouse_y = int(touch_point.pos_y) |
| 322 | } |
| 323 | } |
| 324 | // eprintln('> app.mouse_x: ${app.mouse_x} | app.mouse_y: ${app.mouse_y}') |
| 325 | } |
| 326 | |
| 327 | fn main() { |
| 328 | mut app := &App{} |
| 329 | app.gg = gg.new_context( |
| 330 | width: 800 |
| 331 | height: 800 |
| 332 | window_title: '3D Dual shader Cube - click and rotate with the mouse' |
| 333 | user_data: app |
| 334 | frame_fn: app.frame |
| 335 | init_fn: app.on_init |
| 336 | event_fn: app.on_event |
| 337 | ) |
| 338 | app.gg.run() |
| 339 | } |
| 340 | |