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