v / examples / sokol / 04_multi_shader_glsl / rt_glsl.v
339 lines · 309 sloc · 10.75 KB · a80bc2331450fc28c900097f8afafe173f161d27
Raw
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**********************************************************************/
12import gg
13import gg.m4
14import sokol.sapp
15import sokol.gfx
16import sokol.sgl
17import 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
23fn C.rt_march_shader_desc(gfx.Backend) &gfx.ShaderDesc
24fn C.rt_puppy_shader_desc(gfx.Backend) &gfx.ShaderDesc
25
26const start_ticks = time.ticks()
27
28@[heap]
29struct App {
30mut:
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******************************************************************************/
48fn 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******************************************************************************
72Cube 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*/
81struct Vertex_t {
82 x f32
83 y f32
84 z f32
85 color u32
86 u f32
87 v f32
88}
89
90const vertices = cube_vertices()
91
92fn 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
110fn (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
165fn 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
180fn (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
227fn (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
245fn (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******************************************************************************/
306fn (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
327fn 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