Created
January 29, 2025 20:57
-
-
Save jaytaph/1e165e3cd2d7eacc530732f724c2d72e to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[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" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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