1 //------------------------------------------------------------------------------ 2 // instancingcompute.d 3 // 4 // Like instancing.d, but use a compute shader to update particles. 5 //------------------------------------------------------------------------------ 6 module examples.instancingcompute; 7 8 private: 9 10 import sg = sokol.gfx; 11 import sapp = sokol.app; 12 import slog = sokol.log; 13 import handmade.math: Mat4, Vec3; 14 import sglue = sokol.glue; 15 import shd = examples.shaders.instancingcompute; 16 17 extern (C): 18 @safe nothrow @nogc: 19 20 enum max_particles = 512 * 1024; 21 enum num_particles_emitted_per_frame = 10; 22 23 struct ComputeState { 24 sg.Pipeline pip = {}; 25 sg.Bindings bind = {}; 26 } 27 28 struct DisplayState { 29 sg.Pipeline pip = {}; 30 sg.Bindings bind = {}; 31 sg.PassAction pass_action = { 32 colors: [ 33 { 34 load_action: sg.LoadAction.Clear, 35 clear_value: {r: 0.0, g: 0.1, b: 0.2, a: 1 }, 36 } 37 ] 38 }; 39 } 40 41 struct State { 42 int num_particles = 0; 43 float ry = 0; 44 ComputeState compute; 45 DisplayState display; 46 47 Mat4 view() @nogc nothrow const { 48 return Mat4.lookAt(Vec3(0.0, 1.5, 12.0), Vec3.zero(), Vec3.up()); 49 } 50 } 51 52 static State state; 53 54 void init() { 55 sg.Desc gfxd = { 56 environment: sglue.environment, 57 logger: { func: &slog.func }, 58 }; 59 sg.setup(gfxd); 60 61 // if compute shaders not supported, clear to red color and early out 62 if (!sg.queryFeatures().compute) { 63 state.display.pass_action.colors[0].clear_value.r = 1.0; 64 state.display.pass_action.colors[0].clear_value.g = 0.0; 65 state.display.pass_action.colors[0].clear_value.b = 0.0; 66 return; 67 } 68 69 // a zero-initialized storage buffer for the particle state 70 sg.BufferDesc sbufd = { 71 usage: { storage_buffer: true }, 72 size: shd.Particle.sizeof * max_particles, 73 label: "particle-buffer", 74 }; 75 sg.Buffer sbuf = sg.makeBuffer(sbufd); 76 state.compute.bind.storage_buffers[shd.SBUF_CS_SSBO] = sbuf; 77 state.display.bind.storage_buffers[shd.SBUF_VS_SSBO] = sbuf; 78 79 // a compute shader and pipeline object for updating the particle state 80 sg.PipelineDesc upipd = { 81 compute: true, 82 shader: sg.makeShader(shd.updateShaderDesc(sg.queryBackend())), 83 label: "update-pipeline", 84 }; 85 state.compute.pip = sg.makePipeline(upipd); 86 87 // vertex and index buffer for the particle geometry 88 immutable float r = 0.05; 89 float[42] vertices = [ 90 0.0, -r, 0.0, 1.0, 0.0, 0.0, 1.0, 91 r, 0.0, r, 0.0, 1.0, 0.0, 1.0, 92 r, 0.0, -r, 0.0, 0.0, 1.0, 1.0, 93 -r, 0.0, -r, 1.0, 1.0, 0.0, 1.0, 94 -r, 0.0, r, 0.0, 1.0, 1.0, 1.0, 95 0.0, r, 0.0, 1.0, 0.0, 1.0, 1.0, 96 ]; 97 ushort[24] indices = [ 98 2, 1, 0, 3, 2, 0, 99 4, 3, 0, 1, 4, 0, 100 5, 1, 2, 5, 2, 3, 101 5, 3, 4, 5, 4, 1, 102 ]; 103 sg.BufferDesc vbufd = { 104 data: { ptr: vertices.ptr, size: vertices.sizeof }, 105 label: "geometry-vbuf", 106 }; 107 sg.BufferDesc ibufd = { 108 usage: { index_buffer: true }, 109 data: { ptr: indices.ptr, size: indices.sizeof }, 110 label: "geometry-ibuf", 111 }; 112 state.display.bind.vertex_buffers[0] = sg.makeBuffer(vbufd); 113 state.display.bind.index_buffer = sg.makeBuffer(ibufd); 114 115 // shader and pipeline for rendering the particles, this uses 116 // the compute-updated storage buffer to provide the particle positions 117 sg.PipelineDesc rpipd = { 118 shader: sg.makeShader(shd.displayShaderDesc(sg.queryBackend())), 119 layout: { 120 attrs: [ 121 shd.ATTR_DISPLAY_POS: { format: sg.VertexFormat.Float3 }, 122 shd.ATTR_DISPLAY_COLOR0: { format: sg.VertexFormat.Float4 }, 123 ], 124 }, 125 index_type: sg.IndexType.Uint16, 126 cull_mode: sg.CullMode.Back, 127 depth: { 128 compare: sg.CompareFunc.Less_equal, 129 write_enabled: true, 130 }, 131 label: "render-pipeline", 132 }; 133 state.display.pip = sg.makePipeline(rpipd); 134 135 // one-time init of particle velocities via a compute shader 136 sg.PipelineDesc ipipd = { 137 compute: true, 138 shader: sg.makeShader(shd.initShaderDesc(sg.queryBackend())), 139 }; 140 sg.Pipeline ipip = sg.makePipeline(ipipd); 141 sg.Pass pass = { compute: true }; 142 sg.beginPass(pass); 143 sg.applyPipeline(ipip); 144 sg.applyBindings(state.compute.bind); 145 sg.dispatch(max_particles / 64, 1, 1); 146 sg.endPass(); 147 } 148 149 void frame() { 150 if (!sg.queryFeatures().compute) { 151 drawFallback(); 152 return; 153 } 154 155 state.num_particles += num_particles_emitted_per_frame; 156 if (state.num_particles > max_particles) { 157 state.num_particles = max_particles; 158 } 159 float dt = sapp.frameDuration(); 160 161 // compute pass to update particle positions 162 shd.CsParams cs_params = { 163 dt: dt, 164 num_particles: state.num_particles, 165 }; 166 sg.Range cs_params_range = { ptr: &cs_params, size: cs_params.sizeof }; 167 sg.Pass cpass = { compute: true, label: "compute-pass" }; 168 sg.beginPass(cpass); 169 sg.applyPipeline(state.compute.pip); 170 sg.applyBindings(state.compute.bind); 171 sg.applyUniforms(shd.UB_CS_PARAMS, cs_params_range); 172 sg.dispatch((state.num_particles + 63) / 64, 1, 1); 173 sg.endPass(); 174 175 // render pass to render the particles via instancing, with the 176 // instance positions coming from the storage buffer 177 state.ry += 60.0 * dt; 178 shd.VsParams vs_params = computeVsParams(1.0, state.ry); 179 sg.Range vs_params_range = { ptr: &vs_params, size: vs_params.sizeof }; 180 sg.Pass rpass = { 181 action: state.display.pass_action, 182 swapchain: sglue.swapchain(), 183 label: "render-pass", 184 }; 185 sg.beginPass(rpass); 186 sg.applyPipeline(state.display.pip); 187 sg.applyBindings(state.display.bind); 188 sg.applyUniforms(shd.UB_VS_PARAMS, vs_params_range); 189 sg.draw(0, 24, state.num_particles); 190 sg.endPass(); 191 sg.commit(); 192 } 193 194 void cleanup() { 195 sg.shutdown(); 196 } 197 198 void drawFallback() { 199 sg.Pass rpass = { 200 action: state.display.pass_action, 201 swapchain: sglue.swapchain(), 202 label: "render-pass", 203 }; 204 sg.beginPass(rpass); 205 sg.endPass(); 206 sg.commit(); 207 } 208 209 shd.VsParams computeVsParams(float rx, float ry) @nogc nothrow 210 { 211 immutable proj = Mat4.perspective(60.0, sapp.widthf() / sapp.heightf(), 0.01, 50.0); 212 immutable rxm = Mat4.rotate(rx, Vec3(1.0, 0.0, 0.0)); 213 immutable rym = Mat4.rotate(ry, Vec3(0.0, 1.0, 0.0)); 214 immutable model = Mat4.mul(rxm, rym); 215 immutable view_proj = Mat4.mul(proj, state.view()); 216 shd.VsParams mvp = { mvp: Mat4.mul(view_proj, model) }; 217 return mvp; 218 } 219 220 void main() { 221 sapp.Desc adesc = { 222 init_cb: &init, 223 frame_cb: &frame, 224 cleanup_cb: &cleanup, 225 width: 800, 226 height: 600, 227 sample_count: 4, 228 window_title: "instancingcompute.d", 229 icon: { sokol_default: true }, 230 logger: { func: &slog.func }, 231 }; 232 sapp.run(adesc); 233 }