use std::sync::Arc; use winit::{ event::{Event, WindowEvent}, event_loop::EventLoop, window::WindowBuilder, }; use rootvg::image::{ImagePrimitive, RcTexture}; use rootvg::math::{PhysicalSizeI32, Point, Rect, ScaleFactor, Size}; use rootvg::quad::{SolidQuad, SolidQuadPrimitive}; use rootvg::{ color::{PackedSrgb, RGBA8}, math::PhysicalSizeU32, }; const PREPASS_TEXTURE_SIZE: PhysicalSizeU32 = PhysicalSizeU32::new(200, 200); static SHADER: &'static str = " struct VertexOutput { @builtin(position) clip_position: vec4, }; @vertex fn vs_main( @builtin(vertex_index) in_vertex_index: u32, ) -> VertexOutput { var out: VertexOutput; let x = f32(1 - i32(in_vertex_index)) * 0.9; let y = f32(i32(in_vertex_index & 1u) * 2 - 1) * 0.9; out.clip_position = vec4(x, y, 0.0, 1.0); return out; } @fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { return vec4(0.8, 0.1, 0.1, 1.0); } "; fn main() { // Set up logging stuff let env = env_logger::Env::default().filter_or("LOG_LEVEL", "info"); env_logger::init_from_env(env); // --- Set up winit window ----------------------------------------------------------- let (width, height) = (800, 425); let event_loop = EventLoop::new().unwrap(); let window = Arc::new( WindowBuilder::new() .with_inner_size(winit::dpi::LogicalSize::new(width as f64, height as f64)) .with_title("RootVG Demo") .build(&event_loop) .unwrap(), ); let physical_size = window.inner_size(); // RootVG uses integers to represent physical pixels instead of unsigned integers. let mut physical_size = PhysicalSizeI32::new(physical_size.width as i32, physical_size.height as i32); let mut scale_factor: ScaleFactor = window.scale_factor().into(); // --- Surface ----------------------------------------------------------------------- // RootVG provides an optional default wgpu surface configuration for convenience. let mut surface = rootvg::surface::DefaultSurface::new( physical_size, scale_factor, Arc::clone(&window), rootvg::surface::DefaultSurfaceConfig::default(), ) .unwrap(); // --- Color format ------------------------------------------------------------------ // RootVG uses colors in a packed SRGB format of `[f32; 4]`. // // This is to prevent the need to constantly convert from an 8-bit RGBA // representation to the representation used by the GPU. let clear_color: PackedSrgb = RGBA8::new(15, 15, 15, 255).into(); // --- Canvas ------------------------------------------------------------------------ // A `Canvas` automatically batches primitives and renders them to a // render target (such as the output framebuffer). let mut canvas = rootvg::Canvas::new( &surface.device, &surface.queue, surface.format(), surface.canvas_config(), ); // --- Custom prepass pipeline ------------------------------------------------------- // Create any pipeline that renders to a texture. Here we have a basic pipeline // that just draws red triangle. let prepass_shader = surface .device .create_shader_module(wgpu::ShaderModuleDescriptor { label: Some("Shader"), source: wgpu::ShaderSource::Wgsl(SHADER.into()), }); let prepass_pipeline_layout = surface .device .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { label: Some("Render Pipeline Layout"), bind_group_layouts: &[], push_constant_ranges: &[], }); let prepass_pipeline = surface .device .create_render_pipeline(&wgpu::RenderPipelineDescriptor { label: Some("Render Pipeline"), layout: Some(&prepass_pipeline_layout), vertex: wgpu::VertexState { module: &prepass_shader, entry_point: "vs_main", buffers: &[], }, fragment: Some(wgpu::FragmentState { module: &prepass_shader, entry_point: "fs_main", targets: &[Some(wgpu::ColorTargetState { format: surface.format(), blend: Some(wgpu::BlendState::REPLACE), 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), polygon_mode: wgpu::PolygonMode::Fill, unclipped_depth: false, conservative: false, }, depth_stencil: None, multisample: wgpu::MultisampleState { count: 1, mask: !0, alpha_to_coverage_enabled: false, }, multiview: None, }); let texture_size = wgpu::Extent3d { width: PREPASS_TEXTURE_SIZE.width, height: PREPASS_TEXTURE_SIZE.height, depth_or_array_layers: 1, }; let prepass_texture = surface.device.create_texture(&wgpu::TextureDescriptor { size: texture_size, mip_level_count: 1, sample_count: 1, dimension: wgpu::TextureDimension::D2, format: surface.format(), usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING, label: Some("prepass_texture"), view_formats: &[], }); // --- Image primitive --------------------------------------------------------------- // Create an image primitive that uses the prepass texture as the source. let prepass_rc_texture = RcTexture::from_prepass_texture( prepass_texture.create_view(&wgpu::TextureViewDescriptor::default()), PREPASS_TEXTURE_SIZE, ); let image_primitive = ImagePrimitive::new(prepass_rc_texture, Point::new(180.0, 100.0)); // ----------------------------------------------------------------------------------- event_loop .run(move |event, target| { if let Event::WindowEvent { window_id: _, event, } = event { match event { // Resize the Canvas to match the new window size WindowEvent::Resized(new_size) => { physical_size = PhysicalSizeI32::new(new_size.width as i32, new_size.height as i32); surface.resize(physical_size, scale_factor); window.request_redraw(); } WindowEvent::ScaleFactorChanged { scale_factor: new_scale, inner_size_writer: _, } => { scale_factor = new_scale.into(); surface.resize(physical_size, scale_factor); window.request_redraw(); } WindowEvent::RedrawRequested => { { let mut cx = canvas.begin(physical_size, scale_factor); // Demonstrate that the texture is indeed being drawn as a // RootVG image by drawing a quad below and above it. cx.add(SolidQuadPrimitive::new(&SolidQuad { bounds: Rect::new(Point::new(100.0, 50.0), Size::new(200.0, 200.0)), bg_color: RGBA8::new(100, 100, 100, 255).into(), ..Default::default() })); cx.set_z_index(1); cx.add(image_primitive.clone()); cx.set_z_index(2); cx.add(SolidQuadPrimitive::new(&SolidQuad { bounds: Rect::new(Point::new(275.0, 70.0), Size::new(200.0, 200.0)), bg_color: RGBA8::new(200, 100, 200, 100).into(), ..Default::default() })); } let mut encoder = surface.device.create_command_encoder( &wgpu::CommandEncoderDescriptor { label: None }, ); // Render the texture in a pre-pass. { let view = prepass_texture .create_view(&wgpu::TextureViewDescriptor::default()); let mut pre_render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("Render Pass"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: &view, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0, }), store: wgpu::StoreOp::Store, }, })], depth_stencil_attachment: None, occlusion_query_set: None, timestamp_writes: None, }); pre_render_pass.set_pipeline(&prepass_pipeline); pre_render_pass.draw(0..3, 0..1); } let frame = surface.get_current_texture().unwrap(); let view = frame .texture .create_view(&wgpu::TextureViewDescriptor::default()); // Render the canvas to the target texture. canvas .render_to_target( Some(clear_color), &surface.device, &surface.queue, &mut encoder, &view, physical_size, ) .unwrap(); // Submit the commands and present the frame. surface.queue.submit(Some(encoder.finish())); frame.present(); } WindowEvent::CloseRequested => target.exit(), _ => {} } } }) .unwrap(); }