diff --git a/Cargo.lock b/Cargo.lock index 0bc75b9..eaddf11 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -46,6 +46,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "arrayref" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" + [[package]] name = "arrayvec" version = "0.5.2" @@ -243,6 +249,15 @@ dependencies = [ "os_str_bytes", ] +[[package]] +name = "cmake" +version = "0.1.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db34956e100b30725f2eb215f90d4871051239535632f84fea3bc92722c66b7c" +dependencies = [ + "cc", +] + [[package]] name = "cocoa" version = "0.24.0" @@ -254,7 +269,7 @@ dependencies = [ "cocoa-foundation", "core-foundation", "core-graphics", - "foreign-types", + "foreign-types 0.3.2", "libc", "objc", ] @@ -269,7 +284,7 @@ dependencies = [ "block", "core-foundation", "core-graphics-types", - "foreign-types", + "foreign-types 0.3.2", "libc", "objc", ] @@ -309,7 +324,7 @@ dependencies = [ "bitflags", "core-foundation", "core-graphics-types", - "foreign-types", + "foreign-types 0.3.2", "libc", ] @@ -321,7 +336,19 @@ checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" dependencies = [ "bitflags", "core-foundation", - "foreign-types", + "foreign-types 0.3.2", + "libc", +] + +[[package]] +name = "core-text" +version = "19.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d74ada66e07c1cefa18f8abfba765b486f250de2e4a999e5727fc0dd4b4a25" +dependencies = [ + "core-foundation", + "core-graphics", + "foreign-types 0.3.2", "libc", ] @@ -334,6 +361,29 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "crossfont" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21fd3add36ea31aba1520aa5288714dd63be506106753226d0eb387a93bc9c45" +dependencies = [ + "cocoa", + "core-foundation", + "core-foundation-sys", + "core-graphics", + "core-text", + "dwrote", + "foreign-types 0.5.0", + "freetype-rs", + "libc", + "log", + "objc", + "once_cell", + "pkg-config", + "servo-fontconfig", + "winapi", +] + [[package]] name = "cty" version = "0.2.2" @@ -442,6 +492,20 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" +[[package]] +name = "dwrote" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439a1c2ba5611ad3ed731280541d36d2e9c4ac5e7fb818a27b604bdc5a6aa65b" +dependencies = [ + "lazy_static", + "libc", + "serde", + "serde_derive", + "winapi", + "wio", +] + [[package]] name = "env_logger" version = "0.9.1" @@ -455,6 +519,16 @@ dependencies = [ "termcolor", ] +[[package]] +name = "expat-sys" +version = "2.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658f19728920138342f68408b7cf7644d90d4784353d8ebc32e7e8663dbe45fa" +dependencies = [ + "cmake", + "pkg-config", +] + [[package]] name = "fastrand" version = "1.8.0" @@ -486,7 +560,28 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "foreign-types-shared", + "foreign-types-shared 0.1.1", +] + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared 0.3.1", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8469d0d40519bc608ec6863f1cc88f3f1deee15913f2f3b3e573d81ed38cccc" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -495,6 +590,34 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "freetype-rs" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74eadec9d0a5c28c54bb9882e54787275152a4e36ce206b45d7451384e5bf5fb" +dependencies = [ + "bitflags", + "freetype-sys", + "libc", +] + +[[package]] +name = "freetype-sys" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a37d4011c0cc628dfa766fcc195454f4b068d7afdc2adfd28861191d866e731a" +dependencies = [ + "cmake", + "libc", + "pkg-config", +] + [[package]] name = "futures-core" version = "0.3.25" @@ -800,7 +923,7 @@ dependencies = [ "bitflags", "block", "core-graphics-types", - "foreign-types", + "foreign-types 0.3.2", "log", "objc", ] @@ -1136,7 +1259,7 @@ dependencies = [ "bytemuck", "cocoa-foundation", "core-graphics-types", - "foreign-types", + "foreign-types 0.3.2", "metal", "objc", "raw-window-handle 0.5.0", @@ -1188,6 +1311,7 @@ dependencies = [ "pollster", "roxmltree", "wgpu", + "winit", ] [[package]] @@ -1449,6 +1573,15 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" +[[package]] +name = "safe_arch" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1ff3d6d9696af502cc3110dacce942840fb06ff4514cad92236ecc455f2ce05" +dependencies = [ + "bytemuck", +] + [[package]] name = "scoped-tls" version = "1.0.0" @@ -1461,6 +1594,18 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "sctk-adwaita" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61270629cc6b4d77ec1907db1033d5c2e1a404c412743621981a871dc9c12339" +dependencies = [ + "crossfont", + "log", + "smithay-client-toolkit", + "tiny-skia", +] + [[package]] name = "serde" version = "1.0.147" @@ -1492,6 +1637,27 @@ dependencies = [ "serde", ] +[[package]] +name = "servo-fontconfig" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e3e22fe5fd73d04ebf0daa049d3efe3eae55369ce38ab16d07ddd9ac5c217c" +dependencies = [ + "libc", + "servo-fontconfig-sys", +] + +[[package]] +name = "servo-fontconfig-sys" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36b879db9892dfa40f95da1c38a835d41634b825fbd8c4c418093d53c24b388" +dependencies = [ + "expat-sys", + "freetype-sys", + "pkg-config", +] + [[package]] name = "slotmap" version = "1.0.6" @@ -1629,6 +1795,31 @@ dependencies = [ "syn", ] +[[package]] +name = "tiny-skia" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "642680569bb895b16e4b9d181c60be1ed136fa0c9c7f11d004daf053ba89bf82" +dependencies = [ + "arrayref", + "arrayvec 0.5.2", + "bytemuck", + "cfg-if", + "png", + "safe_arch", + "tiny-skia-path", +] + +[[package]] +name = "tiny-skia-path" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c114d32f0c2ee43d585367cb013dfaba967ab9f62b90d9af0d696e955e70fa6c" +dependencies = [ + "arrayref", + "bytemuck", +] + [[package]] name = "toml" version = "0.5.9" @@ -1895,7 +2086,7 @@ dependencies = [ "block", "core-graphics-types", "d3d12", - "foreign-types", + "foreign-types 0.3.2", "fxhash", "glow", "gpu-alloc", @@ -2083,6 +2274,7 @@ dependencies = [ "percent-encoding", "raw-window-handle 0.4.3", "raw-window-handle 0.5.0", + "sctk-adwaita", "smithay-client-toolkit", "wasm-bindgen", "wayland-client", diff --git a/piet-wgsl/Cargo.toml b/piet-wgsl/Cargo.toml index 99bdb55..d10aa41 100644 --- a/piet-wgsl/Cargo.toml +++ b/piet-wgsl/Cargo.toml @@ -18,3 +18,6 @@ piet-scene = { path = "../piet-scene" } # for picosvg, should be split out roxmltree = "0.13" + +# move this to an example +winit = "0.27.5" diff --git a/piet-wgsl/shader/fine.wgsl b/piet-wgsl/shader/fine.wgsl index 9b8cd4e..87ed3f8 100644 --- a/piet-wgsl/shader/fine.wgsl +++ b/piet-wgsl/shader/fine.wgsl @@ -21,10 +21,6 @@ var tiles: array; @group(0) @binding(2) var segments: array; -// This will become a texture, but keeping things simple for now -@group(0) @binding(3) -var output: array; - #ifdef full #import blend @@ -33,6 +29,9 @@ var output: array; let GRADIENT_WIDTH = 512; let BLEND_STACK_SPLIT = 4u; +@group(0) @binding(3) +var output: texture_storage_2d; + @group(0) @binding(4) var ptcl: array; @@ -78,6 +77,11 @@ fn read_rad_grad(cmd_ix: u32) -> CmdRadGrad { return CmdRadGrad(index, matrx, xlat, c1, ra, roff); } +#else + +@group(0) @binding(3) +var output: texture_storage_2d; + #endif let PIXELS_PER_THREAD = 4u; @@ -273,20 +277,23 @@ fn main( default: {} } } - let out_ix = global_id.y * (config.width_in_tiles * TILE_WIDTH) + global_id.x * PIXELS_PER_THREAD; + let xy_uint = vec2(xy); for (var i = 0u; i < PIXELS_PER_THREAD; i += 1u) { - let fg = rgba[i]; - let a_inv = 1.0 / (fg.a + 1e-6); - let rgba_sep = vec4(fg.r * a_inv, fg.g * a_inv, fg.b * a_inv, fg.a); - let bytes = pack4x8unorm(rgba_sep); - output[out_ix + i] = bytes; - } + let coords = xy_uint + vec2(i, 0u); + if coords.x < config.target_width && coords.y < config.target_height { + textureStore(output, vec2(coords), rgba[i]); + } + } #else let tile = tiles[tile_ix]; let area = fill_path(tile, xy); - let bytes = pack4x8unorm(vec4(area[0], area[1], area[2], area[3])); - let out_ix = global_id.y * (config.width_in_tiles * 4u) + global_id.x; - output[out_ix] = bytes; + let xy_uint = vec2(xy); + for (var i = 0u; i < PIXELS_PER_THREAD; i += 1u) { + let coords = xy_uint + vec2(i, 0u); + if coords.x < config.target_width && coords.y < config.target_height { + textureStore(output, vec2(coords), vec4(area[i])); + } + } #endif } diff --git a/piet-wgsl/shader/shared/config.wgsl b/piet-wgsl/shader/shared/config.wgsl index 2f62354..21d9b0e 100644 --- a/piet-wgsl/shader/shared/config.wgsl +++ b/piet-wgsl/shader/shared/config.wgsl @@ -4,6 +4,9 @@ struct Config { width_in_tiles: u32, height_in_tiles: u32, + target_width: u32, + target_height: u32, + n_drawobj: u32, n_path: u32, n_clip: u32, diff --git a/piet-wgsl/src/engine.rs b/piet-wgsl/src/engine.rs index 049fc80..ac4a049 100644 --- a/piet-wgsl/src/engine.rs +++ b/piet-wgsl/src/engine.rs @@ -59,11 +59,17 @@ pub struct BufProxy { id: Id, } +#[derive(Copy, Clone, PartialEq, Eq)] +pub enum ImageFormat { + Rgba8, + Bgra8, +} + #[derive(Clone, Copy)] pub struct ImageProxy { width: u32, height: u32, - // TODO: format + format: ImageFormat, id: Id, } @@ -73,6 +79,11 @@ pub enum ResourceProxy { Image(ImageProxy), } +pub enum ExternalResource<'a> { + Buf(BufProxy, &'a Buffer), + Image(ImageProxy, &'a TextureView), +} + pub enum Command { Upload(BufProxy, Vec), UploadImage(ImageProxy, Vec), @@ -97,11 +108,9 @@ pub enum BindType { /// A storage buffer with read only access. BufReadOnly, /// A storage image. - #[allow(unused)] // TODO - Image, + Image(ImageFormat), /// A storage image with read only access. - #[allow(unused)] // TODO - ImageRead, + ImageRead(ImageFormat), // TODO: Uniform, Sampler, maybe others } @@ -149,16 +158,26 @@ impl Engine { }, count: None, }, - BindType::ImageRead => wgpu::BindGroupLayoutEntry { - binding: i as u32, - visibility: wgpu::ShaderStages::COMPUTE, - ty: wgpu::BindingType::Texture { - sample_type: wgpu::TextureSampleType::Float { filterable: true }, - view_dimension: wgpu::TextureViewDimension::D2, - multisampled: false, - }, - count: None, - }, + BindType::Image(format) | BindType::ImageRead(format) => { + wgpu::BindGroupLayoutEntry { + binding: i as u32, + visibility: wgpu::ShaderStages::COMPUTE, + ty: if *bind_type == BindType::ImageRead(*format) { + wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + } + } else { + wgpu::BindingType::StorageTexture { + access: wgpu::StorageTextureAccess::WriteOnly, + format: format.to_wgpu(), + view_dimension: wgpu::TextureViewDimension::D2, + } + }, + count: None, + } + } _ => todo!(), }) .collect::>(); @@ -192,6 +211,7 @@ impl Engine { device: &Device, queue: &Queue, recording: &Recording, + external_resources: &[ExternalResource], ) -> Result { let mut bind_map = BindMap::default(); let mut downloads = Downloads::default(); @@ -226,7 +246,7 @@ impl Engine { sample_count: 1, dimension: wgpu::TextureDimension::D2, usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST, - format: TextureFormat::Rgba8Unorm, + format: image_proxy.format.to_wgpu(), }); let texture_view = texture.create_view(&wgpu::TextureViewDescriptor { label: None, @@ -262,10 +282,14 @@ impl Engine { bind_map.insert_image(image_proxy.id, texture, texture_view) } Command::Dispatch(shader_id, wg_size, bindings) => { - println!("dispatching {:?} with {} bindings", wg_size, bindings.len()); + // println!("dispatching {:?} with {} bindings", wg_size, bindings.len()); let shader = &self.shaders[shader_id.0]; - let bind_group = - bind_map.create_bind_group(device, &shader.bind_group_layout, bindings)?; + let bind_group = bind_map.create_bind_group( + device, + &shader.bind_group_layout, + bindings, + external_resources, + )?; let mut cpass = encoder.begin_compute_pass(&Default::default()); cpass.set_pipeline(&shader.pipeline); cpass.set_bind_group(0, &bind_group, &[]); @@ -309,10 +333,11 @@ impl Recording { &mut self, width: u32, height: u32, + format: ImageFormat, data: impl Into>, ) -> ImageProxy { let data = data.into(); - let image_proxy = ImageProxy::new(width, height); + let image_proxy = ImageProxy::new(width, height, format); self.push(Command::UploadImage(image_proxy, data)); image_proxy } @@ -348,10 +373,24 @@ impl BufProxy { } } +impl ImageFormat { + pub fn to_wgpu(self) -> wgpu::TextureFormat { + match self { + Self::Rgba8 => wgpu::TextureFormat::Rgba8Unorm, + Self::Bgra8 => wgpu::TextureFormat::Bgra8Unorm, + } + } +} + impl ImageProxy { - pub fn new(width: u32, height: u32) -> Self { + pub fn new(width: u32, height: u32, format: ImageFormat) -> Self { let id = Id::next(); - ImageProxy { width, height, id } + ImageProxy { + width, + height, + format, + id, + } } } @@ -360,8 +399,8 @@ impl ResourceProxy { Self::Buf(BufProxy::new(size)) } - pub fn new_image(width: u32, height: u32) -> Self { - Self::Image(ImageProxy::new(width, height)) + pub fn new_image(width: u32, height: u32, format: ImageFormat) -> Self { + Self::Image(ImageProxy::new(width, height, format)) } pub fn as_buf(&self) -> Option<&BufProxy> { @@ -413,10 +452,44 @@ impl BindMap { device: &Device, layout: &BindGroupLayout, bindings: &[ResourceProxy], + external_resources: &[ExternalResource], ) -> Result { + // These functions are ugly and linear, but the remap array should generally be + // small. Should find a better solution for this. + fn find_buf<'a>( + resources: &[ExternalResource<'a>], + proxy: &BufProxy, + ) -> Option<&'a Buffer> { + for resource in resources { + match resource { + ExternalResource::Buf(p, buf) if p.id == proxy.id => { + return Some(buf); + } + _ => {} + } + } + None + } + fn find_image<'a>( + resources: &[ExternalResource<'a>], + proxy: &ImageProxy, + ) -> Option<&'a TextureView> { + for resource in resources { + match resource { + ExternalResource::Image(p, view) if p.id == proxy.id => { + return Some(view); + } + _ => {} + } + } + None + } for proxy in bindings { match proxy { ResourceProxy::Buf(proxy) => { + if find_buf(external_resources, proxy).is_some() { + continue; + } if let Entry::Vacant(v) = self.buf_map.entry(proxy.id) { let buf = device.create_buffer(&wgpu::BufferDescriptor { label: None, @@ -430,6 +503,9 @@ impl BindMap { } } ResourceProxy::Image(proxy) => { + if find_image(external_resources, proxy).is_some() { + continue; + } if let Entry::Vacant(v) = self.image_map.entry(proxy.id) { let texture = device.create_texture(&wgpu::TextureDescriptor { label: None, @@ -442,7 +518,7 @@ impl BindMap { sample_count: 1, dimension: wgpu::TextureDimension::D2, usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST, - format: TextureFormat::Rgba8Unorm, + format: proxy.format.to_wgpu(), }); let texture_view = texture.create_view(&wgpu::TextureViewDescriptor { label: None, @@ -452,7 +528,7 @@ impl BindMap { base_mip_level: 0, base_array_layer: 0, array_layer_count: None, - format: Some(TextureFormat::Rgba8Unorm), + format: Some(proxy.format.to_wgpu()), }); v.insert((texture, texture_view)); } @@ -464,17 +540,21 @@ impl BindMap { .enumerate() .map(|(i, proxy)| match proxy { ResourceProxy::Buf(proxy) => { - let buf = self.buf_map.get(&proxy.id).unwrap(); + let buf = find_buf(external_resources, proxy) + .or_else(|| self.buf_map.get(&proxy.id)) + .unwrap(); Ok(wgpu::BindGroupEntry { binding: i as u32, resource: buf.as_entire_binding(), }) } ResourceProxy::Image(proxy) => { - let texture = self.image_map.get(&proxy.id).unwrap(); + let view = find_image(external_resources, proxy) + .or_else(|| self.image_map.get(&proxy.id).map(|v| &v.1)) + .unwrap(); Ok(wgpu::BindGroupEntry { binding: i as u32, - resource: wgpu::BindingResource::TextureView(&texture.1), + resource: wgpu::BindingResource::TextureView(view), }) } }) diff --git a/piet-wgsl/src/main.rs b/piet-wgsl/src/main.rs index 48ddef3..8d735d4 100644 --- a/piet-wgsl/src/main.rs +++ b/piet-wgsl/src/main.rs @@ -16,12 +16,11 @@ //! A simple application to run a compute shader. -use std::{fs::File, io::BufWriter}; +use engine::{Engine, Error, ExternalResource}; -use engine::Engine; - -use render::next_multiple_of; -use wgpu::{Device, Limits, Queue}; +use piet_scene::{Scene, SceneBuilder}; +use wgpu::{Device, Instance, Limits, Queue, Surface, SurfaceConfiguration}; +use winit::window::Window; mod debug; mod engine; @@ -29,103 +28,321 @@ mod pico_svg; mod ramp; mod render; mod shaders; +mod simple_text; mod test_scene; -async fn run(dimensions: &Dimensions) -> Result<(), Box> { - let instance = wgpu::Instance::new(wgpu::Backends::PRIMARY); - let adapter = instance.request_adapter(&Default::default()).await.unwrap(); - let features = adapter.features(); - let mut limits = Limits::default(); - limits.max_storage_buffers_per_shader_stage = 16; - let (device, queue) = adapter - .request_device( - &wgpu::DeviceDescriptor { - label: None, - features: features & wgpu::Features::TIMESTAMP_QUERY, - limits, - }, - None, - ) - .await?; - let mut engine = Engine::new(); - do_render(&device, &queue, &mut engine, dimensions).await?; - - Ok(()) -} - -fn dump_buf(buf: &[u32]) { - for (i, val) in buf.iter().enumerate() { - if *val != 0 { - let lo = val & 0x7fff_ffff; - if lo >= 0x3000_0000 && lo < 0x5000_0000 { - println!("{}: {:x} {}", i, val, f32::from_bits(*val)); - } else { - println!("{}: {:x}", i, val); - } - } - } -} - -async fn do_render( - device: &Device, - queue: &Queue, - engine: &mut Engine, - dimensions: &Dimensions, -) -> Result<(), Box> { - #[allow(unused)] - let shaders = shaders::init_shaders(device, engine)?; - let full_shaders = shaders::full_shaders(device, engine)?; - let scene = test_scene::gen_test_scene(); - //test_scene::dump_scene_info(&scene); - //let (recording, buf) = render::render(&scene, &shaders); - let (recording, buf) = render::render_full(&scene, &full_shaders, dimensions); - let downloads = engine.run_recording(&device, &queue, &recording)?; - let mapped = downloads.map(); - device.poll(wgpu::Maintain::Wait); - let buf = mapped.get_mapped(buf).await?; - - if false { - dump_buf(bytemuck::cast_slice(&buf)); - } else { - let file = File::create("image.png")?; - let w = BufWriter::new(file); - let mut encoder = png::Encoder::new(w, dimensions.width, dimensions.height); - encoder.set_color(png::ColorType::Rgba); - let mut writer = encoder.write_header()?; - let expected_size = (dimensions.width * dimensions.height * 4) as usize; - - let new_width = next_multiple_of(dimensions.width, 16) as usize; - - if expected_size == buf.len() { - writer.write_image_data(&buf)?; - } else { - let mut output = Vec::::with_capacity(expected_size * 4); - for height in 0..(dimensions.height as usize) { - output.extend_from_slice( - &buf[height * new_width * 4..][..dimensions.width as usize * 4], - ); - } - writer.write_image_data(&output)?; - } - } - Ok(()) -} +use pico_svg::PicoSvg; +use simple_text::SimpleText; pub struct Dimensions { width: u32, height: u32, } -fn main() { - let mut args = std::env::args(); - args.next(); - let width = args - .next() - .and_then(|it| it.parse::().ok()) - .unwrap_or(1024); - let height = args - .next() - .and_then(|it| it.parse::().ok()) - .unwrap_or(1024); - pollster::block_on(run(&Dimensions { width, height })).unwrap(); +pub struct WgpuState { + pub instance: Instance, + pub device: Device, + pub queue: Queue, + pub surface: Option, + pub surface_config: SurfaceConfiguration, +} + +impl WgpuState { + pub async fn new(window: Option<&Window>) -> Result> { + let instance = Instance::new(wgpu::Backends::PRIMARY); + let adapter = instance.request_adapter(&Default::default()).await.unwrap(); + let features = adapter.features(); + let mut limits = Limits::default(); + limits.max_storage_buffers_per_shader_stage = 16; + let (device, queue) = adapter + .request_device( + &wgpu::DeviceDescriptor { + label: None, + features: features + & (wgpu::Features::TIMESTAMP_QUERY | wgpu::Features::CLEAR_TEXTURE), + limits, + }, + None, + ) + .await?; + let (surface, surface_config) = if let Some(window) = window { + let surface = unsafe { instance.create_surface(window) }; + let size = window.inner_size(); + // let format = surface.get_supported_formats(&adapter)[0]; + let format = wgpu::TextureFormat::Bgra8Unorm; + println!("surface: {:?} {:?}", size, format); + let surface_config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::RENDER_ATTACHMENT, + format, + width: size.width, + height: size.height, + present_mode: wgpu::PresentMode::Fifo, + alpha_mode: wgpu::CompositeAlphaMode::Auto, + }; + surface.configure(&device, &surface_config); + (Some(surface), surface_config) + } else { + let surface_config = wgpu::SurfaceConfiguration { + usage: wgpu::TextureUsages::empty(), + format: wgpu::TextureFormat::Bgra8Unorm, + width: 0, + height: 0, + present_mode: wgpu::PresentMode::Fifo, + alpha_mode: wgpu::CompositeAlphaMode::Auto, + }; + (None, surface_config) + }; + Ok(Self { + instance, + device, + queue, + surface, + surface_config, + }) + } + + pub fn create_target_texture(&self) -> (wgpu::Texture, wgpu::TextureView) { + let texture = self.device.create_texture(&wgpu::TextureDescriptor { + label: None, + size: wgpu::Extent3d { + width: self.surface_config.width, + height: self.surface_config.height, + depth_or_array_layers: 1, + }, + mip_level_count: 1, + sample_count: 1, + dimension: wgpu::TextureDimension::D2, + usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING, + format: wgpu::TextureFormat::Rgba8Unorm, + }); + let view = texture.create_view(&wgpu::TextureViewDescriptor::default()); + (texture, view) + } +} + +async fn run_interactive() -> Result<(), Error> { + use winit::{ + dpi::LogicalSize, + event::*, + event_loop::{ControlFlow, EventLoop}, + window::WindowBuilder, + }; + let event_loop = EventLoop::new(); + let window = WindowBuilder::new() + .with_inner_size(LogicalSize::new(1044, 800)) + .with_resizable(true) + .build(&event_loop) + .unwrap(); + let mut state = WgpuState::new(Some(&window)).await?; + let mut engine = Engine::new(); + let full_shaders = shaders::full_shaders(&state.device, &mut engine)?; + let (blit_layout, blit_pipeline) = create_blit_pipeline(&state); + let mut simple_text = SimpleText::new(); + let mut current_frame = 0usize; + let mut scene_ix = 0usize; + let (mut _target_texture, mut target_view) = state.create_target_texture(); + event_loop.run(move |event, _, control_flow| match event { + Event::WindowEvent { + ref event, + window_id, + } if window_id == window.id() => match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::KeyboardInput { input, .. } => { + if input.state == ElementState::Pressed { + match input.virtual_keycode { + Some(VirtualKeyCode::Left) => scene_ix = scene_ix.saturating_sub(1), + Some(VirtualKeyCode::Right) => scene_ix = scene_ix.saturating_add(1), + _ => {} + } + } + } + WindowEvent::Resized(size) => { + state.surface_config.width = size.width; + state.surface_config.height = size.height; + state + .surface + .as_ref() + .unwrap() + .configure(&state.device, &state.surface_config); + let (t, v) = state.create_target_texture(); + _target_texture = t; + target_view = v; + window.request_redraw(); + } + _ => {} + }, + Event::MainEventsCleared => { + window.request_redraw(); + } + Event::RedrawRequested(_) => { + current_frame += 1; + let surface_texture = state + .surface + .as_ref() + .unwrap() + .get_current_texture() + .unwrap(); + let dimensions = Dimensions { + width: state.surface_config.width, + height: state.surface_config.height, + }; + let mut scene = Scene::default(); + let mut builder = SceneBuilder::for_scene(&mut scene); + const N_SCENES: usize = 6; + match scene_ix % N_SCENES { + 0 => test_scene::render_anim_frame(&mut builder, &mut simple_text, current_frame), + 1 => test_scene::render_blend_grid(&mut builder), + 2 => test_scene::render_tiger(&mut builder, false), + 3 => test_scene::render_brush_transform(&mut builder, current_frame), + 4 => test_scene::render_funky_paths(&mut builder), + _ => test_scene::render_scene(&mut builder), + } + builder.finish(); + let (recording, target) = render::render_full(&scene, &full_shaders, &dimensions); + let external_resources = [ExternalResource::Image( + *target.as_image().unwrap(), + &target_view, + )]; + let _ = engine + .run_recording(&state.device, &state.queue, &recording, &external_resources) + .unwrap(); + let mut encoder = state + .device + .create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None }); + { + let surface_view = surface_texture + .texture + .create_view(&wgpu::TextureViewDescriptor::default()); + let bind_group = state.device.create_bind_group(&wgpu::BindGroupDescriptor { + label: None, + layout: &blit_layout, + entries: &[wgpu::BindGroupEntry { + binding: 0, + resource: wgpu::BindingResource::TextureView(&target_view), + }], + }); + let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { + label: None, + color_attachments: &[Some(wgpu::RenderPassColorAttachment { + view: &surface_view, + resolve_target: None, + ops: wgpu::Operations { + load: wgpu::LoadOp::Clear(wgpu::Color::default()), + store: true, + }, + })], + depth_stencil_attachment: None, + }); + render_pass.set_pipeline(&blit_pipeline); + render_pass.set_bind_group(0, &bind_group, &[]); + render_pass.draw(0..6, 0..1); + } + state.queue.submit(Some(encoder.finish())); + surface_texture.present(); + } + _ => {} + }); +} + +fn main() { + pollster::block_on(run_interactive()).unwrap(); +} + +// Fit this into the recording code somehow? +fn create_blit_pipeline(state: &WgpuState) -> (wgpu::BindGroupLayout, wgpu::RenderPipeline) { + const SHADERS: &str = r#" + @vertex + fn vs_main(@builtin(vertex_index) ix: u32) -> @builtin(position) vec4 { + // Generate a full screen quad in NDCs + var vertex = vec2(-1.0, 1.0); + switch ix { + case 1u: { + vertex = vec2(-1.0, -1.0); + } + case 2u, 4u: { + vertex = vec2(1.0, -1.0); + } + case 5u: { + vertex = vec2(1.0, 1.0); + } + default: {} + } + return vec4(vertex, 0.0, 1.0); + } + + @group(0) @binding(0) + var fine_output: texture_2d; + + @fragment + fn fs_main(@builtin(position) pos: vec4) -> @location(0) vec4 { + return textureLoad(fine_output, vec2(pos.xy), 0); + } + "#; + + let shader = state + .device + .create_shader_module(wgpu::ShaderModuleDescriptor { + label: Some("blit shaders"), + source: wgpu::ShaderSource::Wgsl(SHADERS.into()), + }); + let bind_group_layout = + state + .device + .create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor { + label: None, + entries: &[wgpu::BindGroupLayoutEntry { + visibility: wgpu::ShaderStages::FRAGMENT, + binding: 0, + ty: wgpu::BindingType::Texture { + sample_type: wgpu::TextureSampleType::Float { filterable: true }, + view_dimension: wgpu::TextureViewDimension::D2, + multisampled: false, + }, + count: None, + }], + }); + let pipeline_layout = state + .device + .create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: None, + bind_group_layouts: &[&bind_group_layout], + push_constant_ranges: &[], + }); + let pipeline = state + .device + .create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: None, + layout: Some(&pipeline_layout), + vertex: wgpu::VertexState { + module: &shader, + entry_point: "vs_main", + buffers: &[], + }, + fragment: Some(wgpu::FragmentState { + module: &shader, + entry_point: "fs_main", + targets: &[Some(wgpu::ColorTargetState { + format: state.surface_config.format, + blend: None, + 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, + }); + (bind_group_layout, pipeline) } diff --git a/piet-wgsl/src/render.rs b/piet-wgsl/src/render.rs index 56951fd..e747d41 100644 --- a/piet-wgsl/src/render.rs +++ b/piet-wgsl/src/render.rs @@ -4,7 +4,7 @@ use bytemuck::{Pod, Zeroable}; use piet_scene::Scene; use crate::{ - engine::{BufProxy, Recording, ResourceProxy}, + engine::{BufProxy, ImageFormat, ImageProxy, Recording, ResourceProxy}, shaders::{self, FullShaders, Shaders}, Dimensions, }; @@ -29,6 +29,8 @@ const BIN_HEADER_SIZE: u64 = 8; struct Config { width_in_tiles: u32, height_in_tiles: u32, + target_width: u32, + target_height: u32, n_drawobj: u32, n_path: u32, n_clip: u32, @@ -76,6 +78,8 @@ fn render(scene: &Scene, shaders: &Shaders) -> (Recording, BufProxy) { let config = Config { width_in_tiles: 64, height_in_tiles: 64, + target_width: 64 * 16, + target_height: 64 * 16, pathtag_base, pathdata_base, ..Default::default() @@ -137,7 +141,7 @@ pub fn render_full( scene: &Scene, shaders: &FullShaders, dimensions: &Dimensions, -) -> (Recording, BufProxy) { +) -> (Recording, ResourceProxy) { let mut recording = Recording::default(); let mut ramps = crate::ramp::RampCache::default(); let mut drawdata_patches: Vec<(usize, u32)> = vec![]; @@ -153,19 +157,19 @@ pub fn render_full( } } let gradient_image = if drawdata_patches.is_empty() { - ResourceProxy::new_image(1, 1) + ResourceProxy::new_image(1, 1, ImageFormat::Rgba8) } else { let data = ramps.data(); let width = ramps.width(); let height = ramps.height(); let data: &[u8] = bytemuck::cast_slice(data); - println!( - "gradient image: {}x{} ({} bytes)", - width, - height, - data.len() - ); - ResourceProxy::Image(recording.upload_image(width, height, data)) + // println!( + // "gradient image: {}x{} ({} bytes)", + // width, + // height, + // data.len() + // ); + ResourceProxy::Image(recording.upload_image(width, height, ImageFormat::Rgba8, data)) }; let n_pathtag = data.tag_stream.len(); let pathtag_padded = align_up(n_pathtag, 4 * shaders::PATHTAG_REDUCE_WG); @@ -206,12 +210,14 @@ pub fn render_full( let n_clip = data.n_clip; let new_width = next_multiple_of(dimensions.width, 16); - let new_height = next_multiple_of(dimensions.width, 16); + let new_height = next_multiple_of(dimensions.height, 16); let config = Config { // TODO: Replace with div_ceil once stable width_in_tiles: new_width / 16, height_in_tiles: new_height / 16, + target_width: dimensions.width, + target_height: dimensions.height, n_drawobj, n_path, n_clip, @@ -222,7 +228,7 @@ pub fn render_full( transform_base, linewidth_base, }; - println!("{:?}", config); + // println!("{:?}", config); let scene_buf = ResourceProxy::Buf(recording.upload(scene)); let config_buf = ResourceProxy::Buf(recording.upload(bytemuck::bytes_of(&config).to_owned())); @@ -396,8 +402,7 @@ pub fn render_full( ptcl_buf, ], ); - let out_buf_size = config.width_in_tiles * config.height_in_tiles * 1024; - let out_buf = BufProxy::new(out_buf_size as u64); + let out_image = ImageProxy::new(dimensions.width, dimensions.height, ImageFormat::Rgba8); recording.dispatch( shaders.fine, (config.width_in_tiles, config.height_in_tiles, 1), @@ -405,15 +410,12 @@ pub fn render_full( config_buf, tile_buf, segments_buf, - ResourceProxy::Buf(out_buf), + ResourceProxy::Image(out_image), ptcl_buf, gradient_image, ], ); - - let download_buf = out_buf; - recording.download(download_buf); - (recording, download_buf) + (recording, ResourceProxy::Image(out_image)) } pub fn align_up(len: usize, alignment: u32) -> usize { diff --git a/piet-wgsl/src/shaders.rs b/piet-wgsl/src/shaders.rs index 0e61710..a011c58 100644 --- a/piet-wgsl/src/shaders.rs +++ b/piet-wgsl/src/shaders.rs @@ -22,7 +22,7 @@ use std::{collections::HashSet, fs, path::Path}; use wgpu::Device; -use crate::engine::{BindType, Engine, Error, ShaderId}; +use crate::engine::{BindType, Engine, Error, ImageFormat, ShaderId}; pub const PATHTAG_REDUCE_WG: u32 = 256; pub const PATH_BBOX_WG: u32 = 256; @@ -281,9 +281,9 @@ pub fn full_shaders(device: &Device, engine: &mut Engine) -> Result Self { + Self { + gcx: GlyphContext::new(), + } + } + + pub fn add( + &mut self, + builder: &mut SceneBuilder, + font: Option<&FontRef>, + size: f32, + brush: Option<&Brush>, + transform: Affine, + text: &str, + ) { + let font = font.unwrap_or(&FontRef { + data: FONT_DATA, + offset: 0, + }); + if let Some(cmap) = font.cmap() { + if let Some(hmtx) = font.hmtx() { + let upem = font.head().map(|head| head.units_per_em()).unwrap_or(1000) as f64; + let scale = size as f64 / upem; + let vars: [(pinot::types::Tag, f32); 0] = []; + let mut provider = self.gcx.new_provider(font, None, size, false, vars); + let hmetrics = hmtx.hmetrics(); + let default_advance = hmetrics + .get(hmetrics.len().saturating_sub(1)) + .map(|h| h.advance_width) + .unwrap_or(0); + let mut pen_x = 0f64; + for ch in text.chars() { + let gid = cmap.map(ch as u32).unwrap_or(0); + let advance = hmetrics + .get(gid as usize) + .map(|h| h.advance_width) + .unwrap_or(default_advance) as f64 + * scale; + if let Some(glyph) = provider.get(gid, brush) { + let xform = transform + * Affine::translate((pen_x, 0.0)) + * Affine::scale_non_uniform(1.0, -1.0); + builder.append(&glyph, Some(xform)); + } + pen_x += advance; + } + } + } + } +} diff --git a/piet-wgsl/src/test_scene.rs b/piet-wgsl/src/test_scene.rs index 84195f7..d5a744d 100644 --- a/piet-wgsl/src/test_scene.rs +++ b/piet-wgsl/src/test_scene.rs @@ -1,79 +1,58 @@ -// Copyright 2022 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// Also licensed under MIT license, at your choice. +use super::PicoSvg; +use piet_scene::kurbo::{Affine, BezPath, Ellipse, PathEl, Point, Rect}; +use piet_scene::*; -use piet_scene::kurbo::{Affine, Ellipse, PathEl, Point, Rect}; -use piet_scene::{ - BlendMode, Brush, Color, Fill, LinearGradient, Mix, RadialGradient, Scene, SceneBuilder, - SceneFragment, Stroke, -}; +use crate::SimpleText; -use crate::pico_svg::PicoSvg; - -pub fn gen_test_scene() -> Scene { - let mut scene = Scene::default(); - let mut builder = SceneBuilder::for_scene(&mut scene); - let scene_ix = 1; - match scene_ix { - 0 => { - let path = [ - PathEl::MoveTo(Point::new(100.0, 100.0)), - PathEl::LineTo(Point::new(500.0, 120.0)), - PathEl::LineTo(Point::new(300.0, 150.0)), - PathEl::LineTo(Point::new(200.0, 260.0)), - PathEl::LineTo(Point::new(150.0, 210.0)), - ]; - let brush = Brush::Solid(Color::rgb8(0x40, 0x40, 0xff)); - builder.fill(Fill::NonZero, Affine::IDENTITY, &brush, None, &path); - let transform = Affine::translate((50.0, 50.0)); - let brush = Brush::Solid(Color::rgba8(0xff, 0xff, 0x00, 0x80)); - builder.fill(Fill::NonZero, transform, &brush, None, &path); - let transform = Affine::translate((100.0, 100.0)); - let style = Stroke::new(1.0); - let brush = Brush::Solid(Color::rgb8(0xa0, 0x00, 0x00)); - builder.stroke(&style, transform, &brush, None, &path); - } - 1 => { - render_blend_grid(&mut builder); - } - _ => { - let xml_str = - std::str::from_utf8(include_bytes!("../../piet-gpu/Ghostscript_Tiger.svg")) - .unwrap(); - let svg = PicoSvg::load(xml_str, 6.0).unwrap(); - render_svg(&mut builder, &svg, false); - } - } - builder.finish(); - scene -} - -#[allow(unused)] -pub fn dump_scene_info(scene: &Scene) { - let data = scene.data(); - println!("tags {:?}", data.tag_stream); - println!( - "pathsegs {:?}", - bytemuck::cast_slice::(&data.pathseg_stream) +pub fn render_funky_paths(sb: &mut SceneBuilder) { + use PathEl::*; + let missing_movetos = [ + LineTo((100.0, 100.0).into()), + LineTo((100.0, 200.0).into()), + ClosePath, + LineTo((0.0, 400.0).into()), + LineTo((100.0, 400.0).into()), + ]; + let only_movetos = [MoveTo((0.0, 0.0).into()), MoveTo((100.0, 100.0).into())]; + let empty: [PathEl; 0] = []; + sb.fill( + Fill::NonZero, + Affine::translate((100.0, 100.0)), + Color::rgb8(0, 0, 255), + None, + &missing_movetos, + ); + sb.fill( + Fill::NonZero, + Affine::IDENTITY, + Color::rgb8(0, 0, 255), + None, + &empty, + ); + sb.fill( + Fill::NonZero, + Affine::IDENTITY, + Color::rgb8(0, 0, 255), + None, + &only_movetos, + ); + sb.stroke( + &Stroke::new(8.0), + Affine::translate((100.0, 100.0)), + Color::rgb8(0, 255, 255), + None, + &missing_movetos, ); } +#[allow(unused)] +const N_CIRCLES: usize = 0; + +#[allow(unused)] pub fn render_svg(sb: &mut SceneBuilder, svg: &PicoSvg, print_stats: bool) { use crate::pico_svg::*; let start = std::time::Instant::now(); - for item in svg.items.iter() { + for item in &svg.items { match item { Item::Fill(fill) => { sb.fill( @@ -100,6 +79,117 @@ pub fn render_svg(sb: &mut SceneBuilder, svg: &PicoSvg, print_stats: bool) { } } +#[allow(unused)] +pub fn render_tiger(sb: &mut SceneBuilder, print_stats: bool) { + use super::pico_svg::*; + let xml_str = + std::str::from_utf8(include_bytes!("../../piet-gpu/Ghostscript_Tiger.svg")).unwrap(); + let start = std::time::Instant::now(); + let svg = PicoSvg::load(xml_str, 6.0).unwrap(); + if print_stats { + println!("parsing time: {:?}", start.elapsed()); + } + render_svg(sb, &svg, print_stats); +} + +pub fn render_scene(sb: &mut SceneBuilder) { + render_cardioid(sb); + render_clip_test(sb); + render_alpha_test(sb); + //render_tiger(sb, false); +} + +#[allow(unused)] +fn render_cardioid(sb: &mut SceneBuilder) { + let n = 601; + let dth = std::f64::consts::PI * 2.0 / (n as f64); + let center = Point::new(1024.0, 768.0); + let r = 750.0; + let mut path = BezPath::new(); + for i in 1..n { + let mut p0 = center; + let a0 = i as f64 * dth; + p0.x += a0.cos() * r; + p0.y += a0.sin() * r; + let mut p1 = center; + let a1 = ((i * 2) % n) as f64 * dth; + p1.x += a1.cos() * r; + p1.y += a1.sin() * r; + path.push(PathEl::MoveTo(p0)); + path.push(PathEl::LineTo(p1)); + } + sb.stroke( + &Stroke::new(2.0), + Affine::IDENTITY, + Color::rgb8(0, 0, 0), + None, + &path, + ); +} + +#[allow(unused)] +fn render_clip_test(sb: &mut SceneBuilder) { + const N: usize = 16; + const X0: f64 = 50.0; + const Y0: f64 = 450.0; + // Note: if it gets much larger, it will exceed the 1MB scratch buffer. + // But this is a pretty demanding test. + const X1: f64 = 550.0; + const Y1: f64 = 950.0; + let step = 1.0 / ((N + 1) as f64); + for i in 0..N { + let t = ((i + 1) as f64) * step; + let path = [ + PathEl::MoveTo((X0, Y0).into()), + PathEl::LineTo((X1, Y0).into()), + PathEl::LineTo((X1, Y0 + t * (Y1 - Y0)).into()), + PathEl::LineTo((X1 + t * (X0 - X1), Y1).into()), + PathEl::LineTo((X0, Y1).into()), + PathEl::ClosePath, + ]; + sb.push_layer(Mix::Clip, Affine::IDENTITY, &path); + } + let rect = Rect::new(X0, Y0, X1, Y1); + sb.fill( + Fill::NonZero, + Affine::IDENTITY, + &Brush::Solid(Color::rgb8(0, 0, 0)), + None, + &rect, + ); + for _ in 0..N { + sb.pop_layer(); + } +} + +#[allow(unused)] +fn render_alpha_test(sb: &mut SceneBuilder) { + // Alpha compositing tests. + sb.fill( + Fill::NonZero, + Affine::IDENTITY, + Color::rgb8(255, 0, 0), + None, + &make_diamond(1024.0, 100.0), + ); + sb.fill( + Fill::NonZero, + Affine::IDENTITY, + Color::rgba8(0, 255, 0, 0x80), + None, + &make_diamond(1024.0, 125.0), + ); + sb.push_layer(Mix::Clip, Affine::IDENTITY, &make_diamond(1024.0, 150.0)); + sb.fill( + Fill::NonZero, + Affine::IDENTITY, + Color::rgba8(0, 0, 255, 0x80), + None, + &make_diamond(1024.0, 175.0), + ); + sb.pop_layer(); +} + #[allow(unused)] pub fn render_blend_grid(sb: &mut SceneBuilder) { const BLEND_MODES: &[Mix] = &[ @@ -181,3 +271,83 @@ fn blend_square(blend: BlendMode) -> SceneFragment { sb.finish(); fragment } + +#[allow(unused)] +pub fn render_anim_frame(sb: &mut SceneBuilder, text: &mut SimpleText, i: usize) { + sb.fill( + Fill::NonZero, + Affine::IDENTITY, + &Brush::Solid(Color::rgb8(128, 128, 128)), + None, + &Rect::from_origin_size(Point::new(0.0, 0.0), (1000.0, 1000.0)), + ); + let text_size = 60.0 + 40.0 * (0.01 * i as f32).sin(); + let s = "\u{1f600}hello piet-gpu text!"; + text.add( + sb, + None, + text_size, + None, + Affine::translate((110.0, 600.0)), + s, + ); + text.add( + sb, + None, + text_size, + None, + Affine::translate((110.0, 700.0)), + s, + ); + let th = (std::f64::consts::PI / 180.0) * (i as f64); + let center = Point::new(500.0, 500.0); + let mut p1 = center; + p1.x += 400.0 * th.cos(); + p1.y += 400.0 * th.sin(); + sb.stroke( + &Stroke::new(5.0), + Affine::IDENTITY, + &Brush::Solid(Color::rgb8(128, 0, 0)), + None, + &&[PathEl::MoveTo(center), PathEl::LineTo(p1)][..], + ); +} + +#[allow(unused)] +pub fn render_brush_transform(sb: &mut SceneBuilder, i: usize) { + let th = (std::f64::consts::PI / 180.0) * (i as f64); + let linear = LinearGradient::new((0.0, 0.0), (0.0, 200.0)).stops([ + Color::RED, + Color::GREEN, + Color::BLUE, + ]); + sb.fill( + Fill::NonZero, + Affine::translate((200.0, 200.0)), + &linear, + Some(around_center(Affine::rotate(th), Point::new(200.0, 100.0))), + &Rect::from_origin_size(Point::default(), (400.0, 200.0)), + ); + sb.stroke( + &Stroke::new(40.0), + Affine::translate((800.0, 200.0)), + &linear, + Some(around_center(Affine::rotate(th), Point::new(200.0, 100.0))), + &Rect::from_origin_size(Point::default(), (400.0, 200.0)), + ); +} + +fn around_center(xform: Affine, center: Point) -> Affine { + Affine::translate(center.to_vec2()) * xform * Affine::translate(-center.to_vec2()) +} + +fn make_diamond(cx: f64, cy: f64) -> [PathEl; 5] { + const SIZE: f64 = 50.0; + [ + PathEl::MoveTo(Point::new(cx, cy - SIZE)), + PathEl::LineTo(Point::new(cx + SIZE, cy)), + PathEl::LineTo(Point::new(cx, cy + SIZE)), + PathEl::LineTo(Point::new(cx - SIZE, cy)), + PathEl::ClosePath, + ] +}