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 }