9#if defined(__EMSCRIPTEN__)
10#include <emscripten/emscripten.h>
25constexpr uint64_t kDummyBindingStride = 256;
27graphics::webgpu::Graphics *requireWebGpuGraphics() {
28 auto *gfx = eve::ModuleManager::getInstance<eve::graphics::Graphics>(
"Graphics");
29 if (!gfx) gfx = eve::graphics::Graphics::create();
30 auto *wgg =
dynamic_cast<graphics::webgpu::Graphics *
>(gfx);
31 if (!wgg)
throw Exception(
"Gpgpu: requires WebGPU Graphics backend");
32 if (!wgg->getDevice())
33 throw Exception(
"Gpgpu: Graphics device not initialized (create a window first)");
37wgpu::Device &gpuDevice() {
return requireWebGpuGraphics()->getDevice(); }
38wgpu::Queue &gpuQueue() {
return requireWebGpuGraphics()->getQueue(); }
39wgpu::Instance &gpuInstance() {
return requireWebGpuGraphics()->getInstance(); }
42WGPUStringView
sv(
const char *
s) {
return WGPUStringView{
s,
s ? std::strlen(
s) : 0}; }
48 auto *gfx = eve::ModuleManager::getInstance<eve::graphics::Graphics>(
"Graphics");
49 if (!gfx)
return false;
51 if (!wgg)
return false;
52 return bool(wgg->getDevice());
66 if (binding < 0 || binding >=
kMaxBindings)
return nullptr;
67 return bindings_[size_t(
binding)];
76 if (index < 0 || index >=
kMaxFloats)
return 0.f;
81 bindings_.fill(
nullptr);
85 auto &
device = gpuDevice();
91 entries[size_t(i)].binding = uint32_t(i);
92 entries[size_t(i)].visibility = WGPUShaderStage_Compute;
93 entries[size_t(i)].buffer.type = WGPUBufferBindingType_Storage;
94 entries[size_t(i)].buffer.hasDynamicOffset =
false;
95 entries[size_t(i)].buffer.minBindingSize = 0;
103 WGPUBindGroupLayoutDescriptor bglDesc{};
104 bglDesc.label = sv(
"eve_compute");
108 device.CreateBindGroupLayout(
reinterpret_cast<const wgpu::BindGroupLayoutDescriptor*
>(&bglDesc));
110 WGPUBindGroupLayout bgl =
shader->setLayout.Get();
111 WGPUPipelineLayoutDescriptor plDesc{};
112 plDesc.label = sv(
"eve_compute_layout");
113 plDesc.bindGroupLayoutCount = 1;
114 plDesc.bindGroupLayouts = &bgl;
116 device.CreatePipelineLayout(
reinterpret_cast<const wgpu::PipelineLayoutDescriptor*
>(&plDesc));
118 WGPUShaderSourceWGSL wd{};
119 wd.chain.sType = WGPUSType_ShaderSourceWGSL;
120 wd.code = sv(wgsl.c_str());
121 WGPUShaderModuleDescriptor md{};
122 md.label = sv(
"eve_compute_module");
123 md.nextInChain = &wd.chain;
124 wgpu::ShaderModule
module = device.CreateShaderModule(reinterpret_cast<const wgpu::ShaderModuleDescriptor*>(&md));
126 WGPUComputePipelineDescriptor pd{};
127 pd.label = sv(
"eve_compute_pipeline");
128 pd.layout =
shader->pipelineLayout.Get();
129 pd.compute.module =
module.Get();
130 pd.compute.entryPoint = sv(
"main");
131 shader->pipeline =
device.CreateComputePipeline(
reinterpret_cast<const wgpu::ComputePipelineDescriptor*
>(&pd));
134 WGPUBufferDescriptor bd{};
135 bd.label = sv(
"eve_compute_push");
137 bd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_Uniform;
138 bd.mappedAtCreation =
false;
139 shader->pushUbo =
device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&bd));
146 throw Exception(
"Gpgpu.newShaderFromSpirv: SPIR-V compute shaders are not supported on the "
147 "WebGPU backend; use WGSL source instead.");
151 if (byteSize <= 0)
throw Exception(
"Gpgpu.newBuffer: byteSize must be > 0");
152 auto &
device = gpuDevice();
153 bool staging =
usage ==
"staging";
154 WGPUBufferUsage bufUsage = staging ? (WGPUBufferUsage_CopyDst | WGPUBufferUsage_MapRead)
155 : (WGPUBufferUsage_Storage | WGPUBufferUsage_CopyDst |
156 WGPUBufferUsage_CopySrc | WGPUBufferUsage_Vertex);
157 WGPUBufferDescriptor bd{};
158 bd.label = sv(
"eve_compute_buffer");
159 bd.size = uint64_t(byteSize);
161 bd.mappedAtCreation =
false;
163 b->buffer =
device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&bd));
164 b->size_ = uint64_t(byteSize);
171bool mapBufferToCpu(wgpu::Buffer
buffer, uint64_t bufferSize, uint64_t srcOffset,
void *dst,
173 if (!
buffer || !dst || nbytes == 0 || srcOffset + nbytes > bufferSize)
return false;
176 bool success =
false;
178 WGPUBufferMapCallbackInfo cbInfo{};
179#if defined(__EMSCRIPTEN__)
180 cbInfo.mode = WGPUCallbackMode_AllowProcessEvents;
182 cbInfo.mode = WGPUCallbackMode_WaitAnyOnly;
184 cbInfo.callback = [](WGPUMapAsyncStatus
status, WGPUStringView,
void *userdata1,
void *) {
185 auto *
state =
static_cast<MapState *
>(userdata1);
186 state->success =
status == WGPUMapAsyncStatus_Success;
189 cbInfo.userdata1 = ↦
190 WGPUFuture
future = wgpuBufferMapAsync(
buffer.Get(), WGPUMapMode_Read, 0, bufferSize, cbInfo);
191#if defined(__EMSCRIPTEN__)
193 while (!map.done && guard++ < 2000) {
195 wgpuInstanceProcessEvents(gpuInstance().Get());
198 WGPUFutureWaitInfo waitInfo{};
200 (void)wgpuInstanceWaitAny(gpuInstance().Get(), 1, &waitInfo, UINT64_MAX);
202 if (!map.success)
return false;
203 const auto *data =
static_cast<const uint8_t *
>(
buffer.GetConstMappedRange(0, bufferSize));
204 if (!data)
return false;
205 std::memcpy(dst, data + srcOffset,
size_t(nbytes));
210bool readBufferToCpu(wgpu::Buffer src, uint64_t srcOffset,
void *dst, uint64_t nbytes) {
211 auto &
device = gpuDevice();
212 auto &queue = gpuQueue();
213 if (!src || nbytes == 0)
return false;
215 WGPUBufferDescriptor bd{};
216 bd.label =
sv(
"eve_compute_readback");
218 bd.usage = WGPUBufferUsage_CopyDst | WGPUBufferUsage_MapRead;
219 bd.mappedAtCreation =
false;
220 wgpu::Buffer staging =
device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&bd));
222 wgpu::CommandEncoder enc =
device.CreateCommandEncoder();
223 enc.CopyBufferToBuffer(src, srcOffset, staging, 0, nbytes);
224 wgpu::CommandBuffer cmd = enc.Finish();
225 queue.Submit(1, &cmd);
226 return mapBufferToCpu(staging, nbytes, 0, dst, nbytes);
233 if (!ws || !ws->ready || !ws->pipeline)
return;
234 if (groupsX <= 0) groupsX = 1;
235 if (groupsY <= 0) groupsY = 1;
236 if (groupsZ <= 0) groupsZ = 1;
238 auto &
device = gpuDevice();
239 auto &queue = gpuQueue();
249 entries[size_t(i)].binding = uint32_t(i);
250 entries[size_t(i)].buffer = (vb && vb->buffer) ? vb->buffer.Get() : dummy->
buffer.Get();
251 entries[size_t(i)].size = (vb && vb->buffer) ? vb->size_ : 4;
252 entries[size_t(i)].offset = (vb && vb->buffer) ? 0 : uint64_t(i) * kDummyBindingStride;
257 WGPUBindGroupDescriptor bgDesc{};
258 bgDesc.layout = ws->setLayout.Get();
261 wgpu::BindGroup bg =
device.CreateBindGroup(
reinterpret_cast<const wgpu::BindGroupDescriptor*
>(&bgDesc));
264 wgpu::CommandEncoder enc =
device.CreateCommandEncoder();
265 WGPUComputePassDescriptor cpDesc{};
266 wgpu::ComputePassEncoder pass = enc.BeginComputePass(
reinterpret_cast<const wgpu::ComputePassDescriptor*
>(&cpDesc));
267 pass.SetPipeline(ws->pipeline);
268 uint32_t offsets[1] = {0};
269 pass.SetBindGroup(0, bg, 1, offsets);
270 pass.DispatchWorkgroups(uint32_t(groupsX), uint32_t(groupsY), uint32_t(groupsZ));
272 wgpu::CommandBuffer cmd = enc.Finish();
273 queue.Submit(1, &cmd);
285 const WGPUBuffer raw =
buffer.Get();
286 static_assert(
sizeof(raw) <=
sizeof(
view.nativeHandle));
287 std::memcpy(&
view.nativeHandle, &raw,
sizeof(raw));
294 if (!
buffer || !src || nbytes == 0)
return;
295 if (dstOffset + nbytes >
size_)
296 throw Exception(
"GpuBuffer.write: out of range");
297 gpuQueue().WriteBuffer(
buffer, dstOffset, src, nbytes);
301 if (!
buffer || !dst || nbytes == 0)
return;
302 if (srcOffset + nbytes >
size_)
303 throw Exception(
"GpuBuffer.read: out of range");
305 mapBufferToCpu(
buffer,
size_, srcOffset, dst, nbytes);
307 readBufferToCpu(
buffer, srcOffset, dst, nbytes);
312 uploadBytes(data->getData(), data->getSize(), uint64_t(dstOffset < 0 ? 0 : dstOffset));
316 const int off = srcOffset < 0 ? 0 : srcOffset;
318 if (nbytes < 0) nbytes = int(
size_) - off;
321 downloadBytes(out->getData(), uint64_t(nbytes), uint64_t(off));
326 if (floatIndex < 0)
return;
331 if (floatIndex < 0)
return 0.f;
333 downloadBytes(&
v,
sizeof(
float), uint64_t(floatIndex) *
sizeof(
float));
338 if (!data ||
count <= 0 || startIndex < 0)
return;
339 uploadBytes(data, uint64_t(
count) *
sizeof(
float), uint64_t(startIndex) *
sizeof(
float));
343 if (!out ||
count <= 0 || startIndex < 0)
return;
344 downloadBytes(out, uint64_t(
count) *
sizeof(
float), uint64_t(startIndex) *
sizeof(
float));
348 if (
size_ <
sizeof(
float))
return;
349 const size_t count = size_t(
size_ /
sizeof(
float));
369 std::array<WebGpuGpuBuffer*, ComputeShader::kMaxBindings>
bindings{};
370 std::array<float, ComputeShader::kMaxFloats>
push{};
375 using Op = std::variant<Upload, Download, Dispatch>;
392 throw Exception(
"Gpgpu.Sequence.begin: previous async submission is still pending");
398 uint64_t dstOffset) {
401 throw Exception(
"Gpgpu.Sequence.recordUpload: invalid WebGPU upload");
402 if ((nbytes & 3u) != 0 || (dstOffset & 3u) != 0)
403 throw Exception(
"Gpgpu.Sequence.recordUpload: byte count and offset must be 4-byte aligned");
406 op.
bytes.resize(
size_t(nbytes));
407 std::memcpy(op.
bytes.data(), src,
size_t(nbytes));
409 sequence->ops.emplace_back(std::move(op));
413 uint64_t srcOffset) {
417 srcOffset + nbytes >
source->size_ || nbytes >
target->size_)
418 throw Exception(
"Gpgpu.Sequence.recordDownload: invalid WebGPU download");
419 if ((nbytes & 3u) != 0 || (srcOffset & 3u) != 0)
420 throw Exception(
"Gpgpu.Sequence.recordDownload: byte count and offset must be 4-byte aligned");
428 throw Exception(
"Gpgpu.Sequence.recordDispatch: invalid WebGPU shader");
431 op.
groupsX = uint32_t(std::max(1, groupsX));
432 op.
groupsY = uint32_t(std::max(1, groupsY));
433 op.
groupsZ = uint32_t(std::max(1, groupsZ));
437 sequence->ops.emplace_back(std::move(op));
443 throw Exception(
"Gpgpu.Sequence.submit: nothing recorded");
444 auto&
device = gpuDevice();
445 auto& queue = gpuQueue();
446 wgpu::CommandEncoder encoder =
device.CreateCommandEncoder();
447 std::vector<wgpu::Buffer> retainedBuffers;
448 std::vector<wgpu::BindGroup> retainedBindGroups;
451 if (
auto* upload = std::get_if<WebGpuSequence::Upload>(&op)) {
452 WGPUBufferDescriptor
desc{};
453 desc.label = sv(
"eve_sequence_upload");
454 desc.size = upload->bytes.size();
455 desc.usage = WGPUBufferUsage_CopySrc;
456 desc.mappedAtCreation =
true;
457 wgpu::Buffer staging =
device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&
desc));
458 std::memcpy(staging.GetMappedRange(0,
desc.size), upload->bytes.data(),
desc.size);
460 encoder.CopyBufferToBuffer(staging, 0, upload->dst->buffer, upload->offset,
desc.size);
461 retainedBuffers.push_back(staging);
464 if (
auto* download = std::get_if<WebGpuSequence::Download>(&op)) {
465 encoder.CopyBufferToBuffer(download->src->buffer, download->srcOffset, download->dst->buffer, 0,
469 auto& dispatch = std::get<WebGpuSequence::Dispatch>(op);
470 WGPUBufferDescriptor pushDesc{};
471 pushDesc.label = sv(
"eve_sequence_push");
473 pushDesc.usage = WGPUBufferUsage_Uniform;
474 pushDesc.mappedAtCreation =
true;
475 wgpu::Buffer push =
device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&pushDesc));
476 std::memcpy(push.GetMappedRange(0, pushDesc.size), dispatch.push.data(), pushDesc.size);
479 WGPUBufferDescriptor dummyDesc{};
480 dummyDesc.label = sv(
"eve_sequence_dummy");
482 dummyDesc.usage = WGPUBufferUsage_Storage;
483 wgpu::Buffer dummy =
device.CreateBuffer(
reinterpret_cast<const wgpu::BufferDescriptor*
>(&dummyDesc));
487 entries[size_t(i)].binding = uint32_t(i);
488 entries[size_t(i)].buffer = bound ? bound->
buffer.Get() : dummy.Get();
490 entries[size_t(i)].offset = bound ? 0 : uint64_t(i) * kDummyBindingStride;
495 WGPUBindGroupDescriptor bindDesc{};
496 bindDesc.layout = dispatch.shader->setLayout.Get();
499 wgpu::BindGroup bindGroup =
500 device.CreateBindGroup(
reinterpret_cast<const wgpu::BindGroupDescriptor*
>(&bindDesc));
502 WGPUComputePassDescriptor passDesc{};
503 wgpu::ComputePassEncoder pass =
504 encoder.BeginComputePass(
reinterpret_cast<const wgpu::ComputePassDescriptor*
>(&passDesc));
505 pass.SetPipeline(dispatch.shader->pipeline);
506 uint32_t offsets[1] = {0};
507 pass.SetBindGroup(0, bindGroup, 1, offsets);
508 pass.DispatchWorkgroups(dispatch.groupsX, dispatch.groupsY, dispatch.groupsZ);
510 retainedBuffers.push_back(push);
511 retainedBuffers.push_back(dummy);
512 retainedBindGroups.push_back(bindGroup);
515 wgpu::CommandBuffer command = encoder.Finish();
516 queue.Submit(1, &command);
518 WGPUQueueWorkDoneCallbackInfo
callback{};
519#if defined(__EMSCRIPTEN__)
520 callback.mode = WGPUCallbackMode_AllowProcessEvents;
522 callback.mode = WGPUCallbackMode_WaitAnyOnly;
524 callback.callback = [](WGPUQueueWorkDoneStatus
status, WGPUStringView,
void *userdata1,
void *) {
537#if defined(__EMSCRIPTEN__)
538 wgpuInstanceProcessEvents(gpuInstance().Get());
540 WGPUFutureWaitInfo waitInfo{};
541 waitInfo.future =
sequence->completion;
542 (void)wgpuInstanceWaitAny(gpuInstance().Get(), 1, &waitInfo, 0);
550#if defined(__EMSCRIPTEN__)
553 wgpuInstanceProcessEvents(gpuInstance().Get());
556 WGPUFutureWaitInfo waitInfo{};
557 waitInfo.future =
sequence->completion;
558 (void)wgpuInstanceWaitAny(gpuInstance().Get(), 1, &waitInfo, UINT64_MAX);
570 throw Exception(
"Gpgpu.Sequence.submit: GPU submission failed");
std::unordered_map< std::string, QuestRuntime > entries
wgpu::PopErrorScopeStatus status
std::unique_ptr< gpgpu::GpuBuffer > buffer
std::unique_ptr< gpgpu::Sequence > sequence
eve::action::ActionVfxBinding binding
const UnitySourceAsset & source
EVENGINE_API_FOUNDATION public API.
In-memory byte buffer implementing eve::Data (ref-counted).
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...
Compute program for the WebGPU backend. Accepts WGSL source; GLSL/SPIR-V input is rejected (browsers ...
float getFloat(int index) const override
Returns the float.
void bindBuffer(int binding, GpuBuffer *buffer) override
Binds buffer.
void setFloat(int index, float value) override
Sets the float.
void clearBindings() override
Clears bindings.
GpuBuffer * getBoundBuffer(int binding) const override
Returns the bound buffer.
~WebGpuComputeShader() override
Releases WebGpuComputeShader resources.
Storage / staging buffer for the WebGPU backend.
float readFloat32(int floatIndex) override
Reads float 32.
void downloadBytes(void *dst, uint64_t nbytes, uint64_t srcOffset=0) const override
Downloads bytes.
data::ByteData * readData(int srcOffset=0, int size=-1) override
Reads data.
~WebGpuGpuBuffer() override
Releases WebGpuGpuBuffer resources.
void writeFloat32(int floatIndex, float value) override
Writes float 32.
void readFloat32s(float *out, int count, int startIndex=0) const override
Reads float 32 s.
void uploadBytes(const void *src, uint64_t nbytes, uint64_t dstOffset=0) override
Uploads bytes.
void writeFloat32s(const float *data, int count, int startIndex=0) override
Writes float 32 s.
void writeData(data::ByteData *data, int dstOffset=0) override
Writes data.
void fillFloat32(float value) override
Fill float 32.
GpuResidentBufferView residentView() const override
Resident view.
std::variant< Upload, Download, Dispatch > Op
std::atomic< SequenceStatus > status
EVENGINE_API_BACKENDS public API.
Fluids module — interactive surface fluid simulation.
SequenceStatus
Observable lifecycle state of a reusable GPU command sequence.
void webgpuSequenceRecordDownload(WebGpuSequence *sequence, GpuBuffer *src, GpuBuffer *staging, uint64_t nbytes, uint64_t srcOffset)
Webgpu sequence record download.
bool webgpuSequenceReady(WebGpuSequence *sequence)
Webgpu sequence ready.
SequenceStatus webgpuSequencePoll(WebGpuSequence *sequence)
Webgpu sequence poll.
void webgpuSequenceBegin(WebGpuSequence *sequence)
Webgpu sequence begin.
bool webgpuGpgpuReady()
Webgpu gpgpu ready.
void webgpuDispatch(ComputeShader *shader, int groupsX, int groupsY, int groupsZ)
Webgpu dispatch.
void webgpuSequenceDestroy(WebGpuSequence *sequence)
Webgpu sequence destroy.
WebGpuGpuBuffer * webgpuNewBuffer(int byteSize, const std::string &usage)
Webgpu new buffer.
SequenceStatus webgpuSequenceWait(WebGpuSequence *sequence)
Webgpu sequence wait.
SequenceStatus webgpuSequenceStatus(const WebGpuSequence *sequence)
Webgpu sequence status.
WebGpuComputeShader * webgpuNewShaderFromSpirv(const std::vector< uint32_t > &spv)
Webgpu new shader from spirv.
WebGpuComputeShader * webgpuNewShaderFromWgsl(const std::string &wgsl)
Webgpu new shader from wgsl.
void webgpuSequenceRecordDispatch(WebGpuSequence *sequence, ComputeShader *shader, int groupsX, int groupsY, int groupsZ)
Webgpu sequence record dispatch.
WebGpuSequence * webgpuSequenceCreate()
Webgpu sequence create.
void webgpuSequenceRecordUpload(WebGpuSequence *sequence, GpuBuffer *dst, const void *src, uint64_t nbytes, uint64_t dstOffset)
Webgpu sequence record upload.
SequenceStatus webgpuSequenceSubmitAsync(WebGpuSequence *sequence)
Webgpu sequence submit async.
void webgpuSequenceSubmit(WebGpuSequence *sequence)
Webgpu sequence submit.
WGPUStringView sv(const char *s)
Build a WGPUStringView from a C string (null-safe, length auto-computed).
Transient backend buffer slice shared between sibling GPU modules. @ownership Non-owning; nativeHandl...
std::array< WebGpuGpuBuffer *, ComputeShader::kMaxBindings > bindings
WebGpuComputeShader * shader
std::array< float, ComputeShader::kMaxFloats > push
std::vector< uint8_t > bytes
std::future< vkb::Instance > future