Merge pull request #212 from dfrg/wgpu-realtime

Make the WGSL port interactive
This commit is contained in:
Chad Brokaw 2022-11-25 21:03:26 -05:00 committed by GitHub
commit 30af6704ed
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 992 additions and 237 deletions

208
Cargo.lock generated
View file

@ -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",

View file

@ -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"

View file

@ -21,10 +21,6 @@ var<storage> tiles: array<Tile>;
@group(0) @binding(2)
var<storage> segments: array<Segment>;
// This will become a texture, but keeping things simple for now
@group(0) @binding(3)
var<storage, read_write> output: array<u32>;
#ifdef full
#import blend
@ -33,6 +29,9 @@ var<storage, read_write> output: array<u32>;
let GRADIENT_WIDTH = 512;
let BLEND_STACK_SPLIT = 4u;
@group(0) @binding(3)
var output: texture_storage_2d<rgba8unorm, write>;
@group(0) @binding(4)
var<storage> ptcl: array<u32>;
@ -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<r8, write>;
#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<u32>(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<i32>(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<u32>(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<i32>(coords), vec4(area[i]));
}
}
#endif
}

View file

@ -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,

View file

@ -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<u8>),
UploadImage(ImageProxy, Vec<u8>),
@ -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::<Vec<_>>();
@ -192,6 +211,7 @@ impl Engine {
device: &Device,
queue: &Queue,
recording: &Recording,
external_resources: &[ExternalResource],
) -> Result<Downloads, Error> {
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<Vec<u8>>,
) -> 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<BindGroup, Error> {
// 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),
})
}
})

View file

@ -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<dyn std::error::Error>> {
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<dyn std::error::Error>> {
#[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::<u8>::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::<u32>().ok())
.unwrap_or(1024);
let height = args
.next()
.and_then(|it| it.parse::<u32>().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<Surface>,
pub surface_config: SurfaceConfiguration,
}
impl WgpuState {
pub async fn new(window: Option<&Window>) -> Result<Self, Box<dyn std::error::Error>> {
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<f32> {
// Generate a full screen quad in NDCs
var vertex = vec2<f32>(-1.0, 1.0);
switch ix {
case 1u: {
vertex = vec2<f32>(-1.0, -1.0);
}
case 2u, 4u: {
vertex = vec2<f32>(1.0, -1.0);
}
case 5u: {
vertex = vec2<f32>(1.0, 1.0);
}
default: {}
}
return vec4<f32>(vertex, 0.0, 1.0);
}
@group(0) @binding(0)
var fine_output: texture_2d<f32>;
@fragment
fn fs_main(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> {
return textureLoad(fine_output, vec2<i32>(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)
}

View file

@ -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 {

View file

@ -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<FullShaders,
BindType::BufReadOnly,
BindType::BufReadOnly,
BindType::BufReadOnly,
BindType::Buffer,
BindType::Image(ImageFormat::Rgba8),
BindType::BufReadOnly,
BindType::ImageRead,
BindType::ImageRead(ImageFormat::Rgba8),
],
)?;
Ok(FullShaders {

View file

@ -0,0 +1,81 @@
// Copyright 2022 The piet-gpu authors.
//
// 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 piet_scene::glyph::{pinot, pinot::TableProvider, GlyphContext};
use piet_scene::kurbo::Affine;
use piet_scene::{Brush, SceneBuilder};
pub use pinot::FontRef;
// This is very much a hack to get things working.
// On Windows, can set this to "c:\\Windows\\Fonts\\seguiemj.ttf" to get color emoji
const FONT_DATA: &[u8] = include_bytes!("../../piet-gpu/third-party/Roboto-Regular.ttf");
pub struct SimpleText {
gcx: GlyphContext,
}
impl SimpleText {
pub fn new() -> 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;
}
}
}
}
}

View file

@ -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::<u8, f32>(&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,
]
}