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 }