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(crate) fn for_frame(h: u32, format: Format, w: u32, hh: u32) -> TextureView {
217 TextureView(Rc::new(ViewInner { h: Handle { h, destroy: false }, format, w, hh, dim: ViewDim::D2, _tex: None }))
218 }
219}
220
221pub(crate) struct TexInner {
222 h: Handle,
223 d: Option<Rc<DevInner>>,
224 desc: TextureDesc,
225 bytes: u64,
226}
227
228impl Drop for TexInner {
229 fn drop(&mut self) {
230 if let Some(d) = &self.d {
231 d.reserved.set(d.reserved.get().saturating_sub(self.bytes));
232 }
233 }
234}
235
236#[derive(Clone)]
238pub struct Texture {
239 t: Rc<TexInner>,
240 view: TextureView,
241}
242
243impl Texture {
244 pub fn view(&self) -> &TextureView {
246 &self.view
247 }
248 pub fn width(&self) -> u32 {
249 self.t.desc.width
250 }
251 pub fn height(&self) -> u32 {
252 self.t.desc.height
253 }
254 pub fn depth(&self) -> u32 {
255 self.t.desc.depth
256 }
257 pub fn layers(&self) -> u32 {
258 self.t.desc.layers
259 }
260 pub fn mips(&self) -> u32 {
261 self.t.desc.mips
262 }
263 pub fn format(&self) -> Format {
264 self.t.desc.format
265 }
266 pub fn usage(&self) -> TextureUsage {
267 self.t.desc.usage
268 }
269 pub(crate) fn handle(&self) -> u32 {
270 self.t.h.h
271 }
272 pub(crate) fn for_frame(h: u32, view: TextureView, format: Format, width: u32, height: u32, usage: TextureUsage) -> Texture {
274 let desc = TextureDesc::d2(width, height, format, usage);
275 Texture { t: Rc::new(TexInner { h: Handle { h, destroy: false }, d: None, desc, bytes: 0 }), view }
276 }
277
278 pub fn create_view(&self, d: &ViewDesc) -> Result<TextureView> {
280 let t = &self.t.desc;
281 let mips = if d.mips == 0 { t.mips.saturating_sub(d.base_mip) } else { d.mips };
282 let total = if t.depth > 1 { 1 } else { t.layers };
283 let layers = if d.layers == 0 { total.saturating_sub(d.base_layer) } else { d.layers };
284 if mips == 0 || d.base_mip + mips > t.mips || layers == 0 || d.base_layer + layers > total {
285 return Err(GpuError::Invalid("create_view: outside the texture"));
286 }
287 make_view(&self.t, d.dim, d.base_mip, mips, d.base_layer, layers)
288 }
289}
290
291fn make_view(t: &Rc<TexInner>, dim: ViewDim, base_mip: u32, mips: u32, base_layer: u32, layers: u32) -> Result<TextureView> {
292 let json = format!(
293 "{{\"dimension\":\"{}\",\"baseMipLevel\":{base_mip},\"mipLevelCount\":{mips},\"baseArrayLayer\":{base_layer},\"arrayLayerCount\":{layers}}}",
294 dim_name(dim)
295 );
296 let h = create(t.h.h, METHOD_VIEW, &json)?;
297 let (w, hh) = ((t.desc.width >> base_mip).max(1), (t.desc.height >> base_mip).max(1));
298 Ok(TextureView(Rc::new(ViewInner { h: Handle { h, destroy: false }, format: t.desc.format, w, hh, dim, _tex: Some(t.clone()) })))
299}
300
301fn default_dim(d: &TextureDesc) -> ViewDim {
302 if d.depth > 1 {
303 ViewDim::D3
304 } else if d.cube {
305 if d.layers == 6 { ViewDim::Cube } else { ViewDim::CubeArray }
306 } else if d.layers > 1 {
307 ViewDim::D2Array
308 } else {
309 ViewDim::D2
310 }
311}
312
313impl Device {
314 pub fn create_texture(&self, desc: &TextureDesc) -> Result<Texture> {
315 self.d.check()?;
316 if desc.width == 0 || desc.height == 0 || desc.depth == 0 || desc.layers == 0 || desc.mips == 0 {
317 return Err(GpuError::Invalid("a texture without size"));
318 }
319 let lim = self.d.limits.max_texture_2d;
320 if desc.width > lim || desc.height > lim {
321 return Err(GpuError::Unsupported("the texture is larger than this device's limit"));
322 }
323 if desc.format.is_compressed() && !self.d.features.texture_bc {
324 return Err(GpuError::Unsupported("BC textures"));
325 }
326 let u = desc.usage.0;
327 let usage = (u & 0xF) | if u & 0x30 != 0 { 16 } else { 0 };
329 let (third, dimension) = if desc.depth > 1 { (desc.depth, "3d") } else { (desc.layers, "2d") };
330 let json = format!(
331 "{{\"size\":[{},{},{third}],\"mipLevelCount\":{},\"format\":\"{}\",\"usage\":{usage},\"dimension\":\"{dimension}\"}}",
332 desc.width, desc.height, desc.mips, format_name(desc.format)
333 );
334 let h = create(self.d.h, METHOD_TEXTURE, &json)?;
335 let dim = desc.width as u64 / desc.format.block_dim() as u64 * (desc.height / desc.format.block_dim()) as u64;
336 let mut bytes = dim * desc.format.block_bytes() as u64 * third as u64;
337 if desc.mips > 1 {
338 bytes += bytes / 3;
339 }
340 self.d.reserved.set(self.d.reserved.get() + bytes);
341 let t = Rc::new(TexInner { h: Handle { h, destroy: true }, d: Some(self.d.clone()), desc: *desc, bytes });
342 let layers = if desc.depth > 1 { 1 } else { desc.layers };
343 let view = make_view(&t, default_dim(desc), 0, desc.mips, 0, layers)?;
344 Ok(Texture { t, view })
345 }
346
347 pub fn create_sampler(&self, d: &SamplerDesc) -> Result<Sampler> {
348 self.d.check()?;
349 let address = |m: AddressMode| match m {
350 AddressMode::Repeat => "repeat",
351 AddressMode::MirrorRepeat => "mirror-repeat",
352 AddressMode::ClampToEdge => "clamp-to-edge",
353 };
354 let filter = |f: Filter| if f == Filter::Linear { "linear" } else { "nearest" };
355 let compare = d.compare.map_or(String::new(), |c| format!(",\"compare\":\"{}\"", compare_name(c)));
356 let json = format!(
357 "{{\"addressModeU\":\"{}\",\"addressModeV\":\"{}\",\"addressModeW\":\"{}\",\"magFilter\":\"{}\",\"minFilter\":\"{}\",\"mipmapFilter\":\"{}\",\"lodMinClamp\":{},\"lodMaxClamp\":{},\"maxAnisotropy\":{}{compare}}}",
358 address(d.address[0]), address(d.address[1]), address(d.address[2]), filter(d.mag), filter(d.min), filter(d.mip),
359 json_f64(d.lod_min as f64), json_f64(d.lod_max as f64), d.anisotropy.max(1),
360 );
361 let h = create(self.d.h, METHOD_SAMPLER, &json)?;
362 let filtering = d.mag == Filter::Linear || d.min == Filter::Linear || d.mip == Filter::Linear;
363 Ok(Sampler(Rc::new(SamplerInner { h: Handle { h, destroy: false }, comparison: d.compare.is_some(), filtering })))
364 }
365}
366
367fn compare_name(c: Compare) -> &'static str {
368 match c {
369 Compare::Never => "never",
370 Compare::Less => "less",
371 Compare::Equal => "equal",
372 Compare::LessEqual => "less-equal",
373 Compare::Greater => "greater",
374 Compare::NotEqual => "not-equal",
375 Compare::GreaterEqual => "greater-equal",
376 Compare::Always => "always",
377 }
378}
379
380pub(crate) struct SamplerInner {
381 h: Handle,
382 comparison: bool,
383 filtering: bool,
384}
385
386#[derive(Clone)]
388pub struct Sampler(pub(crate) Rc<SamplerInner>);
389
390#[derive(Clone, PartialEq)]
394struct Detail {
395 binding: u32,
396 group: u32,
397 json: String,
398}
399
400#[derive(Default)]
402struct Scan {
403 entries: Vec<(u32, String)>,
404 overrides: Vec<(u32, shader::reflect::Kind, f64, String)>,
406 bindings: Vec<Detail>,
407}
408
409fn strip_comments(src: &str) -> String {
411 let b = src.as_bytes();
412 let mut out = Vec::with_capacity(b.len());
413 let (mut i, mut depth) = (0, 0);
414 while i < b.len() {
415 if b[i..].starts_with(b"/*") {
416 depth += 1;
417 i += 2;
418 } else if depth > 0 {
419 if b[i..].starts_with(b"*/") {
420 depth -= 1;
421 i += 2;
422 } else {
423 i += 1;
424 }
425 } else if b[i..].starts_with(b"//") {
426 while i < b.len() && b[i] != b'\n' {
427 i += 1;
428 }
429 } else {
430 out.push(b[i]);
431 i += 1;
432 }
433 }
434 String::from_utf8(out).unwrap_or_default()
435}
436
437fn tokens(s: &str) -> Vec<&str> {
439 let mut out = Vec::new();
440 let mut at = None;
441 for (i, c) in s.char_indices() {
442 let word = c.is_alphanumeric() || c == '_' || c == '.';
443 match (word, at) {
444 (true, None) => at = Some(i),
445 (false, Some(a)) => {
446 out.push(&s[a..i]);
447 at = None;
448 }
449 _ => {}
450 }
451 if !word && !c.is_whitespace() {
452 out.push(&s[i..i + c.len_utf8()]);
453 }
454 }
455 if let Some(a) = at {
456 out.push(&s[a..]);
457 }
458 out
459}
460
461fn scan(src: &str) -> Scan {
462 let text = strip_comments(src);
463 let t = tokens(&text);
464 let mut s = Scan::default();
465 let (mut group, mut binding, mut id, mut stage) = (None, None, None, None);
466 let mut nameless = 0x10000u32;
467 let mut depth = 0i32;
468 let mut i = 0;
469 while i < t.len() {
470 match t[i] {
471 "{" => depth += 1,
472 "}" => depth -= 1,
473 "@" if depth == 0 => {
474 let what = t.get(i + 1).copied().unwrap_or("");
475 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 };
476 match what {
477 "group" => group = arg,
478 "binding" => binding = arg,
479 "id" => id = arg,
480 "vertex" => stage = Some(0),
481 "fragment" => stage = Some(4),
482 "compute" => stage = Some(5),
483 _ => {}
484 }
485 i += 2;
486 continue;
487 }
488 "fn" if depth == 0 => {
489 if let (Some(m), Some(name)) = (stage.take(), t.get(i + 1)) {
490 s.entries.push((m, String::from(*name)));
491 }
492 }
493 "override" if depth == 0 => {
494 let name = t.get(i + 1).copied().unwrap_or("");
495 let mut k = i + 2;
496 let mut kind = shader::reflect::Kind::Float { width: 32 };
497 if t.get(k) == Some(&":") {
498 kind = match t.get(k + 1).copied().unwrap_or("") {
499 "bool" => shader::reflect::Kind::Bool,
500 "i32" => shader::reflect::Kind::Int { width: 32, signed: true },
501 "u32" => shader::reflect::Kind::Int { width: 32, signed: false },
502 "f16" => shader::reflect::Kind::Float { width: 16 },
503 _ => shader::reflect::Kind::Float { width: 32 },
504 };
505 k += 2;
506 }
507 let mut value = 0.0;
508 if t.get(k) == Some(&"=") {
509 let neg = t.get(k + 1) == Some(&"-");
510 let lit = t.get(k + if neg { 2 } else { 1 }).copied().unwrap_or("");
511 value = match lit {
512 "true" => 1.0,
513 "false" => 0.0,
514 _ => lit.trim_end_matches(['u', 'i', 'f', 'h']).parse().unwrap_or(0.0),
515 };
516 if neg {
517 value = -value;
518 }
519 }
520 let n = id.take().unwrap_or_else(|| {
521 let v = nameless;
522 nameless += 1;
523 v
524 });
525 s.overrides.push((n, kind, value, String::from(name)));
526 }
527 "var" if depth == 0 => {
528 if let (Some(g), Some(b)) = (group.take(), binding.take()) {
529 let end = (i..t.len()).find(|&k| t[k] == ";").unwrap_or(t.len());
530 if let Some(json) = binding_detail(&t[i..end]) {
531 s.bindings.push(Detail { group: g, binding: b, json });
532 }
533 }
534 }
535 _ => {}
536 }
537 i += 1;
538 }
539 s
540}
541
542fn binding_detail(t: &[&str]) -> Option<String> {
544 let at = t.iter().position(|w| *w == ":")?;
545 let ty = *t.get(at + 1)?;
546 let args: Vec<&str> = t[at + 2..].iter().copied().filter(|w| !matches!(*w, "<" | ">" | ",")).collect();
547 let dim = |ty: &str| {
548 if ty.ends_with("2d_array") {
549 "2d-array"
550 } else if ty.ends_with("cube_array") {
551 "cube-array"
552 } else if ty.ends_with("cube") {
553 "cube"
554 } else if ty.ends_with("3d") {
555 "3d"
556 } else if ty.ends_with("1d") {
557 "1d"
558 } else {
559 "2d"
560 }
561 };
562 if ty == "sampler" {
563 return Some(String::from("\"sampler\":{\"type\":\"filtering\"}"));
564 }
565 if ty == "sampler_comparison" {
566 return Some(String::from("\"sampler\":{\"type\":\"comparison\"}"));
567 }
568 if ty.starts_with("texture_storage_") {
569 let access = match args.get(1).copied().unwrap_or("write") {
570 "read" => "read-only",
571 "read_write" => "read-write",
572 _ => "write-only",
573 };
574 return Some(format!("\"storageTexture\":{{\"access\":\"{access}\",\"format\":\"{}\",\"viewDimension\":\"{}\"}}", args.first().copied().unwrap_or("rgba8unorm"), dim(ty)));
575 }
576 if ty.starts_with("texture_depth_") {
577 return Some(format!("\"texture\":{{\"sampleType\":\"depth\",\"viewDimension\":\"{}\"}}", dim(ty)));
578 }
579 if ty.starts_with("texture_") {
580 let sample = match args.first().copied().unwrap_or("f32") {
581 "i32" => "sint",
582 "u32" => "uint",
583 _ => "float",
584 };
585 return Some(format!("\"texture\":{{\"sampleType\":\"{sample}\",\"viewDimension\":\"{}\"}}", dim(ty)));
586 }
587 None
588}
589
590pub(crate) struct ShaderInner {
591 h: Handle,
592 scan: Scan,
593}
594
595#[derive(Clone)]
597pub struct ShaderModule(pub(crate) Rc<ShaderInner>);
598
599impl ShaderModule {
600 pub fn from_spirv(_device: &Device, _code: &[u32]) -> Result<ShaderModule> {
602 Err(GpuError::Unsupported("SPIR-V in the browser: ShaderModule::from_wgsl"))
603 }
604
605 pub fn from_wgsl(device: &Device, source: &str) -> Result<ShaderModule> {
608 device.d.check()?;
609 let code = source.replace("var<immediate>", "@group(3) @binding(0) var<uniform>");
610 let h = call(CALL_SHADER, &[device.d.h, code.as_ptr() as u32, code.len() as u32]);
611 if h == 0 {
612 return Err(GpuError::Failed(0));
613 }
614 Ok(ShaderModule(Rc::new(ShaderInner { h: Handle { h, destroy: false }, scan: scan(source) })))
615 }
616
617 pub fn entry_points(&self) -> Vec<(u32, &str)> {
619 self.0.scan.entries.iter().map(|(m, n)| (*m, n.as_str())).collect()
620 }
621
622 pub fn constants(&self) -> Vec<(u32, shader::reflect::Kind, f64)> {
624 self.0.scan.overrides.iter().map(|(i, k, v, _)| (*i, *k, *v)).collect()
625 }
626}
627
628pub(crate) struct BglInner {
631 d: Rc<DevInner>,
632 bindings: Vec<Binding>,
633 h: RefCell<Option<Handle>>,
635}
636
637#[derive(Clone)]
639pub struct BindGroupLayout(pub(crate) Rc<BglInner>);
640
641impl BindGroupLayout {
642 fn make(&self, detail: impl Fn(&Binding) -> Option<String>) -> Result<u32> {
644 if let Some(h) = &*self.0.h.borrow() {
645 return Ok(h.h);
646 }
647 let mut entries = Vec::new();
648 for b in &self.0.bindings {
649 let kind = match detail(b) {
650 Some(j) => j,
651 None => String::from(match b.kind {
652 BindingKind::UniformBuffer => "\"buffer\":{\"type\":\"uniform\"}",
653 BindingKind::StorageBuffer => "\"buffer\":{\"type\":\"storage\"}",
654 BindingKind::StorageBufferRead => "\"buffer\":{\"type\":\"read-only-storage\"}",
655 BindingKind::Texture => "\"texture\":{\"sampleType\":\"float\",\"viewDimension\":\"2d\"}",
656 BindingKind::StorageTexture => return Err(GpuError::Invalid("a storage texture binding: neither a bind group nor WGSL says its format yet")),
657 BindingKind::Sampler => "\"sampler\":{\"type\":\"filtering\"}",
658 }),
659 };
660 entries.push(format!("{{\"binding\":{},\"visibility\":{},{kind}}}", b.binding, b.stages.0 & 7));
661 }
662 let json = format!("{{\"entries\":[{}]}}", entries.join(","));
663 let h = create(self.0.d.h, METHOD_BIND_GROUP_LAYOUT, &json)?;
664 *self.0.h.borrow_mut() = Some(Handle { h, destroy: false });
665 Ok(h)
666 }
667}
668
669pub(crate) struct PlInner {
670 d: Rc<DevInner>,
671 pub groups: Vec<BindGroupLayout>,
672 pub push: u32,
673 h: RefCell<Option<Handle>>,
674}
675
676#[derive(Clone)]
678pub struct PipelineLayout(pub(crate) Rc<PlInner>);
679
680impl PipelineLayout {
681 fn make(&self, modules: &[&ShaderModule]) -> Result<u32> {
683 if let Some(h) = &*self.0.h.borrow() {
684 return Ok(h.h);
685 }
686 let mut args = alloc::vec![self.0.d.h, self.0.push, self.0.groups.len() as u32];
687 for (g, l) in self.0.groups.iter().enumerate() {
688 let h = l.make(|b| {
689 if matches!(b.kind, BindingKind::UniformBuffer | BindingKind::StorageBuffer | BindingKind::StorageBufferRead) {
690 return None;
691 }
692 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()))
693 })?;
694 args.push(h);
695 }
696 match call(CALL_PIPELINE_LAYOUT, &args) {
697 0 => Err(GpuError::Failed(0)),
698 h => {
699 *self.0.h.borrow_mut() = Some(Handle { h, destroy: false });
700 Ok(h)
701 }
702 }
703 }
704}
705
706pub(crate) struct BgInner {
707 pub h: Handle,
708 layout: BindGroupLayout,
709 _keep: Vec<Keep>,
710}
711
712#[allow(dead_code)]
714enum Keep {
715 Buffer(Buffer),
716 View(TextureView),
717 Sampler(Sampler),
718}
719
720#[derive(Clone)]
722pub struct BindGroup(pub(crate) Rc<BgInner>);
723
724impl BindGroup {
725 pub(crate) fn handle(&self) -> u32 {
726 self.0.h.h
727 }
728 pub(crate) fn fits(&self, l: &BindGroupLayout) -> bool {
729 Rc::ptr_eq(&self.0.layout.0, &l.0)
730 }
731}
732
733impl Device {
734 pub fn create_bind_group_layout(&self, bindings: &[Binding]) -> Result<BindGroupLayout> {
735 let mut seen = 0u64;
736 for b in bindings {
737 if b.binding >= 64 || seen & (1 << b.binding) != 0 {
738 return Err(GpuError::Invalid("a binding twice, or beyond 63"));
739 }
740 seen |= 1 << b.binding;
741 }
742 Ok(BindGroupLayout(Rc::new(BglInner { d: self.d.clone(), bindings: bindings.to_vec(), h: RefCell::new(None) })))
743 }
744
745 pub fn create_pipeline_layout(&self, desc: &PipelineLayoutDesc) -> Result<PipelineLayout> {
747 if desc.groups.len() > BIND_GROUPS_MAX as usize {
748 return Err(GpuError::Invalid("too many bind groups"));
749 }
750 if desc.push_constants > PUSH_CONSTANTS_MAX || desc.push_constants % 4 != 0 {
751 return Err(GpuError::Invalid("push constants: a multiple of 4 up to PUSH_CONSTANTS_MAX"));
752 }
753 if desc.push_constants != 0 && desc.groups.len() > 3 {
754 return Err(GpuError::Unsupported("four bind groups and push constants: they take the fourth in the browser"));
755 }
756 Ok(PipelineLayout(Rc::new(PlInner {
757 d: self.d.clone(),
758 groups: desc.groups.iter().map(|g| (*g).clone()).collect(),
759 push: desc.push_constants,
760 h: RefCell::new(None),
761 })))
762 }
763
764 pub fn create_bind_group(&self, desc: &BindGroupDesc) -> Result<BindGroup> {
765 self.d.check()?;
766 let bindings = &desc.layout.0.bindings;
767 if desc.entries.len() != bindings.len() {
768 return Err(GpuError::Invalid("a bind group needs one entry per binding"));
769 }
770 let entry = |b: u32| desc.entries.iter().find(|e| e.binding == b);
771 let layout = desc.layout.make(|b| match (b.kind, entry(b.binding).map(|e| e.resource)) {
772 (BindingKind::Texture, Some(Resource::Texture(v))) => {
773 let f = v.format();
774 let sample = if f.is_depth() {
775 "depth"
776 } else if matches!(f, Format::R32Uint | Format::Rg32Uint | Format::Rgba32Uint) {
777 "uint"
778 } else if f == Format::R32Sint {
779 "sint"
780 } else if matches!(f, Format::R32Float | Format::Rg32Float | Format::Rgba32Float) {
781 "unfilterable-float"
782 } else {
783 "float"
784 };
785 Some(format!("\"texture\":{{\"sampleType\":\"{sample}\",\"viewDimension\":\"{}\"}}", dim_name(v.0.dim)))
786 }
787 (BindingKind::StorageTexture, Some(Resource::StorageTexture(v))) => {
788 Some(format!("\"storageTexture\":{{\"access\":\"write-only\",\"format\":\"{}\",\"viewDimension\":\"{}\"}}", format_name(v.format()), dim_name(v.0.dim)))
789 }
790 (BindingKind::Sampler, Some(Resource::Sampler(s))) => {
791 let t = if s.0.comparison { "comparison" } else if s.0.filtering { "filtering" } else { "non-filtering" };
792 Some(format!("\"sampler\":{{\"type\":\"{t}\"}}"))
793 }
794 _ => None,
795 })?;
796 let mut keep = Vec::with_capacity(desc.entries.len());
797 let mut entries = Vec::with_capacity(desc.entries.len());
798 for e in desc.entries {
799 let Some(b) = bindings.iter().find(|b| b.binding == e.binding) else {
800 return Err(GpuError::Invalid("a bind group entry for a binding the layout lacks"));
801 };
802 let res = match (b.kind, e.resource) {
803 (BindingKind::UniformBuffer | BindingKind::StorageBuffer | BindingKind::StorageBufferRead, Resource::Buffer { buffer, offset, size }) => {
804 let need = if b.kind == BindingKind::UniformBuffer { BufferUsage::UNIFORM } else { BufferUsage::STORAGE };
805 if !buffer.usage().contains(need) {
806 return Err(GpuError::Invalid("a bind group's buffer lacks the usage"));
807 }
808 keep.push(Keep::Buffer(buffer.clone()));
809 let size = if size == 0 { buffer.size().saturating_sub(offset) } else { size };
810 format!("{{\"buffer\":{{\"$\":{}}},\"offset\":{offset},\"size\":{size}}}", buffer.handle())
811 }
812 (BindingKind::Texture, Resource::Texture(v)) | (BindingKind::StorageTexture, Resource::StorageTexture(v)) => {
813 keep.push(Keep::View(v.clone()));
814 format!("{{\"$\":{}}}", v.handle())
815 }
816 (BindingKind::Sampler, Resource::Sampler(s)) => {
817 keep.push(Keep::Sampler(s.clone()));
818 format!("{{\"$\":{}}}", s.0.h.h)
819 }
820 _ => return Err(GpuError::Invalid("a bind group entry of the wrong kind")),
821 };
822 entries.push(format!("{{\"binding\":{},\"resource\":{res}}}", e.binding));
823 }
824 let json = format!("{{\"layout\":{{\"$\":{layout}}},\"entries\":[{}]}}", entries.join(","));
825 let h = create(self.d.h, METHOD_BIND_GROUP, &json)?;
826 Ok(BindGroup(Rc::new(BgInner { h: Handle { h, destroy: false }, layout: desc.layout.clone(), _keep: keep })))
827 }
828
829 pub fn create_bindless(&self, _desc: &BindlessDesc) -> Result<Bindless> {
831 Err(GpuError::Unsupported("Bindless: level Voll, not in the browser"))
832 }
833}
834
835pub struct Bindless {
837 group: BindGroup,
838 layout: BindGroupLayout,
839}
840
841impl Bindless {
842 pub fn group(&self) -> &BindGroup {
843 &self.group
844 }
845 pub fn layout(&self) -> &BindGroupLayout {
846 &self.layout
847 }
848 pub fn counts(&self) -> [u32; 4] {
849 [0; 4]
850 }
851 pub fn set_texture(&self, _slot: u32, _view: &TextureView) -> Result<()> {
852 Err(GpuError::Unsupported("Bindless"))
853 }
854 pub fn set_sampler(&self, _slot: u32, _s: &Sampler) -> Result<()> {
855 Err(GpuError::Unsupported("Bindless"))
856 }
857 pub fn set_buffer(&self, _slot: u32, _buf: &Buffer, _offset: u64, _size: u64) -> Result<()> {
858 Err(GpuError::Unsupported("Bindless"))
859 }
860 pub fn set_storage_texture(&self, _slot: u32, _view: &TextureView) -> Result<()> {
861 Err(GpuError::Unsupported("Bindless"))
862 }
863}
864
865fn stage_json(s: &ShaderStage, model: u32, extra: &str) -> Result<String> {
869 let scan = &s.module.0.scan;
870 if !scan.entries.iter().any(|(m, n)| *m == model && n == s.entry) {
871 return Err(GpuError::Invalid("the module has no such entry point for this stage"));
872 }
873 let mut constants = Vec::with_capacity(s.constants.len());
874 for (i, c) in s.constants.iter().enumerate() {
875 let Some(o) = scan.overrides.iter().find(|o| o.0 == c.id) else {
876 return Err(GpuError::Invalid("a pipeline constant the module does not declare"));
877 };
878 if s.constants[..i].iter().any(|p| p.id == c.id) {
879 return Err(GpuError::Invalid("a pipeline constant twice"));
880 }
881 if c.value.is_nan() && o.1 != shader::reflect::Kind::Bool || c.value.is_infinite() {
882 return Err(GpuError::Invalid("a pipeline constant out of range"));
883 }
884 let key = if c.id >= 0x10000 { o.3.clone() } else { format!("{}", c.id) };
885 let value = if c.value.is_nan() { String::from("0") } else { json_f64(c.value) };
886 constants.push(format!("\"{key}\":{value}"));
887 }
888 Ok(format!("{{\"module\":{{\"$\":{}}},\"entryPoint\":\"{}\",\"constants\":{{{}}}{extra}}}", s.module.0.h.h, s.entry, constants.join(",")))
889}
890
891pub(crate) struct PipeInner {
892 pub h: Handle,
893 pub layout: PipelineLayout,
894 pub targets: Vec<Format>,
895 pub depth: Option<Format>,
896}
897
898#[derive(Clone)]
900pub struct ComputePipeline(pub(crate) Rc<PipeInner>);
901
902#[derive(Clone)]
904pub struct RenderPipeline(pub(crate) Rc<PipeInner>);
905
906fn blend_factor(f: BlendFactor) -> &'static str {
907 match f {
908 BlendFactor::Zero => "zero",
909 BlendFactor::One => "one",
910 BlendFactor::Src => "src",
911 BlendFactor::OneMinusSrc => "one-minus-src",
912 BlendFactor::Dst => "dst",
913 BlendFactor::OneMinusDst => "one-minus-dst",
914 BlendFactor::SrcAlpha => "src-alpha",
915 BlendFactor::OneMinusSrcAlpha => "one-minus-src-alpha",
916 BlendFactor::DstAlpha => "dst-alpha",
917 BlendFactor::OneMinusDstAlpha => "one-minus-dst-alpha",
918 }
919}
920
921fn blend_component(c: BlendComponent) -> String {
922 let op = match c.op {
923 BlendOp::Add => "add",
924 BlendOp::Subtract => "subtract",
925 BlendOp::ReverseSubtract => "reverse-subtract",
926 BlendOp::Min => "min",
927 BlendOp::Max => "max",
928 };
929 format!("{{\"operation\":\"{op}\",\"srcFactor\":\"{}\",\"dstFactor\":\"{}\"}}", blend_factor(c.src), blend_factor(c.dst))
930}
931
932impl Device {
933 pub fn create_compute_pipeline(&self, desc: &ComputePipelineDesc) -> Result<ComputePipeline> {
934 self.d.check()?;
935 let layout = desc.layout.make(&[desc.shader.module])?;
936 let json = format!("{{\"layout\":{{\"$\":{layout}}},\"compute\":{}}}", stage_json(&desc.shader, 5, "")?);
937 let h = create(self.d.h, METHOD_COMPUTE_PIPELINE, &json)?;
938 Ok(ComputePipeline(Rc::new(PipeInner { h: Handle { h, destroy: false }, layout: desc.layout.clone(), targets: Vec::new(), depth: None })))
939 }
940
941 pub fn create_compute_pipeline_sized(&self, desc: &ComputePipelineDesc, size: u32) -> Result<ComputePipeline> {
943 if size != 0 {
944 return Err(GpuError::Unsupported("a required subgroup size in the browser"));
945 }
946 self.create_compute_pipeline(desc)
947 }
948
949 pub fn create_render_pipeline(&self, desc: &RenderPipelineDesc) -> Result<RenderPipeline> {
950 self.d.check()?;
951 let mut modules = alloc::vec![desc.vertex.module];
952 if let Some(f) = &desc.fragment {
953 modules.push(f.module);
954 }
955 let layout = desc.layout.make(&modules)?;
956 let mut buffers = Vec::with_capacity(desc.buffers.len());
957 for b in desc.buffers {
958 let attrs: Vec<String> = b.attributes.iter().map(|a| {
959 let format = match a.format {
960 VertexFormat::F32 => "float32",
961 VertexFormat::F32x2 => "float32x2",
962 VertexFormat::F32x3 => "float32x3",
963 VertexFormat::F32x4 => "float32x4",
964 VertexFormat::U32 => "uint32",
965 VertexFormat::U32x2 => "uint32x2",
966 VertexFormat::U32x4 => "uint32x4",
967 VertexFormat::Unorm8x4 => "unorm8x4",
968 };
969 format!("{{\"format\":\"{format}\",\"offset\":{},\"shaderLocation\":{}}}", a.offset, a.location)
970 }).collect();
971 buffers.push(format!("{{\"arrayStride\":{},\"stepMode\":\"{}\",\"attributes\":[{}]}}", b.stride, if b.per_instance { "instance" } else { "vertex" }, attrs.join(",")));
972 }
973 let vertex = stage_json(&desc.vertex, 0, &format!(",\"buffers\":[{}]", buffers.join(",")))?;
974 let mut json = format!("{{\"layout\":{{\"$\":{layout}}},\"vertex\":{vertex}");
975 if let Some(f) = &desc.fragment {
976 let targets: Vec<String> = desc.targets.iter().map(|t| {
977 let blend = t.blend.map_or(String::new(), |b| format!(",\"blend\":{{\"color\":{},\"alpha\":{}}}", blend_component(b.color), blend_component(b.alpha)));
978 format!("{{\"format\":\"{}\",\"writeMask\":{}{blend}}}", format_name(t.format), t.write_mask & 0xF)
979 }).collect();
980 json.push_str(&format!(",\"fragment\":{}", stage_json(f, 4, &format!(",\"targets\":[{}]", targets.join(",")))?));
981 }
982 let topology = match desc.topology {
983 Topology::Points => "point-list",
984 Topology::Lines => "line-list",
985 Topology::LineStrip => "line-strip",
986 Topology::Triangles => "triangle-list",
987 Topology::TriangleStrip => "triangle-strip",
988 };
989 let cull = match desc.cull {
990 Cull::None => "none",
991 Cull::Front => "front",
992 Cull::Back => "back",
993 };
994 json.push_str(&format!(",\"primitive\":{{\"topology\":\"{topology}\",\"cullMode\":\"{cull}\",\"frontFace\":\"{}\"}}", if desc.front_cw { "cw" } else { "ccw" }));
995 if let Some(d) = desc.depth {
996 json.push_str(&format!(",\"depthStencil\":{{\"format\":\"{}\",\"depthWriteEnabled\":{},\"depthCompare\":\"{}\"}}", format_name(d.format), d.write, compare_name(d.compare)));
997 }
998 json.push('}');
999 let h = create(self.d.h, METHOD_RENDER_PIPELINE, &json)?;
1000 Ok(RenderPipeline(Rc::new(PipeInner {
1001 h: Handle { h, destroy: false },
1002 layout: desc.layout.clone(),
1003 targets: desc.targets.iter().map(|t| t.format).collect(),
1004 depth: desc.depth.map(|d| d.format),
1005 })))
1006 }
1007
1008 pub fn create_timestamps(&self, _count: u32) -> Result<Timestamps> {
1011 Err(GpuError::Unsupported("timestamps in the browser"))
1012 }
1013}
1014
1015pub struct Timestamps {
1017 _none: (),
1018}
1019
1020impl Timestamps {
1021 pub fn count(&self) -> u32 {
1022 0
1023 }
1024 pub fn read_ticks(&self, _first: u32, _n: u32) -> Result<Vec<u64>> {
1025 Err(GpuError::Unsupported("timestamps in the browser"))
1026 }
1027 pub fn read_ms(&self, _first: u32, _n: u32) -> Result<Vec<f64>> {
1028 Err(GpuError::Unsupported("timestamps in the browser"))
1029 }
1030}