diff --git a/Cargo.lock b/Cargo.lock index 26cfe6492..2e56a6c26 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4284,6 +4284,7 @@ version = "0.0.0" dependencies = [ "android_logger", "anyhow", + "bytemuck", "clap", "console_error_panic_hook", "console_log", @@ -4305,6 +4306,7 @@ dependencies = [ "wasm-bindgen-futures", "web-sys", "web-time", + "wgpu", "wgpu-profiler", "winit", ] diff --git a/examples/simple/src/main.rs b/examples/simple/src/main.rs index 3b1eb8bf1..c9873d286 100644 --- a/examples/simple/src/main.rs +++ b/examples/simple/src/main.rs @@ -4,8 +4,9 @@ //! Simple example. use anyhow::Result; +use std::f64::consts::{FRAC_PI_2, FRAC_PI_4}; use std::sync::Arc; -use vello::kurbo::{Affine, Circle, Ellipse, Line, RoundedRect, Stroke}; +use vello::kurbo::{Affine, BezPath, Circle, Ellipse, Line, RoundedRect, Shape, Stroke}; use vello::peniko::Color; use vello::peniko::color::palette; use vello::util::{RenderContext, RenderSurface}; diff --git a/examples/with_winit/Cargo.toml b/examples/with_winit/Cargo.toml index 73573966d..7868d19c9 100644 --- a/examples/with_winit/Cargo.toml +++ b/examples/with_winit/Cargo.toml @@ -35,10 +35,12 @@ scenes = { workspace = true } anyhow = { workspace = true } clap = { workspace = true, features = ["derive"] } pollster = { workspace = true } +wgpu = { workspace = true } wgpu-profiler = { workspace = true, optional = true } winit = { workspace = true } log = { workspace = true } +bytemuck = { workspace = true } # We're still using env-logger, but we want to use tracing spans to allow using # tracing_android_trace diff --git a/examples/with_winit/src/lib.rs b/examples/with_winit/src/lib.rs index bdb2409d1..28cb1e3d5 100644 --- a/examples/with_winit/src/lib.rs +++ b/examples/with_winit/src/lib.rs @@ -22,8 +22,11 @@ use minimal_pipeline_cache::{get_cache_directory, load_pipeline_cache, write_pip #[cfg(not(target_arch = "wasm32"))] use std::time::Instant; use vello::low_level::DebugLayers; +use vello::peniko::Blob; #[cfg(target_arch = "wasm32")] use web_time::Instant; +use wgpu::TexelCopyTextureInfoBase; +use wgpu_shader::DemoRenderer; use winit::application::ApplicationHandler; use winit::event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase, WindowEvent}; use winit::keyboard::{Key, ModifiersState, NamedKey}; @@ -51,6 +54,7 @@ mod hot_reload; mod minimal_pipeline_cache; mod multi_touch; mod stats; +mod wgpu_shader; #[derive(Parser, Debug)] #[command(about, long_about = None, bin_name="cargo run -p with_winit --")] @@ -170,6 +174,8 @@ struct VelloApp<'s> { prev_scene_ix: i32, modifiers: ModifiersState, + demo_renderer: Option, + debug: DebugLayers, #[cfg(not(target_arch = "wasm32"))] @@ -205,6 +211,14 @@ impl ApplicationHandler for VelloApp<'_> { self.renderers .resize_with(self.context.devices.len(), || None); let id = render_state.surface.dev_id; + + // Initialise demo renderer + let device_handle = &self.context.devices[id]; + self.demo_renderer = Some(DemoRenderer::new( + &device_handle.device, + &device_handle.queue, + )); + self.renderers[id].get_or_insert_with(|| { let device_handle = &self.context.devices[id]; let cache = if let Some((dir, tx)) = self.cache_data.as_ref() { @@ -484,6 +498,40 @@ impl ApplicationHandler for VelloApp<'_> { let device_handle = &self.context.devices[surface.dev_id]; let snapshot = self.stats.snapshot(); + // Render demo and store as override_image + // TODO: reuse dummy images and texture mapping + + let mut demo_texture = None; + let demo_w = width / 2; + let demo_h = height / 2; + let demo_x = width / 4; + let demo_y = height / 4; + if let Some(renderer) = self.renderers[surface.dev_id].as_mut() { + if let Some(demo) = self.demo_renderer.as_mut() { + let texture = demo.render(1.0, 0.0, 0.0, demo_w, demo_h); + let dummy_image = vello::peniko::Image { + data: Blob::new(Arc::new([])), // will be ignored + format: vello::peniko::ImageFormat::Rgba8, + width: demo_w, + height: demo_h, + x_extend: vello::peniko::Extend::Pad, + y_extend: vello::peniko::Extend::Pad, + quality: vello::peniko::ImageQuality::High, + alpha: 1.0, + }; + + let base = TexelCopyTextureInfoBase { + texture, + mip_level: 0, + origin: wgpu::Origin3d::ZERO, + aspect: wgpu::TextureAspect::All, + }; + renderer.override_image(&dummy_image, Some(base)); + + demo_texture = Some(dummy_image); + } + } + // Allow looping forever self.scene_ix = self.scene_ix.rem_euclid(self.scenes.len() as i32); self.aa_config_ix = self.aa_config_ix.rem_euclid(AA_CONFIGS.len() as i32); @@ -531,6 +579,17 @@ impl ApplicationHandler for VelloApp<'_> { transform *= Affine::scale(scale_factor); } self.scene.append(&self.fragment, Some(transform)); + + if let Some(demo_texture) = demo_texture { + self.scene.draw_image( + &demo_texture, + Affine::translate(Vec2 { + x: demo_x as f64, + y: demo_y as f64, + }), + ); + }; + if self.stats_shown { snapshot.draw_layer( &mut self.scene, @@ -821,6 +880,8 @@ fn run( frame_start_time: Instant::now(), start: Instant::now(), + demo_renderer: None, + touch_state: multi_touch::TouchState::new(), navigation_fingers: HashSet::new(), transform: Affine::IDENTITY, diff --git a/examples/with_winit/src/shader.wgsl b/examples/with_winit/src/shader.wgsl new file mode 100644 index 000000000..e4b761159 --- /dev/null +++ b/examples/with_winit/src/shader.wgsl @@ -0,0 +1,111 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) frag_position: vec2, +}; + +@vertex +fn vs_main( + @builtin(vertex_index) vertex_index: u32 +) -> VertexOutput { + var output: VertexOutput; + + var positions = array, 3>( + vec2(-1.0, 3.0), + vec2(-1.0, -1.0), + vec2( 3.0, -1.0) + ); + + let pos = positions[vertex_index]; + output.position = vec4(pos.x, -pos.y, 0.0, 1.0); + output.frag_position = pos; + return output; +} + +struct PushConstants { + light_color_and_time: vec4, +}; + +var pc: PushConstants; + +fn sdRoundBox(p: vec3, b: vec3, r: f32) -> f32 { + let q = abs(p) - b; + return length(max(q, vec3(0.0))) + min(max(q.x, max(q.y, q.z)), 0.0) - r; +} + +fn rotateY(r: vec3, angle: f32) -> vec3 { + let c = cos(angle); + let s = sin(angle); + let rotation_matrix = mat3x3( + vec3( c, 0.0, s), + vec3(0.0, 1.0, 0.0), + vec3(-s, 0.0, c) + ); + return rotation_matrix * r; +} + +fn rotateZ(r: vec3, angle: f32) -> vec3 { + let c = cos(angle); + let s = sin(angle); + let rotation_matrix = mat3x3( + vec3( c, -s, 0.0), + vec3( s, c, 0.0), + vec3(0.0, 0.0, 1.0) + ); + return rotation_matrix * r; +} + +// Distance from the scene +fn scene(r: vec3) -> f32 { + let iTime = pc.light_color_and_time.w; + let pos = rotateZ(rotateY(r + vec3(-1.0, -1.0, 4.0), iTime), iTime); + let cube = vec3(0.5, 0.5, 0.5); + let edge = 0.1; + return sdRoundBox(pos, cube, edge); +} + +// https://iquilezles.org/articles/normalsSDF +fn normal(pos: vec3) -> vec3 { + let e = vec2(1.0, -1.0) * 0.5773; + let eps = 0.0005; + return normalize( + e.xyy * scene(pos + e.xyy * eps) + + e.yyx * scene(pos + e.yyx * eps) + + e.yxy * scene(pos + e.yxy * eps) + + e.xxx * scene(pos + e.xxx * eps) + ); +} + +fn render(fragCoord: vec2, light_color: vec3) -> vec4 { + var color = vec4(0.0, 0.0, 0.0, 1.0); + + var camera = vec3(1.0, 2.0, 1.0); + var p = vec3(fragCoord.x, fragCoord.y + 1.0, -1.0); + var dir = normalize(p - camera); + + var i = 0; + loop { + if (i >= 90) { break; } + let dist = scene(p); + if (dist < 0.0001) { break; } + p = p + dir * dist; + i = i + 1; + } + + let surf_normal = normal(p); + let light_position = vec3(2.0, 4.0, -0.5); + var light = 7.0 + 2.0 * dot(surf_normal, light_position); + light = light / (0.2 * pow(length(light_position - p), 3.5)); + + let alpha = select(0.0, 1.0, i < 90); + return vec4(light * light_color, alpha) * 2.0; +} + +@fragment +fn fs_main(@location(0) frag_position: vec2) -> @location(0) vec4 { + let selected_light_color = pc.light_color_and_time.xyz; + let r = vec2(0.5 * frag_position.x + 1.0, 0.5 - 0.5 * frag_position.y); + return render(r, selected_light_color); +} diff --git a/examples/with_winit/src/wgpu_shader.rs b/examples/with_winit/src/wgpu_shader.rs new file mode 100644 index 000000000..7d58acf7a --- /dev/null +++ b/examples/with_winit/src/wgpu_shader.rs @@ -0,0 +1,146 @@ +// Copyright © SixtyFPS GmbH +// SPDX-License-Identifier: MIT + +pub(crate) struct DemoRenderer { + device: wgpu::Device, + queue: wgpu::Queue, + pipeline: wgpu::RenderPipeline, + displayed_texture: wgpu::Texture, + next_texture: wgpu::Texture, + start_time: std::time::Instant, +} + +#[repr(C)] +#[derive(Clone, Copy, bytemuck::Pod, bytemuck::Zeroable)] +pub(crate) struct PushConstants { + light_color_and_time: [f32; 4], +} + +impl DemoRenderer { + pub(crate) fn new(device: &wgpu::Device, queue: &wgpu::Queue) -> Self { + let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor { + label: None, + source: wgpu::ShaderSource::Wgsl(std::borrow::Cow::Borrowed(include_str!( + "shader.wgsl" + ))), + }); + + let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[], + push_constant_ranges: &[wgpu::PushConstantRange { + stages: wgpu::ShaderStages::FRAGMENT, + range: 0..16, // full size in bytes, aligned + }], + }); + + let pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: Some("vs_main"), + buffers: &[], + compilation_options: Default::default(), + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: Some("fs_main"), + compilation_options: Default::default(), + targets: &[Some(wgpu::TextureFormat::Rgba8Unorm.into())], + }), + primitive: wgpu::PrimitiveState::default(), + depth_stencil: None, + multisample: wgpu::MultisampleState::default(), + multiview: None, + cache: None, + }); + + let displayed_texture = Self::create_texture(&device, 320, 200); + let next_texture = Self::create_texture(&device, 320, 200); + + Self { + device: device.clone(), + queue: queue.clone(), + pipeline, + displayed_texture, + next_texture, + start_time: std::time::Instant::now(), + } + } + + fn create_texture(device: &wgpu::Device, width: u32, height: u32) -> wgpu::Texture { + device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width, + height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + format: wgpu::TextureFormat::Rgba8Unorm, + usage: wgpu::TextureUsages::RENDER_ATTACHMENT + | wgpu::TextureUsages::TEXTURE_BINDING + | wgpu::TextureUsages::COPY_SRC, + view_formats: &[], + }) + } + + pub(crate) fn render( + &mut self, + light_red: f32, + light_green: f32, + light_blue: f32, + width: u32, + height: u32, + ) -> wgpu::Texture { + if self.next_texture.size().width != width || self.next_texture.size().height != height { + let mut new_texture = Self::create_texture(&self.device, width, height); + std::mem::swap(&mut self.next_texture, &mut new_texture); + } + + let elapsed: f32 = self.start_time.elapsed().as_millis() as f32 / 500.; + let push_constants = PushConstants { + light_color_and_time: [light_red, light_green, light_blue, elapsed], + }; + + let mut encoder = self + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + { + let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &self + .next_texture + .create_view(&wgpu::TextureViewDescriptor::default()), + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::GREEN), + store: wgpu::StoreOp::Store, + }, + })], + depth_stencil_attachment: None, + timestamp_writes: None, + occlusion_query_set: None, + }); + rpass.set_pipeline(&self.pipeline); + rpass.set_push_constants( + wgpu::ShaderStages::FRAGMENT, // Stage (your constants are for fragment shader) + 0, // Offset in bytes (start at 0) + bytemuck::bytes_of(&push_constants), + ); + rpass.draw(0..3, 0..1); + } + + self.queue.submit(Some(encoder.finish())); + + let result_texture = self.next_texture.clone(); + + std::mem::swap(&mut self.next_texture, &mut self.displayed_texture); + + result_texture + } +} diff --git a/vello/src/util.rs b/vello/src/util.rs index fec98ab75..4accdc5bf 100644 --- a/vello/src/util.rs +++ b/vello/src/util.rs @@ -155,8 +155,11 @@ impl RenderContext { wgpu::util::initialize_adapter_from_env_or_default(&self.instance, compatible_surface) .await?; let features = adapter.features(); - let limits = Limits::default(); - let maybe_features = wgpu::Features::CLEAR_TEXTURE | wgpu::Features::PIPELINE_CACHE; + let mut limits = Limits::default(); + limits.max_push_constant_size = 16; + let maybe_features = wgpu::Features::CLEAR_TEXTURE + | wgpu::Features::PIPELINE_CACHE + | wgpu::Features::PUSH_CONSTANTS; #[cfg(feature = "wgpu-profiler")] let maybe_features = maybe_features | wgpu_profiler::GpuProfiler::ALL_WGPU_TIMER_FEATURES;