1 //------------------------------------------------------------------------------ 2 // instancing.d 3 // 4 // Demonstrate simple hardware-instancing using a static geometry buffer 5 // and a dynamic instance-data buffer. 6 //------------------------------------------------------------------------------ 7 module examples.instancing; 8 9 private: 10 11 import sg = sokol.gfx; 12 import app = sokol.app; 13 import log = sokol.log; 14 import handmade.math : Mat4, Vec3; 15 import sglue = sokol.glue; 16 import shd = shaders.instancing; 17 18 extern (C): 19 @safe nothrow @nogc: 20 21 enum max_particles = 512 * 1024; 22 enum num_particles_emitted_per_frame = 10; 23 24 struct State 25 { 26 float ry = 0; 27 Vec3[max_particles] pos; 28 Vec3[max_particles] vel; 29 int cur_num_particles = 0; 30 sg.Pipeline pip = {}; 31 sg.Bindings bind = {}; 32 // dfmt off 33 sg.PassAction passAction = { 34 colors: [ 35 { 36 load_action: sg.LoadAction.Clear, 37 clear_value: {r: 0.0, g: 0.0, b: 0.0, a: 1.0} 38 } 39 ] 40 }; 41 // dfmt on 42 43 Mat4 view() @nogc nothrow const 44 { 45 return Mat4.lookAt(Vec3(0.0, 1.5, 12.0), Vec3.zero(), Vec3.up()); 46 } 47 } 48 49 static State state; 50 51 void init() 52 { 53 sg.Desc gfxd = {environment: sglue.environment, 54 logger: {func: &log.func}}; 55 sg.setup(gfxd); 56 57 immutable float r = 0.05; 58 float[42] vertices = [ 59 0.0, -r, 0.0, 1.0, 0.0, 0.0, 1.0, 60 r, 0.0, r, 0.0, 1.0, 0.0, 1.0, 61 r, 0.0, -r, 0.0, 0.0, 1.0, 1.0, 62 -r, 0.0, -r, 1.0, 1.0, 0.0, 1.0, 63 -r, 0.0, r, 0.0, 1.0, 1.0, 1.0, 64 0.0, r, 0.0, 1.0, 0.0, 1.0, 1.0, 65 ]; 66 sg.BufferDesc vbufd0 = {data: {ptr: vertices.ptr, size: vertices.sizeof},}; 67 state.bind.vertex_buffers[0] = sg.makeBuffer(vbufd0); 68 69 ushort[24] indices = [ 70 2, 1, 0, 3, 2, 0, 71 4, 3, 0, 1, 4, 0, 72 5, 1, 2, 5, 2, 3, 73 5, 3, 4, 5, 4, 1, 74 ]; 75 sg.BufferDesc ibufd = { 76 usage: {index_buffer: true}, 77 data: {ptr: indices.ptr, size: indices.sizeof}}; 78 state.bind.index_buffer = sg.makeBuffer(ibufd); 79 80 sg.BufferDesc vbufd1 = { 81 usage: {stream_update: true}, 82 size: max_particles * Vec3.sizeof 83 }; 84 state.bind.vertex_buffers[1] = sg.makeBuffer(vbufd1); 85 86 sg.PipelineDesc pld = { 87 layout: { 88 attrs: [ 89 shd.ATTR_INSTANCING_POS: { 90 format: sg.VertexFormat.Float3, buffer_index: 0}, 91 shd.ATTR_INSTANCING_COLOR0 : { 92 format: sg.VertexFormat.Float4, buffer_index: 0 93 }, 94 shd.ATTR_INSTANCING_INST_POS : { 95 format: sg.VertexFormat.Float3, buffer_index: 1 96 }], 97 }, 98 shader: sg.makeShader(shd.instancingShaderDesc(sg.queryBackend())), 99 index_type: sg.IndexType.Uint16, 100 cull_mode: sg.CullMode.Back, 101 depth: {write_enabled: true, 102 compare: sg.CompareFunc.Less_equal 103 }, 104 }; 105 pld.layout.buffers[1].step_func = sg.VertexStep.Per_instance; 106 state.pip = sg.makePipeline(pld); 107 } 108 109 void frame() 110 { 111 immutable float frame_time = cast(float) app.frameDuration(); 112 113 // emit new particles 114 foreach (i; 0 .. num_particles_emitted_per_frame) 115 { 116 if (state.cur_num_particles < max_particles) 117 { 118 state.pos[state.cur_num_particles] = Vec3.zero(); 119 state.vel[state.cur_num_particles] = Vec3( 120 rand(-0.5, 0.5), 121 rand(2.0, 2.5), 122 rand(-0.5, 0.5) 123 ); 124 state.cur_num_particles++; 125 } 126 else 127 { 128 break; 129 } 130 } 131 132 // update particle positions 133 foreach (i; 0 .. max_particles) 134 { 135 Vec3* vel = &state.vel[i]; 136 Vec3* pos = &state.pos[i]; 137 vel.y -= 1.0 * frame_time; 138 *pos = Vec3.add(*pos, Vec3.mul(*vel, frame_time)); 139 if (pos.y < -2.0) 140 { 141 pos.y = -1.8; 142 vel.y = -vel.y; 143 *vel = Vec3.mul(*vel, 0.8); 144 } 145 } 146 147 sg.Range ub = {ptr: &state.pos, size: state.pos.sizeof}; 148 sg.updateBuffer(state.bind.vertex_buffers[1], ub); 149 state.ry += 1.0 * frame_time; 150 151 shd.VsParams vsParams = computeMvp(1.0, state.ry); 152 153 sg.Pass pass = {action: state.passAction, swapchain: sglue.swapchain() 154 }; 155 sg.beginPass(pass); 156 sg.applyPipeline(state.pip); 157 sg.applyBindings(state.bind); 158 sg.Range r_vsparams = {ptr: &vsParams, size: vsParams.sizeof}; 159 sg.applyUniforms(shd.UB_VS_PARAMS, r_vsparams); 160 sg.draw(0, 24, state.cur_num_particles); 161 sg.endPass(); 162 sg.commit(); 163 } 164 165 void cleanup() 166 { 167 sg.shutdown(); 168 } 169 170 shd.VsParams computeMvp(float rx, float ry) @nogc nothrow 171 { 172 immutable proj = Mat4.perspective(60.0, app.widthf() / app.heightf(), 0.01, 50.0); 173 immutable rxm = Mat4.rotate(rx, Vec3(1.0, 0.0, 0.0)); 174 immutable rym = Mat4.rotate(ry, Vec3(0.0, 1.0, 0.0)); 175 immutable model = Mat4.mul(rxm, rym); 176 immutable view_proj = Mat4.mul(proj, state.view()); 177 // dfmt off 178 shd.VsParams mvp = { mvp: Mat4.mul(view_proj, model) }; 179 // dfmt on 180 return mvp; 181 } 182 183 void main() 184 { 185 // dfmt off 186 app.Desc runner = { 187 window_title: "instancing.d", 188 init_cb: &init, 189 frame_cb: &frame, 190 cleanup_cb: &cleanup, 191 width: 800, 192 height: 600, 193 sample_count: 4, 194 icon: {sokol_default: true}, 195 logger: {func: &log.func} 196 }; 197 // dfmt on 198 app.run(runner); 199 } 200 201 uint xorshift32() 202 { 203 static uint X = 0x12345678; 204 205 uint x = X; 206 x ^= x << 13; 207 x ^= x >> 17; 208 x ^= x << 5; 209 X = x; 210 return x; 211 } 212 213 float rand(inout(float) min, inout(float) max) 214 { 215 return (cast(float)(xorshift32 & 0xFFFF) / 0x10000) * (max - min) + min; 216 } 217 218 version (WebAssembly) 219 { 220 debug 221 { 222 import emscripten.assertd; 223 } 224 }