1use alloc::format;
24use alloc::rc::Rc;
25use alloc::string::String;
26use alloc::vec::Vec;
27use core::cell::RefCell;
28
29use crate::web_dev::{call, create, json_f64, DevInner, Device, Handle, CALL_PIPELINE_LAYOUT, CALL_READ_BUFFER, CALL_SHADER, CALL_WRITE_BUFFER};
30use crate::*;
31
32const METHOD_BUFFER: u32 = 0;
33const METHOD_TEXTURE: u32 = 1;
34const METHOD_SAMPLER: u32 = 2;
35const METHOD_BIND_GROUP_LAYOUT: u32 = 3;
36const METHOD_BIND_GROUP: u32 = 4;
37const METHOD_COMPUTE_PIPELINE: u32 = 5;
38const METHOD_RENDER_PIPELINE: u32 = 6;
39const METHOD_VIEW: u32 = 100;
40
41const BU_MAP_READ: u32 = 1;
43const BU_COPY_SRC: u32 = 4;
44const BU_COPY_DST: u32 = 8;
45const BU_INDEX: u32 = 16;
46const BU_VERTEX: u32 = 32;
47const BU_UNIFORM: u32 = 64;
48const BU_STORAGE: u32 = 128;
49const BU_INDIRECT: u32 = 256;
50
51pub(crate) struct BufInner {
52 h: Handle,
53 d: Rc<DevInner>,
54 size: u64,
55 usage: BufferUsage,
56 memory: MemoryKind,
57}
58
59impl Drop for BufInner {
60 fn drop(&mut self) {
61 self.d.reserved.set(self.d.reserved.get().saturating_sub(self.size));
62 }
63}
64
65#[derive(Clone)]
67pub struct Buffer(pub(crate) Rc<BufInner>);
68
69impl Buffer {
70 pub fn size(&self) -> u64 {
71 self.0.size
72 }
73 pub fn usage(&self) -> BufferUsage {
74 self.0.usage
75 }
76 pub fn memory(&self) -> MemoryKind {
77 self.0.memory
78 }
79 pub(crate) fn handle(&self) -> u32 {
80 self.0.h.h
81 }
82
83 pub fn write<T: Pod>(&self, offset: u64, data: &[T]) -> Result<()> {
85 if self.0.memory == MemoryKind::Device {
86 return Err(GpuError::Invalid("Buffer::write: an Upload buffer"));
87 }
88 if self.0.memory == MemoryKind::Readback {
89 return Err(GpuError::Unsupported("Buffer::write on a Readback buffer: WebGPU maps it for reading only"));
90 }
91 let b = bytes_of(data);
92 if offset % 4 != 0 || b.len() % 4 != 0 || offset.checked_add(b.len() as u64).is_none_or(|e| e > self.0.size) {
93 return Err(GpuError::Invalid("Buffer::write: outside the buffer, or not in multiples of 4"));
94 }
95 call(CALL_WRITE_BUFFER, &[self.0.d.h, self.handle(), offset as u32, (offset >> 32) as u32, b.as_ptr() as u32, b.len() as u32]);
96 Ok(())
97 }
98
99 pub fn read<T: Pod>(&self, offset: u64, out: &mut [T]) -> Result<()> {
102 if self.0.memory != MemoryKind::Readback {
103 return Err(GpuError::Unsupported("Buffer::read: only a Readback buffer in the browser"));
104 }
105 let n = core::mem::size_of_val(out);
106 if offset.checked_add(n as u64).is_none_or(|e| e > self.0.size) {
107 return Err(GpuError::Invalid("Buffer::read: outside the buffer"));
108 }
109 self.0.d.check()?;
110 match call(CALL_READ_BUFFER, &[self.handle(), offset as u32, (offset >> 32) as u32, out.as_mut_ptr() as u32, n as u32]) {
111 0 => Err(GpuError::NotReady),
112 _ => Ok(()),
113 }
114 }
115}
116
117impl Device {
118 pub fn create_buffer(&self, desc: &BufferDesc) -> Result<Buffer> {
119 self.d.check()?;
120 if desc.size == 0 {
121 return Err(GpuError::Invalid("a buffer of 0 bytes"));
122 }
123 let u = desc.usage;
124 let mut bits = 0;
125 for (f, b) in [(BufferUsage::UNIFORM, BU_UNIFORM), (BufferUsage::STORAGE, BU_STORAGE), (BufferUsage::INDEX, BU_INDEX), (BufferUsage::VERTEX, BU_VERTEX), (BufferUsage::INDIRECT, BU_INDIRECT)] {
126 if u.contains(f) {
127 bits |= b;
128 }
129 }
130 let usage = if desc.memory == MemoryKind::Readback {
131 if bits != 0 {
132 return Err(GpuError::Unsupported("a Readback buffer takes copies only in WebGPU"));
133 }
134 BU_MAP_READ | BU_COPY_DST
135 } else {
136 bits | BU_COPY_SRC | BU_COPY_DST
137 };
138 let h = create(self.d.h, METHOD_BUFFER, &format!("{{\"size\":{},\"usage\":{usage}}}", desc.size))?;
139 self.d.reserved.set(self.d.reserved.get() + desc.size);
140 Ok(Buffer(Rc::new(BufInner { h: Handle { h, destroy: true }, d: self.d.clone(), size: desc.size, usage: desc.usage, memory: desc.memory })))
141 }
142}
143
144pub(crate) fn format_name(f: Format) -> &'static str {
146 use Format::*;
147 match f {
148 R8Unorm => "r8unorm",
149 Rg8Unorm => "rg8unorm",
150 Rgba8Unorm => "rgba8unorm",
151 Rgba8Srgb => "rgba8unorm-srgb",
152 Bgra8Unorm => "bgra8unorm",
153 Bgra8Srgb => "bgra8unorm-srgb",
154 Rgb10a2Unorm => "rgb10a2unorm",
155 Rg11b10Float => "rg11b10ufloat",
156 R16Float => "r16float",
157 Rg16Float => "rg16float",
158 Rgba16Float => "rgba16float",
159 R32Uint => "r32uint",
160 R32Sint => "r32sint",
161 R32Float => "r32float",
162 Rg32Uint => "rg32uint",
163 Rg32Float => "rg32float",
164 Rgba32Uint => "rgba32uint",
165 Rgba32Float => "rgba32float",
166 Depth16 => "depth16unorm",
167 Depth32Float => "depth32float",
168 Depth24Stencil8 => "depth24plus-stencil8",
169 Depth32FloatStencil8 => "depth32float-stencil8",
170 Bc1Unorm => "bc1-rgba-unorm",
171 Bc1Srgb => "bc1-rgba-unorm-srgb",
172 Bc3Unorm => "bc3-rgba-unorm",
173 Bc3Srgb => "bc3-rgba-unorm-srgb",
174 Bc4Unorm => "bc4-r-unorm",
175 Bc5Unorm => "bc5-rg-unorm",
176 Bc7Unorm => "bc7-rgba-unorm",
177 Bc7Srgb => "bc7-rgba-unorm-srgb",
178 }
179}
180
181fn dim_name(d: ViewDim) -> &'static str {
182 match d {
183 ViewDim::D2 => "2d",
184 ViewDim::D2Array => "2d-array",
185 ViewDim::Cube => "cube",
186 ViewDim::CubeArray => "cube-array",
187 ViewDim::D3 => "3d",
188 }
189}
190
191pub(crate) struct ViewInner {
192 pub h: Handle,
193 format: Format,
194 w: u32,
195 hh: u32,
196 dim: ViewDim,
197 _tex: Option<Rc<TexInner>>,
198}
199
200#[derive(Clone)]
202pub struct TextureView(pub(crate) Rc<ViewInner>);
203
204impl TextureView {
205 pub fn format(&self) -> Format {
206 self.0.format
207 }
208 pub fn size(&self) -> (u32, u32) {
210 (self.0.w, self.0.hh)
211 }
212 pub(crate) fn handle(&self) -> u32 {
213 self.0.h.h
214 }
215 pub fn usage(&self) -> TextureUsage {
217 self.0._tex.as_ref().map_or(TextureUsage::COLOR_TARGET, |t| t.desc.usage)
218 }
219 pub fn same(&self, other: &TextureView) -> bool {
221 Rc::ptr_eq(&self.0, &other.0)
222 }
223 pub(crate) fn for_frame(h: u32, format: Format, w: u32, hh: u32) -> TextureView {
225 TextureView(Rc::new(ViewInner { h: Handle { h, destroy: false }, format, w, hh, dim: ViewDim::D2, _tex: None }))
226 }
227}
228
229pub(crate) struct TexInner {
230 h: Handle,
231 d: Option<Rc<DevInner>>,
232 desc: TextureDesc,
233 bytes: u64,
234}
235
236impl Drop for TexInner {
237 fn drop(&mut self) {
238 if let Some(d) = &self.d {
239 d.reserved.set(d.reserved.get().saturating_sub(self.bytes));
240 }
241 }
242}
243
244#[derive(Clone)]
246pub struct Texture {
247 t: Rc<TexInner>,
248 view: TextureView,
249}
250
251impl Texture {
252 pub fn view(&self) -> &TextureView {
254 &self.view
255 }
256 pub fn width(&self) -> u32 {
257 self.t.desc.width
258 }
259 pub fn height(&self) -> u32 {
260 self.t.desc.height
261 }
262 pub fn depth(&self) -> u32 {
263 self.t.desc.depth
264 }
265 pub fn layers(&self) -> u32 {
266 self.t.desc.layers
267 }
268 pub fn mips(&self) -> u32 {
269 self.t.desc.mips
270 }
271 pub fn format(&self) -> Format {
272 self.t.desc.format
273 }
274 pub fn usage(&self) -> TextureUsage {
275 self.t.desc.usage
276 }
277 pub(crate) fn handle(&self) -> u32 {
278 self.t.h.h
279 }
280 pub(crate) fn for_frame(h: u32, view: TextureView, format: Format, width: u32, height: u32, usage: TextureUsage) -> Texture {
282 let desc = TextureDesc::d2(width, height, format, usage);
283 Texture { t: Rc::new(TexInner { h: Handle { h, destroy: false }, d: None, desc, bytes: 0 }), view }
284 }
285
286 pub fn create_view(&self, d: &ViewDesc) -> Result<TextureView> {
288 let t = &self.t.desc;
289 let mips = if d.mips == 0 { t.mips.saturating_sub(d.base_mip) } else { d.mips };
290 let total = if t.depth > 1 { 1 } else { t.layers };
291 let layers = if d.layers == 0 { total.saturating_sub(d.base_layer) } else { d.layers };
292 if mips == 0 || d.base_mip.checked_add(mips).is_none_or(|e| e > t.mips) || layers == 0
293 || d.base_layer.checked_add(layers).is_none_or(|e| e > total) {
294 return Err(GpuError::Invalid("create_view: outside the texture"));
295 }
296 make_view(&self.t, d.dim, d.base_mip, mips, d.base_layer, layers)
297 }
298}
299
300fn make_view(t: &Rc<TexInner>, dim: ViewDim, base_mip: u32, mips: u32, base_layer: u32, layers: u32) -> Result<TextureView> {
301 let json = format!(
302 "{{\"dimension\":\"{}\",\"baseMipLevel\":{base_mip},\"mipLevelCount\":{mips},\"baseArrayLayer\":{base_layer},\"arrayLayerCount\":{layers}}}",
303 dim_name(dim)
304 );
305 let h = create(t.h.h, METHOD_VIEW, &json)?;
306 let (w, hh) = ((t.desc.width >> base_mip).max(1), (t.desc.height >> base_mip).max(1));
307 Ok(TextureView(Rc::new(ViewInner { h: Handle { h, destroy: false }, format: t.desc.format, w, hh, dim, _tex: Some(t.clone()) })))
308}
309
310fn default_dim(d: &TextureDesc) -> ViewDim {
311 if d.depth > 1 {
312 ViewDim::D3
313 } else if d.cube {
314 if d.layers == 6 { ViewDim::Cube } else { ViewDim::CubeArray }
315 } else if d.layers > 1 {
316 ViewDim::D2Array
317 } else {
318 ViewDim::D2
319 }
320}
321
322impl Device {
323 pub fn create_texture(&self, desc: &TextureDesc) -> Result<Texture> {
324 self.d.check()?;
325 if desc.width == 0 || desc.height == 0 || desc.depth == 0 || desc.layers == 0 || desc.mips == 0 {
326 return Err(GpuError::Invalid("a texture without size"));
327 }
328 let lim = self.d.limits.max_texture_2d;
329 if desc.width > lim || desc.height > lim {
330 return Err(GpuError::Unsupported("the texture is larger than this device's limit"));
331 }
332 if desc.format.is_compressed() && !self.d.features.texture_bc {
333 return Err(GpuError::Unsupported("BC textures"));
334 }
335 let u = desc.usage.0;
336 let usage = (u & 0xF) | if u & 0x30 != 0 { 16 } else { 0 };
338 let (third, dimension) = if desc.depth > 1 { (desc.depth, "3d") } else { (desc.layers, "2d") };
339 let json = format!(
340 "{{\"size\":[{},{},{third}],\"mipLevelCount\":{},\"format\":\"{}\",\"usage\":{usage},\"dimension\":\"{dimension}\"}}",
341 desc.width, desc.height, desc.mips, format_name(desc.format)
342 );
343 let h = create(self.d.h, METHOD_TEXTURE, &json)?;
344 let dim = desc.width as u64 / desc.format.block_dim() as u64 * (desc.height / desc.format.block_dim()) as u64;
345 let mut bytes = dim * desc.format.block_bytes() as u64 * third as u64;
346 if desc.mips > 1 {
347 bytes += bytes / 3;
348 }
349 self.d.reserved.set(self.d.reserved.get() + bytes);
350 let t = Rc::new(TexInner { h: Handle { h, destroy: true }, d: Some(self.d.clone()), desc: *desc, bytes });
351 let layers = if desc.depth > 1 { 1 } else { desc.layers };
352 let view = make_view(&t, default_dim(desc), 0, desc.mips, 0, layers)?;
353 Ok(Texture { t, view })
354 }
355
356 pub fn create_sampler(&self, d: &SamplerDesc) -> Result<Sampler> {
357 self.d.check()?;
358 let address = |m: AddressMode| match m {
359 AddressMode::Repeat => "repeat",
360 AddressMode::MirrorRepeat => "mirror-repeat",
361 AddressMode::ClampToEdge => "clamp-to-edge",
362 };
363 let filter = |f: Filter| if f == Filter::Linear { "linear" } else { "nearest" };
364 let compare = d.compare.map_or(String::new(), |c| format!(",\"compare\":\"{}\"", compare_name(c)));
365 let json = format!(
366 "{{\"addressModeU\":\"{}\",\"addressModeV\":\"{}\",\"addressModeW\":\"{}\",\"magFilter\":\"{}\",\"minFilter\":\"{}\",\"mipmapFilter\":\"{}\",\"lodMinClamp\":{},\"lodMaxClamp\":{},\"maxAnisotropy\":{}{compare}}}",
367 address(d.address[0]), address(d.address[1]), address(d.address[2]), filter(d.mag), filter(d.min), filter(d.mip),
368 json_f64(d.lod_min as f64), json_f64(d.lod_max as f64), d.anisotropy.max(1),
369 );
370 let h = create(self.d.h, METHOD_SAMPLER, &json)?;
371 let filtering = d.mag == Filter::Linear || d.min == Filter::Linear || d.mip == Filter::Linear;
372 Ok(Sampler(Rc::new(SamplerInner { h: Handle { h, destroy: false }, comparison: d.compare.is_some(), filtering })))
373 }
374}
375
376fn compare_name(c: Compare) -> &'static str {
377 match c {
378 Compare::Never => "never",
379 Compare::Less => "less",
380 Compare::Equal => "equal",
381 Compare::LessEqual => "less-equal",
382 Compare::Greater => "greater",
383 Compare::NotEqual => "not-equal",
384 Compare::GreaterEqual => "greater-equal",
385 Compare::Always => "always",
386 }
387}
388
389pub(crate) struct SamplerInner {
390 h: Handle,
391 comparison: bool,
392 filtering: bool,
393}
394
395#[derive(Clone)]
397pub struct Sampler(pub(crate) Rc<SamplerInner>);
398
399#[derive(Clone, PartialEq)]
403struct Detail {
404 binding: u32,
405 group: u32,
406 json: String,
407}
408
409#[derive(Default)]
411struct Scan {
412 entries: Vec<(u32, String)>,
413 overrides: Vec<(u32, shader::reflect::Kind, f64, String)>,
415 bindings: Vec<Detail>,
416}
417
418fn strip_comments(src: &str) -> String {
420 let b = src.as_bytes();
421 let mut out = Vec::with_capacity(b.len());
422 let (mut i, mut depth) = (0, 0);
423 while i < b.len() {
424 if b[i..].starts_with(b"/*") {
425 depth += 1;
426 i += 2;
427 } else if depth > 0 {
428 if b[i..].starts_with(b"*/") {
429 depth -= 1;
430 i += 2;
431 } else {
432 i += 1;
433 }
434 } else if b[i..].starts_with(b"//") {
435 while i < b.len() && b[i] != b'\n' {
436 i += 1;
437 }
438 } else {
439 out.push(b[i]);
440 i += 1;
441 }
442 }
443 String::from_utf8(out).unwrap_or_default()
444}
445
446fn tokens(s: &str) -> Vec<&str> {
448 let mut out = Vec::new();
449 let mut at = None;
450 for (i, c) in s.char_indices() {
451 let word = c.is_alphanumeric() || c == '_' || c == '.';
452 match (word, at) {
453 (true, None) => at = Some(i),
454 (false, Some(a)) => {
455 out.push(&s[a..i]);
456 at = None;
457 }
458 _ => {}
459 }
460 if !word && !c.is_whitespace() {
461 out.push(&s[i..i + c.len_utf8()]);
462 }
463 }
464 if let Some(a) = at {
465 out.push(&s[a..]);
466 }
467 out
468}
469
470fn scan(src: &str) -> Scan {
471 let text = strip_comments(src);
472 let t = tokens(&text);
473 let mut s = Scan::default();
474 let (mut group, mut binding, mut id, mut stage) = (None, None, None, None);
475 let mut nameless = 0x10000u32;
476 let mut depth = 0i32;
477 let mut i = 0;
478 while i < t.len() {
479 match t[i] {
480 "{" => depth += 1,
481 "}" => depth -= 1,
482 "@" if depth == 0 => {
483 let what = t.get(i + 1).copied().unwrap_or("");
484 let arg = if t.get(i + 2) == Some(&"(") { t.get(i + 3).and_then(|v| v.trim_end_matches(['u', 'i']).parse::<u32>().ok()) } else { None };
485 match what {
486 "group" => group = arg,
487 "binding" => binding = arg,
488 "id" => id = arg,
489 "vertex" => stage = Some(0),
490 "fragment" => stage = Some(4),
491 "compute" => stage = Some(5),
492 _ => {}
493 }
494 i += 2;
495 continue;
496 }
497 "fn" if depth == 0 => {
498 if let (Some(m), Some(name)) = (stage.take(), t.get(i + 1)) {
499 s.entries.push((m, String::from(*name)));
500 }
501 }
502 "override" if depth == 0 => {
503 let name = t.get(i + 1).copied().unwrap_or("");
504 let mut k = i + 2;
505 let mut kind = shader::reflect::Kind::Float { width: 32 };
506 if t.get(k) == Some(&":") {
507 kind = match t.get(k + 1).copied().unwrap_or("") {
508 "bool" => shader::reflect::Kind::Bool,
509 "i32" => shader::reflect::Kind::Int { width: 32, signed: true },
510 "u32" => shader::reflect::Kind::Int { width: 32, signed: false },
511 "f16" => shader::reflect::Kind::Float { width: 16 },
512 _ => shader::reflect::Kind::Float { width: 32 },
513 };
514 k += 2;
515 }
516 let mut value = 0.0;
517 if t.get(k) == Some(&"=") {
518 let neg = t.get(k + 1) == Some(&"-");
519 let lit = t.get(k + if neg { 2 } else { 1 }).copied().unwrap_or("");
520 value = match lit {
521 "true" => 1.0,
522 "false" => 0.0,
523 _ => lit.trim_end_matches(['u', 'i', 'f', 'h']).parse().unwrap_or(0.0),
524 };
525 if neg {
526 value = -value;
527 }
528 }
529 let n = id.take().unwrap_or_else(|| {
530 let v = nameless;
531 nameless += 1;
532 v
533 });
534 s.overrides.push((n, kind, value, String::from(name)));
535 }
536 "var" if depth == 0 => {
537 if let (Some(g), Some(b)) = (group.take(), binding.take()) {
538 let end = (i..t.len()).find(|&k| t[k] == ";").unwrap_or(t.len());
539 if let Some(json) = binding_detail(&t[i..end]) {
540 s.bindings.push(Detail { group: g, binding: b, json });
541 }
542 }
543 }
544 _ => {}
545 }
546 i += 1;
547 }
548 s
549}
550
551fn binding_detail(t: &[&str]) -> Option<String> {
553 let at = t.iter().position(|w| *w == ":")?;
554 let ty = *t.get(at + 1)?;
555 let args: Vec<&str> = t[at + 2..].iter().copied().filter(|w| !matches!(*w, "<" | ">" | ",")).collect();
556 let dim = |ty: &str| {
557 if ty.ends_with("2d_array") {
558 "2d-array"
559 } else if ty.ends_with("cube_array") {
560 "cube-array"
561 } else if ty.ends_with("cube") {
562 "cube"
563 } else if ty.ends_with("3d") {
564 "3d"
565 } else if ty.ends_with("1d") {
566 "1d"
567 } else {
568 "2d"
569 }
570 };
571 if ty == "sampler" {
572 return Some(String::from("\"sampler\":{\"type\":\"filtering\"}"));
573 }
574 if ty == "sampler_comparison" {
575 return Some(String::from("\"sampler\":{\"type\":\"comparison\"}"));
576 }
577 if ty.starts_with("texture_storage_") {
578 let access = match args.get(1).copied().unwrap_or("write") {
579 "read" => "read-only",
580 "read_write" => "read-write",
581 _ => "write-only",
582 };
583 return Some(format!("\"storageTexture\":{{\"access\":\"{access}\",\"format\":\"{}\",\"viewDimension\":\"{}\"}}", args.first().copied().unwrap_or("rgba8unorm"), dim(ty)));
584 }
585 if ty.starts_with("texture_depth_") {
586 return Some(format!("\"texture\":{{\"sampleType\":\"depth\",\"viewDimension\":\"{}\"}}", dim(ty)));
587 }
588 if ty.starts_with("texture_") {
589 let sample = match args.first().copied().unwrap_or("f32") {
590 "i32" => "sint",
591 "u32" => "uint",
592 _ => "float",
593 };
594 return Some(format!("\"texture\":{{\"sampleType\":\"{sample}\",\"viewDimension\":\"{}\"}}", dim(ty)));
595 }
596 None
597}
598
599pub(crate) struct ShaderInner {
600 h: Handle,
601 scan: Scan,
602}
603
604#[derive(Clone)]
606pub struct ShaderModule(pub(crate) Rc<ShaderInner>);
607
608impl ShaderModule {
609 pub fn from_spirv(_device: &Device, _code: &[u32]) -> Result<ShaderModule> {
611 Err(GpuError::Unsupported("SPIR-V in the browser: ShaderModule::from_wgsl"))
612 }
613
614 pub fn from_wgsl(device: &Device, source: &str) -> Result<ShaderModule> {
617 device.d.check()?;
618 let code = source.replace("var<immediate>", "@group(3) @binding(0) var<uniform>");
619 let h = call(CALL_SHADER, &[device.d.h, code.as_ptr() as u32, code.len() as u32]);
620 if h == 0 {
621 return Err(GpuError::Failed(0));
622 }
623 Ok(ShaderModule(Rc::new(ShaderInner { h: Handle { h, destroy: false }, scan: scan(source) })))
624 }
625
626 pub fn entry_points(&self) -> Vec<(u32, &str)> {
628 self.0.scan.entries.iter().map(|(m, n)| (*m, n.as_str())).collect()
629 }
630
631 pub fn constants(&self) -> Vec<(u32, shader::reflect::Kind, f64)> {
633 self.0.scan.overrides.iter().map(|(i, k, v, _)| (*i, *k, *v)).collect()
634 }
635}
636
637pub(crate) struct BglInner {
640 d: Rc<DevInner>,
641 bindings: Vec<Binding>,
642 h: RefCell<Option<Handle>>,
644}
645
646#[derive(Clone)]
648pub struct BindGroupLayout(pub(crate) Rc<BglInner>);
649
650impl BindGroupLayout {
651 fn make(&self, detail: impl Fn(&Binding) -> Option<String>) -> Result<u32> {
653 if let Some(h) = &*self.0.h.borrow() {
654 return Ok(h.h);
655 }
656 let mut entries = Vec::new();
657 for b in &self.0.bindings {
658 let kind = match detail(b) {
659 Some(j) => j,
660 None => String::from(match b.kind {
661 BindingKind::UniformBuffer => "\"buffer\":{\"type\":\"uniform\"}",
662 BindingKind::StorageBuffer => "\"buffer\":{\"type\":\"storage\"}",
663 BindingKind::StorageBufferRead => "\"buffer\":{\"type\":\"read-only-storage\"}",
664 BindingKind::Texture => "\"texture\":{\"sampleType\":\"float\",\"viewDimension\":\"2d\"}",
665 BindingKind::StorageTexture => return Err(GpuError::Invalid("a storage texture binding: neither a bind group nor WGSL says its format yet")),
666 BindingKind::Sampler => "\"sampler\":{\"type\":\"filtering\"}",
667 }),
668 };
669 entries.push(format!("{{\"binding\":{},\"visibility\":{},{kind}}}", b.binding, b.stages.0 & 7));
670 }
671 let json = format!("{{\"entries\":[{}]}}", entries.join(","));
672 let h = create(self.0.d.h, METHOD_BIND_GROUP_LAYOUT, &json)?;
673 *self.0.h.borrow_mut() = Some(Handle { h, destroy: false });
674 Ok(h)
675 }
676}
677
678pub(crate) struct PlInner {
679 d: Rc<DevInner>,
680 pub groups: Vec<BindGroupLayout>,
681 pub push: u32,
682 h: RefCell<Option<Handle>>,
683}
684
685#[derive(Clone)]
687pub struct PipelineLayout(pub(crate) Rc<PlInner>);
688
689impl PipelineLayout {
690 fn make(&self, modules: &[&ShaderModule]) -> Result<u32> {
692 if let Some(h) = &*self.0.h.borrow() {
693 return Ok(h.h);
694 }
695 let mut args = alloc::vec![self.0.d.h, self.0.push, self.0.groups.len() as u32];
696 for (g, l) in self.0.groups.iter().enumerate() {
697 let h = l.make(|b| {
698 if matches!(b.kind, BindingKind::UniformBuffer | BindingKind::StorageBuffer | BindingKind::StorageBufferRead) {
699 return None;
700 }
701 modules.iter().find_map(|m| m.0.scan.bindings.iter().find(|d| d.group == g as u32 && d.binding == b.binding).map(|d| d.json.clone()))
702 })?;
703 args.push(h);
704 }
705 match call(CALL_PIPELINE_LAYOUT, &args) {
706 0 => Err(GpuError::Failed(0)),
707 h => {
708 *self.0.h.borrow_mut() = Some(Handle { h, destroy: false });
709 Ok(h)
710 }
711 }
712 }
713}
714
715pub(crate) struct BgInner {
716 pub h: Handle,
717 layout: BindGroupLayout,
718 _keep: Vec<Keep>,
719}
720
721#[allow(dead_code)]
723enum Keep {
724 Buffer(Buffer),
725 View(TextureView),
726 Sampler(Sampler),
727}
728
729#[derive(Clone)]
731pub struct BindGroup(pub(crate) Rc<BgInner>);
732
733impl BindGroup {
734 pub(crate) fn handle(&self) -> u32 {
735 self.0.h.h
736 }
737 pub(crate) fn fits(&self, l: &BindGroupLayout) -> bool {
738 Rc::ptr_eq(&self.0.layout.0, &l.0)
739 }
740}
741
742impl Device {
743 pub fn create_bind_group_layout(&self, bindings: &[Binding]) -> Result<BindGroupLayout> {
744 let mut seen = 0u64;
745 for b in bindings {
746 if b.binding >= 64 || seen & (1 << b.binding) != 0 {
747 return Err(GpuError::Invalid("a binding twice, or beyond 63"));
748 }
749 seen |= 1 << b.binding;
750 }
751 Ok(BindGroupLayout(Rc::new(BglInner { d: self.d.clone(), bindings: bindings.to_vec(), h: RefCell::new(None) })))
752 }
753
754 pub fn create_pipeline_layout(&self, desc: &PipelineLayoutDesc) -> Result<PipelineLayout> {
756 if desc.groups.len() > BIND_GROUPS_MAX as usize {
757 return Err(GpuError::Invalid("too many bind groups"));
758 }
759 if desc.push_constants > PUSH_CONSTANTS_MAX || desc.push_constants % 4 != 0 {
760 return Err(GpuError::Invalid("push constants: a multiple of 4 up to PUSH_CONSTANTS_MAX"));
761 }
762 if desc.push_constants != 0 && desc.groups.len() > 3 {
763 return Err(GpuError::Unsupported("four bind groups and push constants: they take the fourth in the browser"));
764 }
765 Ok(PipelineLayout(Rc::new(PlInner {
766 d: self.d.clone(),
767 groups: desc.groups.iter().map(|g| (*g).clone()).collect(),
768 push: desc.push_constants,
769 h: RefCell::new(None),
770 })))
771 }
772
773 pub fn create_bind_group(&self, desc: &BindGroupDesc) -> Result<BindGroup> {
774 self.d.check()?;
775 let bindings = &desc.layout.0.bindings;
776 if desc.entries.len() != bindings.len() {
777 return Err(GpuError::Invalid("a bind group needs one entry per binding"));
778 }
779 let entry = |b: u32| desc.entries.iter().find(|e| e.binding == b);
780 let layout = desc.layout.make(|b| match (b.kind, entry(b.binding).map(|e| e.resource)) {
781 (BindingKind::Texture, Some(Resource::Texture(v))) => {
782 let f = v.format();
783 let sample = if f.is_depth() {
784 "depth"
785 } else if matches!(f, Format::R32Uint | Format::Rg32Uint | Format::Rgba32Uint) {
786 "uint"
787 } else if f == Format::R32Sint {
788 "sint"
789 } else if matches!(f, Format::R32Float | Format::Rg32Float | Format::Rgba32Float) {
790 "unfilterable-float"
791 } else {
792 "float"
793 };
794 Some(format!("\"texture\":{{\"sampleType\":\"{sample}\",\"viewDimension\":\"{}\"}}", dim_name(v.0.dim)))
795 }
796 (BindingKind::StorageTexture, Some(Resource::StorageTexture(v))) => {
797 Some(format!("\"storageTexture\":{{\"access\":\"write-only\",\"format\":\"{}\",\"viewDimension\":\"{}\"}}", format_name(v.format()), dim_name(v.0.dim)))
798 }
799 (BindingKind::Sampler, Some(Resource::Sampler(s))) => {
800 let t = if s.0.comparison { "comparison" } else if s.0.filtering { "filtering" } else { "non-filtering" };
801 Some(format!("\"sampler\":{{\"type\":\"{t}\"}}"))
802 }
803 _ => None,
804 })?;
805 let mut keep = Vec::with_capacity(desc.entries.len());
806 let mut entries = Vec::with_capacity(desc.entries.len());
807 for e in desc.entries {
808 let Some(b) = bindings.iter().find(|b| b.binding == e.binding) else {
809 return Err(GpuError::Invalid("a bind group entry for a binding the layout lacks"));
810 };
811 let res = match (b.kind, e.resource) {
812 (BindingKind::UniformBuffer | BindingKind::StorageBuffer | BindingKind::StorageBufferRead, Resource::Buffer { buffer, offset, size }) => {
813 let need = if b.kind == BindingKind::UniformBuffer { BufferUsage::UNIFORM } else { BufferUsage::STORAGE };
814 if !buffer.usage().contains(need) {
815 return Err(GpuError::Invalid("a bind group's buffer lacks the usage"));
816 }
817 keep.push(Keep::Buffer(buffer.clone()));
818 let size = if size == 0 { buffer.size().saturating_sub(offset) } else { size };
819 format!("{{\"buffer\":{{\"$\":{}}},\"offset\":{offset},\"size\":{size}}}", buffer.handle())
820 }
821 (BindingKind::Texture, Resource::Texture(v)) | (BindingKind::StorageTexture, Resource::StorageTexture(v)) => {
822 keep.push(Keep::View(v.clone()));
823 format!("{{\"$\":{}}}", v.handle())
824 }
825 (BindingKind::Sampler, Resource::Sampler(s)) => {
826 keep.push(Keep::Sampler(s.clone()));
827 format!("{{\"$\":{}}}", s.0.h.h)
828 }
829 _ => return Err(GpuError::Invalid("a bind group entry of the wrong kind")),
830 };
831 entries.push(format!("{{\"binding\":{},\"resource\":{res}}}", e.binding));
832 }
833 let json = format!("{{\"layout\":{{\"$\":{layout}}},\"entries\":[{}]}}", entries.join(","));
834 let h = create(self.d.h, METHOD_BIND_GROUP, &json)?;
835 Ok(BindGroup(Rc::new(BgInner { h: Handle { h, destroy: false }, layout: desc.layout.clone(), _keep: keep })))
836 }
837
838 pub fn create_bindless(&self, _desc: &BindlessDesc) -> Result<Bindless> {
840 Err(GpuError::Unsupported("Bindless: level Voll, not in the browser"))
841 }
842}
843
844pub struct Bindless {
846 group: BindGroup,
847 layout: BindGroupLayout,
848}
849
850impl Bindless {
851 pub fn group(&self) -> &BindGroup {
852 &self.group
853 }
854 pub fn layout(&self) -> &BindGroupLayout {
855 &self.layout
856 }
857 pub fn counts(&self) -> [u32; 4] {
858 [0; 4]
859 }
860 pub fn set_texture(&self, _slot: u32, _view: &TextureView) -> Result<()> {
861 Err(GpuError::Unsupported("Bindless"))
862 }
863 pub fn set_sampler(&self, _slot: u32, _s: &Sampler) -> Result<()> {
864 Err(GpuError::Unsupported("Bindless"))
865 }
866 pub fn set_buffer(&self, _slot: u32, _buf: &Buffer, _offset: u64, _size: u64) -> Result<()> {
867 Err(GpuError::Unsupported("Bindless"))
868 }
869 pub fn set_storage_texture(&self, _slot: u32, _view: &TextureView) -> Result<()> {
870 Err(GpuError::Unsupported("Bindless"))
871 }
872}
873
874fn stage_json(s: &ShaderStage, model: u32, extra: &str) -> Result<String> {
878 let scan = &s.module.0.scan;
879 if !scan.entries.iter().any(|(m, n)| *m == model && n == s.entry) {
880 return Err(GpuError::Invalid("the module has no such entry point for this stage"));
881 }
882 let mut constants = Vec::with_capacity(s.constants.len());
883 for (i, c) in s.constants.iter().enumerate() {
884 let Some(o) = scan.overrides.iter().find(|o| o.0 == c.id) else {
885 return Err(GpuError::Invalid("a pipeline constant the module does not declare"));
886 };
887 if s.constants[..i].iter().any(|p| p.id == c.id) {
888 return Err(GpuError::Invalid("a pipeline constant twice"));
889 }
890 if c.value.is_nan() && o.1 != shader::reflect::Kind::Bool || c.value.is_infinite() {
891 return Err(GpuError::Invalid("a pipeline constant out of range"));
892 }
893 let key = if c.id >= 0x10000 { o.3.clone() } else { format!("{}", c.id) };
894 let value = if c.value.is_nan() { String::from("0") } else { json_f64(c.value) };
895 constants.push(format!("\"{key}\":{value}"));
896 }
897 Ok(format!("{{\"module\":{{\"$\":{}}},\"entryPoint\":\"{}\",\"constants\":{{{}}}{extra}}}", s.module.0.h.h, s.entry, constants.join(",")))
898}
899
900pub(crate) struct PipeInner {
901 pub h: Handle,
902 pub layout: PipelineLayout,
903 pub targets: Vec<Format>,
904 pub depth: Option<Format>,
905}
906
907#[derive(Clone)]
909pub struct ComputePipeline(pub(crate) Rc<PipeInner>);
910
911#[derive(Clone)]
913pub struct RenderPipeline(pub(crate) Rc<PipeInner>);
914
915fn blend_factor(f: BlendFactor) -> &'static str {
916 match f {
917 BlendFactor::Zero => "zero",
918 BlendFactor::One => "one",
919 BlendFactor::Src => "src",
920 BlendFactor::OneMinusSrc => "one-minus-src",
921 BlendFactor::Dst => "dst",
922 BlendFactor::OneMinusDst => "one-minus-dst",
923 BlendFactor::SrcAlpha => "src-alpha",
924 BlendFactor::OneMinusSrcAlpha => "one-minus-src-alpha",
925 BlendFactor::DstAlpha => "dst-alpha",
926 BlendFactor::OneMinusDstAlpha => "one-minus-dst-alpha",
927 }
928}
929
930fn blend_component(c: BlendComponent) -> String {
931 let op = match c.op {
932 BlendOp::Add => "add",
933 BlendOp::Subtract => "subtract",
934 BlendOp::ReverseSubtract => "reverse-subtract",
935 BlendOp::Min => "min",
936 BlendOp::Max => "max",
937 };
938 format!("{{\"operation\":\"{op}\",\"srcFactor\":\"{}\",\"dstFactor\":\"{}\"}}", blend_factor(c.src), blend_factor(c.dst))
939}
940
941impl Device {
942 pub fn create_compute_pipeline(&self, desc: &ComputePipelineDesc) -> Result<ComputePipeline> {
943 self.d.check()?;
944 let layout = desc.layout.make(&[desc.shader.module])?;
945 let json = format!("{{\"layout\":{{\"$\":{layout}}},\"compute\":{}}}", stage_json(&desc.shader, 5, "")?);
946 let h = create(self.d.h, METHOD_COMPUTE_PIPELINE, &json)?;
947 Ok(ComputePipeline(Rc::new(PipeInner { h: Handle { h, destroy: false }, layout: desc.layout.clone(), targets: Vec::new(), depth: None })))
948 }
949
950 pub fn create_compute_pipeline_sized(&self, desc: &ComputePipelineDesc, size: u32) -> Result<ComputePipeline> {
952 if size != 0 {
953 return Err(GpuError::Unsupported("a required subgroup size in the browser"));
954 }
955 self.create_compute_pipeline(desc)
956 }
957
958 pub fn create_render_pipeline(&self, desc: &RenderPipelineDesc) -> Result<RenderPipeline> {
959 self.d.check()?;
960 let mut modules = alloc::vec![desc.vertex.module];
961 if let Some(f) = &desc.fragment {
962 modules.push(f.module);
963 }
964 let layout = desc.layout.make(&modules)?;
965 let mut buffers = Vec::with_capacity(desc.buffers.len());
966 for b in desc.buffers {
967 let attrs: Vec<String> = b.attributes.iter().map(|a| {
968 let format = match a.format {
969 VertexFormat::F32 => "float32",
970 VertexFormat::F32x2 => "float32x2",
971 VertexFormat::F32x3 => "float32x3",
972 VertexFormat::F32x4 => "float32x4",
973 VertexFormat::U32 => "uint32",
974 VertexFormat::U32x2 => "uint32x2",
975 VertexFormat::U32x4 => "uint32x4",
976 VertexFormat::Unorm8x4 => "unorm8x4",
977 };
978 format!("{{\"format\":\"{format}\",\"offset\":{},\"shaderLocation\":{}}}", a.offset, a.location)
979 }).collect();
980 buffers.push(format!("{{\"arrayStride\":{},\"stepMode\":\"{}\",\"attributes\":[{}]}}", b.stride, if b.per_instance { "instance" } else { "vertex" }, attrs.join(",")));
981 }
982 let vertex = stage_json(&desc.vertex, 0, &format!(",\"buffers\":[{}]", buffers.join(",")))?;
983 let mut json = format!("{{\"layout\":{{\"$\":{layout}}},\"vertex\":{vertex}");
984 if let Some(f) = &desc.fragment {
985 let targets: Vec<String> = desc.targets.iter().map(|t| {
986 let blend = t.blend.map_or(String::new(), |b| format!(",\"blend\":{{\"color\":{},\"alpha\":{}}}", blend_component(b.color), blend_component(b.alpha)));
987 format!("{{\"format\":\"{}\",\"writeMask\":{}{blend}}}", format_name(t.format), t.write_mask & 0xF)
988 }).collect();
989 json.push_str(&format!(",\"fragment\":{}", stage_json(f, 4, &format!(",\"targets\":[{}]", targets.join(",")))?));
990 }
991 let topology = match desc.topology {
992 Topology::Points => "point-list",
993 Topology::Lines => "line-list",
994 Topology::LineStrip => "line-strip",
995 Topology::Triangles => "triangle-list",
996 Topology::TriangleStrip => "triangle-strip",
997 };
998 let cull = match desc.cull {
999 Cull::None => "none",
1000 Cull::Front => "front",
1001 Cull::Back => "back",
1002 };
1003 json.push_str(&format!(",\"primitive\":{{\"topology\":\"{topology}\",\"cullMode\":\"{cull}\",\"frontFace\":\"{}\"}}", if desc.front_cw { "cw" } else { "ccw" }));
1004 if let Some(d) = desc.depth {
1005 json.push_str(&format!(",\"depthStencil\":{{\"format\":\"{}\",\"depthWriteEnabled\":{},\"depthCompare\":\"{}\"}}", format_name(d.format), d.write, compare_name(d.compare)));
1006 }
1007 json.push('}');
1008 let h = create(self.d.h, METHOD_RENDER_PIPELINE, &json)?;
1009 Ok(RenderPipeline(Rc::new(PipeInner {
1010 h: Handle { h, destroy: false },
1011 layout: desc.layout.clone(),
1012 targets: desc.targets.iter().map(|t| t.format).collect(),
1013 depth: desc.depth.map(|d| d.format),
1014 })))
1015 }
1016
1017 pub fn create_timestamps(&self, _count: u32) -> Result<Timestamps> {
1020 Err(GpuError::Unsupported("timestamps in the browser"))
1021 }
1022}
1023
1024pub struct Timestamps {
1026 _none: (),
1027}
1028
1029impl Timestamps {
1030 pub fn count(&self) -> u32 {
1031 0
1032 }
1033 pub fn read_ticks(&self, _first: u32, _n: u32) -> Result<Vec<u64>> {
1034 Err(GpuError::Unsupported("timestamps in the browser"))
1035 }
1036 pub fn read_ms(&self, _first: u32, _n: u32) -> Result<Vec<f64>> {
1037 Err(GpuError::Unsupported("timestamps in the browser"))
1038 }
1039}