Skip to main content

scirs2_core/gpu/backends/
wgpu.rs

1//! WebGPU backend implementation for GPU operations
2//!
3//! This module provides WebGPU-specific implementations for cross-platform GPU operations.
4
5use std::collections::HashMap;
6#[cfg(feature = "wgpu")]
7// wgpu 26 removed earlier Poll enum; Device::poll exists but Maintain enum not re-exported here; we avoid explicit polling for now.
8use 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// Fallback types for when WebGPU is not available
24#[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/// A compiled WebGPU compute pipeline, containing all state needed to dispatch a compute shader.
34///
35/// Created by [`try_compile_wgsl`]. On hosts without a GPU adapter this is never constructed
36/// and that function returns an error instead.
37#[cfg(feature = "wgpu")]
38pub struct WgpuComputePipeline {
39    /// The underlying wgpu compute pipeline.
40    pub pipeline: ComputePipeline,
41    /// The bind group layout derived from WGSL source inspection.
42    pub bind_group_layout: BindGroupLayout,
43    /// Workgroup size extracted from the `@workgroup_size(...)` attribute; defaults to `[64, 1, 1]`.
44    pub workgroup_size: [u32; 3],
45}
46
47#[cfg(feature = "wgpu")]
48// SAFETY: wgpu's `ComputePipeline` and `BindGroupLayout` are `Send + Sync` on all native backends.
49unsafe impl Send for WgpuComputePipeline {}
50#[cfg(feature = "wgpu")]
51unsafe impl Sync for WgpuComputePipeline {}
52
53/// Attempt to compile `source` as a WGSL compute shader and return a [`WgpuComputePipeline`].
54///
55/// # Errors
56/// - Returns an error if no wgpu adapter is available on the host (e.g. headless CI without GPU).
57/// - Returns an error if `source` contains invalid WGSL (wgpu panics on truly invalid WGSL;
58///   syntactically valid but semantically broken shaders will fail at pipeline creation).
59///
60/// # Example
61/// ```rust,no_run
62/// # #[cfg(feature = "wgpu")]
63/// # {
64/// use scirs2_core::gpu::backends::try_compile_wgsl;
65/// let pipeline = try_compile_wgsl(r#"
66///     @group(0) @binding(0) var<storage, read_write> out: array<f32>;
67///     @compute @workgroup_size(64)
68///     fn main(@builtin(global_invocation_id) gid: vec3<u32>) { out[gid.x] = f32(gid.x); }
69/// "#).expect("shader compiled");
70/// let _ = pipeline;
71/// # }
72/// ```
73#[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/// Run a vector-add compute shader end-to-end on the GPU.
80///
81/// Uploads `a` and `b` to device buffers, dispatches the WGSL kernel, then reads back the result.
82/// Returns `Ok(result_vec)` on success. Returns `Err` if no adapter is available.
83#[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
89// WebGPU shader source templates — used by the kernel registry in kernels/mod.rs
90// for the GEMM BLAS kernel (exposed here as a named constant for external use).
91
92/// WGSL source for the GEMM kernel (tiled 8×8 matrix multiply).
93///
94/// Computes C = alpha * A * B + beta * C where A is M×K, B is K×N, C is M×N.
95///
96/// Buffers: 0 → matrix_a (read), 1 → matrix_b (read), 2 → matrix_c (read_write)
97/// Uniforms: M, N, K, alpha, beta
98pub 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
130/// WebGPU context wrapper
131pub 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
144// WebGPU handles are safe to send between threads when properly synchronized
145unsafe impl Send for WebGPUContext {}
146unsafe impl Sync for WebGPUContext {}
147
148impl WebGPUContext {
149    /// Create a new WebGPU context
150    pub fn new() -> Result<Self, GpuError> {
151        #[cfg(feature = "wgpu")]
152        {
153            // Real WebGPU implementation.
154            //
155            // Restrict to `Backends::PRIMARY` (Vulkan/Metal/DX12/BrowserWebGpu) rather than
156            // `Backends::all()`. The secondary GL/GLES backend can appear "compatible" on hosts
157            // with a broken or missing Vulkan loader (falling back to a surfaceless EGL context),
158            // but that GL context is frequently too limited for wgpu's mandatory internal
159            // indirect-dispatch validation pipeline (missing compute/storage-buffer features),
160            // so the device comes back already lost. Restricting to PRIMARY makes adapter
161            // discovery fail cleanly instead of handing back a device that dies on first use.
162            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                // Newer wgpu versions removed/changed some fields (e.g. trace Option). Use defaults for the rest.
183                ..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))), // 1GB pool
194            })
195        }
196        #[cfg(not(feature = "wgpu"))]
197        {
198            // Fallback implementation
199            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))), // 1GB pool
207            })
208        }
209    }
210
211    /// Check if WebGPU is available and working
212    pub fn is_available() -> bool {
213        #[cfg(feature = "wgpu")]
214        {
215            // Real WebGPU implementation - try to create an instance and adapter.
216            // See the comment in `new()` for why this is restricted to `Backends::PRIMARY`.
217            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            // Try to get an adapter (this is async, so we use a simple runtime check)
227            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            // Fallback: return false since we don't have real WebGPU
241            false
242        }
243    }
244
245    /// Compile a shader from WGSL source
246    fn compile_shader_internal(&self, source: &str, name: &str) -> Result<WebGPUShader, GpuError> {
247        #[cfg(feature = "wgpu")]
248        {
249            // Real WebGPU implementation
250            let shader_module = self.device.create_shader_module(ShaderModuleDescriptor {
251                label: Some(name),
252                source: ShaderSource::Wgsl(source.into()),
253            });
254
255            // Extract entry point from source or use default
256            let entry_point = Self::extract_entry_point(source).unwrap_or("main");
257
258            // Create bind group layout + reflection infos
259            let (bind_group_layout, binding_infos) =
260                self.create_bind_group_layout_from_source(source, name)?;
261
262            // Create pipeline layout with our bind group layout
263            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                        // wgpu 28+: immediate_size replaces push_constant_ranges
269                        ..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            // Fallback implementation
296            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    /// Create bind group layout from WGSL source analysis
309    #[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                // variable declaration
351                if pending.group.unwrap_or(0) == 0 {
352                    // only group 0 for now
353                    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        // Deduplicate by binding number
422        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    /// Return a reference to the underlying `wgpu::Device`.
442    #[cfg(feature = "wgpu")]
443    pub fn device(&self) -> &Device {
444        &self.device
445    }
446
447    /// Return a reference to the underlying `wgpu::Queue`.
448    #[cfg(feature = "wgpu")]
449    pub fn queue(&self) -> &Queue {
450        &self.queue
451    }
452
453    /// Allocate device memory
454    #[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    /// Allocate device memory (fallback)
467    #[cfg(not(feature = "wgpu"))]
468    pub fn allocate_device_memory_2(&self, size: usize) -> Result<WgpuBuffer, GpuError> {
469        // Fallback implementation: return a simulated buffer handle
470        Ok((0x1000 + size) as WgpuBuffer)
471    }
472
473    // Fallback methods for when WebGPU is not available
474    #[cfg(not(feature = "wgpu"))]
475    fn initialize_webgpu() -> Result<WgpuDevice, GpuError> {
476        // Stub implementation
477        Ok(0x1 as WgpuDevice)
478    }
479
480    #[cfg(not(feature = "wgpu"))]
481    fn create_queue(device: WgpuDevice) -> Result<WgpuQueue, GpuError> {
482        // Stub implementation
483        Ok(0x2 as WgpuQueue)
484    }
485
486    #[cfg(not(feature = "wgpu"))]
487    fn compile_wgsl_source(source: &str, name: &str) -> Result<WgpuComputePipeline, GpuError> {
488        // Stub implementation
489        Ok(0x3 as WgpuComputePipeline)
490    }
491
492    /// Compile WGSL source into a [`WgpuComputePipeline`] (real-wgpu path only).
493    ///
494    /// This exposes the same compilation path as [`try_compile_wgsl`] but operates
495    /// on an already-created context so the adapter/device creation overhead is
496    /// incurred only once.
497    #[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    /// Run a vector-add end-to-end: upload `a` and `b`, dispatch, read back `result`.
508    #[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        // Compile shader
534        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        // Build bind group layout explicitly (3 storage bindings)
540        let bgl = self
541            .device
542            .create_bind_group_layout(&BindGroupLayoutDescriptor {
543                label: Some("vector-add-bgl"),
544                entries: &[
545                    // binding 0: a (read-only storage)
546                    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                    // binding 1: b (read-only storage)
557                    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                    // binding 2: result (read-write storage)
568                    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        // Upload input buffers
601        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        // Bind group
627        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        // Encode and dispatch
647        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        // Readback via staging buffer
664        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        // Poll until GPU work completes (required on native backends before map_async fires)
674        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        // Poll again to drive the map callback to completion
685        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    /// Extract the entry point function name from WGSL source code
705    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            // Check if this line contains @compute
712            if trimmed.contains("@compute") {
713                // The function might be on the same line or the next line
714                let mut search_line = trimmed;
715                let mut search_idx = 0;
716
717                // If @compute and function are not on the same line, check next line
718                if !search_line.contains("fn ") && search_idx + 1 < lines.len() {
719                    search_idx += 1;
720                    search_line = lines[search_idx].trim();
721                }
722
723                // Extract function name
724                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        // Try to allocate from memory pool first
741        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        // Fallback to direct allocation
758        let device_buffer = match self.allocate_device_memory(size) {
759            Ok(buffer) => buffer,
760            Err(e) => {
761                // Log the WebGPU allocation failure and create a CPU fallback
762                eprintln!(
763                    "Warning: WebGPU buffer allocation failed ({}), creating CPU fallback buffer",
764                    e
765                );
766
767                #[cfg(feature = "wgpu")]
768                {
769                    // Create a CPU fallback buffer with minimal size for WebGPU compatibility
770                    // This is a last resort when GPU memory is exhausted
771                    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
819/// WebGPU shader wrapper (augmented with basic reflection info)
820struct 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>, // basic reflection info (names may be synthetic when parser can't extract)
835    #[allow(dead_code)]
836    workgroup_size: [u32; 3],
837}
838
839// WebGPU shader handles are safe to send between threads when properly synchronized
840unsafe impl Send for WebGPUShader {}
841unsafe impl Sync for WebGPUShader {}
842
843/// WebGPU compiler implementation
844struct 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
892/// WebGPU kernel handle for execution
893struct 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
937/// Extract the `@workgroup_size(x [, y [, z]])` values from WGSL source.
938/// Returns `[64, 1, 1]` as a sensible default if the attribute is not present or unparseable.
939fn 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    // raw bytes helper removed from trait; use internal helper if needed
1012
1013    fn dispatch(&self, workgroups: [u32; 3]) {
1014        #[cfg(feature = "wgpu")]
1015        {
1016            // Real WebGPU compute dispatch
1017            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                // Create command encoder
1028                let mut encoder =
1029                    self.device
1030                        .create_command_encoder(&wgpu::CommandEncoderDescriptor {
1031                            label: Some("Compute Command Encoder"),
1032                        });
1033
1034                // Begin compute pass
1035                {
1036                    let mut compute_pass =
1037                        encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
1038                            label: Some("Compute Pass"),
1039                            timestamp_writes: None,
1040                        });
1041
1042                    // Set the compute pipeline
1043                    compute_pass.set_pipeline(&shader.pipeline);
1044
1045                    if let Ok(bind_group) = self.create_bind_group_from_params(shader, &params) {
1046                        compute_pass.set_bind_group(0, &bind_group, &[]);
1047                    }
1048                    // else: bind group creation failed; dispatch will proceed but produce undefined results
1049
1050                    // Dispatch the compute shader
1051                    compute_pass.dispatch_workgroups(workgroups[0], workgroups[1], workgroups[2]);
1052                }
1053
1054                // Submit the command buffer
1055                let command_buffer = encoder.finish();
1056                self.queue.submit(std::iter::once(command_buffer));
1057            }
1058        }
1059        #[cfg(not(feature = "wgpu"))]
1060        {
1061            // Fallback: no GPU available; dispatch is a no-op
1062            let _ = workgroups;
1063            let _ = &self.shader_name;
1064        }
1065    }
1066}
1067
1068/// WebGPU buffer implementation
1069struct 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
1084// WebGPU buffer handles are safe to send between threads when properly synchronized
1085// The real wgpu types (Buffer, Queue) are Send + Sync
1086// For fallback types (raw pointers), we assume proper synchronization is handled externally
1087unsafe 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            // Validate data size
1099            if size > self.size {
1100                // In unsafe context, we can't return an error, so we'll just log and return
1101                eprintln!(
1102                    "Warning: Data size {} exceeds buffer size {}",
1103                    size, self.size
1104                );
1105                return;
1106            }
1107
1108            // Convert raw pointer to slice for WebGPU API
1109            let data_slice = std::slice::from_raw_parts(data, size);
1110
1111            // Real WebGPU implementation - write data to buffer
1112            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            // Fallback implementation - just validate
1119            if size > self.size {
1120                eprintln!(
1121                    "Warning: Data size {} exceeds buffer size {}",
1122                    size, self.size
1123                );
1124            }
1125            // In fallback mode, we just simulate the operation
1126        }
1127    }
1128
1129    unsafe fn copy_to_host(&self, data: *mut u8, size: usize) {
1130        #[cfg(feature = "wgpu")]
1131        {
1132            // Validate data size
1133            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                // Poll the device until all submitted work completes before mapping.
1157                // This is required on all native wgpu backends (Vulkan, Metal, DX12)
1158                // to ensure the copy completes before the slice can be mapped.
1159                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                // Drive map callback to completion
1167                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            // Fallback implementation - just validate and zero out
1182            if size > self.size {
1183                eprintln!(
1184                    "Warning: Data size {} exceeds buffer size {}",
1185                    size, self.size
1186                );
1187            }
1188
1189            // Zero out the data as a placeholder
1190            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            // WebGPU doesn't expose raw device pointers, so we return a placeholder
1199            // In a real implementation, this might return a handle or ID
1200            &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        // Hold uniform buffers so their lifetime extends until after bind_group creation
1222        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                    // Collect all scalars/bytes with key prefix or exact match
1249                    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        // Return buffer to memory pool if possible
1319        if let Ok(mut pool) = self.memory_pool.lock() {
1320            #[cfg(feature = "wgpu")]
1321            {
1322                // In real implementation, would return buffer to pool
1323                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
1337/// CPU fallback buffer for when WebGPU buffer allocation fails
1338/// This provides a graceful degradation when GPU memory is exhausted
1339struct 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        // Since this is a CPU fallback, we can use safe Rust internally
1358        let data_slice = std::slice::from_raw_parts(data, size);
1359        // We can't mutate self.data directly since &self is immutable
1360        // In a real implementation, this would require interior mutability
1361        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        // Copy from CPU buffer to host
1374        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
1393// Safety: WebGPUCpuFallbackBuffer is thread-safe since it only contains owned data
1394unsafe impl Send for WebGPUCpuFallbackBuffer {}
1395unsafe impl Sync for WebGPUCpuFallbackBuffer {}
1396
1397/// WebGPU memory pool for efficient buffer management
1398struct 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        // Try to find a suitable buffer in the pool
1420        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        // Try to find a suitable buffer in the pool
1432        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        // Return buffer to pool
1444        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        // Fallback implementation - track the buffer
1455        let size = 1024; // Placeholder size
1456        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}