diff --git a/.github/workflows/compile_shaders_in_examples.sh b/.github/workflows/compile_shaders_in_examples.sh new file mode 100755 index 00000000000000..50b0df0b233d36 --- /dev/null +++ b/.github/workflows/compile_shaders_in_examples.sh @@ -0,0 +1,7 @@ +#!/bin/bash + +for f in examples/sokol/*/ ; do + echo "compiling shaders for $f ..."; + time ./v shader $f; + echo "done"; +done; diff --git a/.github/workflows/other_ci.yml b/.github/workflows/other_ci.yml index 7a8a3bd1a24924..43cda690a9b19d 100644 --- a/.github/workflows/other_ci.yml +++ b/.github/workflows/other_ci.yml @@ -102,18 +102,7 @@ jobs: - name: Shader examples can be built run: | - wget https://github.com/floooh/sokol-tools-bin/raw/6b84ea387a323db9e8e17f5abed2b254a6e409fe/bin/linux/sokol-shdc - chmod +x ./sokol-shdc - for f in examples/sokol/02_cubes_glsl/cube_glsl \ - examples/sokol/03_march_tracing_glsl/rt_glsl \ - examples/sokol/04_multi_shader_glsl/rt_glsl_puppy \ - examples/sokol/04_multi_shader_glsl/rt_glsl_march \ - examples/sokol/05_instancing_glsl/rt_glsl_instancing \ - examples/sokol/06_obj_viewer/gouraud \ - ; do \ - echo "compiling shader $f.glsl ..."; \ - ./sokol-shdc --input $f.glsl --output $f.h --slang glsl330 ; \ - done + .github/workflows/compile_shaders_in_examples.sh ./v should-compile-all examples/sokol/*.v examples/sokol/0?*/*.v parser-silent: diff --git a/cmd/tools/modules/testing/common.v b/cmd/tools/modules/testing/common.v index efa37c21a6cef3..18133c90bc7dba 100644 --- a/cmd/tools/modules/testing/common.v +++ b/cmd/tools/modules/testing/common.v @@ -283,6 +283,7 @@ pub fn new_test_session(_vargs string, will_compile bool) TestSession { // These examples need .h files that are produced from the supplied .glsl files, // using by the shader compiler tools in https://github.com/floooh/sokol-tools-bin/archive/pre-feb2021-api-changes.tar.gz skip_files << 'examples/sokol/simple_shader_glsl/simple_shader.v' + skip_files << 'examples/sokol/sdf/sdf.v' skip_files << 'examples/sokol/02_cubes_glsl/cube_glsl.v' skip_files << 'examples/sokol/03_march_tracing_glsl/rt_glsl.v' skip_files << 'examples/sokol/04_multi_shader_glsl/rt_glsl.v' diff --git a/cmd/tools/vgret.defaults.toml b/cmd/tools/vgret.defaults.toml index 4039eda253d595..1537d73d28871a 100644 --- a/cmd/tools/vgret.defaults.toml +++ b/cmd/tools/vgret.defaults.toml @@ -72,3 +72,4 @@ # 'examples/sokol/04_multi_shader_glsl/rt_glsl.v', // Inaccurate captures # 'examples/sokol/05_instancing_glsl/rt_glsl.v', // Inaccurate captures # 'examples/sokol/06_obj_viewer/show_obj.v', // Inaccurate captures +# 'examples/sokol/sdf/sdf.v', // Inaccurate captures diff --git a/examples/sokol/clear.v b/examples/sokol/clear.v new file mode 100644 index 00000000000000..63956e2aa029ec --- /dev/null +++ b/examples/sokol/clear.v @@ -0,0 +1,25 @@ +// This example shows how to clear your window on each frame, with a different color, using a Sokol pass. +// It is ported from https://github.com/floooh/sokol-samples/blob/master/sapp/clear-sapp.c . +import sokol.gfx +import sokol.sapp + +fn frame(mut action gfx.PassAction) { + g := f32(action.colors[0].clear_value.g + 0.01) + action.colors[0].clear_value.g = if g > 1.0 { 0 } else { g } + gfx.begin_pass(sapp.create_default_pass(action)) + gfx.end_pass() + gfx.commit() +} + +fn main() { + action := gfx.create_clear_pass_action(1.0, 0, 0, 1.0) + sapp.run( + window_title: c'Clear (sokol app)' + width: 400 + height: 300 + init_cb: || gfx.setup(sapp.create_desc()) + cleanup_cb: || gfx.shutdown() + frame_userdata_cb: frame + user_data: &action + ) +} diff --git a/examples/sokol/sdf/.gitignore b/examples/sokol/sdf/.gitignore new file mode 100644 index 00000000000000..972293d4a9587c --- /dev/null +++ b/examples/sokol/sdf/.gitignore @@ -0,0 +1,2 @@ +sdf-sapp.c +sdf.h diff --git a/examples/sokol/sdf/sdf.glsl b/examples/sokol/sdf/sdf.glsl new file mode 100644 index 00000000000000..9bca7355b7cdbd --- /dev/null +++ b/examples/sokol/sdf/sdf.glsl @@ -0,0 +1,184 @@ +//------------------------------------------------------------------------------ +// Signed-distance-field raymarching shaders, see: +// https://iquilezles.org/articles/mandelbulb +// https://www.shadertoy.com/view/ltfSWn +//------------------------------------------------------------------------------ + +//--- vertex shader +@vs vs +uniform vs_params { + float aspect; + float time; +}; +in vec4 position; + +out vec2 pos; +out vec3 eye; +out vec3 up; +out vec3 right; +out vec3 fwd; + +// compute eye position (orbit around center) +vec3 eye_pos(float time, vec3 center) { + return center + vec3(sin(time * 0.05) * 3.0, sin(time * 0.1) * 2.0, cos(time * 0.05) * 3.0); +} + +// a lookat function +void lookat(vec3 eye, vec3 center, vec3 up, out vec3 out_fwd, out vec3 out_right, out vec3 out_up) { + out_fwd = normalize(center - eye); + out_right = normalize(cross(out_fwd, up)); + out_up = cross(out_right, out_fwd); +} + +void main() { + gl_Position = position; + pos.x = position.x * aspect; + pos.y = position.y; + const vec3 center = vec3(0.0, 0.0, 0.0); + const vec3 up_vec = vec3(0.0, 1.0, 0.0); + eye = eye_pos(time * 5, center); + lookat(eye, center, up_vec, fwd, right, up); +} +@end + +//--- fragment shader +@fs fs +in vec2 pos; +in vec3 eye; +in vec3 up; +in vec3 right; +in vec3 fwd; + +out vec4 frag_color; + +float sd_sphere(vec3 p, float s) { + return length(p) - s; +} + +float sd_mandelbulb(vec3 p, out vec4 res_color) { + vec3 w = p; + float m = dot(w,w); + + vec4 trap = vec4(abs(w),m); + float dz = 1.0; + + for( int i=0; i<4; i++ ) { + float m2 = m*m; + float m4 = m2*m2; + dz = 8.0*sqrt(m4*m2*m)*dz + 1.0; + + float x = w.x; float x2 = x*x; float x4 = x2*x2; + float y = w.y; float y2 = y*y; float y4 = y2*y2; + float z = w.z; float z2 = z*z; float z4 = z2*z2; + + float k3 = x2 + z2; + float k2 = inversesqrt( k3*k3*k3*k3*k3*k3*k3 ); + float k1 = x4 + y4 + z4 - 6.0*y2*z2 - 6.0*x2*y2 + 2.0*z2*x2; + float k4 = x2 - y2 + z2; + + w.x = p.x + 64.0*x*y*z*(x2-z2)*k4*(x4-6.0*x2*z2+z4)*k1*k2; + w.y = p.y + -16.0*y2*k3*k4*k4 + k1*k1; + w.z = p.z + -8.0*y*k4*(x4*x4 - 28.0*x4*x2*z2 + 70.0*x4*z4 - 28.0*x2*z2*z4 + z4*z4)*k1*k2; + + trap = min( trap, vec4(abs(w),m) ); + + m = dot(w,w); + if( m > 256.0 ) { + break; + } + } + res_color = vec4(m,trap.yzw); + return 0.25*log(m)*sqrt(m)/dz; +} + +float d_scene(vec3 p, out vec4 res_color) { + float d = sd_sphere(p, 1.1); + if (d < 0.1) { + d = sd_mandelbulb(p, res_color); + } + else { + res_color = vec4(0.0); + } + return d; +} + +// surface normal estimation +vec3 surface_normal(vec3 p, float dp) { + const float eps = 0.001; + const vec2 d = vec2(eps, 0); + vec4 tra; + float x = d_scene(p + d.xyy, tra) - dp; + float y = d_scene(p + d.yxy, tra) - dp; + float z = d_scene(p + d.yyx, tra) - dp; + return normalize(vec3(x, y, z)); +} + +vec3 calc_color(vec3 ro, vec3 rd, float t, vec4 tra) { + const vec3 light1 = vec3( 0.577, 0.577, -0.577); + const vec3 light2 = vec3(-0.707, 0.000, 0.707); + + vec3 pos = ro + rd * t; + vec3 nrm = surface_normal(pos, t); + vec3 hal = normalize(light1 - rd); + float occ = clamp(0.05 * log(tra.x), 0.0, 1.0); + float fac = clamp(1.0 + dot(rd, nrm), 0.0, 1.0); + + // sun + float dif1 = clamp(dot( light1, nrm), 0.0, 1.0); + float spe1 = pow(clamp(dot(nrm, hal), 0.0, 1.0), 32.0 )*dif1*(0.04+0.96*pow(clamp(1.0-dot(hal,light1),0.0,1.0),5.0)); + // bounce + float dif2 = clamp( 0.5 + 0.5*dot( light2, nrm ), 0.0, 1.0 )*occ; + // sky + float dif3 = (0.7+0.3*nrm.y)*(0.2+0.8*occ); + + vec3 col = vec3(0.01); + col = mix(col, vec3(0.10,0.20,0.30), clamp(tra.y,0.0,1.0) ); + col = mix(col, vec3(0.02,0.10,0.30), clamp(tra.z*tra.z,0.0,1.0) ); + col = mix(col, vec3(0.30,0.10,0.02), clamp(pow(tra.w,6.0),0.0,1.0) ); + + vec3 lin = vec3(0.0); + lin += 7.0*vec3(1.50,1.10,0.70)*dif1; + lin += 4.0*vec3(0.25,0.20,0.15)*dif2; + lin += 1.5*vec3(0.10,0.20,0.30)*dif3; + lin += 2.5*vec3(0.35,0.30,0.25)*(0.05+0.95*occ); // ambient + lin += 4.0*fac*occ; // fake SSS + col *= lin; + col = pow( col, vec3(0.7,0.9,1.0)); // fake SSS + col += spe1*15.0; + + // gamma + col = sqrt(col); + + return col; +} + +void main() { + const float epsilon = 0.001; + const float focal_length = 1.8; + + vec3 ray_origin = eye + fwd * focal_length + right * pos.x + up * pos.y; + vec3 ray_direction = normalize(ray_origin - eye); + + vec4 tra; + vec4 color = vec4(0.10,0.20,0.30,1.0); + float t = 0.0; + for (int i = 0; i < 96; i++) { + vec3 p = ray_origin + ray_direction * t; + float d = d_scene(p, tra); + if (d < epsilon) { + color.xyz = calc_color(p, ray_direction, d, tra); + break; + } + else { + color.xyz += vec3(0.003, 0.001, 0.0) * i; + } + if (t > 3) { + break; + } + t += d; + } + frag_color = color; +} +@end + +@program sdf vs fs diff --git a/examples/sokol/sdf/sdf.v b/examples/sokol/sdf/sdf.v new file mode 100644 index 00000000000000..d8c09f6b05025e --- /dev/null +++ b/examples/sokol/sdf/sdf.v @@ -0,0 +1,69 @@ +// A Signed Distance Field rendering demo, ported from https://github.com/floooh/sokol-samples/blob/master/sapp/sdf-sapp.c +// which in turn is based on https://iquilezles.org/articles/mandelbulb/ and https://www.shadertoy.com/view/ltfSWn +import sokol.sapp +import sokol.gfx + +#include "@VMODROOT/sdf.h" # It should be generated with `v shader .` + +fn C.sdf_shader_desc(gfx.Backend) &gfx.ShaderDesc + +@[packed] +struct C.vs_params_t { +mut: + aspect f32 + time f32 +} + +struct State { +mut: + pip gfx.Pipeline + bind gfx.Bindings + paction gfx.PassAction + params C.vs_params_t +} + +fn init(mut state State) { + gfx.setup(sapp.create_desc()) + + fsq_verts := [f32(-1.0), -3.0, 3.0, 1.0, -1.0, 1.0]! + state.bind.vertex_buffers[0] = gfx.make_buffer(gfx.BufferDesc{ + label: c'fsq vertices' + data: unsafe { gfx.Range{&fsq_verts[0], sizeof(fsq_verts)} } + }) + + mut pipeline := gfx.PipelineDesc{} + pipeline.layout.attrs[C.ATTR_vs_position].format = .float2 + pipeline.shader = gfx.make_shader(C.sdf_shader_desc(gfx.query_backend())) + state.pip = gfx.make_pipeline(&pipeline) + + // No need to clear the window, since the shader will overwrite the whole framebuffer + state.paction.colors[0].load_action = .dontcare +} + +fn frame(mut state State) { + w, h := sapp.width(), sapp.height() + state.params.time += f32(sapp.frame_duration()) + state.params.aspect = f32(w) / f32(h) + gfx.begin_pass(sapp.create_default_pass(state.paction)) + gfx.apply_pipeline(state.pip) + gfx.apply_bindings(state.bind) + gfx.apply_uniforms(.vs, C.SLOT_vs_params, unsafe { gfx.Range{&state.params, sizeof(state.params)} }) + gfx.draw(0, 3, 1) + gfx.end_pass() + gfx.commit() +} + +fn main() { + sapp.run(sapp.Desc{ + window_title: c'SDF Rendering' + width: 512 + height: 512 + frame_userdata_cb: frame + init_userdata_cb: init + cleanup_cb: gfx.shutdown + icon: sapp.IconDesc{ + sokol_default: true + } + user_data: &State{} + }) +} diff --git a/examples/sokol/sdf/v.mod b/examples/sokol/sdf/v.mod new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/vlib/sokol/sapp/sapp_structs.c.v b/vlib/sokol/sapp/sapp_structs.c.v index 14da83931c370c..64bd2fe39cd556 100644 --- a/vlib/sokol/sapp/sapp_structs.c.v +++ b/vlib/sokol/sapp/sapp_structs.c.v @@ -27,6 +27,7 @@ pub type ImageDesc = C.sapp_image_desc @[typedef] pub struct C.sapp_icon_desc { +pub: sokol_default bool images [max_iconimages]ImageDesc }