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 }