use wgpu::{Device, Queue, Buffer as WgpuBuffer, BufferUsages, Instance};
use std::sync::Arc;
use crate::buffer::{Header, Slot};
use pollster;
pub struct WebGpuProducer {
device: Arc<Device>,
queue: Arc<Queue>,
ring_buffer: WgpuBuffer,
header_buffer: WgpuBuffer,
n_slots: usize,
}
impl WebGpuProducer {
pub async fn new_async(n_slots: usize) -> Result<Self, String> {
let instance = Instance::new(&wgpu::InstanceDescriptor::default());
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
force_fallback_adapter: false,
compatible_surface: None,
})
.await
.map_err(|e| format!("Failed to get GPU adapter: {}", e))?;
let info = adapter.get_info();
println!("WebGPU Adapter: {} ({:?})", info.name, info.backend);
println!(" Device Type: {:?}", info.device_type);
println!(" Driver: {}", info.driver);
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: Some("Perdix WebGPU Device"),
required_features: wgpu::Features::empty(),
required_limits: wgpu::Limits::default(),
memory_hints: Default::default(),
trace: Default::default(),
}
)
.await
.map_err(|e| format!("Failed to create WebGPU device: {}", e))?;
let device: Arc<Device> = Arc::new(device);
let queue: Arc<Queue> = Arc::new(queue);
let ring_buffer_size = n_slots * std::mem::size_of::<Slot>();
let ring_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Perdix Ring Buffer"),
size: ring_buffer_size as u64,
usage: BufferUsages::STORAGE | BufferUsages::COPY_DST | BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let header_size = std::mem::size_of::<Header>();
let header_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Perdix Header"),
size: header_size as u64,
usage: BufferUsages::STORAGE | BufferUsages::COPY_DST | BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
Ok(Self {
device,
queue,
ring_buffer,
header_buffer,
n_slots,
})
}
pub fn new(n_slots: usize) -> Result<Self, String> {
pollster::block_on(Self::new_async(n_slots))
}
pub fn produce(&mut self, data: &[u8], seq: u64) -> Result<(), String> {
if data.len() > 240 {
return Err("Message too large".to_string());
}
let mut slot_data = vec![0u8; std::mem::size_of::<Slot>()];
slot_data[0..8].copy_from_slice(&seq.to_le_bytes());
slot_data[8..12].copy_from_slice(&(data.len() as u32).to_le_bytes());
slot_data[12..16].copy_from_slice(&0u32.to_le_bytes());
slot_data[20..20 + data.len()].copy_from_slice(data);
let slot_idx = (seq as usize) & (self.n_slots - 1);
let offset = slot_idx * std::mem::size_of::<Slot>();
self.queue.write_buffer(
&self.ring_buffer,
offset as u64,
&slot_data,
);
self.queue.submit(None);
Ok(())
}
pub fn launch_compute(&self, n_messages: u32) -> Result<(), String> {
println!("WebGPU: Would launch compute shader for {} messages", n_messages);
Ok(())
}
}
pub struct WebGpuBuffer {
device: Arc<Device>,
queue: Arc<Queue>,
slots_buffer: WgpuBuffer,
header_buffer: WgpuBuffer,
n_slots: usize,
}
impl WebGpuBuffer {
pub fn new(n_slots: usize) -> Result<Self, String> {
pollster::block_on(Self::new_async(n_slots))
}
pub async fn new_async(n_slots: usize) -> Result<Self, String> {
if !n_slots.is_power_of_two() {
return Err("n_slots must be power of 2".to_string());
}
let instance = Instance::default();
let adapter = instance
.request_adapter(&wgpu::RequestAdapterOptions {
power_preference: wgpu::PowerPreference::HighPerformance,
..Default::default()
})
.await
.map_err(|e| format!("Failed to get GPU adapter: {}", e))?;
let (device, queue) = adapter
.request_device(&Default::default())
.await
.map_err(|e| format!("Device creation failed: {}", e))?;
let device: Arc<Device> = Arc::new(device);
let queue: Arc<Queue> = Arc::new(queue);
let slots_size = n_slots * std::mem::size_of::<Slot>();
let slots_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Ring Buffer Slots"),
size: slots_size as u64,
usage: BufferUsages::STORAGE | BufferUsages::COPY_DST | BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
let header_buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("Ring Buffer Header"),
size: std::mem::size_of::<Header>() as u64,
usage: BufferUsages::STORAGE | BufferUsages::COPY_DST | BufferUsages::COPY_SRC,
mapped_at_creation: false,
});
Ok(Self {
device,
queue,
slots_buffer,
header_buffer,
n_slots,
})
}
pub fn get_buffers(&self) -> (&WgpuBuffer, &WgpuBuffer) {
(&self.slots_buffer, &self.header_buffer)
}
}
pub struct WebGpuCompute {
device: Arc<Device>,
queue: Arc<Queue>,
pipeline: wgpu::ComputePipeline,
bind_group: wgpu::BindGroup,
}
impl WebGpuCompute {
pub fn new(device: Arc<Device>, queue: Arc<Queue>, buffer: &WebGpuBuffer) -> Result<Self, String> {
let shader_source = r#"
struct Slot {
seq: u64,
len: u32,
flags: u32,
payload: array<u32, 60>, // 240 bytes / 4
}
struct Header {
producer_write_idx: atomic<u64>,
producer_msg_count: atomic<u64>,
producer_pad: array<u32, 12>,
consumer_read_idx: atomic<u64>,
consumer_msg_count: atomic<u64>,
consumer_pad: array<u32, 12>,
wrap_mask: u64,
slot_count: u32,
payload_size: u32,
batch_size: u32,
}
@group(0) @binding(0)
var<storage, read_write> slots: array<Slot>;
@group(0) @binding(1)
var<storage, read_write> header: Header;
// ANSI color codes for different agent types
const ANSI_RESET: u32 = 0x1b5b306d; // \x1b[0m
const ANSI_CYAN: u32 = 0x1b5b3336; // \x1b[36m
const ANSI_GREEN: u32 = 0x1b5b3332; // \x1b[32m
const ANSI_YELLOW: u32 = 0x1b5b3333; // \x1b[33m
const ANSI_RED: u32 = 0x1b5b3331; // \x1b[31m
@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) global_id: vec3<u32>) {
let idx = global_id.x;
// Get current write position atomically
let write_pos = atomicAdd(&header.producer_write_idx, 1u);
let slot_idx = write_pos & header.wrap_mask;
// Bounds check
if (slot_idx >= header.slot_count) {
return;
}
// Get slot reference
let slot = &slots[slot_idx];
// Apply ANSI formatting based on agent type (flags field)
let agent_type = slot.flags & 0xFFu;
// Inject ANSI color codes at start of payload
if (agent_type == 1u) { // System
// Prepend cyan color
// In real implementation, would modify payload
} else if (agent_type == 2u) { // Assistant
// Prepend green color
} else if (agent_type == 3u) { // User
// Prepend yellow color
} else if (agent_type == 4u) { // Error
// Prepend red color
}
// Update sequence number for consumer
slot.seq = write_pos;
// Memory fence to ensure visibility
storageBarrier();
// Update message count
atomicAdd(&header.producer_msg_count, 1u);
}
"#;
let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("Perdix Compute Shader"),
source: wgpu::ShaderSource::Wgsl(shader_source.into()),
});
let bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: Some("Perdix Bind Group Layout"),
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
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,
},
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 pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: Some("Perdix Pipeline Layout"),
bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
});
let pipeline = device.create_compute_pipeline(&wgpu::ComputePipelineDescriptor {
label: Some("Perdix Compute Pipeline"),
layout: Some(&pipeline_layout),
module: &shader,
entry_point: Some("main"),
compilation_options: Default::default(),
cache: None,
});
let (slots_buffer, header_buffer) = buffer.get_buffers();
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: Some("Perdix Bind Group"),
layout: &bind_group_layout,
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: slots_buffer.as_entire_binding(),
},
wgpu::BindGroupEntry {
binding: 1,
resource: header_buffer.as_entire_binding(),
},
],
});
Ok(Self {
device,
queue,
pipeline,
bind_group,
})
}
pub fn dispatch(&self, workgroups: u32) {
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("Perdix Compute Encoder"),
});
{
let mut compute_pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor {
label: Some("Perdix Compute Pass"),
timestamp_writes: None,
});
compute_pass.set_pipeline(&self.pipeline);
compute_pass.set_bind_group(0, &self.bind_group, &[]);
compute_pass.dispatch_workgroups(workgroups, 1, 1);
}
self.queue.submit(Some(encoder.finish()));
}
}