1use std::collections::HashMap;
6#[cfg(feature = "wgpu")]
7use std::sync::{Arc, Mutex};
9
10use crate::gpu::{GpuBufferImpl, GpuCompilerImpl, GpuContextImpl, GpuError, GpuKernelImpl};
11
12#[cfg(feature = "wgpu")]
13#[allow(unused_imports)]
14use wgpu::{
15 util::DeviceExt, Backends, BindGroupDescriptor, BindGroupEntry, BindGroupLayout,
16 BindGroupLayoutDescriptor, BindGroupLayoutEntry, BindingResource, BindingType, Buffer,
17 BufferBindingType, BufferDescriptor, BufferUsages, ComputePipeline, Device, DeviceDescriptor,
18 Features, Instance, InstanceDescriptor, Limits, PowerPreference, Queue, RequestAdapterOptions,
19 ShaderModuleDescriptor, ShaderSource, ShaderStages, StorageTextureAccess, TextureFormat,
20 TextureSampleType, TextureViewDimension,
21};
22
23#[cfg(not(feature = "wgpu"))]
25type WgpuDevice = *mut std::ffi::c_void;
26#[cfg(not(feature = "wgpu"))]
27type WgpuQueue = *mut std::ffi::c_void;
28#[cfg(not(feature = "wgpu"))]
29type WgpuBuffer = *mut std::ffi::c_void;
30#[cfg(not(feature = "wgpu"))]
31type WgpuComputePipeline = *mut std::ffi::c_void;
32
33#[cfg(feature = "wgpu")]
38pub struct WgpuComputePipeline {
39 pub pipeline: ComputePipeline,
41 pub bind_group_layout: BindGroupLayout,
43 pub workgroup_size: [u32; 3],
45}
46
47#[cfg(feature = "wgpu")]
48unsafe impl Send for WgpuComputePipeline {}
50#[cfg(feature = "wgpu")]
51unsafe impl Sync for WgpuComputePipeline {}
52
53#[cfg(feature = "wgpu")]
74pub fn try_compile_wgsl(source: &str) -> Result<WgpuComputePipeline, GpuError> {
75 let ctx = WebGPUContext::new()?;
76 ctx.compile_to_pipeline(source)
77}
78
79#[cfg(feature = "wgpu")]
84pub fn run_vector_add_wgsl(a: &[f32], b: &[f32]) -> Result<Vec<f32>, GpuError> {
85 let ctx = WebGPUContext::new()?;
86 ctx.run_vector_add(a, b)
87}
88
89pub const GEMM_SHADER_WGSL: &str = r#"
99@group(0) @binding(0) var<storage, read> matrix_a: array<f32>;
100@group(0) @binding(1) var<storage, read> matrix_b: array<f32>;
101@group(0) @binding(2) var<storage, read_write> matrix_c: array<f32>;
102
103struct GemmUniforms {
104 M: u32,
105 N: u32,
106 K: u32,
107 alpha: f32,
108 beta: f32,
109};
110
111@group(0) @binding(3) var<uniform> uniforms: GemmUniforms;
112
113@compute @workgroup_size(8, 8)
114fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
115 let row = global_id.x;
116 let col = global_id.y;
117
118 if row >= uniforms.M || col >= uniforms.N { return; }
119
120 var sum = 0.0f;
121 for (var k = 0u; k < uniforms.K; k++) {
122 sum += matrix_a[row * uniforms.K + k] * matrix_b[k * uniforms.N + col];
123 }
124
125 let idx = row * uniforms.N + col;
126 matrix_c[idx] = uniforms.alpha * sum + uniforms.beta * matrix_c[idx];
127}
128"#;
129
130pub struct WebGPUContext {
132 #[cfg(feature = "wgpu")]
133 device: Arc<Device>,
134 #[cfg(feature = "wgpu")]
135 queue: Arc<Queue>,
136 #[cfg(not(feature = "wgpu"))]
137 device: Arc<WgpuDevice>,
138 #[cfg(not(feature = "wgpu"))]
139 queue: Arc<WgpuQueue>,
140 compiled_shaders: Arc<Mutex<HashMap<String, WebGPUShader>>>,
141 memory_pool: Arc<Mutex<WebGPUMemoryPool>>,
142}
143
144unsafe impl Send for WebGPUContext {}
146unsafe impl Sync for WebGPUContext {}
147
148impl WebGPUContext {
149 pub fn new() -> Result<Self, GpuError> {
151 #[cfg(feature = "wgpu")]
152 {
153 let instance_desc = InstanceDescriptor {
163 backends: Backends::PRIMARY,
164 flags: wgpu::InstanceFlags::default(),
165 memory_budget_thresholds: Default::default(),
166 backend_options: Default::default(),
167 display: None,
168 };
169 let instance = Instance::new(instance_desc);
170
171 let adapter = pollster::block_on(instance.request_adapter(&RequestAdapterOptions {
172 power_preference: PowerPreference::HighPerformance,
173 compatible_surface: None,
174 force_fallback_adapter: false,
175 }))
176 .map_err(|e| GpuError::Other(format!("Failed to find WebGPU adapter: {e}")))?;
177
178 let device_descriptor = DeviceDescriptor {
179 label: Some("SciRS2 WebGPU Device"),
180 required_features: Features::empty(),
181 required_limits: Limits::default(),
182 ..Default::default()
184 };
185
186 let (device, queue) = pollster::block_on(adapter.request_device(&device_descriptor))
187 .map_err(|e| GpuError::Other(format!("{e}")))?;
188
189 Ok(Self {
190 device: Arc::new(device),
191 queue: Arc::new(queue),
192 compiled_shaders: Arc::new(Mutex::new(HashMap::new())),
193 memory_pool: Arc::new(Mutex::new(WebGPUMemoryPool::new(1024 * 1024 * 1024))), })
195 }
196 #[cfg(not(feature = "wgpu"))]
197 {
198 let device = Self::initialize_webgpu()?;
200 let queue = Self::create_queue(device)?;
201
202 Ok(Self {
203 device,
204 queue,
205 compiled_shaders: Arc::new(Mutex::new(HashMap::new())),
206 memory_pool: Arc::new(Mutex::new(WebGPUMemoryPool::new(1024 * 1024 * 1024))), })
208 }
209 }
210
211 pub fn is_available() -> bool {
213 #[cfg(feature = "wgpu")]
214 {
215 let instance_desc = InstanceDescriptor {
218 backends: Backends::PRIMARY,
219 flags: wgpu::InstanceFlags::default(),
220 memory_budget_thresholds: Default::default(),
221 backend_options: Default::default(),
222 display: None,
223 };
224 let instance = Instance::new(instance_desc);
225
226 pollster::block_on(async {
228 instance
229 .request_adapter(&RequestAdapterOptions {
230 power_preference: PowerPreference::default(),
231 compatible_surface: None,
232 force_fallback_adapter: false,
233 })
234 .await
235 .is_ok()
236 })
237 }
238 #[cfg(not(feature = "wgpu"))]
239 {
240 false
242 }
243 }
244
245 fn compile_shader_internal(&self, source: &str, name: &str) -> Result<WebGPUShader, GpuError> {
247 #[cfg(feature = "wgpu")]
248 {
249 let shader_module = self.device.create_shader_module(ShaderModuleDescriptor {
251 label: Some(name),
252 source: ShaderSource::Wgsl(source.into()),
253 });
254
255 let entry_point = Self::extract_entry_point(source).unwrap_or("main");
257
258 let (bind_group_layout, binding_infos) =
260 self.create_bind_group_layout_from_source(source, name)?;
261
262 let pipeline_layout =
264 self.device
265 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
266 label: Some(&format!("{}_layout", name)),
267 bind_group_layouts: &[Some(&bind_group_layout)],
268 ..Default::default()
270 });
271
272 let compute_pipeline =
273 self.device
274 .create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
275 label: Some(&format!("{}_pipeline", name)),
276 layout: Some(&pipeline_layout),
277 module: &shader_module,
278 entry_point: Some(entry_point),
279 compilation_options: Default::default(),
280 cache: None,
281 });
282
283 let workgroup_size = extract_workgroup_size(source);
284
285 Ok(WebGPUShader {
286 pipeline: compute_pipeline,
287 bind_group_layout,
288 name: name.to_string(),
289 binding_infos,
290 workgroup_size,
291 })
292 }
293 #[cfg(not(feature = "wgpu"))]
294 {
295 let pipeline = Self::compile_wgsl_source(source, name)?;
297
298 Ok(WebGPUShader {
299 pipeline,
300 bind_group_layout: std::ptr::null_mut(),
301 name: name.to_string(),
302 binding_infos: Vec::new(),
303 workgroup_size: [64, 1, 1],
304 })
305 }
306 }
307
308 #[cfg(feature = "wgpu")]
310 fn create_bind_group_layout_from_source(
311 &self,
312 source: &str,
313 name: &str,
314 ) -> Result<(BindGroupLayout, Vec<BindingInfo>), GpuError> {
315 #[derive(Default)]
316 struct PendingAttr {
317 group: Option<u32>,
318 binding: Option<u32>,
319 }
320 let mut pending = PendingAttr::default();
321 let mut entries: Vec<BindGroupLayoutEntry> = Vec::new();
322 let mut infos: Vec<BindingInfo> = Vec::new();
323
324 fn strip_comment(line: &str) -> &str {
325 line.split_once("//").map(|(a, _)| a).unwrap_or(line)
326 }
327
328 for raw_line in source.lines() {
329 let line = strip_comment(raw_line).trim();
330 if line.is_empty() {
331 continue;
332 }
333
334 if let Some(i) = line.find("@group(") {
335 if let Some(end) = line[i + 7..].find(')') {
336 if let Ok(g) = line[i + 7..i + 7 + end].parse::<u32>() {
337 pending.group = Some(g);
338 }
339 }
340 }
341 if let Some(i) = line.find("@binding(") {
342 if let Some(end) = line[i + 9..].find(')') {
343 if let Ok(b) = line[i + 9..i + 9 + end].parse::<u32>() {
344 pending.binding = Some(b);
345 }
346 }
347 }
348
349 if line.contains("var<") {
350 if pending.group.unwrap_or(0) == 0 {
352 let binding_num = pending.binding.unwrap_or_else(|| entries.len() as u32);
354 let name = extract_var_name(line).unwrap_or("");
355 let storage = line.contains("var<storage");
356 let uniform = line.contains("var<uniform");
357 let read_only = storage
358 && (line.contains(", read>")
359 || line.contains("var<storage, read>")
360 || line.contains("var<storage, read,"));
361 if storage {
362 entries.push(BindGroupLayoutEntry {
363 binding: binding_num,
364 visibility: ShaderStages::COMPUTE,
365 ty: BindingType::Buffer {
366 ty: BufferBindingType::Storage { read_only },
367 has_dynamic_offset: false,
368 min_binding_size: None,
369 },
370 count: None,
371 });
372 infos.push(BindingInfo {
373 binding: binding_num,
374 name: name.to_string(),
375 kind: if read_only {
376 BindingKind::StorageRead
377 } else {
378 BindingKind::StorageRw
379 },
380 });
381 } else if uniform {
382 entries.push(BindGroupLayoutEntry {
383 binding: binding_num,
384 visibility: ShaderStages::COMPUTE,
385 ty: BindingType::Buffer {
386 ty: BufferBindingType::Uniform,
387 has_dynamic_offset: false,
388 min_binding_size: None,
389 },
390 count: None,
391 });
392 infos.push(BindingInfo {
393 binding: binding_num,
394 name: name.to_string(),
395 kind: BindingKind::Uniform,
396 });
397 }
398 }
399 pending = PendingAttr::default();
400 }
401 }
402
403 if entries.is_empty() {
404 entries.push(BindGroupLayoutEntry {
405 binding: 0,
406 visibility: ShaderStages::COMPUTE,
407 ty: BindingType::Buffer {
408 ty: BufferBindingType::Storage { read_only: false },
409 has_dynamic_offset: false,
410 min_binding_size: None,
411 },
412 count: None,
413 });
414 infos.push(BindingInfo {
415 binding: 0,
416 name: "_unnamed".into(),
417 kind: BindingKind::StorageRw,
418 });
419 }
420
421 let mut seen = std::collections::HashSet::new();
423 let mut dedup_entries = Vec::new();
424 let mut dedup_infos = Vec::new();
425 for (e, info) in entries.into_iter().zip(infos) {
426 if seen.insert(e.binding) {
427 dedup_entries.push(e);
428 dedup_infos.push(info);
429 }
430 }
431
432 let bind_group_layout = self
433 .device
434 .create_bind_group_layout(&BindGroupLayoutDescriptor {
435 label: Some(&format!("{}_bind_group_layout", name)),
436 entries: &dedup_entries,
437 });
438 Ok((bind_group_layout, dedup_infos))
439 }
440
441 #[cfg(feature = "wgpu")]
443 pub fn device(&self) -> &Device {
444 &self.device
445 }
446
447 #[cfg(feature = "wgpu")]
449 pub fn queue(&self) -> &Queue {
450 &self.queue
451 }
452
453 #[cfg(feature = "wgpu")]
455 pub fn allocate_device_memory(&self, size: usize) -> Result<Buffer, GpuError> {
456 let buffer = self.device.create_buffer(&BufferDescriptor {
457 label: Some("SciRS2 Buffer"),
458 size: size as u64,
459 usage: BufferUsages::STORAGE | BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
460 mapped_at_creation: false,
461 });
462
463 Ok(buffer)
464 }
465
466 #[cfg(not(feature = "wgpu"))]
468 pub fn allocate_device_memory_2(&self, size: usize) -> Result<WgpuBuffer, GpuError> {
469 Ok((0x1000 + size) as WgpuBuffer)
471 }
472
473 #[cfg(not(feature = "wgpu"))]
475 fn initialize_webgpu() -> Result<WgpuDevice, GpuError> {
476 Ok(0x1 as WgpuDevice)
478 }
479
480 #[cfg(not(feature = "wgpu"))]
481 fn create_queue(device: WgpuDevice) -> Result<WgpuQueue, GpuError> {
482 Ok(0x2 as WgpuQueue)
484 }
485
486 #[cfg(not(feature = "wgpu"))]
487 fn compile_wgsl_source(source: &str, name: &str) -> Result<WgpuComputePipeline, GpuError> {
488 Ok(0x3 as WgpuComputePipeline)
490 }
491
492 #[cfg(feature = "wgpu")]
498 pub fn compile_to_pipeline(&self, source: &str) -> Result<WgpuComputePipeline, GpuError> {
499 let shader = self.compile_shader_internal(source, "scirs2-pipeline")?;
500 Ok(WgpuComputePipeline {
501 pipeline: shader.pipeline,
502 bind_group_layout: shader.bind_group_layout,
503 workgroup_size: shader.workgroup_size,
504 })
505 }
506
507 #[cfg(feature = "wgpu")]
509 pub fn run_vector_add(&self, a: &[f32], b: &[f32]) -> Result<Vec<f32>, GpuError> {
510 use wgpu::{util::DeviceExt as _, BufferUsages};
511
512 let n = a.len();
513 if n != b.len() {
514 return Err(GpuError::InvalidParameter(
515 "vectors must have equal length".into(),
516 ));
517 }
518
519 const VECTOR_ADD_WGSL: &str = r#"
520@group(0) @binding(0) var<storage, read> a : array<f32>;
521@group(0) @binding(1) var<storage, read> b : array<f32>;
522@group(0) @binding(2) var<storage, read_write> result : array<f32>;
523
524@compute @workgroup_size(64)
525fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
526 let idx = global_id.x;
527 if idx < arrayLength(&result) {
528 result[idx] = a[idx] + b[idx];
529 }
530}
531"#;
532
533 let shader_module = self.device.create_shader_module(ShaderModuleDescriptor {
535 label: Some("vector-add"),
536 source: ShaderSource::Wgsl(VECTOR_ADD_WGSL.into()),
537 });
538
539 let bgl = self
541 .device
542 .create_bind_group_layout(&BindGroupLayoutDescriptor {
543 label: Some("vector-add-bgl"),
544 entries: &[
545 BindGroupLayoutEntry {
547 binding: 0,
548 visibility: ShaderStages::COMPUTE,
549 ty: BindingType::Buffer {
550 ty: BufferBindingType::Storage { read_only: true },
551 has_dynamic_offset: false,
552 min_binding_size: None,
553 },
554 count: None,
555 },
556 BindGroupLayoutEntry {
558 binding: 1,
559 visibility: ShaderStages::COMPUTE,
560 ty: BindingType::Buffer {
561 ty: BufferBindingType::Storage { read_only: true },
562 has_dynamic_offset: false,
563 min_binding_size: None,
564 },
565 count: None,
566 },
567 BindGroupLayoutEntry {
569 binding: 2,
570 visibility: ShaderStages::COMPUTE,
571 ty: BindingType::Buffer {
572 ty: BufferBindingType::Storage { read_only: false },
573 has_dynamic_offset: false,
574 min_binding_size: None,
575 },
576 count: None,
577 },
578 ],
579 });
580
581 let pipeline_layout = self
582 .device
583 .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
584 label: Some("vector-add-layout"),
585 bind_group_layouts: &[Some(&bgl)],
586 ..Default::default()
587 });
588
589 let pipeline = self
590 .device
591 .create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
592 label: Some("vector-add-pipeline"),
593 layout: Some(&pipeline_layout),
594 module: &shader_module,
595 entry_point: Some("main"),
596 compilation_options: Default::default(),
597 cache: None,
598 });
599
600 let a_bytes: Vec<u8> = a.iter().flat_map(|f| f.to_le_bytes()).collect();
602 let b_bytes: Vec<u8> = b.iter().flat_map(|f| f.to_le_bytes()).collect();
603 let result_size = std::mem::size_of_val(a) as u64;
604
605 let buf_a = self
606 .device
607 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
608 label: Some("vector-add-a"),
609 contents: &a_bytes,
610 usage: BufferUsages::STORAGE | BufferUsages::COPY_SRC,
611 });
612 let buf_b = self
613 .device
614 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
615 label: Some("vector-add-b"),
616 contents: &b_bytes,
617 usage: BufferUsages::STORAGE | BufferUsages::COPY_SRC,
618 });
619 let buf_result = self.device.create_buffer(&BufferDescriptor {
620 label: Some("vector-add-result"),
621 size: result_size,
622 usage: BufferUsages::STORAGE | BufferUsages::COPY_SRC | BufferUsages::COPY_DST,
623 mapped_at_creation: false,
624 });
625
626 let bind_group = self.device.create_bind_group(&BindGroupDescriptor {
628 label: Some("vector-add-bg"),
629 layout: &bgl,
630 entries: &[
631 BindGroupEntry {
632 binding: 0,
633 resource: buf_a.as_entire_binding(),
634 },
635 BindGroupEntry {
636 binding: 1,
637 resource: buf_b.as_entire_binding(),
638 },
639 BindGroupEntry {
640 binding: 2,
641 resource: buf_result.as_entire_binding(),
642 },
643 ],
644 });
645
646 let mut encoder = self
648 .device
649 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
650 label: Some("vector-add-encoder"),
651 });
652 {
653 let mut cpass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
654 label: Some("vector-add-pass"),
655 timestamp_writes: None,
656 });
657 cpass.set_pipeline(&pipeline);
658 cpass.set_bind_group(0, &bind_group, &[]);
659 let workgroups = (n as u32 + 63) / 64;
660 cpass.dispatch_workgroups(workgroups, 1, 1);
661 }
662
663 let staging = self.device.create_buffer(&wgpu::BufferDescriptor {
665 label: Some("vector-add-staging"),
666 size: result_size,
667 usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
668 mapped_at_creation: false,
669 });
670 encoder.copy_buffer_to_buffer(&buf_result, 0, &staging, 0, result_size);
671 self.queue.submit(Some(encoder.finish()));
672
673 self.device
675 .poll(wgpu::PollType::wait_indefinitely())
676 .map_err(|e| GpuError::Other(format!("GPU poll error: {e:?}")))?;
677
678 let slice = staging.slice(0..result_size);
679 let (tx, rx) = std::sync::mpsc::channel();
680 slice.map_async(wgpu::MapMode::Read, move |r| {
681 let _ = tx.send(r);
682 });
683
684 self.device
686 .poll(wgpu::PollType::wait_indefinitely())
687 .map_err(|e| GpuError::Other(format!("GPU poll error during map: {e:?}")))?;
688
689 rx.recv()
690 .map_err(|_| GpuError::Other("Channel closed during map_async".into()))?
691 .map_err(|e| GpuError::Other(format!("map_async failed: {e:?}")))?;
692
693 let mapped = slice.get_mapped_range();
694 let result: Vec<f32> = mapped
695 .chunks_exact(4)
696 .map(|b| f32::from_le_bytes([b[0], b[1], b[2], b[3]]))
697 .collect();
698 drop(mapped);
699 staging.unmap();
700
701 Ok(result)
702 }
703
704 fn extract_entry_point(source: &str) -> Option<&str> {
706 let lines: Vec<&str> = source.lines().collect();
707
708 for (i, line) in lines.iter().enumerate() {
709 let trimmed = line.trim();
710
711 if trimmed.contains("@compute") {
713 let mut search_line = trimmed;
715 let mut search_idx = 0;
716
717 if !search_line.contains("fn ") && search_idx + 1 < lines.len() {
719 search_idx += 1;
720 search_line = lines[search_idx].trim();
721 }
722
723 if let Some(start) = search_line.find("fn ") {
725 let remaining = &search_line[start + 3..];
726 if let Some(end) = remaining.find('(') {
727 let funcname = remaining[..end].trim();
728 return Some(funcname);
729 }
730 }
731 }
732 }
733
734 None
735 }
736}
737
738impl GpuContextImpl for WebGPUContext {
739 fn create_buffer(&self, size: usize) -> Arc<dyn GpuBufferImpl> {
740 if let Ok(mut pool) = self.memory_pool.lock() {
742 if let Some(device_buffer) = pool.allocate(size) {
743 return Arc::new(WebGPUBuffer {
744 device_buffer: Some(device_buffer),
745 #[cfg(feature = "wgpu")]
746 queue: Arc::clone(&self.queue),
747 #[cfg(feature = "wgpu")]
748 device: Arc::clone(&self.device),
749 #[cfg(not(feature = "wgpu"))]
750 queue: self.queue,
751 size,
752 memory_pool: Arc::clone(&self.memory_pool),
753 });
754 }
755 }
756
757 let device_buffer = match self.allocate_device_memory(size) {
759 Ok(buffer) => buffer,
760 Err(e) => {
761 eprintln!(
763 "Warning: WebGPU buffer allocation failed ({}), creating CPU fallback buffer",
764 e
765 );
766
767 #[cfg(feature = "wgpu")]
768 {
769 return Arc::new(WebGPUCpuFallbackBuffer {
772 data: vec![0u8; size],
773 size,
774 memory_pool: Arc::clone(&self.memory_pool),
775 });
776 }
777 #[cfg(not(feature = "wgpu"))]
778 {
779 (0x2000 + size) as WgpuBuffer
780 }
781 }
782 };
783
784 Arc::new(WebGPUBuffer {
785 device_buffer: Some(device_buffer),
786 #[cfg(feature = "wgpu")]
787 queue: Arc::clone(&self.queue),
788 #[cfg(feature = "wgpu")]
789 device: Arc::clone(&self.device),
790 #[cfg(not(feature = "wgpu"))]
791 queue: self.queue,
792 size,
793 memory_pool: Arc::clone(&self.memory_pool),
794 })
795 }
796
797 fn create_compiler(&self) -> Arc<dyn GpuCompilerImpl> {
798 Arc::new(WebGPUCompiler {
799 context: Arc::new(WebGPUContext {
800 memory_pool: Arc::clone(&self.memory_pool),
801 compiled_shaders: Arc::clone(&self.compiled_shaders),
802 #[cfg(feature = "wgpu")]
803 device: Arc::clone(&self.device),
804 #[cfg(feature = "wgpu")]
805 queue: Arc::clone(&self.queue),
806 #[cfg(not(feature = "wgpu"))]
807 device: Arc::clone(&self.device),
808 #[cfg(not(feature = "wgpu"))]
809 queue: Arc::clone(&self.queue),
810 }),
811 })
812 }
813
814 fn as_any(&self) -> &dyn std::any::Any {
815 self
816 }
817}
818
819struct WebGPUShader {
821 #[cfg(feature = "wgpu")]
822 pipeline: ComputePipeline,
823 #[cfg(not(feature = "wgpu"))]
824 pipeline: WgpuComputePipeline,
825 #[cfg(feature = "wgpu")]
826 #[allow(dead_code)]
827 bind_group_layout: BindGroupLayout,
828 #[cfg(not(feature = "wgpu"))]
829 #[allow(dead_code)]
830 bind_group_layout: *mut std::ffi::c_void,
831 #[allow(dead_code)]
832 name: String,
833 #[allow(dead_code)]
834 binding_infos: Vec<BindingInfo>, #[allow(dead_code)]
836 workgroup_size: [u32; 3],
837}
838
839unsafe impl Send for WebGPUShader {}
841unsafe impl Sync for WebGPUShader {}
842
843struct WebGPUCompiler {
845 context: Arc<WebGPUContext>,
846}
847
848impl GpuCompilerImpl for WebGPUCompiler {
849 fn compile(&self, source: &str) -> Result<Arc<dyn GpuKernelImpl>, GpuError> {
850 let shader = self.context.compile_shader_internal(source, "shader")?;
851 Ok(Arc::new(WebGPUKernelHandle {
852 shader_name: shader.name.clone(),
853 compiled_shaders: Arc::clone(&self.context.compiled_shaders),
854 params: Arc::new(Mutex::new(HashMap::new())),
855 #[cfg(feature = "wgpu")]
856 device: Arc::clone(&self.context.device),
857 #[cfg(feature = "wgpu")]
858 queue: Arc::clone(&self.context.queue),
859 #[cfg(feature = "wgpu")]
860 ephemeral_uniforms: Mutex::new(Vec::new()),
861 #[cfg(not(feature = "wgpu"))]
862 device: self.context.device,
863 #[cfg(not(feature = "wgpu"))]
864 queue: self.context.queue,
865 }))
866 }
867
868 fn compile_typed(
869 &self,
870 name: &str,
871 _input_type: std::any::TypeId,
872 _output_type: std::any::TypeId,
873 ) -> Arc<dyn GpuKernelImpl> {
874 Arc::new(WebGPUKernelHandle {
875 shader_name: name.to_string(),
876 compiled_shaders: Arc::clone(&self.context.compiled_shaders),
877 params: Arc::new(Mutex::new(HashMap::new())),
878 #[cfg(feature = "wgpu")]
879 device: Arc::clone(&self.context.device),
880 #[cfg(feature = "wgpu")]
881 queue: Arc::clone(&self.context.queue),
882 #[cfg(feature = "wgpu")]
883 ephemeral_uniforms: Mutex::new(Vec::new()),
884 #[cfg(not(feature = "wgpu"))]
885 device: self.context.device,
886 #[cfg(not(feature = "wgpu"))]
887 queue: self.context.queue,
888 })
889 }
890}
891
892struct WebGPUKernelHandle {
894 shader_name: String,
895 compiled_shaders: Arc<Mutex<HashMap<String, WebGPUShader>>>,
896 params: Arc<Mutex<HashMap<String, KernelParam>>>,
897 #[cfg(feature = "wgpu")]
898 device: Arc<Device>,
899 #[cfg(feature = "wgpu")]
900 queue: Arc<Queue>,
901 #[cfg(feature = "wgpu")]
902 ephemeral_uniforms: Mutex<Vec<wgpu::Buffer>>,
903 #[cfg(not(feature = "wgpu"))]
904 device: WgpuDevice,
905 #[cfg(not(feature = "wgpu"))]
906 queue: WgpuQueue,
907}
908
909enum KernelParam {
910 #[allow(dead_code)]
911 Buffer(Arc<dyn GpuBufferImpl>),
912 #[allow(dead_code)]
913 U32(u32),
914 #[allow(dead_code)]
915 I32(i32),
916 #[allow(dead_code)]
917 F32(f32),
918 #[allow(dead_code)]
919 F64(f64),
920 Bytes(Vec<u8>),
921}
922
923#[derive(Clone, Debug)]
924enum BindingKind {
925 StorageRw,
926 StorageRead,
927 Uniform,
928}
929
930#[derive(Clone, Debug)]
931struct BindingInfo {
932 binding: u32,
933 name: String,
934 kind: BindingKind,
935}
936
937fn extract_workgroup_size(source: &str) -> [u32; 3] {
940 for line in source.lines() {
941 let trimmed = line.trim();
942 if let Some(start) = trimmed.find("@workgroup_size(") {
943 let after = &trimmed[start + "@workgroup_size(".len()..];
944 if let Some(end) = after.find(')') {
945 let inner = &after[..end];
946 let parts: Vec<u32> = inner
947 .split(',')
948 .filter_map(|s| s.trim().parse::<u32>().ok())
949 .collect();
950 return match parts.as_slice() {
951 [x] => [*x, 1, 1],
952 [x, y] => [*x, *y, 1],
953 [x, y, z, ..] => [*x, *y, *z],
954 _ => [64, 1, 1],
955 };
956 }
957 }
958 }
959 [64, 1, 1]
960}
961
962fn extract_var_name(line: &str) -> Option<&str> {
963 if let Some(var_start) = line.find("var<") {
964 let after_var = &line[var_start..];
965 if let Some(close) = after_var.find('>') {
966 let after = &after_var[close + 1..];
967 let after = after.trim_start();
968 if let Some(colon) = after.find(':') {
969 let name_part = after[..colon].trim();
970 if !name_part.is_empty() {
971 return Some(name_part);
972 }
973 }
974 }
975 }
976 None
977}
978
979impl GpuKernelImpl for WebGPUKernelHandle {
980 fn set_buffer(&self, name: &str, buffer: &Arc<dyn GpuBufferImpl>) {
981 if let Ok(mut params) = self.params.lock() {
982 params.insert(name.to_string(), KernelParam::Buffer(Arc::clone(buffer)));
983 }
984 }
985
986 fn set_u32(&self, name: &str, value: u32) {
987 if let Ok(mut params) = self.params.lock() {
988 params.insert(name.to_string(), KernelParam::U32(value));
989 }
990 }
991
992 fn set_i32(&self, name: &str, value: i32) {
993 if let Ok(mut params) = self.params.lock() {
994 params.insert(name.to_string(), KernelParam::I32(value));
995 }
996 }
997
998 fn set_f32(&self, name: &str, value: f32) {
999 if let Ok(mut params) = self.params.lock() {
1000 params.insert(name.to_string(), KernelParam::F32(value));
1001 }
1002 }
1003
1004 fn set_f64(&self, name: &str, value: f64) {
1005 if let Ok(mut params) = self.params.lock() {
1006 params.insert(name.to_string(), KernelParam::F64(value));
1007 }
1008 }
1009
1010 #[allow(dead_code)]
1011 fn dispatch(&self, workgroups: [u32; 3]) {
1014 #[cfg(feature = "wgpu")]
1015 {
1016 let shaders = match self.compiled_shaders.lock() {
1018 Ok(g) => g,
1019 Err(_) => return,
1020 };
1021 if let Some(shader) = shaders.get(&self.shader_name) {
1022 let params = match self.params.lock() {
1023 Ok(g) => g,
1024 Err(_) => return,
1025 };
1026
1027 let mut encoder =
1029 self.device
1030 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1031 label: Some("Compute Command Encoder"),
1032 });
1033
1034 {
1036 let mut compute_pass =
1037 encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
1038 label: Some("Compute Pass"),
1039 timestamp_writes: None,
1040 });
1041
1042 compute_pass.set_pipeline(&shader.pipeline);
1044
1045 if let Ok(bind_group) = self.create_bind_group_from_params(shader, ¶ms) {
1046 compute_pass.set_bind_group(0, &bind_group, &[]);
1047 }
1048 compute_pass.dispatch_workgroups(workgroups[0], workgroups[1], workgroups[2]);
1052 }
1053
1054 let command_buffer = encoder.finish();
1056 self.queue.submit(std::iter::once(command_buffer));
1057 }
1058 }
1059 #[cfg(not(feature = "wgpu"))]
1060 {
1061 let _ = workgroups;
1063 let _ = &self.shader_name;
1064 }
1065 }
1066}
1067
1068struct WebGPUBuffer {
1070 #[cfg(feature = "wgpu")]
1071 device_buffer: Option<Buffer>,
1072 #[cfg(feature = "wgpu")]
1073 queue: Arc<Queue>,
1074 #[cfg(feature = "wgpu")]
1075 device: Arc<Device>,
1076 #[cfg(not(feature = "wgpu"))]
1077 device_buffer: Option<WgpuBuffer>,
1078 #[cfg(not(feature = "wgpu"))]
1079 queue: WgpuQueue,
1080 size: usize,
1081 memory_pool: Arc<Mutex<WebGPUMemoryPool>>,
1082}
1083
1084unsafe impl Send for WebGPUBuffer {}
1088unsafe impl Sync for WebGPUBuffer {}
1089
1090impl GpuBufferImpl for WebGPUBuffer {
1091 fn size(&self) -> usize {
1092 self.size
1093 }
1094
1095 unsafe fn copy_from_host(&self, data: *const u8, size: usize) {
1096 #[cfg(feature = "wgpu")]
1097 {
1098 if size > self.size {
1100 eprintln!(
1102 "Warning: Data size {} exceeds buffer size {}",
1103 size, self.size
1104 );
1105 return;
1106 }
1107
1108 let data_slice = std::slice::from_raw_parts(data, size);
1110
1111 if let Some(ref buffer) = self.device_buffer {
1113 self.queue.write_buffer(buffer, 0, data_slice);
1114 }
1115 }
1116 #[cfg(not(feature = "wgpu"))]
1117 {
1118 if size > self.size {
1120 eprintln!(
1121 "Warning: Data size {} exceeds buffer size {}",
1122 size, self.size
1123 );
1124 }
1125 }
1127 }
1128
1129 unsafe fn copy_to_host(&self, data: *mut u8, size: usize) {
1130 #[cfg(feature = "wgpu")]
1131 {
1132 if size > self.size {
1134 eprintln!(
1135 "Warning: Data size {} exceeds buffer size {}",
1136 size, self.size
1137 );
1138 return;
1139 }
1140
1141 if let Some(ref buffer) = self.device_buffer {
1142 let staging = self.device.create_buffer(&wgpu::BufferDescriptor {
1143 label: Some("scirs2-readback"),
1144 size: size as u64,
1145 usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
1146 mapped_at_creation: false,
1147 });
1148 let mut encoder =
1149 self.device
1150 .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1151 label: Some("scirs2-readback-enc"),
1152 });
1153 encoder.copy_buffer_to_buffer(buffer, 0, &staging, 0, size as u64);
1154 self.queue.submit(Some(encoder.finish()));
1155
1156 let _ = self.device.poll(wgpu::PollType::wait_indefinitely());
1160
1161 let slice = staging.slice(0..size as u64);
1162 let (tx, rx) = std::sync::mpsc::channel();
1163 slice.map_async(wgpu::MapMode::Read, move |r| {
1164 let _ = tx.send(r);
1165 });
1166 let _ = self.device.poll(wgpu::PollType::wait_indefinitely());
1168 if let Ok(Ok(())) = rx.recv() {
1169 let mapped = slice.get_mapped_range();
1170 let dst = std::slice::from_raw_parts_mut(data, size);
1171 dst.copy_from_slice(&mapped);
1172 drop(mapped);
1173 staging.unmap();
1174 } else {
1175 eprintln!("Warning: map_async failed for readback");
1176 }
1177 }
1178 }
1179 #[cfg(not(feature = "wgpu"))]
1180 {
1181 if size > self.size {
1183 eprintln!(
1184 "Warning: Data size {} exceeds buffer size {}",
1185 size, self.size
1186 );
1187 }
1188
1189 let data_slice = std::slice::from_raw_parts_mut(data, size);
1191 data_slice.fill(0);
1192 }
1193 }
1194
1195 fn device_ptr(&self) -> u64 {
1196 #[cfg(feature = "wgpu")]
1197 {
1198 &self.device_buffer as *const _ as u64
1201 }
1202 #[cfg(not(feature = "wgpu"))]
1203 {
1204 self.device_buffer as u64
1205 }
1206 }
1207
1208 fn as_any(&self) -> &dyn std::any::Any {
1209 self
1210 }
1211}
1212
1213#[cfg(feature = "wgpu")]
1214impl WebGPUKernelHandle {
1215 fn create_bind_group_from_params(
1216 &self,
1217 shader: &WebGPUShader,
1218 params: &HashMap<String, KernelParam>,
1219 ) -> Result<wgpu::BindGroup, GpuError> {
1220 let mut entries: Vec<wgpu::BindGroupEntry> = Vec::new();
1221 let mut owned_uniform_buffers: Vec<wgpu::Buffer> = Vec::new();
1223 let mut uniform_bytes: Vec<u8> = Vec::new();
1224 for info in &shader.binding_infos {
1225 match info.kind {
1226 BindingKind::StorageRw | BindingKind::StorageRead => {
1227 if let Some(KernelParam::Buffer(buf)) = params.get(&info.name) {
1228 if let Some(wbuf) = buf.as_any().downcast_ref::<WebGPUBuffer>() {
1229 if let Some(ref inner) = wbuf.device_buffer {
1230 entries.push(wgpu::BindGroupEntry {
1231 binding: info.binding,
1232 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
1233 buffer: inner,
1234 offset: 0,
1235 size: None,
1236 }),
1237 });
1238 }
1239 }
1240 } else {
1241 return Err(GpuError::InvalidParameter(format!(
1242 "Missing buffer param '{}'",
1243 info.name
1244 )));
1245 }
1246 }
1247 BindingKind::Uniform => {
1248 for (k, v) in params.iter() {
1250 if k == &info.name || k.starts_with(&(info.name.clone() + ".")) {
1251 match v {
1252 KernelParam::U32(u) => {
1253 uniform_bytes.extend_from_slice(&u.to_le_bytes())
1254 }
1255 KernelParam::I32(i) => {
1256 uniform_bytes.extend_from_slice(&i.to_le_bytes())
1257 }
1258 KernelParam::F32(f) => {
1259 uniform_bytes.extend_from_slice(&f.to_le_bytes())
1260 }
1261 KernelParam::F64(f) => {
1262 uniform_bytes.extend_from_slice(&f.to_le_bytes())
1263 }
1264 KernelParam::Bytes(b) => uniform_bytes.extend_from_slice(b),
1265 KernelParam::Buffer(_) => {}
1266 }
1267 }
1268 }
1269 }
1270 }
1271 }
1272 if !uniform_bytes.is_empty() {
1273 while uniform_bytes.len() % 16 != 0 {
1274 uniform_bytes.push(0);
1275 }
1276 if let Some(uinfo) = shader
1277 .binding_infos
1278 .iter()
1279 .find(|b| matches!(b.kind, BindingKind::Uniform))
1280 {
1281 if let Ok(mut list) = self.ephemeral_uniforms.lock() {
1282 list.clear();
1283 let ubuf = self
1284 .device
1285 .create_buffer_init(&wgpu::util::BufferInitDescriptor {
1286 label: Some("scirs2-uniforms"),
1287 contents: &uniform_bytes,
1288 usage: wgpu::BufferUsages::UNIFORM | wgpu::BufferUsages::COPY_DST,
1289 });
1290 list.push(ubuf.clone());
1291 owned_uniform_buffers.push(ubuf.clone());
1292 let idx = owned_uniform_buffers.len() - 1;
1293 let buf_ref = &owned_uniform_buffers[idx];
1294 entries.push(wgpu::BindGroupEntry {
1295 binding: uinfo.binding,
1296 resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
1297 buffer: buf_ref,
1298 offset: 0,
1299 size: None,
1300 }),
1301 });
1302 }
1303 }
1304 } else if let Ok(mut list) = self.ephemeral_uniforms.lock() {
1305 list.clear();
1306 }
1307 let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
1308 label: Some("scirs2-bind-group"),
1309 layout: &shader.bind_group_layout,
1310 entries: &entries,
1311 });
1312 Ok(bind_group)
1313 }
1314}
1315
1316impl Drop for WebGPUBuffer {
1317 fn drop(&mut self) {
1318 if let Ok(mut pool) = self.memory_pool.lock() {
1320 #[cfg(feature = "wgpu")]
1321 {
1322 if let Some(buffer) = self.device_buffer.take() {
1324 pool.deallocate(buffer);
1325 }
1326 }
1327 #[cfg(not(feature = "wgpu"))]
1328 {
1329 if let Some(buffer) = self.device_buffer.take() {
1330 pool.deallocate(buffer);
1331 }
1332 }
1333 }
1334 }
1335}
1336
1337struct WebGPUCpuFallbackBuffer {
1340 data: Vec<u8>,
1341 size: usize,
1342 #[allow(dead_code)]
1343 memory_pool: Arc<Mutex<WebGPUMemoryPool>>,
1344}
1345
1346impl GpuBufferImpl for WebGPUCpuFallbackBuffer {
1347 fn size(&self) -> usize {
1348 self.size
1349 }
1350
1351 unsafe fn copy_from_host(&self, data: *const u8, size: usize) {
1352 if size > self.size {
1353 eprintln!("Warning: WebGPU CPU fallback buffer copy_from_host size mismatch");
1354 return;
1355 }
1356
1357 let data_slice = std::slice::from_raw_parts(data, size);
1359 eprintln!(
1362 "Warning: CPU fallback buffer copy_from_host called (size: {})",
1363 size
1364 );
1365 }
1366
1367 unsafe fn copy_to_host(&self, data: *mut u8, size: usize) {
1368 if size > self.size {
1369 eprintln!("Warning: WebGPU CPU fallback buffer copy_to_host size mismatch");
1370 return;
1371 }
1372
1373 let data_slice = std::slice::from_raw_parts_mut(data, size);
1375 let copy_size = size.min(self.data.len());
1376 data_slice[..copy_size].copy_from_slice(&self.data[..copy_size]);
1377
1378 eprintln!(
1379 "Warning: CPU fallback buffer copy_to_host called (size: {})",
1380 size
1381 );
1382 }
1383
1384 fn device_ptr(&self) -> u64 {
1385 self.data.as_ptr() as u64
1386 }
1387
1388 fn as_any(&self) -> &dyn std::any::Any {
1389 self
1390 }
1391}
1392
1393unsafe impl Send for WebGPUCpuFallbackBuffer {}
1395unsafe impl Sync for WebGPUCpuFallbackBuffer {}
1396
1397struct WebGPUMemoryPool {
1399 #[cfg(feature = "wgpu")]
1400 available_buffers: HashMap<usize, Vec<Buffer>>,
1401 #[cfg(not(feature = "wgpu"))]
1402 available_buffers: HashMap<usize, Vec<WgpuBuffer>>,
1403 #[allow(dead_code)]
1404 total_size: usize,
1405 used_size: usize,
1406}
1407
1408impl WebGPUMemoryPool {
1409 fn new(totalsize: usize) -> Self {
1410 Self {
1411 available_buffers: HashMap::new(),
1412 total_size: totalsize,
1413 used_size: 0,
1414 }
1415 }
1416
1417 #[cfg(feature = "wgpu")]
1418 fn allocate(&mut self, size: usize) -> Option<Buffer> {
1419 if let Some(buffers) = self.available_buffers.get_mut(&size) {
1421 if let Some(buffer) = buffers.pop() {
1422 self.used_size += size;
1423 return Some(buffer);
1424 }
1425 }
1426 None
1427 }
1428
1429 #[cfg(not(feature = "wgpu"))]
1430 fn allocate(&mut self, size: usize) -> Option<WgpuBuffer> {
1431 if let Some(buffers) = self.available_buffers.get_mut(&size) {
1433 if let Some(buffer) = buffers.pop() {
1434 self.used_size += size;
1435 return Some(buffer);
1436 }
1437 }
1438 None
1439 }
1440
1441 #[cfg(feature = "wgpu")]
1442 fn deallocate(&mut self, buffer: Buffer) {
1443 let size = buffer.size() as usize;
1445 self.available_buffers
1446 .entry(size)
1447 .or_insert_with(Vec::new)
1448 .push(buffer);
1449 self.used_size = self.used_size.saturating_sub(size);
1450 }
1451
1452 #[cfg(not(feature = "wgpu"))]
1453 fn deallocate(&mut self, buffer: WgpuBuffer) {
1454 let size = 1024; self.available_buffers
1457 .entry(size)
1458 .or_insert_with(Vec::new)
1459 .push(buffer);
1460 self.used_size = self.used_size.saturating_sub(size);
1461 }
1462
1463 #[allow(dead_code)]
1464 fn get_memory_usage(&self) -> (usize, usize) {
1465 (self.used_size, self.total_size)
1466 }
1467}