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 = examples.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 }; 79 state.bind.index_buffer = sg.makeBuffer(ibufd); 80 81 sg.BufferDesc vbufd1 = { 82 usage: {stream_update: true}, 83 size: max_particles * Vec3.sizeof 84 }; 85 state.bind.vertex_buffers[1] = sg.makeBuffer(vbufd1); 86 87 sg.PipelineDesc pld = { 88 layout: { 89 attrs: [ 90 shd.ATTR_INSTANCING_POS: { 91 format: sg.VertexFormat.Float3, buffer_index: 0 92 }, 93 shd.ATTR_INSTANCING_COLOR0: { 94 format: sg.VertexFormat.Float4, buffer_index: 0 95 }, 96 shd.ATTR_INSTANCING_INST_POS: { 97 format: sg.VertexFormat.Float3, buffer_index: 1 98 } 99 ], 100 }, 101 shader: sg.makeShader(shd.instancingShaderDesc(sg.queryBackend())), 102 index_type: sg.IndexType.Uint16, 103 cull_mode: sg.CullMode.Back, 104 depth: { 105 write_enabled: true, 106 compare: sg.CompareFunc.Less_equal 107 }, 108 }; 109 pld.layout.buffers[1].step_func = sg.VertexStep.Per_instance; 110 state.pip = sg.makePipeline(pld); 111 } 112 113 void frame() 114 { 115 immutable float frame_time = cast(float) app.frameDuration(); 116 117 // emit new particles 118 foreach (i; 0 .. num_particles_emitted_per_frame) 119 { 120 if (state.cur_num_particles < max_particles) 121 { 122 state.pos[state.cur_num_particles] = Vec3.zero(); 123 state.vel[state.cur_num_particles] = Vec3( 124 rand(-0.5, 0.5), 125 rand(2.0, 2.5), 126 rand(-0.5, 0.5) 127 ); 128 state.cur_num_particles++; 129 } 130 else 131 { 132 break; 133 } 134 } 135 136 // update particle positions 137 foreach (i; 0 .. max_particles) 138 { 139 Vec3* vel = &state.vel[i]; 140 Vec3* pos = &state.pos[i]; 141 vel.y -= 1.0 * frame_time; 142 *pos = Vec3.add(*pos, Vec3.mul(*vel, frame_time)); 143 if (pos.y < -2.0) 144 { 145 pos.y = -1.8; 146 vel.y = -vel.y; 147 *vel = Vec3.mul(*vel, 0.8); 148 } 149 } 150 151 sg.Range ub = {ptr: &state.pos, size: state.pos.sizeof}; 152 sg.updateBuffer(state.bind.vertex_buffers[1], ub); 153 state.ry += 1.0 * frame_time; 154 155 shd.VsParams vsParams = computeMvp(1.0, state.ry); 156 157 sg.Pass pass = {action: state.passAction, swapchain: sglue.swapchain() 158 }; 159 sg.beginPass(pass); 160 sg.applyPipeline(state.pip); 161 sg.applyBindings(state.bind); 162 sg.Range r_vsparams = {ptr: &vsParams, size: vsParams.sizeof}; 163 sg.applyUniforms(shd.UB_VS_PARAMS, r_vsparams); 164 sg.draw(0, 24, state.cur_num_particles); 165 sg.endPass(); 166 sg.commit(); 167 } 168 169 void cleanup() 170 { 171 sg.shutdown(); 172 } 173 174 shd.VsParams computeMvp(float rx, float ry) @nogc nothrow 175 { 176 immutable proj = Mat4.perspective(60.0, app.widthf() / app.heightf(), 0.01, 50.0); 177 immutable rxm = Mat4.rotate(rx, Vec3(1.0, 0.0, 0.0)); 178 immutable rym = Mat4.rotate(ry, Vec3(0.0, 1.0, 0.0)); 179 immutable model = Mat4.mul(rxm, rym); 180 immutable view_proj = Mat4.mul(proj, state.view()); 181 // dfmt off 182 shd.VsParams mvp = { mvp: Mat4.mul(view_proj, model) }; 183 // dfmt on 184 return mvp; 185 } 186 187 void main() 188 { 189 // dfmt off 190 app.Desc runner = { 191 window_title: "instancing.d", 192 init_cb: &init, 193 frame_cb: &frame, 194 cleanup_cb: &cleanup, 195 width: 800, 196 height: 600, 197 sample_count: 4, 198 icon: {sokol_default: true}, 199 logger: {func: &log.func} 200 }; 201 // dfmt on 202 app.run(runner); 203 } 204 205 uint xorshift32() 206 { 207 static uint X = 0x12345678; 208 209 uint x = X; 210 x ^= x << 13; 211 x ^= x >> 17; 212 x ^= x << 5; 213 X = x; 214 return x; 215 } 216 217 float rand(inout(float) min, inout(float) max) 218 { 219 return (cast(float)(xorshift32 & 0xFFFF) / 0x10000) * (max - min) + min; 220 }