Skip to content

Instantly share code, notes, and snippets.

@jaytaph
Created January 29, 2025 20:57
Show Gist options
  • Save jaytaph/1e165e3cd2d7eacc530732f724c2d72e to your computer and use it in GitHub Desktop.
Save jaytaph/1e165e3cd2d7eacc530732f724c2d72e to your computer and use it in GitHub Desktop.
[package]
name = "wgpu-test"
version = "0.1.0"
edition = "2024"
[dependencies]
vello = "0.4.0"
winit = "0.30.8"
pollster = "0.4.0"
image = "0.25.5"
use std::f32::consts::PI;
use std::num::NonZeroUsize;
use std::sync::Arc;
use std::time::{Duration, Instant};
use image::ImageReader;
use vello::{kurbo::{Affine, Circle, Rect}, peniko::{Color, Fill}, wgpu, AaConfig, RenderParams, Renderer, Scene};
use vello::kurbo::Point;
use vello::peniko::{Blob, Image, ImageFormat, Mix};
use vello::util::{DeviceHandle, RenderContext, RenderSurface};
use vello::RendererOptions;
use winit::{
event::WindowEvent,
event_loop::EventLoop,
window::Window,
};
use winit::application::ApplicationHandler;
use winit::event_loop::{ActiveEventLoop, ControlFlow};
use winit::window::WindowId;
struct Layer {
id: u32,
content: Scene,
transform: Affine,
opacity: f32,
z_index: i32,
}
impl Layer {
fn new(id: u32, content: Scene, transform: Affine, opacity: f32, z_index: i32) -> Self {
Self {
id,
content,
transform,
opacity,
z_index,
}
}
}
// --- Compositor ---
#[derive(Default)]
struct Compositor {
layers: Vec<Layer>,
}
impl Compositor {
fn new() -> Self {
Self { layers: Vec::new() }
}
// Add a layer (sorted by z-index)
fn add_layer(&mut self, layer: Layer) {
self.layers.push(layer);
self.layers.sort_by_key(|l| l.z_index);
}
// Update layer properties
fn update_layer(&mut self, id: u32, transform: Option<Affine>, opacity: Option<f32>) {
if let Some(layer) = self.layers.iter_mut().find(|l| l.id == id) {
if let Some(t) = transform {
layer.transform = t;
}
if let Some(o) = opacity {
layer.opacity = o;
println!("Layer {} opacity: {}", id, o);
}
}
}
// Compose all layers into a final scene
fn compose(&self, scene: &mut Scene) {
for layer in &self.layers {
let full_rect = Rect::from_origin_size(Point::new(0.0, 0.0), (1000.0, 1000.0));
scene.push_layer(Mix::Normal, layer.opacity, layer.transform, &full_rect);
// scene.push_alpha(layer.opacity as f64);
scene.append(&layer.content, None);
scene.pop_layer(); // Restore transform/alpha
}
}
}
const AA_CONFIGS: [AaConfig; 3] = [AaConfig::Area, AaConfig::Msaa8, AaConfig::Msaa16];
struct App<'s> {
compositor: Compositor,
render_ctx: RenderContext,
window: Option<Arc<Window>>,
renderer: Option<Renderer>,
surface: Option<RenderSurface<'s>>,
frame_count: u64,
next_redraw_time: Instant,
}
impl App<'_> {
fn new() -> Self {
Self {
compositor: Compositor::new(),
render_ctx: RenderContext::new(),
window: None,
renderer: None,
surface: None,
frame_count: 0,
next_redraw_time: Instant::now(),
}
}
}
impl ApplicationHandler for App<'_> {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
if self.window.is_some() {
return;
}
let mut attribs = Window::default_attributes();
attribs.title = "Pipeline WGPU test".to_string();
let window = Arc::new(event_loop.create_window(attribs).unwrap());
let size = window.inner_size();
let surface_future = self.render_ctx.create_surface(window.clone(), size.width, size.height, wgpu::PresentMode::AutoVsync);
let surface = pollster::block_on(surface_future).unwrap();
let dev_handle = &self.render_ctx.devices[surface.dev_id];
let renderer = Renderer::new(&dev_handle.device, RendererOptions {
surface_format: Some(surface.format),
use_cpu: false,
antialiasing_support: AA_CONFIGS.iter().copied().collect(),
num_init_threads: NonZeroUsize::new(0),
});
self.window = Some(window);
self.surface = Some(surface);
self.renderer = Some(renderer.unwrap());
// Create a red rectangle (Layer 1)
let mut scene_red = Scene::new();
scene_red.fill(
Fill::NonZero,
Affine::IDENTITY,
Color::from_rgba8(255, 0, 0, 255),
None,
&Rect::new(0.0, 0.0, 200.0, 200.0),
);
self.compositor.add_layer(Layer::new(
1,
scene_red,
Affine::translate((50.0, 50.0)),
1.0,
0,
));
// let img = load_image("./gosub-logo.png");
// let mut scene_green = Scene::new();
// scene_green.draw_image(
// &img,
// Affine::IDENTITY,
// );
// self.compositor.add_layer(Layer::new(
// 3,
// scene_green,
// Affine::translate((50.0, 50.0)),
// 1.0,
// 0,
// ));
// Create a blue circle (Layer 2)
let mut scene_blue = Scene::new();
scene_blue.fill(
Fill::NonZero,
Affine::IDENTITY,
Color::from_rgba8(0, 0, 255, 255),
None,
&Circle::new((100.0, 100.0), 50.0),
);
self.compositor.add_layer(Layer::new(
2,
scene_blue,
Affine::translate((100.0, 100.0)),
1.0,
1,
));
// Animation state
self.frame_count = 0;
self.next_redraw_time = Instant::now() + Duration::from_secs_f64(1.0 / 60.0);
self.window.as_ref().unwrap().request_redraw();
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
match event {
WindowEvent::CloseRequested => {
event_loop.exit();
}
WindowEvent::Resized(size) => {
self.render_ctx.resize_surface(self.surface.as_mut().unwrap(), size.width, size.height);
}
WindowEvent::RedrawRequested => {
self.frame_count += 1;
println!("Frame: {}", self.frame_count);
// self.compositor.update_layer(
// 2,
// Some(Affine::translate((100.0 + self.frame_count as f64, 100.0))),
// None,
// );
let th = self.frame_count as f64 * (PI as f64 / 180.0);
// let th = self.frame_count as f64 * (PI / 180.0);
let op = ((self.frame_count as f64 * 0.01 * 2.0 * PI as f64).sin() + 1.0) / 2.0;
self.compositor.update_layer(
1,
Some(Affine::rotate_about(th, Point::new(100.0, 100.0))),
Some(op as f32),
);
let mut final_scene = Scene::new();
self.compositor.compose(&mut final_scene);
let surface = self.surface.as_ref().unwrap();
let dev_id = surface.dev_id;
let DeviceHandle { device, queue, .. } = &self.render_ctx.devices[dev_id];
let width = surface.config.width;
let height = surface.config.height;
let surface_texture = surface.surface.get_current_texture().expect("Failed to get current texture");
let render_params = RenderParams {
base_color: Color::BLACK,
width,
height,
antialiasing_method: AaConfig::Area,
};
let _ = self.renderer.as_mut().unwrap().render_to_surface(
&device,
&queue,
&final_scene,
&surface_texture,
&render_params
);
surface_texture.present();
self.next_redraw_time += Duration::from_secs_f64(1.0 / 60.0);
event_loop.set_control_flow(ControlFlow::WaitUntil(self.next_redraw_time));
self.window.as_ref().unwrap().request_redraw();
}
_ => ()
}
}
}
// Load a PNG image from disk and create a Vello Image
fn load_image(path: &str) -> Image {
let img = ImageReader::open(path)
.expect("Failed to open image")
.decode()
.expect("Failed to decode image")
.into_rgba8();
let (width, height) = img.dimensions();
let data = img.into_raw();
Image::new(Blob::from(data), ImageFormat::Rgba8, width, height)
}
fn main() {
// Initialize the event loop and window
let event_loop = EventLoop::new().unwrap();
event_loop.set_control_flow(ControlFlow::Poll);
let mut app = App::new();
let _ = event_loop.run_app(&mut app);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment