1 // Copyright 2017 The Dawn Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 #include "SampleUtils.h"
16
17 #include "utils/ComboRenderPipelineDescriptor.h"
18 #include "utils/ScopedAutoreleasePool.h"
19 #include "utils/SystemUtils.h"
20 #include "utils/WGPUHelpers.h"
21
22 #include <array>
23 #include <cstring>
24 #include <random>
25
26 #include <glm/glm.hpp>
27
28 wgpu::Device device;
29 wgpu::Queue queue;
30 wgpu::SwapChain swapchain;
31 wgpu::TextureView depthStencilView;
32
33 wgpu::Buffer modelBuffer;
34 std::array<wgpu::Buffer, 2> particleBuffers;
35
36 wgpu::RenderPipeline renderPipeline;
37
38 wgpu::Buffer updateParams;
39 wgpu::ComputePipeline updatePipeline;
40 std::array<wgpu::BindGroup, 2> updateBGs;
41
42 size_t pingpong = 0;
43
44 static const uint32_t kNumParticles = 1000;
45
46 struct Particle {
47 glm::vec2 pos;
48 glm::vec2 vel;
49 };
50
51 struct SimParams {
52 float deltaT;
53 float rule1Distance;
54 float rule2Distance;
55 float rule3Distance;
56 float rule1Scale;
57 float rule2Scale;
58 float rule3Scale;
59 int particleCount;
60 };
61
initBuffers()62 void initBuffers() {
63 glm::vec2 model[3] = {
64 {-0.01, -0.02},
65 {0.01, -0.02},
66 {0.00, 0.02},
67 };
68 modelBuffer =
69 utils::CreateBufferFromData(device, model, sizeof(model), wgpu::BufferUsage::Vertex);
70
71 SimParams params = {0.04f, 0.1f, 0.025f, 0.025f, 0.02f, 0.05f, 0.005f, kNumParticles};
72 updateParams =
73 utils::CreateBufferFromData(device, ¶ms, sizeof(params), wgpu::BufferUsage::Uniform);
74
75 std::vector<Particle> initialParticles(kNumParticles);
76 {
77 std::mt19937 generator;
78 std::uniform_real_distribution<float> dist(-1.0f, 1.0f);
79 for (auto& p : initialParticles) {
80 p.pos = glm::vec2(dist(generator), dist(generator));
81 p.vel = glm::vec2(dist(generator), dist(generator)) * 0.1f;
82 }
83 }
84
85 for (size_t i = 0; i < 2; i++) {
86 wgpu::BufferDescriptor descriptor;
87 descriptor.size = sizeof(Particle) * kNumParticles;
88 descriptor.usage =
89 wgpu::BufferUsage::CopyDst | wgpu::BufferUsage::Vertex | wgpu::BufferUsage::Storage;
90 particleBuffers[i] = device.CreateBuffer(&descriptor);
91
92 queue.WriteBuffer(particleBuffers[i], 0,
93 reinterpret_cast<uint8_t*>(initialParticles.data()),
94 sizeof(Particle) * kNumParticles);
95 }
96 }
97
initRender()98 void initRender() {
99 wgpu::ShaderModule vsModule = utils::CreateShaderModule(device, R"(
100 struct VertexIn {
101 [[location(0)]] a_particlePos : vec2<f32>;
102 [[location(1)]] a_particleVel : vec2<f32>;
103 [[location(2)]] a_pos : vec2<f32>;
104 };
105
106 [[stage(vertex)]]
107 fn main(input : VertexIn) -> [[builtin(position)]] vec4<f32> {
108 var angle : f32 = -atan2(input.a_particleVel.x, input.a_particleVel.y);
109 var pos : vec2<f32> = vec2<f32>(
110 (input.a_pos.x * cos(angle)) - (input.a_pos.y * sin(angle)),
111 (input.a_pos.x * sin(angle)) + (input.a_pos.y * cos(angle)));
112 return vec4<f32>(pos + input.a_particlePos, 0.0, 1.0);
113 }
114 )");
115
116 wgpu::ShaderModule fsModule = utils::CreateShaderModule(device, R"(
117 [[stage(fragment)]]
118 fn main() -> [[location(0)]] vec4<f32> {
119 return vec4<f32>(1.0, 1.0, 1.0, 1.0);
120 }
121 )");
122
123 depthStencilView = CreateDefaultDepthStencilView(device);
124
125 utils::ComboRenderPipelineDescriptor descriptor;
126
127 descriptor.vertex.module = vsModule;
128 descriptor.vertex.bufferCount = 2;
129 descriptor.cBuffers[0].arrayStride = sizeof(Particle);
130 descriptor.cBuffers[0].stepMode = wgpu::VertexStepMode::Instance;
131 descriptor.cBuffers[0].attributeCount = 2;
132 descriptor.cAttributes[0].offset = offsetof(Particle, pos);
133 descriptor.cAttributes[0].format = wgpu::VertexFormat::Float32x2;
134 descriptor.cAttributes[1].shaderLocation = 1;
135 descriptor.cAttributes[1].offset = offsetof(Particle, vel);
136 descriptor.cAttributes[1].format = wgpu::VertexFormat::Float32x2;
137 descriptor.cBuffers[1].arrayStride = sizeof(glm::vec2);
138 descriptor.cBuffers[1].attributeCount = 1;
139 descriptor.cBuffers[1].attributes = &descriptor.cAttributes[2];
140 descriptor.cAttributes[2].shaderLocation = 2;
141 descriptor.cAttributes[2].format = wgpu::VertexFormat::Float32x2;
142
143 descriptor.cFragment.module = fsModule;
144 descriptor.EnableDepthStencil(wgpu::TextureFormat::Depth24PlusStencil8);
145 descriptor.cTargets[0].format = GetPreferredSwapChainTextureFormat();
146
147 renderPipeline = device.CreateRenderPipeline(&descriptor);
148 }
149
initSim()150 void initSim() {
151 wgpu::ShaderModule module = utils::CreateShaderModule(device, R"(
152 struct Particle {
153 pos : vec2<f32>;
154 vel : vec2<f32>;
155 };
156 [[block]] struct SimParams {
157 deltaT : f32;
158 rule1Distance : f32;
159 rule2Distance : f32;
160 rule3Distance : f32;
161 rule1Scale : f32;
162 rule2Scale : f32;
163 rule3Scale : f32;
164 particleCount : u32;
165 };
166 [[block]] struct Particles {
167 particles : array<Particle>;
168 };
169 [[binding(0), group(0)]] var<uniform> params : SimParams;
170 [[binding(1), group(0)]] var<storage, read> particlesA : Particles;
171 [[binding(2), group(0)]] var<storage, read_write> particlesB : Particles;
172
173 // https://github.com/austinEng/Project6-Vulkan-Flocking/blob/master/data/shaders/computeparticles/particle.comp
174 [[stage(compute), workgroup_size(1)]]
175 fn main([[builtin(global_invocation_id)]] GlobalInvocationID : vec3<u32>) {
176 var index : u32 = GlobalInvocationID.x;
177 if (index >= params.particleCount) {
178 return;
179 }
180 var vPos : vec2<f32> = particlesA.particles[index].pos;
181 var vVel : vec2<f32> = particlesA.particles[index].vel;
182 var cMass : vec2<f32> = vec2<f32>(0.0, 0.0);
183 var cVel : vec2<f32> = vec2<f32>(0.0, 0.0);
184 var colVel : vec2<f32> = vec2<f32>(0.0, 0.0);
185 var cMassCount : u32 = 0u;
186 var cVelCount : u32 = 0u;
187 var pos : vec2<f32>;
188 var vel : vec2<f32>;
189
190 for (var i : u32 = 0u; i < params.particleCount; i = i + 1u) {
191 if (i == index) {
192 continue;
193 }
194
195 pos = particlesA.particles[i].pos.xy;
196 vel = particlesA.particles[i].vel.xy;
197 if (distance(pos, vPos) < params.rule1Distance) {
198 cMass = cMass + pos;
199 cMassCount = cMassCount + 1u;
200 }
201 if (distance(pos, vPos) < params.rule2Distance) {
202 colVel = colVel - (pos - vPos);
203 }
204 if (distance(pos, vPos) < params.rule3Distance) {
205 cVel = cVel + vel;
206 cVelCount = cVelCount + 1u;
207 }
208 }
209
210 if (cMassCount > 0u) {
211 cMass = (cMass / vec2<f32>(f32(cMassCount), f32(cMassCount))) - vPos;
212 }
213
214 if (cVelCount > 0u) {
215 cVel = cVel / vec2<f32>(f32(cVelCount), f32(cVelCount));
216 }
217 vVel = vVel + (cMass * params.rule1Scale) + (colVel * params.rule2Scale) +
218 (cVel * params.rule3Scale);
219
220 // clamp velocity for a more pleasing simulation
221 vVel = normalize(vVel) * clamp(length(vVel), 0.0, 0.1);
222 // kinematic update
223 vPos = vPos + (vVel * params.deltaT);
224
225 // Wrap around boundary
226 if (vPos.x < -1.0) {
227 vPos.x = 1.0;
228 }
229 if (vPos.x > 1.0) {
230 vPos.x = -1.0;
231 }
232 if (vPos.y < -1.0) {
233 vPos.y = 1.0;
234 }
235 if (vPos.y > 1.0) {
236 vPos.y = -1.0;
237 }
238
239 // Write back
240 particlesB.particles[index].pos = vPos;
241 particlesB.particles[index].vel = vVel;
242 return;
243 }
244 )");
245
246 auto bgl = utils::MakeBindGroupLayout(
247 device, {
248 {0, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Uniform},
249 {1, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage},
250 {2, wgpu::ShaderStage::Compute, wgpu::BufferBindingType::Storage},
251 });
252
253 wgpu::PipelineLayout pl = utils::MakeBasicPipelineLayout(device, &bgl);
254
255 wgpu::ComputePipelineDescriptor csDesc;
256 csDesc.layout = pl;
257 csDesc.compute.module = module;
258 csDesc.compute.entryPoint = "main";
259 updatePipeline = device.CreateComputePipeline(&csDesc);
260
261 for (uint32_t i = 0; i < 2; ++i) {
262 updateBGs[i] = utils::MakeBindGroup(
263 device, bgl,
264 {
265 {0, updateParams, 0, sizeof(SimParams)},
266 {1, particleBuffers[i], 0, kNumParticles * sizeof(Particle)},
267 {2, particleBuffers[(i + 1) % 2], 0, kNumParticles * sizeof(Particle)},
268 });
269 }
270 }
271
createCommandBuffer(const wgpu::TextureView backbufferView,size_t i)272 wgpu::CommandBuffer createCommandBuffer(const wgpu::TextureView backbufferView, size_t i) {
273 auto& bufferDst = particleBuffers[(i + 1) % 2];
274 wgpu::CommandEncoder encoder = device.CreateCommandEncoder();
275
276 {
277 wgpu::ComputePassEncoder pass = encoder.BeginComputePass();
278 pass.SetPipeline(updatePipeline);
279 pass.SetBindGroup(0, updateBGs[i]);
280 pass.Dispatch(kNumParticles);
281 pass.EndPass();
282 }
283
284 {
285 utils::ComboRenderPassDescriptor renderPass({backbufferView}, depthStencilView);
286 wgpu::RenderPassEncoder pass = encoder.BeginRenderPass(&renderPass);
287 pass.SetPipeline(renderPipeline);
288 pass.SetVertexBuffer(0, bufferDst);
289 pass.SetVertexBuffer(1, modelBuffer);
290 pass.Draw(3, kNumParticles);
291 pass.EndPass();
292 }
293
294 return encoder.Finish();
295 }
296
init()297 void init() {
298 device = CreateCppDawnDevice();
299
300 queue = device.GetQueue();
301 swapchain = GetSwapChain(device);
302 swapchain.Configure(GetPreferredSwapChainTextureFormat(), wgpu::TextureUsage::RenderAttachment,
303 640, 480);
304
305 initBuffers();
306 initRender();
307 initSim();
308 }
309
frame()310 void frame() {
311 wgpu::TextureView backbufferView = swapchain.GetCurrentTextureView();
312
313 wgpu::CommandBuffer commandBuffer = createCommandBuffer(backbufferView, pingpong);
314 queue.Submit(1, &commandBuffer);
315 swapchain.Present();
316 DoFlush();
317
318 pingpong = (pingpong + 1) % 2;
319 }
320
main(int argc,const char * argv[])321 int main(int argc, const char* argv[]) {
322 if (!InitSample(argc, argv)) {
323 return 1;
324 }
325 init();
326
327 while (!ShouldQuit()) {
328 utils::ScopedAutoreleasePool pool;
329 frame();
330 utils::USleep(16000);
331 }
332
333 // TODO release stuff
334 }
335