claw-code/.guardrails/examples/rust/egui-overlay/wgpu-integration.rs

437 lines
14 KiB
Rust

// wgpu Integration - GPU-Accelerated UI Rendering for egui
// Production-ready GPU-accelerated rendering patterns
//
// Last Updated: 2026-03-14
// egui Version: 0.27+
// Rust Version: 1.85+
// wgpu Version: 0.20+
use egui::{Context, ClippedPrimitive, TexturesDelta};
use wgpu::{
Device, Queue, Buffer, RenderPipeline, RenderPassDescriptor,
Texture, TextureView, BufferInitDescriptor, BufferUsages,
CommandEncoder, TextureAspect, ImageInfo,
};
use std::sync::Arc;
/// WgpuRenderer - GPU-accelerated egui rendering
pub struct WgpuRenderer {
/// wgpu device
pub device: Arc<Device>,
/// wgpu queue
pub queue: Arc<Queue>,
/// egui render pipeline
pub pipeline: RenderPipeline,
/// egui vertex buffer
pub vertex_buffer: Buffer,
/// egui texture
pub texture: Texture,
/// Texture view
pub texture_view: TextureView,
/// Current sequence
pub sequence: u64,
/// GPU memory usage
pub gpu_memory_bytes: u64,
/// Validation enabled
pub validation_enabled: bool,
}
/// Implementation: WgpuRenderer
impl WgpuRenderer {
/// Create new wgpu renderer
pub fn new(device: Device, queue: Queue, ctx: Context) -> Self {
let pipeline = Self::create_render_pipeline(&device, &ctx);
let vertex_buffer = Self::create_vertex_buffer(&device);
let texture = Self::create_texture(&device);
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor {
label: "egui texture view",
..Default::default()
});
Self {
device: Arc::new(device),
queue: Arc::new(queue),
pipeline,
vertex_buffer,
texture,
texture_view,
sequence: 0,
gpu_memory_bytes: 0,
validation_enabled: true,
}
}
/// Create render pipeline - egui wgpu integration
fn create_render_pipeline(device: &Device, ctx: &Context) -> RenderPipeline {
let module = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: "egui shader",
source: wgpu::ShaderSource::Wgui(ctx.shader_source()),
});
let layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: "egui pipeline layout",
bind_group_layouts: &[&Self::create_bind_group_layout(device)],
push_constant_ranges: &[],
});
device.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: "egui render pipeline",
layout: Some(layout),
vertex: wgpu::VertexState {
module: &module,
entry_point: "vs_main",
buffers: &[Self::vertex_buffer_layout()],
},
fragment: Some(wgpu::FragmentState {
module: &module,
entry_point: "fs_main",
targets: &[Some(wgpu::ColorTargetState {
format: wgpu::TextureFormat::Rgba8Unorm,
blend: Some(wgpu::BlendState::PREMULTIPLIED_ALPHA),
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back),
unclipped_depth: false,
polygon_mode: wgpu::PolygonMode::Fill,
conservative: false,
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
sample: 0,
},
multiview: None,
})
}
/// Create bind group layout
fn create_bind_group_layout(device: &Device) -> wgpu::BindGroupLayout {
device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: "egui bind group layout",
entries: &[
wgpu::BindGroupLayoutEntry {
binding: 0,
visibility: wgpu::ShaderStages::VERTEX,
ty: wgpu::BindingType::Buffer {
ty: wgpu::BufferBindingType::Uniform,
has_dynamic_offset: true,
min_binding_size: None,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 1,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
},
wgpu::BindGroupLayoutEntry {
binding: 2,
visibility: wgpu::ShaderStages::FRAGMENT,
ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
count: None,
},
],
})
}
/// Create vertex buffer
fn create_vertex_buffer(device: &Device) -> Buffer {
device.create_buffer(&BufferInitDescriptor {
label: "egui vertex buffer",
size: 1024,
usage: BufferUsages::VERTEX | BufferUsages::COPY_DST,
mapped_at_creation: false,
})
}
/// Create texture
fn create_texture(device: &Device) -> Texture {
device.create_texture(&wgpu::TextureDescriptor {
label: "egui texture",
size: wgpu::Extent3d {
width: 256,
height: 256,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Rgba8Unorm,
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
view_formats: &[],
})
}
/// Vertex buffer layout
fn vertex_buffer_layout() -> wgpu::VertexBufferLayout {
wgpu::VertexBufferLayout {
array_stride: 32,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x2,
offset: 0,
shader_location: 0,
},
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x2,
offset: 8,
shader_location: 1,
},
wgpu::VertexAttribute {
format: wgpu::VertexFormat::Float32x4,
offset: 16,
shader_location: 2,
},
],
}
}
/// Render frame - GPU render pass
pub fn render_frame(&self, clipped_primitives: &[ClippedPrimitive], textures_delta: &TexturesDelta) {
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: "egui render encoder",
});
// Upload texture deltas
for (id, image_delta) in textures_delta.set {
self.upload_texture_delta(id, image_delta, &mut encoder);
}
// Begin render pass
let mut render_pass = encoder.begin_render_pass(&RenderPassDescriptor {
label: "egui render pass",
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &self.texture_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::TRANSPARENT),
store: wgpu::StoreOp::Store,
},
})],
depth_stencil_attachment: None,
timestamp_writes: None,
occlusion_query_set: None,
});
// Draw clipped primitives
for clipped_primitive in clipped_primitives {
self.draw_primitive(clipped_primitive, &mut render_pass);
}
// Submit commands
let commands = encoder.finish();
self.queue.submit(&[commands]);
self.sequence += 1;
log::debug!("Frame rendered: sequence {}", self.sequence);
}
/// Upload texture delta
fn upload_texture_delta(&self, id: u32, delta: egui::epaint::ImageDelta, encoder: &mut CommandEncoder) {
let pixels = delta.image.bytes.as_slice();
// GPU texture upload - direct write
encoder.write_texture(
&self.texture,
TextureAspect::All,
pixels,
ImageInfo {
offset: (delta.pos.x, delta.pos.y, 0),
size: wgpu::Extent3d {
width: delta.image.width() as u32,
height: delta.image.height() as u32,
depth_or_array_layers: 1,
},
copy_size: wgpu::Extent3d {
width: delta.image.width() as u32,
height: delta.image.height() as u32,
depth_or_array_layers: 1,
},
..Default::default()
},
);
self.gpu_memory_bytes += pixels.len() as u64;
}
/// Draw primitive
fn draw_primitive(&self, primitive: &ClippedPrimitive, render_pass: &mut wgpu::RenderPass) {
// Validate primitive before draw
if self.validation_enabled {
self.validate_primitive(primitive);
}
// Set pipeline
render_pass.set_pipeline(&self.pipeline);
// Set vertex buffer
render_pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
// Set bind group
let bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: "egui bind group",
layout: &Self::create_bind_group_layout(&self.device),
entries: &[
wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::Buffer(wgpu::BufferBinding {
buffer: &self.vertex_buffer,
offset: 0,
size: Some(self.vertex_buffer.size()),
}),
},
wgpu::BindGroupEntry {
binding: 1,
resource: wgpu::BindingResource::TextureView(&self.texture_view),
},
wgpu::BindGroupEntry {
binding: 2,
resource: wgpu::BindingResource::Sampler(
&self.device.create_sampler(&wgpu::SamplerDescriptor::default()),
),
},
],
});
render_pass.set_bind_group(0, &bind_group, &[0]);
// Draw
render_pass.draw(0..6, 0..1);
}
/// Validate primitive - GPU validation
fn validate_primitive(&self, primitive: &ClippedPrimitive) {
// Clip rect validation
if primitive.clip_rect.width() > 4096.0 || primitive.clip_rect.height() > 4096.0 {
log::warn("Primitive clip rect too large");
}
// Vertex count validation
if primitive.primitive.primitives_count() > 10000 {
log::warn("Primitive count too high: {}", primitive.primitive.primitives_count());
}
}
/// Get GPU memory usage
pub fn gpu_memory_usage(&self) -> u64 {
self.gpu_memory_bytes
}
/// Get sequence
pub fn sequence(&self) -> u64 {
self.sequence
}
/// Is validation enabled?
pub fn is_validation_enabled(&self) -> bool {
self.validation_enabled
}
/// Toggle validation
pub fn toggle_validation(&mut self) {
self.validation_enabled = !self.validation_enabled;
log::info!("Validation: {}", self.validation_enabled);
}
/// Clear GPU buffer
pub fn clear_buffer(&mut self) {
self.gpu_memory_bytes = 0;
log::info("GPU buffer cleared");
}
}
/// TextureUsages - Texture usage flags
const TextureUsages: wgpu::TextureUsages = wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST;
/// wgpu render system - Main GPU render loop
pub fn wgpu_render_system(
mut renderer: ResMut<WgpuRenderer>,
frame_data: Res<FrameData>,
) {
// Render frame via GPU
renderer.render_frame(&frame_data.clipped_primitives, &frame_data.textures_delta);
// Log GPU memory
log::debug("GPU memory: {} bytes", renderer.gpu_memory_usage());
}
/// Frame data resource for wgpu
#[derive(Resource, Clone, Debug)]
pub struct FrameData {
pub sequence: u64,
pub clipped_primitives: Vec<ClippedPrimitive>,
pub textures_delta: TexturesDelta,
}
/// wgpu renderer marker resource
#[derive(Resource, Debug)]
pub struct WgpuRendererMarker;
/// Performance benchmark - GPU vs CPU rendering
#[cfg(test)]
mod tests {
use super::*;
use std::time::Instant;
#[test]
fn test_gpu_render_performance() {
// Mock device/queue
let device = Device::default();
let queue = Queue::default();
let ctx = Context::default();
let renderer = WgpuRenderer::new(device, queue, ctx);
let primitives = vec![ClippedPrimitive::default(); 1000];
let textures_delta = TexturesDelta::default();
let start = Instant::now();
renderer.render_frame(&primitives, &textures_delta);
let duration = start.elapsed();
log::info!("GPU render: {}ms", duration.as_millis());
assert!(duration.as_millis() < 50, "GPU render too slow");
}
#[test]
fn test_gpu_memory_tracking() {
let device = Device::default();
let queue = Queue::default();
let ctx = Context::default();
let renderer = WgpuRenderer::new(device, queue, ctx);
assert!(renderer.gpu_memory_usage() > 0);
assert_eq!(renderer.sequence(), 0);
}
#[test]
fn test_primitive_validation() {
let device = Device::default();
let queue = Queue::default();
let ctx = Context::default();
let renderer = WgpuRenderer::new(device, queue, ctx);
assert!(renderer.is_validation_enabled());
let large_primitive = ClippedPrimitive {
clip_rect: egui::Rect::from_min_size(egui::pos2(0.0, 0.0), egui::vec2(5000.0, 5000.0)),
primitive: egui::epaint::Primitive::default(),
};
renderer.validate_primitive(&large_primitive);
// Validation should log warning
}
}