| 1 | // vtest build: misc-tooling // needs .h files that are produced by `v shader` |
| 2 | /********************************************************************** |
| 3 | * |
| 4 | * Sokol 3d cube multishader demo |
| 5 | * |
| 6 | * Copyright (c) 2021 Dario Deledda. All rights reserved. |
| 7 | * Use of this source code is governed by an MIT license |
| 8 | * that can be found in the LICENSE file. |
| 9 | * |
| 10 | * HOW TO COMPILE SHADERS: |
| 11 | * Run `v shader .` in this directory to compile the shaders. |
| 12 | * For more info and help with shader compilation see `docs.md` and `v help shader`. |
| 13 | * |
| 14 | * TODO: |
| 15 | * - frame counter |
| 16 | **********************************************************************/ |
| 17 | import gg |
| 18 | import gg.m4 |
| 19 | import math |
| 20 | import sokol.gfx |
| 21 | // import sokol.sgl |
| 22 | import time |
| 23 | |
| 24 | const win_width = 800 |
| 25 | const win_height = 800 |
| 26 | const bg_color = gg.white |
| 27 | const num_inst = 16384 |
| 28 | |
| 29 | struct App { |
| 30 | mut: |
| 31 | gg &gg.Context = unsafe { nil } |
| 32 | texture gfx.Image |
| 33 | sampler gfx.Sampler |
| 34 | init_flag bool |
| 35 | frame_count int |
| 36 | |
| 37 | mouse_x int = 903 |
| 38 | mouse_y int = 638 |
| 39 | mouse_down bool |
| 40 | // glsl |
| 41 | cube_pip_glsl gfx.Pipeline |
| 42 | cube_bind gfx.Bindings |
| 43 | |
| 44 | pipe map[string]gfx.Pipeline |
| 45 | bind map[string]gfx.Bindings |
| 46 | // time |
| 47 | ticks i64 |
| 48 | // instances |
| 49 | inst_pos [num_inst]m4.Vec4 |
| 50 | // camera |
| 51 | camera_x f32 = -8 |
| 52 | camera_z f32 = 47 |
| 53 | } |
| 54 | |
| 55 | /****************************************************************************** |
| 56 | * GLSL Include and functions |
| 57 | ******************************************************************************/ |
| 58 | #include "@VMODROOT/rt_glsl_instancing.h" # It should be generated with `v shader .` (see the instructions at the top of this file) |
| 59 | |
| 60 | fn C.instancing_shader_desc(gfx.Backend) &gfx.ShaderDesc |
| 61 | |
| 62 | /****************************************************************************** |
| 63 | * Texture functions |
| 64 | ******************************************************************************/ |
| 65 | fn create_texture(w int, h int, buf byteptr) (gfx.Image, gfx.Sampler) { |
| 66 | sz := w * h * 4 |
| 67 | // vfmt off |
| 68 | mut img_desc := gfx.ImageDesc{ |
| 69 | width: w |
| 70 | height: h |
| 71 | num_mipmaps: 0 |
| 72 | // min_filter: .linear |
| 73 | // mag_filter: .linear |
| 74 | //usage: .dynamic |
| 75 | // wrap_u: .clamp_to_edge |
| 76 | // wrap_v: .clamp_to_edge |
| 77 | label: &char(unsafe { nil }) |
| 78 | d3d11_texture: 0 |
| 79 | } |
| 80 | // vfmt on |
| 81 | // comment if .dynamic is enabled |
| 82 | img_desc.data.subimage[0][0] = gfx.Range{ |
| 83 | ptr: buf |
| 84 | size: usize(sz) |
| 85 | } |
| 86 | |
| 87 | sg_img := gfx.make_image(&img_desc) |
| 88 | |
| 89 | mut smp_desc := gfx.SamplerDesc{ |
| 90 | min_filter: .linear |
| 91 | mag_filter: .linear |
| 92 | wrap_u: .clamp_to_edge |
| 93 | wrap_v: .clamp_to_edge |
| 94 | } |
| 95 | |
| 96 | sg_smp := gfx.make_sampler(&smp_desc) |
| 97 | return sg_img, sg_smp |
| 98 | } |
| 99 | |
| 100 | fn destroy_texture(sg_img gfx.Image) { |
| 101 | gfx.destroy_image(sg_img) |
| 102 | } |
| 103 | |
| 104 | // Use only if usage: .dynamic is enabled |
| 105 | fn update_text_texture(sg_img gfx.Image, w int, h int, buf byteptr) { |
| 106 | sz := w * h * 4 |
| 107 | mut tmp_sbc := gfx.ImageData{} |
| 108 | tmp_sbc.subimage[0][0] = gfx.Range{ |
| 109 | ptr: buf |
| 110 | size: usize(sz) |
| 111 | } |
| 112 | gfx.update_image(sg_img, &tmp_sbc) |
| 113 | } |
| 114 | |
| 115 | /****************************************************************************** |
| 116 | * Draw functions |
| 117 | ****************************************************************************** |
| 118 | Cube vertex buffer with packed vertex formats for color and texture coords. |
| 119 | Note that a vertex format which must be portable across all |
| 120 | backends must only use the normalized integer formats |
| 121 | (BYTE4N, UBYTE4N, SHORT2N, SHORT4N), which can be converted |
| 122 | to floating point formats in the vertex shader inputs. |
| 123 | The reason is that D3D11 cannot convert from non-normalized |
| 124 | formats to floating point inputs (only to integer inputs), |
| 125 | and WebGL2 / GLES2 don't support integer vertex shader inputs. |
| 126 | */ |
| 127 | |
| 128 | struct Vertex_t { |
| 129 | x f32 |
| 130 | y f32 |
| 131 | z f32 |
| 132 | color u32 |
| 133 | // u u16 // for compatibility with D3D11 |
| 134 | // v u16 // for compatibility with D3D11 |
| 135 | u f32 |
| 136 | v f32 |
| 137 | } |
| 138 | |
| 139 | // march shader init |
| 140 | fn init_cube_glsl_i(mut app App) { |
| 141 | // cube vertex buffer |
| 142 | // d := u16(32767) // for compatibility with D3D11, 32767 stand for 1 |
| 143 | d := f32(1.0) |
| 144 | c := u32(0xFFFFFF_FF) // color RGBA8 |
| 145 | // vfmt off |
| 146 | vertices := [ |
| 147 | // Face 0 |
| 148 | Vertex_t{-1.0, -1.0, -1.0, c, 0, 0}, |
| 149 | Vertex_t{ 1.0, -1.0, -1.0, c, d, 0}, |
| 150 | Vertex_t{ 1.0, 1.0, -1.0, c, d, d}, |
| 151 | Vertex_t{-1.0, 1.0, -1.0, c, 0, d}, |
| 152 | // Face 1 |
| 153 | Vertex_t{-1.0, -1.0, 1.0, c, 0, 0}, |
| 154 | Vertex_t{ 1.0, -1.0, 1.0, c, d, 0}, |
| 155 | Vertex_t{ 1.0, 1.0, 1.0, c, d, d}, |
| 156 | Vertex_t{-1.0, 1.0, 1.0, c, 0, d}, |
| 157 | // Face 2 |
| 158 | Vertex_t{-1.0, -1.0, -1.0, c, 0, 0}, |
| 159 | Vertex_t{-1.0, 1.0, -1.0, c, d, 0}, |
| 160 | Vertex_t{-1.0, 1.0, 1.0, c, d, d}, |
| 161 | Vertex_t{-1.0, -1.0, 1.0, c, 0, d}, |
| 162 | // Face 3 |
| 163 | Vertex_t{ 1.0, -1.0, -1.0, c, 0, 0}, |
| 164 | Vertex_t{ 1.0, 1.0, -1.0, c, d, 0}, |
| 165 | Vertex_t{ 1.0, 1.0, 1.0, c, d, d}, |
| 166 | Vertex_t{ 1.0, -1.0, 1.0, c, 0, d}, |
| 167 | // Face 4 |
| 168 | Vertex_t{-1.0, -1.0, -1.0, c, 0, 0}, |
| 169 | Vertex_t{-1.0, -1.0, 1.0, c, d, 0}, |
| 170 | Vertex_t{ 1.0, -1.0, 1.0, c, d, d}, |
| 171 | Vertex_t{ 1.0, -1.0, -1.0, c, 0, d}, |
| 172 | // Face 5 |
| 173 | Vertex_t{-1.0, 1.0, -1.0, c, 0, 0}, |
| 174 | Vertex_t{-1.0, 1.0, 1.0, c, d, 0}, |
| 175 | Vertex_t{ 1.0, 1.0, 1.0, c, d, d}, |
| 176 | Vertex_t{ 1.0, 1.0, -1.0, c, 0, d}, |
| 177 | ] |
| 178 | // vfmt on |
| 179 | |
| 180 | mut vert_buffer_desc := gfx.BufferDesc{ |
| 181 | label: c'cube-vertices' |
| 182 | } |
| 183 | unsafe { vmemset(&vert_buffer_desc, 0, int(sizeof(vert_buffer_desc))) } |
| 184 | vert_buffer_desc.size = usize(vertices.len * int(sizeof(Vertex_t))) |
| 185 | vert_buffer_desc.data = gfx.Range{ |
| 186 | ptr: vertices.data |
| 187 | size: usize(vertices.len * int(sizeof(Vertex_t))) |
| 188 | } |
| 189 | vert_buffer_desc.type = .vertexbuffer |
| 190 | vbuf := gfx.make_buffer(&vert_buffer_desc) |
| 191 | |
| 192 | // create an instance buffer for the cube |
| 193 | mut inst_buffer_desc := gfx.BufferDesc{ |
| 194 | label: c'instance-data' |
| 195 | } |
| 196 | unsafe { vmemset(&inst_buffer_desc, 0, int(sizeof(inst_buffer_desc))) } |
| 197 | |
| 198 | inst_buffer_desc.size = usize(num_inst * int(sizeof(m4.Vec4))) |
| 199 | inst_buffer_desc.type = .vertexbuffer |
| 200 | inst_buffer_desc.usage = .stream |
| 201 | inst_buf := gfx.make_buffer(&inst_buffer_desc) |
| 202 | |
| 203 | // create an index buffer for the cube |
| 204 | // vfmt off |
| 205 | indices := [ |
| 206 | u16(0), 1, 2, 0, 2, 3, |
| 207 | 6, 5, 4, 7, 6, 4, |
| 208 | 8, 9, 10, 8, 10, 11, |
| 209 | 14, 13, 12, 15, 14, 12, |
| 210 | 16, 17, 18, 16, 18, 19, |
| 211 | 22, 21, 20, 23, 22, 20, |
| 212 | ] |
| 213 | // vfmt on |
| 214 | |
| 215 | mut index_buffer_desc := gfx.BufferDesc{ |
| 216 | label: c'cube-indices' |
| 217 | } |
| 218 | unsafe { vmemset(&index_buffer_desc, 0, int(sizeof(index_buffer_desc))) } |
| 219 | index_buffer_desc.size = usize(indices.len * int(sizeof(u16))) |
| 220 | index_buffer_desc.data = gfx.Range{ |
| 221 | ptr: indices.data |
| 222 | size: usize(indices.len * int(sizeof(u16))) |
| 223 | } |
| 224 | index_buffer_desc.type = .indexbuffer |
| 225 | ibuf := gfx.make_buffer(&index_buffer_desc) |
| 226 | |
| 227 | // create shader |
| 228 | shader := gfx.make_shader(voidptr(C.instancing_shader_desc(C.sg_query_backend()))) |
| 229 | |
| 230 | mut pipdesc := gfx.PipelineDesc{} |
| 231 | unsafe { vmemset(&pipdesc, 0, int(sizeof(pipdesc))) } |
| 232 | pipdesc.layout.buffers[0].stride = int(sizeof(Vertex_t)) |
| 233 | |
| 234 | // vfmt off |
| 235 | // the constants [C.ATTR_vs_m_pos, C.ATTR_vs_m_color0, C.ATTR_vs_m_texcoord0] are generated by sokol-shdc |
| 236 | pipdesc.layout.attrs[C.ATTR_vs_i_pos ].format = .float3 // x,y,z as f32 |
| 237 | pipdesc.layout.attrs[C.ATTR_vs_i_pos ].buffer_index = 0 |
| 238 | pipdesc.layout.attrs[C.ATTR_vs_i_color0 ].format = .ubyte4n // color as u32 |
| 239 | pipdesc.layout.attrs[C.ATTR_vs_i_pos ].buffer_index = 0 |
| 240 | pipdesc.layout.attrs[C.ATTR_vs_i_texcoord0].format = .float2 // u,v as f32 |
| 241 | pipdesc.layout.attrs[C.ATTR_vs_i_pos ].buffer_index = 0 |
| 242 | |
| 243 | // instancing |
| 244 | // the constant ATTR_vs_i_inst_pos is generated by sokol-shdc |
| 245 | pipdesc.layout.buffers[1].stride = int(sizeof(m4.Vec4)) |
| 246 | pipdesc.layout.buffers[1].step_func = .per_instance // we will pass a single parameter for each instance!! |
| 247 | pipdesc.layout.attrs[C.ATTR_vs_i_inst_pos ].format = .float4 |
| 248 | pipdesc.layout.attrs[C.ATTR_vs_i_inst_pos ].buffer_index = 1 |
| 249 | // vfmt on |
| 250 | |
| 251 | pipdesc.shader = shader |
| 252 | pipdesc.index_type = .uint16 |
| 253 | |
| 254 | pipdesc.depth = gfx.DepthState{ |
| 255 | write_enabled: true |
| 256 | compare: .less_equal |
| 257 | } |
| 258 | pipdesc.cull_mode = .back |
| 259 | |
| 260 | pipdesc.label = c'glsl_shader pipeline' |
| 261 | |
| 262 | mut bind := gfx.Bindings{} |
| 263 | unsafe { vmemset(&bind, 0, int(sizeof(bind))) } |
| 264 | bind.vertex_buffers[0] = vbuf // vertex buffer |
| 265 | bind.vertex_buffers[1] = inst_buf // instance buffer |
| 266 | bind.index_buffer = ibuf |
| 267 | bind.fs.images[C.SLOT_tex] = app.texture |
| 268 | bind.fs.samplers[C.SLOT_smp] = app.sampler |
| 269 | app.bind['inst'] = bind |
| 270 | app.pipe['inst'] = gfx.make_pipeline(&pipdesc) |
| 271 | |
| 272 | println('GLSL March init DONE!') |
| 273 | } |
| 274 | |
| 275 | fn calc_tr_matrices(w f32, h f32, rx f32, ry f32, in_scale f32) m4.Mat4 { |
| 276 | // vfmt off |
| 277 | proj := m4.perspective(60, w/h, 0.01, 4000.0) |
| 278 | view := m4.look_at(m4.Vec4{e:[f32(0.0),100,6,0]!}, m4.Vec4{e:[f32(0),0,0,0]!}, m4.Vec4{e:[f32(0),1.0,0,0]!}) |
| 279 | view_proj := view * proj |
| 280 | |
| 281 | rxm := m4.rotate(m4.rad(rx), m4.Vec4{e:[f32(1),0,0,0]!}) |
| 282 | rym := m4.rotate(m4.rad(ry), m4.Vec4{e:[f32(0),1,0,0]!}) |
| 283 | // vfmt on |
| 284 | |
| 285 | model := rym * rxm |
| 286 | scale_m := m4.scale(m4.Vec4{ e: [in_scale, in_scale, in_scale, 1]! }) |
| 287 | |
| 288 | res := (scale_m * model) * view_proj |
| 289 | return res |
| 290 | } |
| 291 | |
| 292 | // triangles draw |
| 293 | fn draw_cube_glsl_i(mut app App) { |
| 294 | if app.init_flag == false { |
| 295 | return |
| 296 | } |
| 297 | |
| 298 | ws := gg.window_size_real_pixels() |
| 299 | // ratio := f32(ws.width) / ws.height |
| 300 | dw := f32(ws.width / 2) |
| 301 | dh := f32(ws.height / 2) |
| 302 | |
| 303 | rot := [f32(app.mouse_y), f32(app.mouse_x)] |
| 304 | tr_matrix := calc_tr_matrices(dw, dh, rot[0], rot[1], 2.3) |
| 305 | |
| 306 | gfx.apply_pipeline(app.pipe['inst']) |
| 307 | gfx.apply_bindings(app.bind['inst']) |
| 308 | |
| 309 | //*************** |
| 310 | // Instancing |
| 311 | //*************** |
| 312 | // passing the instancing to the vs |
| 313 | time_ticks := f32(time.ticks() - app.ticks) / 1000 |
| 314 | cube_size := 2 |
| 315 | sz := 128 // field size dimension |
| 316 | cx := 64 // x center for the cubes |
| 317 | cz := 64 // z center for the cubes |
| 318 | // frame := (app.frame_count/4) % 100 |
| 319 | for index in 0 .. num_inst { |
| 320 | x := f32(index % sz) |
| 321 | z := f32(index / sz) |
| 322 | // simply waves |
| 323 | y := f32(math.cos((x + time_ticks) / 2.0) * math.sin(z / 2.0)) * 2 |
| 324 | // sombrero function |
| 325 | // r := ((x-cx)*(x-cx)+(z-cz)*(z-cz))/(sz/2) |
| 326 | // y := f32(math.sin(r+time_ticks)*4.0) |
| 327 | spare_param := f32(index % 10) |
| 328 | // vfmt off |
| 329 | app.inst_pos[index] = m4.Vec4{e:[f32((x - cx - app.camera_x) * cube_size),y ,f32( (z - cz - app.camera_z) * cube_size),spare_param]!} |
| 330 | // vfmt on |
| 331 | } |
| 332 | range := gfx.Range{ |
| 333 | ptr: unsafe { &app.inst_pos } |
| 334 | size: usize(num_inst * int(sizeof(m4.Vec4))) |
| 335 | } |
| 336 | gfx.update_buffer(app.bind['inst'].vertex_buffers[1], &range) |
| 337 | |
| 338 | // Uniforms |
| 339 | // *** vertex shadeer uniforms *** |
| 340 | // passing the view matrix as uniform |
| 341 | // res is a 4x4 matrix of f32 thus: 4*16 byte of size |
| 342 | vs_uniforms_range := gfx.Range{ |
| 343 | ptr: unsafe { &tr_matrix } |
| 344 | size: usize(4 * 16) |
| 345 | } |
| 346 | gfx.apply_uniforms(.vs, C.SLOT_vs_params_i, &vs_uniforms_range) |
| 347 | |
| 348 | /* |
| 349 | // *** fragment shader uniforms *** |
| 350 | time_ticks := f32(time.ticks() - app.ticks) / 1000 |
| 351 | // vfmt off |
| 352 | mut tmp_fs_params := [ |
| 353 | f32(ws.width), ws.height * ratio, // x,y resolution to pass to FS |
| 354 | 0,0, // dont send mouse position |
| 355 | //app.mouse_x, // mouse x |
| 356 | //ws.height - app.mouse_y*2, // mouse y scaled |
| 357 | time_ticks, // time as f32 |
| 358 | app.frame_count, // frame count |
| 359 | 0,0 // padding bytes , see "fs_params" struct paddings in rt_glsl.h |
| 360 | ]! |
| 361 | // vfmt on |
| 362 | fs_uniforms_range := gfx.Range{ |
| 363 | ptr: unsafe { &tmp_fs_params } |
| 364 | size: usize(sizeof(tmp_fs_params)) |
| 365 | } |
| 366 | gfx.apply_uniforms(.fs, C.SLOT_fs_params, &fs_uniforms_range) |
| 367 | */ |
| 368 | // 3 vertices for triangle * 2 triangles per face * 6 faces = 36 vertices to draw for num_inst times |
| 369 | gfx.draw(0, (3 * 2) * 6, num_inst) |
| 370 | } |
| 371 | |
| 372 | fn draw_start_glsl(app App) { |
| 373 | if app.init_flag == false { |
| 374 | return |
| 375 | } |
| 376 | |
| 377 | ws := gg.window_size_real_pixels() |
| 378 | // ratio := f32(ws.width) / ws.height |
| 379 | // dw := f32(ws.width / 2) |
| 380 | // dh := f32(ws.height / 2) |
| 381 | |
| 382 | gfx.apply_viewport(0, 0, ws.width, ws.height, true) |
| 383 | } |
| 384 | |
| 385 | fn draw_end_glsl(app App) { |
| 386 | gfx.end_pass() |
| 387 | gfx.commit() |
| 388 | } |
| 389 | |
| 390 | fn frame(mut app App) { |
| 391 | // clear |
| 392 | mut color_action := gfx.ColorAttachmentAction{ |
| 393 | load_action: .clear |
| 394 | clear_value: gfx.Color{ |
| 395 | r: 0.0 |
| 396 | g: 0.0 |
| 397 | b: 0.0 |
| 398 | a: 1.0 |
| 399 | } |
| 400 | } |
| 401 | mut pass_action := gfx.PassAction{} |
| 402 | pass_action.colors[0] = color_action |
| 403 | pass := gg.create_default_pass(pass_action) |
| 404 | gfx.begin_pass(&pass) |
| 405 | |
| 406 | draw_start_glsl(app) |
| 407 | draw_cube_glsl_i(mut app) |
| 408 | draw_end_glsl(app) |
| 409 | app.frame_count++ |
| 410 | } |
| 411 | |
| 412 | /****************************************************************************** |
| 413 | * Init / Cleanup |
| 414 | ******************************************************************************/ |
| 415 | fn my_init(mut app App) { |
| 416 | // create chessboard texture 256*256 RGBA |
| 417 | w := 256 |
| 418 | h := 256 |
| 419 | sz := w * h * 4 |
| 420 | tmp_txt := unsafe { malloc(sz) } |
| 421 | mut i := 0 |
| 422 | for i < sz { |
| 423 | unsafe { |
| 424 | y := (i >> 0x8) >> 5 // 8 cell |
| 425 | x := (i & 0xFF) >> 5 // 8 cell |
| 426 | // upper left corner |
| 427 | if x == 0 && y == 0 { |
| 428 | tmp_txt[i + 0] = u8(0xFF) |
| 429 | tmp_txt[i + 1] = u8(0) |
| 430 | tmp_txt[i + 2] = u8(0) |
| 431 | tmp_txt[i + 3] = u8(0xFF) |
| 432 | } |
| 433 | // low right corner |
| 434 | else if x == 7 && y == 7 { |
| 435 | tmp_txt[i + 0] = u8(0) |
| 436 | tmp_txt[i + 1] = u8(0xFF) |
| 437 | tmp_txt[i + 2] = u8(0) |
| 438 | tmp_txt[i + 3] = u8(0xFF) |
| 439 | } else { |
| 440 | col := if ((x + y) & 1) == 1 { 0xFF } else { 128 } |
| 441 | tmp_txt[i + 0] = u8(col) // red |
| 442 | tmp_txt[i + 1] = u8(col) // green |
| 443 | tmp_txt[i + 2] = u8(col) // blue |
| 444 | tmp_txt[i + 3] = u8(0xFF) // alpha |
| 445 | } |
| 446 | i += 4 |
| 447 | } |
| 448 | } |
| 449 | unsafe { |
| 450 | app.texture, app.sampler = create_texture(w, h, tmp_txt) |
| 451 | free(tmp_txt) |
| 452 | } |
| 453 | // glsl |
| 454 | init_cube_glsl_i(mut app) |
| 455 | app.init_flag = true |
| 456 | } |
| 457 | |
| 458 | /****************************************************************************** |
| 459 | * events handling |
| 460 | ******************************************************************************/ |
| 461 | fn my_event_manager(mut ev gg.Event, mut app App) { |
| 462 | if ev.typ == .mouse_down { |
| 463 | app.mouse_down = true |
| 464 | } |
| 465 | if ev.typ == .mouse_up { |
| 466 | app.mouse_down = false |
| 467 | } |
| 468 | if app.mouse_down == true && ev.typ == .mouse_move { |
| 469 | app.mouse_x = int(ev.mouse_x) |
| 470 | app.mouse_y = int(ev.mouse_y) |
| 471 | } |
| 472 | if ev.typ == .touches_began || ev.typ == .touches_moved { |
| 473 | if ev.num_touches > 0 { |
| 474 | touch_point := ev.touches[0] |
| 475 | app.mouse_x = int(touch_point.pos_x) |
| 476 | app.mouse_y = int(touch_point.pos_y) |
| 477 | } |
| 478 | } |
| 479 | |
| 480 | // keyboard |
| 481 | if ev.typ == .key_down { |
| 482 | step := f32(1.0) |
| 483 | match ev.key_code { |
| 484 | .w { app.camera_z += step } |
| 485 | .s { app.camera_z -= step } |
| 486 | .a { app.camera_x -= step } |
| 487 | .d { app.camera_x += step } |
| 488 | else {} |
| 489 | } |
| 490 | } |
| 491 | eprintln('>> app.camera_x: ${app.camera_x} , app.camera_z: ${app.camera_z}, app.mouse_x: ${app.mouse_x}, app.mouse_y: ${app.mouse_y}') |
| 492 | } |
| 493 | |
| 494 | fn main() { |
| 495 | mut app := &App{} |
| 496 | // vfmt off |
| 497 | app.gg = gg.new_context( |
| 498 | width: win_width |
| 499 | height: win_height |
| 500 | create_window: true |
| 501 | window_title: 'Instancing Cube' |
| 502 | user_data: app |
| 503 | bg_color: bg_color |
| 504 | frame_fn: frame |
| 505 | init_fn: my_init |
| 506 | event_fn: my_event_manager |
| 507 | ) |
| 508 | // vfmt on |
| 509 | app.ticks = time.ticks() |
| 510 | app.gg.run() |
| 511 | } |
| 512 | |