1use alloc::format;
30use alloc::rc::Rc;
31use alloc::string::String;
32use alloc::vec;
33use alloc::vec::Vec;
34use core::cell::{Cell, RefCell};
35
36use crate::*;
37
38#[link(wasm_import_module = "jfgui")]
39unsafe extern "C" {
40 #[link_name = "gpu_call"]
41 fn host_call(op: u32, args: *const u32, n: usize) -> u32;
42}
43
44pub(crate) const CALL_PREPARE: u32 = 0;
46pub(crate) const CALL_INFO: u32 = 1;
47pub(crate) const CALL_DEVICE: u32 = 2;
48pub(crate) const CALL_FREE: u32 = 3;
49pub(crate) const CALL_CREATE: u32 = 4;
50pub(crate) const CALL_SHADER: u32 = 5;
51pub(crate) const CALL_WRITE_BUFFER: u32 = 6;
52pub(crate) const CALL_READ_BUFFER: u32 = 7;
53pub(crate) const CALL_WRITE_TEXTURE: u32 = 8;
54pub(crate) const CALL_WRITE_RECTS: u32 = 9;
55pub(crate) const CALL_CONTEXT: u32 = 10;
56pub(crate) const CALL_CURRENT: u32 = 11;
57pub(crate) const CALL_RESIZE: u32 = 12;
58pub(crate) const CALL_SUBMIT: u32 = 13;
59pub(crate) const CALL_STATE: u32 = 14;
60pub(crate) const CALL_DONE: u32 = 16;
61pub(crate) const CALL_LOSE: u32 = 17;
62pub(crate) const CALL_RESERVE: u32 = 18;
63pub(crate) const CALL_PIPELINE_LAYOUT: u32 = 19;
64pub(crate) const CALL_RGBA: u32 = 20;
65
66pub(crate) fn call(op: u32, args: &[u32]) -> u32 {
68 unsafe { host_call(op, args.as_ptr(), args.len()) }
71}
72
73pub(crate) struct Handle {
76 pub h: u32,
77 pub destroy: bool,
78}
79
80impl Drop for Handle {
81 fn drop(&mut self) {
82 call(CALL_FREE, &[self.h, self.destroy as u32]);
83 }
84}
85
86pub(crate) fn create(parent: u32, method: u32, json: &str) -> Result<u32> {
89 match call(CALL_CREATE, &[parent, method, json.as_ptr() as u32, json.len() as u32]) {
90 0 => Err(GpuError::Failed(0)),
91 h => Ok(h),
92 }
93}
94
95const STATE_LOST: u32 = 1;
96const STATE_VALIDATION: u32 = 2;
97const STATE_MEMORY: u32 = 4;
98const STATE_INTERNAL: u32 = 8;
99
100pub(crate) struct DevInner {
101 pub h: u32,
102 pub info: AdapterInfo,
103 pub features: Features,
104 pub limits: Limits,
105 lost: Cell<bool>,
107 last: Cell<u64>,
108 done: Cell<u64>,
109 pub reserved: Cell<u64>,
111 pub spare: RefCell<Vec<u32>>,
113}
114
115impl DevInner {
116 pub fn check(&self) -> Result<()> {
118 if self.lost.get() {
119 return Err(GpuError::DeviceLost);
120 }
121 let s = call(CALL_STATE, &[self.h]);
122 if s & STATE_LOST != 0 {
123 self.lost.set(true);
124 return Err(GpuError::DeviceLost);
125 }
126 if s & STATE_MEMORY != 0 {
127 return Err(GpuError::OutOfMemory);
128 }
129 if s & STATE_VALIDATION != 0 {
130 return Err(GpuError::Invalid("the browser's WebGPU refused a call (the console says which)"));
131 }
132 if s & STATE_INTERNAL != 0 {
133 return Err(GpuError::Failed(1));
134 }
135 Ok(())
136 }
137}
138
139#[derive(Clone)]
141pub struct Instance {
142 adapter: Rc<RefCell<Option<Adapter>>>,
143}
144
145fn features_of(list: &str) -> Features {
147 let has = |n: &str| list.split(',').any(|f| f == n);
148 Features {
149 draw_indirect_first_instance: has("indirect-first-instance"),
150 subgroups_webgpu: has("subgroups"),
151 shader_f16: has("shader-f16"),
152 texture_bc: has("texture-compression-bc"),
153 anisotropy: true,
154 depth_clamp: has("depth-clip-control"),
155 fragment_stores: true,
156 clip_distances: has("clip-distances"),
157 primitive_index: has("primitive-index"),
158 swapchain: true,
159 ..Features::default()
160 }
161}
162
163fn vendor_name(v: &str) -> &'static str {
164 match v {
165 "nvidia" => "NVIDIA",
166 "amd" | "ati" => "AMD",
167 "intel" => "Intel",
168 "apple" => "Apple",
169 "arm" => "ARM",
170 "qualcomm" => "Qualcomm",
171 "imagination" | "img-tec" => "Imagination",
172 "samsung" => "Samsung",
173 "google" => "Google",
174 "microsoft" => "Microsoft",
175 "mesa" => "Mesa",
176 _ => "",
177 }
178}
179
180fn read_adapter() -> Option<Adapter> {
181 let mut buf = vec![0u8; 8192];
182 let n = call(CALL_INFO, &[buf.as_mut_ptr() as u32, buf.len() as u32]) as usize;
183 let text = core::str::from_utf8(&buf[..n]).ok()?;
184 if text.is_empty() {
185 return None;
186 }
187 let get = |k: &str| text.lines().find_map(|l| l.strip_prefix(k).and_then(|r| r.strip_prefix('='))).unwrap_or("");
188 let num = |k: &str| get(k).parse::<f64>().map_or(0, |v| v.min(u32::MAX as f64) as u32);
189 let (vendor, arch, device, desc) = (get("vendor"), get("arch"), get("device"), get("desc"));
190 let name = if !desc.is_empty() {
191 String::from(desc)
192 } else {
193 let mut s = String::from(vendor);
194 for p in [arch, device] {
195 if !p.is_empty() {
196 s.push(' ');
197 s.push_str(p);
198 }
199 }
200 s
201 };
202 let fallback = get("fallback") == "1";
203 let info = AdapterInfo {
204 name,
205 vendor_id: 0,
206 device_id: 0,
207 vendor: vendor_name(vendor),
208 kind: if fallback { AdapterKind::Cpu } else { AdapterKind::Other },
210 driver: String::from(vendor),
211 driver_info: String::from(arch),
212 driver_version: String::new(),
213 api_version: (1, 0, 0),
214 backend: "WebGPU",
215 missing: None,
216 };
217 let limits = Limits {
218 max_texture_2d: num("maxTextureDimension2D"),
219 max_texture_3d: num("maxTextureDimension3D"),
220 max_texture_layers: num("maxTextureArrayLayers"),
221 max_storage_binding: num("maxStorageBufferBindingSize"),
222 max_uniform_binding: num("maxUniformBufferBindingSize"),
223 max_push_constants: PUSH_CONSTANTS_MAX,
224 max_bind_groups: num("maxBindGroups"),
226 max_compute_workgroup_size: [num("maxComputeWorkgroupSizeX"), num("maxComputeWorkgroupSizeY"), num("maxComputeWorkgroupSizeZ")],
227 max_compute_invocations: num("maxComputeInvocationsPerWorkgroup"),
228 max_compute_workgroups: [num("maxComputeWorkgroupsPerDimension"); 3],
229 max_compute_shared_memory: num("maxComputeWorkgroupStorageSize"),
230 min_uniform_offset_alignment: num("minUniformBufferOffsetAlignment"),
231 min_storage_offset_alignment: num("minStorageBufferOffsetAlignment"),
232 max_color_targets: num("maxColorAttachments"),
233 max_vertex_buffers: num("maxVertexBuffers"),
234 max_vertex_attributes: num("maxVertexAttributes"),
235 max_draw_indirect_count: 1,
236 max_anisotropy: 16.0,
237 timestamp_period_ns: 0.0,
238 subgroup_size: 0,
239 subgroup_min_size: num("minSubgroupSize"),
240 subgroup_max_size: num("maxSubgroupSize"),
241 ..Limits::default()
242 };
243 Some(Adapter(Rc::new(AdapterInner { info, features: features_of(get("features")), limits, taken: Cell::new(false) })))
244}
245
246impl Instance {
247 pub fn new() -> Result<Instance> {
249 Instance::with(&InstanceDesc::DEFAULT)
250 }
251
252 pub fn with(_desc: &InstanceDesc) -> Result<Instance> {
254 if matches!(plat::env("jfgui_GPU").as_deref(), Some("0") | Some("off")) {
255 return Err(GpuError::Disabled);
256 }
257 match call(CALL_PREPARE, &[1]) {
258 0 => Err(GpuError::NoLoader),
259 _ => Ok(Instance { adapter: Rc::new(RefCell::new(None)) }),
260 }
261 }
262
263 pub fn prepare(&self) -> Result<bool> {
266 if self.adapter.borrow().is_some() {
267 return Ok(true);
268 }
269 match call(CALL_PREPARE, &[0]) {
270 0 => Err(GpuError::NoLoader),
271 1 => Ok(false),
272 2 => {
273 let a = read_adapter().ok_or(GpuError::NoAdapter)?;
274 *self.adapter.borrow_mut() = Some(a);
275 Ok(true)
276 }
277 3 => Err(GpuError::NoAdapter),
278 _ => Err(GpuError::Failed(0)),
279 }
280 }
281
282 pub fn validating(&self) -> bool {
283 false
284 }
285
286 pub fn adapters(&self) -> Result<Vec<Adapter>> {
288 Ok(vec![self.adapter()?])
289 }
290
291 pub fn adapter(&self) -> Result<Adapter> {
292 self.prepare()?;
293 self.adapter.borrow().clone().ok_or(GpuError::NotReady)
294 }
295
296 pub fn adapters_low_power(&self, _display: Option<(u32, u32)>) -> Result<Vec<Adapter>> {
298 let a = self.adapter()?;
299 Ok(if a.info().kind == AdapterKind::Cpu { Vec::new() } else { vec![a] })
300 }
301}
302
303pub fn low_power_rank(kind: AdapterKind, _drives_display: bool) -> Option<u32> {
305 (kind != AdapterKind::Cpu).then_some(0)
306}
307
308pub fn validation_messages() -> (u32, u32) {
310 (0, 0)
311}
312
313struct AdapterInner {
314 info: AdapterInfo,
315 features: Features,
316 limits: Limits,
317 taken: Cell<bool>,
318}
319
320#[derive(Clone)]
322pub struct Adapter(Rc<AdapterInner>);
323
324impl Adapter {
325 pub fn info(&self) -> &AdapterInfo {
326 &self.0.info
327 }
328 pub fn features(&self) -> &Features {
329 &self.0.features
330 }
331 pub fn limits(&self) -> &Limits {
332 &self.0.limits
333 }
334 pub fn level(&self) -> Level {
336 Level::Basis
337 }
338 pub fn can_present(&self, _surface: &crate::Surface) -> bool {
339 true
340 }
341
342 pub fn create_device_with(&self, desc: &DeviceDesc, _opts: &DeviceOptions) -> Result<Device> {
344 self.create_device(desc)
345 }
346
347 pub fn create_device(&self, _desc: &DeviceDesc) -> Result<Device> {
350 if self.0.taken.replace(true) {
351 return Err(GpuError::Unsupported("one device per Instance in the browser"));
352 }
353 let h = call(CALL_DEVICE, &[]);
354 if h == 0 {
355 return Err(GpuError::NotReady);
356 }
357 let d = Rc::new(DevInner {
358 h,
359 info: self.0.info.clone(),
360 features: self.0.features,
361 limits: self.0.limits,
362 lost: Cell::new(false),
363 last: Cell::new(0),
364 done: Cell::new(0),
365 reserved: Cell::new(0),
366 spare: RefCell::new(Vec::new()),
367 });
368 Ok(Device { d: d.clone(), queue: Queue { d } })
369 }
370}
371
372#[derive(Clone)]
374pub struct Device {
375 pub(crate) d: Rc<DevInner>,
376 queue: Queue,
377}
378
379#[derive(Clone)]
381pub struct Queue {
382 pub(crate) d: Rc<DevInner>,
383}
384
385#[derive(Clone)]
388pub struct ComputeQueue {
389 _none: (),
390}
391
392impl ComputeQueue {
393 pub fn encoder(&self) -> Result<crate::CommandEncoder<'_>> {
394 Err(GpuError::Unsupported("a compute queue in the browser"))
395 }
396 pub fn submit<'a, I: IntoIterator<Item = crate::CommandBuffer<'a>>>(&self, _cmds: I) -> Result<SubmitId> {
397 Err(GpuError::Unsupported("a compute queue in the browser"))
398 }
399 pub fn submit_after<'a, I: IntoIterator<Item = crate::CommandBuffer<'a>>>(&self, _wait: &[SubmitId], _cmds: I) -> Result<SubmitId> {
400 Err(GpuError::Unsupported("a compute queue in the browser"))
401 }
402}
403
404impl Device {
405 pub fn queue(&self) -> &Queue {
406 &self.queue
407 }
408 pub fn compute_queue(&self) -> Option<&ComputeQueue> {
410 None
411 }
412 pub fn info(&self) -> &AdapterInfo {
413 &self.d.info
414 }
415 pub fn features(&self) -> &Features {
416 &self.d.features
417 }
418 pub fn limits(&self) -> &Limits {
419 &self.d.limits
420 }
421 pub fn level(&self) -> Level {
422 Level::Basis
423 }
424 pub fn is_lost(&self) -> bool {
426 matches!(self.d.check(), Err(GpuError::DeviceLost))
427 }
428 pub fn test_lose(&self) {
430 call(CALL_LOSE, &[self.d.h]);
431 self.d.lost.set(true);
432 }
433 pub fn validating(&self) -> bool {
434 false
435 }
436 pub fn poll(&self) -> Result<()> {
438 self.d.check()
439 }
440 pub fn wait(&self, id: SubmitId, _timeout_ns: u64) -> Result<bool> {
442 self.d.check()?;
443 Ok(self.is_done(id))
444 }
445 pub fn wait_idle(&self) -> Result<()> {
446 Err(GpuError::Unsupported("waiting for the GPU in the browser: Device::is_done"))
447 }
448 pub fn completed(&self) -> SubmitId {
449 SubmitId(self.d.done.get())
450 }
451 pub fn is_done(&self, id: SubmitId) -> bool {
453 if self.d.done.get() >= id.0 {
454 return true;
455 }
456 if call(CALL_DONE, &[self.d.h, id.0 as u32]) != 0 {
457 self.d.done.set(id.0);
458 return true;
459 }
460 false
461 }
462 pub fn memory_reserved(&self) -> u64 {
464 self.d.reserved.get()
465 }
466}
467
468fn split(v: u64) -> (u32, u32) {
469 (v as u32, (v >> 32) as u32)
470}
471
472impl Queue {
473 pub fn submit<'a, I: IntoIterator<Item = crate::CommandBuffer<'a>>>(&self, cmds: I) -> Result<SubmitId> {
475 self.d.check()?;
476 let mut words: Vec<u32> = core::mem::take(&mut *self.d.spare.borrow_mut());
477 words.clear();
478 for c in cmds {
479 words.extend_from_slice(&c.words);
480 words.push(0);
481 }
482 let id = call(CALL_SUBMIT, &[self.d.h, words.as_ptr() as u32, words.len() as u32]);
483 *self.d.spare.borrow_mut() = words;
484 self.d.last.set(id as u64);
485 Ok(SubmitId(id as u64))
486 }
487
488 pub fn submit_after<'a, I: IntoIterator<Item = crate::CommandBuffer<'a>>>(&self, _wait: &[SubmitId], cmds: I) -> Result<SubmitId> {
490 self.submit(cmds)
491 }
492
493 pub fn flush(&self) -> Result<SubmitId> {
495 self.d.check()?;
496 Ok(SubmitId(self.d.last.get()))
497 }
498
499 pub fn write_buffer<T: Pod>(&self, buf: &crate::Buffer, offset: u64, data: &[T]) -> Result<()> {
501 let bytes = bytes_of(data);
502 if offset % 4 != 0 || bytes.len() % 4 != 0 {
503 return Err(GpuError::Invalid("write_buffer: offset and size must be multiples of 4 in WebGPU"));
504 }
505 if offset.checked_add(bytes.len() as u64).is_none_or(|e| e > buf.size()) {
506 return Err(GpuError::Invalid("write_buffer: outside the buffer"));
507 }
508 let (lo, hi) = split(offset);
509 call(CALL_WRITE_BUFFER, &[self.d.h, buf.handle(), lo, hi, bytes.as_ptr() as u32, bytes.len() as u32]);
510 Ok(())
511 }
512
513 pub fn write_texture(&self, dst: &TextureRegion, data: &[u8], layout: DataLayout, _before: Layout, _after: Layout) -> Result<()> {
515 let f = dst.texture.format();
516 let row = layout.bytes_per_row.max(((dst.size[0] / f.block_dim()).max(1)) * f.block_bytes());
517 let rows = if layout.rows_per_image != 0 { layout.rows_per_image } else { dst.size[1] / f.block_dim() };
518 let z = if dst.texture.depth() > 1 { dst.origin[2] } else { dst.base_layer };
519 let d = if dst.texture.depth() > 1 { dst.size[2].max(1) } else { dst.layers.max(1) };
520 if layout.offset > data.len() as u64 {
521 return Err(GpuError::Invalid("write_texture: the offset lies outside the data"));
522 }
523 let base = data.as_ptr() as u32 + layout.offset as u32;
524 call(CALL_WRITE_TEXTURE, &[self.d.h, dst.texture.handle(), dst.mip, dst.origin[0], dst.origin[1], z, dst.size[0], dst.size[1], d, base, data.len() as u32, row, rows]);
525 Ok(())
526 }
527
528 pub fn write_texture_rects(&self, t: &crate::Texture, rects: &[[i32; 4]], data: &[u8], stride: u32, before: Layout, after: Layout) -> Result<()> {
531 self.write_texture_rects_at(t, rects, (0, 0), data, stride, before, after)
532 }
533
534 pub fn write_texture_rects_at(&self, t: &crate::Texture, rects: &[[i32; 4]], origin: (i32, i32), data: &[u8], stride: u32, _before: Layout, _after: Layout) -> Result<()> {
537 let (tw, th) = (t.width() as i32, t.height() as i32);
538 let bytes = t.format().block_bytes() as i32;
539 if stride == 0 || bytes != 4 {
540 return Err(GpuError::Invalid("write_texture_rects: a 32-bit format and a stride"));
541 }
542 let (img_w, img_h) = (stride as i32 / 4, (data.len() / stride as usize) as i32);
543 let mut list: Vec<[i32; 4]> = Vec::with_capacity(rects.len());
544 for r in rects {
545 let x0 = r[0].max(origin.0).max(0);
546 let y0 = r[1].max(origin.1).max(0);
547 let x1 = r[0].saturating_add(r[2].max(0)).min(origin.0.saturating_add(tw)).min(img_w);
548 let y1 = r[1].saturating_add(r[3].max(0)).min(origin.1.saturating_add(th)).min(img_h);
549 if x0 < x1 && y0 < y1 {
550 list.push([x0, y0, x1 - x0, y1 - y0]);
551 }
552 }
553 if !list.is_empty() {
554 call(CALL_WRITE_RECTS, &[self.d.h, t.handle(), list.as_ptr() as u32, list.len() as u32, data.as_ptr() as u32, data.len() as u32, stride, origin.0 as u32, origin.1 as u32]);
555 }
556 Ok(())
557 }
558
559 pub fn read_buffer<T: Pod>(&self, _buf: &crate::Buffer, _offset: u64, _out: &mut [T]) -> Result<()> {
561 Err(GpuError::Unsupported("Queue::read_buffer waits for the GPU; in the browser a Readback buffer and Buffer::read"))
562 }
563}
564
565pub(crate) fn json_f64(v: f64) -> String {
567 if v.is_finite() { format!("{v}") } else { String::from("0") }
568}