use std::borrow::Cow;
use wgpu::util::DeviceExt;
#[derive(Debug, thiserror::Error)]
pub enum GpuError {
#[error("GPU device initialization failed: {0}")]
InitializationFailed(String),
#[error("Shader compilation failed: {0}")]
ShaderCompilationFailed(String),
#[error("Buffer creation failed: {0}")]
BufferCreationFailed(String),
#[error("Compute execution failed: {0}")]
ComputeExecutionFailed(String),
#[error("Data transfer failed: {0}")]
DataTransferFailed(String),
}
pub struct GpuPipeline {
device: wgpu::Device,
queue: wgpu::Queue,
}
impl GpuPipeline {
pub async fn new() -> Result<Self, GpuError> {
let instance = wgpu::Instance::new(wgpu::InstanceDescriptor {
backends: wgpu::Backends::all(),
..Default::default()
});
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::default(),
compatible_surface: None,
force_fallback_adapter: false,
})
.await
.ok_or_else(|| GpuError::InitializationFailed("No suitable GPU adapter found".to_string()))?;
let adapter_info = adapter.get_info();
println!("🎮 GPU DEVICE DETECTED:");
println!(" 📱 Name: {}", adapter_info.name);
println!(" 🏭 Vendor: {:?}", adapter_info.vendor);
println!(" 🔧 Device Type: {:?}", adapter_info.device_type);
println!(" 🖥️ Backend: {:?}", adapter_info.backend);
if adapter_info.name.contains("Apple") || adapter_info.name.contains("M1") ||
adapter_info.name.contains("M2") || adapter_info.name.contains("M3") {
println!(" 🚀 APPLE SILICON DETECTED! Using Metal backend!");
}
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: Some("Pipex GPU Device"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
},
None,
)
.await
.map_err(|e| GpuError::InitializationFailed(format!("Device request failed: {}", e)))?;
println!(" ✅ GPU initialization successful!");
Ok(Self { device, queue })
}
pub async fn execute_kernel<T>(&self, input: Vec<T>, kernel_source: &str) -> Result<Vec<T>, GpuError>
where
T: bytemuck::Pod + bytemuck::Zeroable + Clone,
{
let input_size = input.len();
if input_size == 0 {
return Ok(Vec::new());
}
let shader = self.device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Pipex Compute Shader"),
source: wgpu::ShaderSource::Wgsl(Cow::Borrowed(kernel_source)),
});
let input_buffer = self.device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: Some("Input Buffer"),
contents: bytemuck::cast_slice(&input),
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_DST,
});
let output_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Output Buffer"),
size: (input_size * std::mem::size_of::<T>()) as u64,
usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let staging_buffer = self.device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Staging Buffer"),
size: (input_size * std::mem::size_of::<T>()) as u64,
usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST,
mapped_at_creation: false,
});
let bind_group_layout = self.device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Compute Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: true },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::COMPUTE,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Storage { read_only: false },
has_dynamic_offset: false,
min_binding_size: None,
},
count: None,
},
],
});
let compute_pipeline_layout = self.device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Compute Pipeline Layout"),
bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
});
let compute_pipeline = self.device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("Compute Pipeline"),
layout: Some(&compute_pipeline_layout),
module: &shader,
entry_point: "main",
});
let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Compute Bind Group"),
layout: &bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: input_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: output_buffer.as_entire_binding(),
},
],
});
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Compute Encoder"),
});
{
let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("Compute Pass"),
timestamp_writes: None,
});
compute_pass.set_pipeline(&compute_pipeline);
compute_pass.set_bind_group(0, &bind_group, &[]);
compute_pass.dispatch_workgroups(input_size as u32, 1, 1);
}
encoder.copy_buffer_to_buffer(&output_buffer, 0, &staging_buffer, 0, (input_size * std::mem::size_of::<T>()) as u64);
self.queue.submit(std::iter::once(encoder.finish()));
let buffer_slice = staging_buffer.slice(..);
let (sender, receiver) = futures_channel::oneshot::channel();
buffer_slice.map_async(wgpu::MapMode::Read, move |v| sender.send(v).unwrap());
self.device.poll(wgpu::Maintain::wait()).panic_on_timeout();
receiver.await.unwrap().map_err(|e| GpuError::DataTransferFailed(format!("Buffer mapping failed: {:?}", e)))?;
let data = buffer_slice.get_mapped_range();
let result: Vec<T> = bytemuck::cast_slice(&data).to_vec();
drop(data);
staging_buffer.unmap();
Ok(result)
}
}
static GPU_PIPELINE: std::sync::OnceLock<std::sync::Arc<GpuPipeline>> = std::sync::OnceLock::new();
pub async fn init_gpu() -> Result<(), GpuError> {
let pipeline = GpuPipeline::new().await?;
GPU_PIPELINE.set(std::sync::Arc::new(pipeline))
.map_err(|_| GpuError::InitializationFailed("GPU pipeline already initialized".to_string()))?;
Ok(())
}
pub async fn execute_gpu_kernel<T>(input: Vec<T>, kernel_source: &str) -> Result<Vec<T>, GpuError>
where
T: bytemuck::Pod + bytemuck::Zeroable + Clone,
{
if GPU_PIPELINE.get().is_none() {
init_gpu().await?;
}
let pipeline = GPU_PIPELINE.get()
.ok_or_else(|| GpuError::InitializationFailed("GPU pipeline initialization failed".to_string()))?;
pipeline.execute_kernel(input, kernel_source).await
}
fn handle_method_calls(expr: &str, method_name: &str) -> String {
let method_pattern = format!(".{}()", method_name);
let mut result = expr.to_string();
while let Some(method_pos) = result.find(&method_pattern) {
let mut paren_count = 0;
let mut start_pos = method_pos;
while start_pos > 0 {
start_pos -= 1;
let ch = result.chars().nth(start_pos).unwrap();
if ch == ')' {
paren_count += 1;
} else if ch == '(' {
if paren_count == 0 {
break;
} else {
paren_count -= 1;
}
}
}
if start_pos < method_pos && result.chars().nth(start_pos) == Some('(') {
let expr_content = &result[start_pos + 1..method_pos];
let end_pos = method_pos + method_pattern.len();
let replacement = format!("{}({})", method_name, expr_content);
result.replace_range(start_pos..end_pos, &replacement);
} else {
break;
}
}
result
}
pub fn transpile_rust_expression(expr_str: &str, var_name: &str) -> String {
let mut wgsl_expr = expr_str.to_string();
wgsl_expr = wgsl_expr.replace(var_name, "input[index]");
wgsl_expr = wgsl_expr.replace("input[index].abs()", "abs(input[index])");
wgsl_expr = wgsl_expr.replace("input[index].sqrt()", "sqrt(input[index])");
wgsl_expr = wgsl_expr.replace("input[index].sin()", "sin(input[index])");
wgsl_expr = wgsl_expr.replace("input[index].cos()", "cos(input[index])");
wgsl_expr = handle_method_calls(&wgsl_expr, "abs");
wgsl_expr = handle_method_calls(&wgsl_expr, "sqrt");
wgsl_expr = handle_method_calls(&wgsl_expr, "sin");
wgsl_expr = handle_method_calls(&wgsl_expr, "cos");
wgsl_expr = handle_method_calls(&wgsl_expr, "tan");
wgsl_expr = wgsl_expr.replace(" 1 ", " 1.0 ");
wgsl_expr = wgsl_expr.replace(" 2 ", " 2.0 ");
wgsl_expr = wgsl_expr.replace(" 3 ", " 3.0 ");
wgsl_expr = wgsl_expr.replace(" 4 ", " 4.0 ");
wgsl_expr = wgsl_expr.replace(" 5 ", " 5.0 ");
if wgsl_expr.starts_with("1 ") { wgsl_expr = wgsl_expr.replacen("1 ", "1.0 ", 1); }
if wgsl_expr.starts_with("2 ") { wgsl_expr = wgsl_expr.replacen("2 ", "2.0 ", 1); }
if wgsl_expr.ends_with(" 1") { wgsl_expr = wgsl_expr.replace(" 1", " 1.0"); }
if wgsl_expr.ends_with(" 2") { wgsl_expr = wgsl_expr.replace(" 2", " 2.0"); }
format!(r#"
@group(0) @binding(0) var<storage, read> input: array<f32>;
@group(0) @binding(1) var<storage, read_write> output: array<f32>;
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {{
let index = global_id.x;
if (index >= arrayLength(&input)) {{ return; }}
output[index] = {};
}}
"#, wgsl_expr)
}