载入中...
搜索中...
未找到
WebGpuGpgpu.cpp
浏览该文件的文档.
2
3#include "common/Exception.h"
4#include "common/Module.h"
5#include "data/ByteData.h"
6#include "graphics/Graphics.h"
8
9#if defined(__EMSCRIPTEN__)
10#include <emscripten/emscripten.h>
11#endif
12
13#include <cstring>
14#include <vector>
15
16namespace eve::gpgpu {
17
18namespace {
19
20graphics::webgpu::Graphics *requireWebGpuGraphics() {
21 auto *gfx = eve::ModuleManager::getInstance<eve::graphics::Graphics>("Graphics");
22 if (!gfx) gfx = eve::graphics::Graphics::create();
23 auto *wgg = dynamic_cast<graphics::webgpu::Graphics *>(gfx);
24 if (!wgg) throw Exception("Gpgpu: requires WebGPU Graphics backend");
25 if (!wgg->getDevice())
26 throw Exception("Gpgpu: Graphics device not initialized (create a window first)");
27 return wgg;
28}
29
30wgpu::Device &gpuDevice() { return requireWebGpuGraphics()->getDevice(); }
31wgpu::Queue &gpuQueue() { return requireWebGpuGraphics()->getQueue(); }
32wgpu::Instance &gpuInstance() { return requireWebGpuGraphics()->getInstance(); }
33
35WGPUStringView sv(const char *s) { return WGPUStringView{s, s ? std::strlen(s) : 0}; }
36
37} // namespace
38
40 try {
41 auto *gfx = eve::ModuleManager::getInstance<eve::graphics::Graphics>("Graphics");
42 if (!gfx) return false;
43 auto *wgg = dynamic_cast<graphics::webgpu::Graphics *>(gfx);
44 if (!wgg) return false;
45 return bool(wgg->getDevice());
46 } catch (...) {
47 return false;
48 }
49}
50
52
53void WebGpuComputeShader::bindBuffer(int binding, GpuBuffer *buffer) {
54 if (binding < 0 || binding >= kMaxBindings) return;
55 bindings_[size_t(binding)] = buffer;
56}
57
59 if (binding < 0 || binding >= kMaxBindings) return nullptr;
60 return bindings_[size_t(binding)];
61}
62
63void WebGpuComputeShader::setFloat(int index, float value) {
64 if (index < 0 || index >= kMaxFloats) return;
65 push_[size_t(index)] = value;
66}
67
68float WebGpuComputeShader::getFloat(int index) const {
69 if (index < 0 || index >= kMaxFloats) return 0.f;
70 return push_[size_t(index)];
71}
72
74 bindings_.fill(nullptr);
75}
76
78 auto &device = gpuDevice();
79 auto *shader = new WebGpuComputeShader();
80
81 // Bind group layout: 8 storage buffers (bindings 0..7) + push UBO (binding 8).
82 WGPUBindGroupLayoutEntry entries[ComputeShader::kMaxBindings + 1]{};
83 for (int i = 0; i < ComputeShader::kMaxBindings; ++i) {
84 entries[size_t(i)].binding = uint32_t(i);
85 entries[size_t(i)].visibility = WGPUShaderStage_Compute;
86 entries[size_t(i)].buffer.type = WGPUBufferBindingType_Storage;
87 entries[size_t(i)].buffer.hasDynamicOffset = false;
88 entries[size_t(i)].buffer.minBindingSize = 0;
89 }
91 entries[ComputeShader::kMaxBindings].visibility = WGPUShaderStage_Compute;
92 entries[ComputeShader::kMaxBindings].buffer.type = WGPUBufferBindingType_Uniform;
93 entries[ComputeShader::kMaxBindings].buffer.hasDynamicOffset = true;
95
96 WGPUBindGroupLayoutDescriptor bglDesc{};
97 bglDesc.label = sv("eve_compute");
98 bglDesc.entryCount = ComputeShader::kMaxBindings + 1;
99 bglDesc.entries = entries;
100 shader->setLayout = device.CreateBindGroupLayout(reinterpret_cast<const wgpu::BindGroupLayoutDescriptor*>(&bglDesc));
101
102 WGPUBindGroupLayout bgl = shader->setLayout.Get();
103 WGPUPipelineLayoutDescriptor plDesc{};
104 plDesc.label = sv("eve_compute_layout");
105 plDesc.bindGroupLayoutCount = 1;
106 plDesc.bindGroupLayouts = &bgl;
107 shader->pipelineLayout = device.CreatePipelineLayout(reinterpret_cast<const wgpu::PipelineLayoutDescriptor*>(&plDesc));
108
109 WGPUShaderSourceWGSL wd{};
110 wd.chain.sType = WGPUSType_ShaderSourceWGSL;
111 wd.code = sv(wgsl.c_str());
112 WGPUShaderModuleDescriptor md{};
113 md.label = sv("eve_compute_module");
114 md.nextInChain = &wd.chain;
115 wgpu::ShaderModule module = device.CreateShaderModule(reinterpret_cast<const wgpu::ShaderModuleDescriptor*>(&md));
116
117 WGPUComputePipelineDescriptor pd{};
118 pd.label = sv("eve_compute_pipeline");
119 pd.layout = shader->pipelineLayout.Get();
120 pd.compute.module = module.Get();
121 pd.compute.entryPoint = sv("main");
122 shader->pipeline = device.CreateComputePipeline(reinterpret_cast<const wgpu::ComputePipelineDescriptor*>(&pd));
123
124 // Push-constant UBO (128 bytes, 256-aligned for dynamic offsets).
125 WGPUBufferDescriptor bd{};
126 bd.label = sv("eve_compute_push");
128 bd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Uniform;
129 bd.mappedAtCreation = false;
130 shader->pushUbo = device.CreateBuffer(reinterpret_cast<const wgpu::BufferDescriptor*>(&bd));
131 shader->ready = true;
132 return shader;
133}
134
135WebGpuComputeShader *webgpuNewShaderFromSpirv(const std::vector<uint32_t> &spv) {
136 (void)spv;
137 throw Exception("Gpgpu.newShaderFromSpirv: SPIR-V compute shaders are not supported on the "
138 "WebGPU backend; use WGSL source instead.");
139}
140
141WebGpuGpuBuffer *webgpuNewBuffer(int byteSize, const std::string &usage) {
142 if (byteSize <= 0) throw Exception("Gpgpu.newBuffer: byteSize must be > 0");
143 auto &device = gpuDevice();
144 bool staging = usage == "staging";
145 WGPUBufferUsage bufUsage = staging ? (WGPUBufferUsage_CopySrc | WGPUBufferUsage_MapRead)
146 : (WGPUBufferUsage_Storage | WGPUBufferUsage_CopyDst |
147 WGPUBufferUsage_CopySrc | WGPUBufferUsage_Vertex);
148 WGPUBufferDescriptor bd{};
149 bd.label = sv("eve_compute_buffer");
150 bd.size = uint64_t(byteSize);
151 bd.usage = bufUsage;
152 bd.mappedAtCreation = false;
153 auto *b = new WebGpuGpuBuffer();
154 b->buffer = device.CreateBuffer(reinterpret_cast<const wgpu::BufferDescriptor*>(&bd));
155 b->size_ = uint64_t(byteSize);
156 b->usage_ = usage;
157 return b;
158}
159
160namespace {
161
162bool readBufferToCpu(wgpu::Buffer src, uint64_t srcOffset, void *dst, uint64_t nbytes) {
163 auto &device = gpuDevice();
164 auto &queue = gpuQueue();
165 if (!src || nbytes == 0) return false;
166
167 WGPUBufferDescriptor bd{};
168 bd.label = sv("eve_compute_readback");
169 bd.size = nbytes;
170 bd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_MapRead;
171 bd.mappedAtCreation = false;
172 wgpu::Buffer staging = device.CreateBuffer(reinterpret_cast<const wgpu::BufferDescriptor*>(&bd));
173
174 wgpu::CommandEncoder enc = device.CreateCommandEncoder();
175 enc.CopyBufferToBuffer(src, srcOffset, staging, 0, nbytes);
176 wgpu::CommandBuffer cmd = enc.Finish();
177 queue.Submit(1, &cmd);
178
179 bool mapped = false;
180 WGPUBufferMapCallbackInfo cbInfo{};
181 cbInfo.mode = WGPUCallbackMode_AllowProcessEvents;
182 cbInfo.callback = [](WGPUMapAsyncStatus status, WGPUStringView /*message*/, void *userdata1,
183 void * /*userdata2*/) {
184 bool *ok = static_cast<bool *>(userdata1);
185 *ok = (status == WGPUMapAsyncStatus_Success);
186 };
187 cbInfo.userdata1 = &mapped;
188 wgpuBufferMapAsync(staging.Get(), WGPUMapMode_Read, 0, nbytes, cbInfo);
189
190 int guard = 0;
191 while (!mapped && guard < 2000) {
192#if defined(__EMSCRIPTEN__)
193 emscripten_sleep(0);
194#endif
195 wgpuInstanceProcessEvents(gpuInstance().Get());
196 ++guard;
197 }
198 if (!mapped) return false;
199 const uint8_t *data = static_cast<const uint8_t *>(staging.GetConstMappedRange(0, nbytes));
200 if (!data) return false;
201 std::memcpy(dst, data, size_t(nbytes));
202 staging.Unmap();
203 return true;
204}
205
206} // namespace
207
208void webgpuDispatch(ComputeShader *shader, int groupsX, int groupsY, int groupsZ) {
209 auto *ws = dynamic_cast<WebGpuComputeShader *>(shader);
210 if (!ws || !ws->ready || !ws->pipeline) return;
211 if (groupsX <= 0) groupsX = 1;
212 if (groupsY <= 0) groupsY = 1;
213 if (groupsZ <= 0) groupsZ = 1;
214
215 auto &device = gpuDevice();
216 auto &queue = gpuQueue();
217
218 // Push constants -> uniform buffer (dynamic offset 0).
219 queue.WriteBuffer(ws->pushUbo, 0, shader->pushConstantData(), ComputeShader::kPushConstantBytes);
220
221 // Build a bind group for the current storage-buffer bindings.
222 WGPUBindGroupEntry entries[ComputeShader::kMaxBindings + 1]{};
223 WebGpuGpuBuffer *dummy = webgpuNewBuffer(4, "storage");
224 for (int i = 0; i < ComputeShader::kMaxBindings; ++i) {
225 auto *vb = dynamic_cast<WebGpuGpuBuffer *>(ws->getBoundBuffer(i));
226 entries[size_t(i)].binding = uint32_t(i);
227 entries[size_t(i)].buffer = (vb && vb->buffer) ? vb->buffer.Get() : dummy->buffer.Get();
228 entries[size_t(i)].size = (vb && vb->buffer) ? vb->size_ : 4;
229 }
230 entries[ComputeShader::kMaxBindings].binding = uint32_t(ComputeShader::kMaxBindings);
231 entries[ComputeShader::kMaxBindings].buffer = ws->pushUbo.Get();
233 WGPUBindGroupDescriptor bgDesc{};
234 bgDesc.layout = ws->setLayout.Get();
235 bgDesc.entryCount = ComputeShader::kMaxBindings + 1;
236 bgDesc.entries = entries;
237 wgpu::BindGroup bg = device.CreateBindGroup(reinterpret_cast<const wgpu::BindGroupDescriptor*>(&bgDesc));
238 delete dummy;
239
240 wgpu::CommandEncoder enc = device.CreateCommandEncoder();
241 WGPUComputePassDescriptor cpDesc{};
242 wgpu::ComputePassEncoder pass = enc.BeginComputePass(reinterpret_cast<const wgpu::ComputePassDescriptor*>(&cpDesc));
243 pass.SetPipeline(ws->pipeline);
244 uint32_t offsets[1] = {0};
245 pass.SetBindGroup(0, bg, 1, offsets);
246 pass.DispatchWorkgroups(uint32_t(groupsX), uint32_t(groupsY), uint32_t(groupsZ));
247 pass.End();
248 wgpu::CommandBuffer cmd = enc.Finish();
249 queue.Submit(1, &cmd);
250}
251
252// ---------------------------------------------------------------------------
253// WebGpuGpuBuffer
254// ---------------------------------------------------------------------------
255
257
258void WebGpuGpuBuffer::uploadBytes(const void *src, uint64_t nbytes, uint64_t dstOffset) {
259 if (!buffer || !src || nbytes == 0) return;
260 if (dstOffset + nbytes > size_)
261 throw Exception("GpuBuffer.write: out of range");
262 gpuQueue().WriteBuffer(buffer, dstOffset, src, nbytes);
263}
264
265void WebGpuGpuBuffer::downloadBytes(void *dst, uint64_t nbytes, uint64_t srcOffset) const {
266 if (!buffer || !dst || nbytes == 0) return;
267 if (srcOffset + nbytes > size_)
268 throw Exception("GpuBuffer.read: out of range");
269 readBufferToCpu(buffer, srcOffset, dst, nbytes);
270}
271
272void WebGpuGpuBuffer::writeData(data::ByteData *data, int dstOffset) {
273 if (!data) return;
274 uploadBytes(data->getData(), data->getSize(), uint64_t(dstOffset < 0 ? 0 : dstOffset));
275}
276
277data::ByteData *WebGpuGpuBuffer::readData(int srcOffset, int size) {
278 const int off = srcOffset < 0 ? 0 : srcOffset;
279 int nbytes = size;
280 if (nbytes < 0) nbytes = int(size_) - off;
281 if (nbytes <= 0) return new data::ByteData(size_t(0));
282 auto *out = new data::ByteData(size_t(nbytes));
283 downloadBytes(out->getData(), uint64_t(nbytes), uint64_t(off));
284 return out;
285}
286
287void WebGpuGpuBuffer::writeFloat32(int floatIndex, float value) {
288 if (floatIndex < 0) return;
289 uploadBytes(&value, sizeof(float), uint64_t(floatIndex) * sizeof(float));
290}
291
292float WebGpuGpuBuffer::readFloat32(int floatIndex) {
293 if (floatIndex < 0) return 0.f;
294 float v = 0.f;
295 downloadBytes(&v, sizeof(float), uint64_t(floatIndex) * sizeof(float));
296 return v;
297}
298
299void WebGpuGpuBuffer::writeFloat32s(const float *data, int count, int startIndex) {
300 if (!data || count <= 0 || startIndex < 0) return;
301 uploadBytes(data, uint64_t(count) * sizeof(float), uint64_t(startIndex) * sizeof(float));
302}
303
304void WebGpuGpuBuffer::readFloat32s(float *out, int count, int startIndex) const {
305 if (!out || count <= 0 || startIndex < 0) return;
306 downloadBytes(out, uint64_t(count) * sizeof(float), uint64_t(startIndex) * sizeof(float));
307}
308
310 if (size_ < sizeof(float)) return;
311 const size_t count = size_t(size_ / sizeof(float));
312 std::vector<float> tmp(count, value);
313 uploadBytes(tmp.data(), count * sizeof(float), 0);
314}
315
316} // namespace eve::gpgpu
std::string value
vkb::Device & device
JobStatus status
uint32_t b
Shader * shader
Light2D::Data * data
int v
uint32_t s
Definition Weather.cpp:28
In-memory byte buffer implementing eve::Data (ref-counted).
Definition ByteData.h:11
Backend-agnostic compute program. Bind storage buffers then dispatch via Gpgpu::dispatch....
static constexpr int kMaxBindings
static constexpr int kMaxFloats
std::array< float, kMaxFloats > push_
static constexpr uint32_t kPushConstantBytes
Backend-agnostic GPU buffer for compute (storage) or CPU staging transfers. Squirrel-owned; derived c...
Definition GpuBuffer.h:16
Compute program for the WebGPU backend. Accepts WGSL source; GLSL/SPIR-V input is rejected (browsers ...
Definition WebGpuGpgpu.h:20
float getFloat(int index) const override
void bindBuffer(int binding, GpuBuffer *buffer) override
Bind a storage buffer to set=0 binding. binding in [0, kMaxBindings).
void setFloat(int index, float value) override
GpuBuffer * getBoundBuffer(int binding) const override
Storage / staging buffer for the WebGPU backend.
Definition WebGpuGpgpu.h:44
float readFloat32(int floatIndex) override
void downloadBytes(void *dst, uint64_t nbytes, uint64_t srcOffset=0) const override
data::ByteData * readData(int srcOffset=0, int size=-1) override
void writeFloat32(int floatIndex, float value) override
void readFloat32s(float *out, int count, int startIndex=0) const override
void uploadBytes(const void *src, uint64_t nbytes, uint64_t dstOffset=0) override
void writeFloat32s(const float *data, int count, int startIndex=0) override
Bulk float upload/download (one transfer). startIndex is in floats.
void writeData(data::ByteData *data, int dstOffset=0) override
void fillFloat32(float value) override
bool webgpuGpgpuReady()
void webgpuDispatch(ComputeShader *shader, int groupsX, int groupsY, int groupsZ)
WebGpuGpuBuffer * webgpuNewBuffer(int byteSize, const std::string &usage)
WebGpuComputeShader * webgpuNewShaderFromSpirv(const std::vector< uint32_t > &spv)
WebGpuComputeShader * webgpuNewShaderFromWgsl(const std::string &wgsl)