finish gradient support

This commit is contained in:
Chad Brokaw 2022-11-16 10:49:38 -05:00
parent ef3ed3c9d7
commit 20f7b68514
6 changed files with 459 additions and 76 deletions

View file

@ -17,7 +17,7 @@
use std::{
borrow::Cow,
collections::{hash_map::Entry, HashMap},
num::NonZeroU64,
num::{NonZeroU32, NonZeroU64},
sync::atomic::{AtomicU64, Ordering},
};
@ -25,7 +25,8 @@ use futures_intrusive::channel::shared::GenericOneshotReceiver;
use parking_lot::RawMutex;
use wgpu::{
util::DeviceExt, BindGroup, BindGroupLayout, Buffer, BufferAsyncError, BufferSlice, BufferView,
ComputePipeline, Device, Queue,
ComputePipeline, Device, Queue, Texture, TextureAspect, TextureFormat, TextureUsages,
TextureView, TextureViewDimension,
};
pub type Error = Box<dyn std::error::Error>;
@ -58,12 +59,27 @@ pub struct BufProxy {
id: Id,
}
#[derive(Clone, Copy)]
pub struct ImageProxy {
width: u32,
height: u32,
// TODO: format
id: Id,
}
#[derive(Clone, Copy)]
pub enum ResourceProxy {
Buf(BufProxy),
Image(ImageProxy),
}
pub enum Command {
Upload(BufProxy, Vec<u8>),
UploadImage(ImageProxy, Vec<u8>),
// Discussion question: third argument is vec of resources?
// Maybe use tricks to make more ergonomic?
// Alternative: provide bufs & images as separate sequences, like piet-gpu.
Dispatch(ShaderId, (u32, u32, u32), Vec<BufProxy>),
Dispatch(ShaderId, (u32, u32, u32), Vec<ResourceProxy>),
Download(BufProxy),
Clear(BufProxy, u64, Option<NonZeroU64>),
}
@ -92,6 +108,7 @@ pub enum BindType {
#[derive(Default)]
struct BindMap {
buf_map: HashMap<Id, Buffer>,
image_map: HashMap<Id, (Texture, TextureView)>,
}
impl Engine {
@ -132,6 +149,16 @@ 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,
},
_ => todo!(),
})
.collect::<Vec<_>>();
@ -182,6 +209,58 @@ impl Engine {
});
bind_map.insert_buf(buf_proxy.id, buf);
}
Command::UploadImage(image_proxy, bytes) => {
let buf = device.create_buffer_init(&wgpu::util::BufferInitDescriptor {
label: None,
contents: &bytes,
usage: wgpu::BufferUsages::COPY_SRC,
});
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: None,
size: wgpu::Extent3d {
width: image_proxy.width,
height: image_proxy.height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
format: TextureFormat::Rgba8Unorm,
});
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor {
label: None,
dimension: Some(TextureViewDimension::D2),
aspect: TextureAspect::All,
mip_level_count: None,
base_mip_level: 0,
base_array_layer: 0,
array_layer_count: None,
format: Some(TextureFormat::Rgba8Unorm),
});
encoder.copy_buffer_to_texture(
wgpu::ImageCopyBuffer {
buffer: &buf,
layout: wgpu::ImageDataLayout {
offset: 0,
bytes_per_row: NonZeroU32::new(image_proxy.width * 4),
rows_per_image: None,
},
},
wgpu::ImageCopyTexture {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d { x: 0, y: 0, z: 0 },
aspect: TextureAspect::All,
},
wgpu::Extent3d {
width: image_proxy.width,
height: image_proxy.height,
depth_or_array_layers: 1,
},
);
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());
let shader = &self.shaders[shader_id.0];
@ -226,13 +305,23 @@ impl Recording {
buf_proxy
}
pub fn dispatch(
&mut self,
shader: ShaderId,
wg_size: (u32, u32, u32),
resources: impl Into<Vec<BufProxy>>,
) {
self.push(Command::Dispatch(shader, wg_size, resources.into()));
pub fn upload_image(&mut self, width: u32, height: u32, data: impl Into<Vec<u8>>) -> ImageProxy {
let data = data.into();
let image_proxy = ImageProxy::new(width, height);
self.push(Command::UploadImage(image_proxy, data));
image_proxy
}
pub fn dispatch<R>(&mut self, shader: ShaderId, wg_size: (u32, u32, u32), resources: R)
where
R: IntoIterator,
R::Item: Into<ResourceProxy>,
{
self.push(Command::Dispatch(
shader,
wg_size,
resources.into_iter().map(|r| r.into()).collect(),
));
}
pub fn download(&mut self, buf: BufProxy) {
@ -251,6 +340,35 @@ impl BufProxy {
}
}
impl ImageProxy {
pub fn new(width: u32, height: u32) -> Self {
let id = Id::next();
ImageProxy { width, height, id }
}
}
impl ResourceProxy {
pub fn new_buf(size: u64) -> Self {
Self::Buf(BufProxy::new(size))
}
pub fn new_image(width: u32, height: u32) -> Self {
Self::Image(ImageProxy::new(width, height))
}
}
impl From<BufProxy> for ResourceProxy {
fn from(value: BufProxy) -> Self {
Self::Buf(value)
}
}
impl From<ImageProxy> for ResourceProxy {
fn from(value: ImageProxy) -> Self {
Self::Image(value)
}
}
impl Id {
pub fn next() -> Id {
let val = ID_COUNTER.fetch_add(1, Ordering::Relaxed);
@ -264,13 +382,19 @@ impl BindMap {
self.buf_map.insert(id, buf);
}
fn insert_image(&mut self, id: Id, image: Texture, image_view: TextureView) {
self.image_map.insert(id, (image, image_view));
}
fn create_bind_group(
&mut self,
device: &Device,
layout: &BindGroupLayout,
bindings: &[BufProxy],
bindings: &[ResourceProxy],
) -> Result<BindGroup, Error> {
for proxy in bindings {
match proxy {
ResourceProxy::Buf(proxy) => {
if let Entry::Vacant(v) = self.buf_map.entry(proxy.id) {
let buf = device.create_buffer(&wgpu::BufferDescriptor {
label: None,
@ -283,15 +407,54 @@ impl BindMap {
v.insert(buf);
}
}
ResourceProxy::Image(proxy) => {
if let Entry::Vacant(v) = self.image_map.entry(proxy.id) {
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: None,
size: wgpu::Extent3d {
width: proxy.width,
height: proxy.height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
usage: TextureUsages::TEXTURE_BINDING | TextureUsages::COPY_DST,
format: TextureFormat::Rgba8Unorm,
});
let texture_view = texture.create_view(&wgpu::TextureViewDescriptor {
label: None,
dimension: Some(TextureViewDimension::D2),
aspect: TextureAspect::All,
mip_level_count: None,
base_mip_level: 0,
base_array_layer: 0,
array_layer_count: None,
format: Some(TextureFormat::Rgba8Unorm),
});
v.insert((texture, texture_view));
}
}
}
}
let entries = bindings
.iter()
.enumerate()
.map(|(i, proxy)| {
.map(|(i, proxy)| match proxy {
ResourceProxy::Buf(proxy) => {
let buf = 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();
Ok(wgpu::BindGroupEntry {
binding: i as u32,
resource: wgpu::BindingResource::TextureView(&texture.1),
})
}
})
.collect::<Result<Vec<_>, Error>>()?;
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {

View file

@ -24,6 +24,7 @@ use wgpu::{Device, Limits, Queue};
mod engine;
mod pico_svg;
mod ramp;
mod render;
mod shaders;
mod test_scene;
@ -58,7 +59,6 @@ fn dump_buf(buf: &[u32]) {
println!("{}: {:x} {}", i, val, f32::from_bits(*val));
} else {
println!("{}: {:x}", i, val);
}
}
}

137
piet-wgsl/src/ramp.rs Normal file
View file

@ -0,0 +1,137 @@
use piet_scene::{Color, GradientStop, GradientStops};
use std::collections::HashMap;
const N_SAMPLES: usize = 512;
const RETAINED_COUNT: usize = 64;
#[derive(Default)]
pub struct RampCache {
epoch: u64,
map: HashMap<GradientStops, (u32, u64)>,
data: Vec<u32>,
}
impl RampCache {
pub fn advance(&mut self) {
self.epoch += 1;
if self.map.len() > RETAINED_COUNT {
self.map
.retain(|_key, value| value.0 < RETAINED_COUNT as u32);
self.data.truncate(RETAINED_COUNT * N_SAMPLES);
}
}
pub fn add(&mut self, stops: &[GradientStop]) -> u32 {
if let Some(entry) = self.map.get_mut(stops) {
entry.1 = self.epoch;
entry.0
} else if self.map.len() < RETAINED_COUNT {
let id = (self.data.len() / N_SAMPLES) as u32;
self.data.extend(make_ramp(stops));
self.map.insert(stops.into(), (id, self.epoch));
id
} else {
let mut reuse = None;
for (stops, (id, epoch)) in &self.map {
if *epoch + 2 < self.epoch {
reuse = Some((stops.to_owned(), *id));
break;
}
}
if let Some((old_stops, id)) = reuse {
self.map.remove(&old_stops);
let start = id as usize * N_SAMPLES;
for (dst, src) in self.data[start..start + N_SAMPLES]
.iter_mut()
.zip(make_ramp(stops))
{
*dst = src;
}
self.map.insert(stops.into(), (id, self.epoch));
id
} else {
let id = (self.data.len() / N_SAMPLES) as u32;
self.data.extend(make_ramp(stops));
self.map.insert(stops.into(), (id, self.epoch));
id
}
}
}
pub fn data(&self) -> &[u32] {
&self.data
}
pub fn width(&self) -> u32 {
N_SAMPLES as u32
}
pub fn height(&self) -> u32 {
(self.data.len() / N_SAMPLES) as u32
}
}
fn make_ramp<'a>(stops: &'a [GradientStop]) -> impl Iterator<Item = u32> + 'a {
let mut last_u = 0.0;
let mut last_c = ColorF64::from_color(stops[0].color);
let mut this_u = last_u;
let mut this_c = last_c;
let mut j = 0;
(0..N_SAMPLES).map(move |i| {
let u = (i as f64) / (N_SAMPLES - 1) as f64;
while u > this_u {
last_u = this_u;
last_c = this_c;
if let Some(s) = stops.get(j + 1) {
this_u = s.offset as f64;
this_c = ColorF64::from_color(s.color);
j += 1;
} else {
break;
}
}
let du = this_u - last_u;
let c = if du < 1e-9 {
this_c
} else {
last_c.lerp(&this_c, (u - last_u) / du)
};
c.to_premul_u32()
})
}
#[derive(Copy, Clone, Debug)]
struct ColorF64([f64; 4]);
impl ColorF64 {
fn from_color(color: Color) -> Self {
Self([
color.r as f64 / 255.0,
color.g as f64 / 255.0,
color.b as f64 / 255.0,
color.a as f64 / 255.0,
])
}
fn lerp(&self, other: &Self, a: f64) -> Self {
fn l(x: f64, y: f64, a: f64) -> f64 {
x * (1.0 - a) + y * a
}
Self([
l(self.0[0], other.0[0], a),
l(self.0[1], other.0[1], a),
l(self.0[2], other.0[2], a),
l(self.0[3], other.0[3], a),
])
}
fn to_premul_u32(&self) -> u32 {
let a = self.0[3].min(1.0).max(0.0);
let r = ((self.0[0] * a).min(1.0).max(0.0) * 255.0) as u32;
let g = ((self.0[1] * a).min(1.0).max(0.0) * 255.0) as u32;
let b = ((self.0[2] * a).min(1.0).max(0.0) * 255.0) as u32;
let a = (a * 255.0) as u32;
r | (g << 8) | (b << 16) | (a << 24)
}
}

View file

@ -4,7 +4,7 @@ use bytemuck::{Pod, Zeroable};
use piet_scene::Scene;
use crate::{
engine::{BufProxy, Recording},
engine::{BufProxy, Recording, ResourceProxy},
shaders::{self, FullShaders, Shaders},
};
@ -87,7 +87,8 @@ pub fn render(scene: &Scene, shaders: &Shaders) -> (Recording, BufProxy) {
[config_buf, scene_buf, reduced_buf, tagmonoid_buf],
);
let path_coarse_wgs = (n_pathtag as u32 + shaders::PATH_COARSE_WG - 1) / shaders::PATH_COARSE_WG;
let path_coarse_wgs =
(n_pathtag as u32 + shaders::PATH_COARSE_WG - 1) / shaders::PATH_COARSE_WG;
// TODO: more principled size calc
let tiles_buf = BufProxy::new(4097 * 8);
let segments_buf = BufProxy::new(256 * 24);
@ -122,7 +123,29 @@ pub fn render(scene: &Scene, shaders: &Shaders) -> (Recording, BufProxy) {
pub fn render_full(scene: &Scene, shaders: &FullShaders) -> (Recording, BufProxy) {
let mut recording = Recording::default();
let mut ramps = crate::ramp::RampCache::default();
let mut drawdata_patches: Vec<(usize, u32)> = vec![];
let data = scene.data();
let stop_data = &data.resources.stops;
for patch in &data.resources.patches {
use piet_scene::ResourcePatch;
match patch {
ResourcePatch::Ramp { offset, stops } => {
let ramp_id = ramps.add(&stop_data[stops.clone()]);
drawdata_patches.push((*offset, ramp_id));
}
}
}
let gradient_image = if drawdata_patches.is_empty() {
ResourceProxy::new_image(1, 1)
} 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))
};
let n_pathtag = data.tag_stream.len();
let pathtag_padded = align_up(n_pathtag, 4 * shaders::PATHTAG_REDUCE_WG);
// TODO: can compute size accurately, avoid reallocation
@ -135,12 +158,27 @@ pub fn render_full(scene: &Scene, shaders: &FullShaders) -> (Recording, BufProxy
let drawtag_base = size_to_words(scene.len());
scene.extend(bytemuck::cast_slice(&data.drawtag_stream));
let drawdata_base = size_to_words(scene.len());
if !drawdata_patches.is_empty() {
let mut pos = 0;
for patch in drawdata_patches {
let offset = patch.0;
let value = patch.1;
if pos < offset {
scene.extend_from_slice(&data.drawdata_stream[pos..offset]);
}
scene.extend_from_slice(bytemuck::bytes_of(&value));
pos = offset + 4;
}
if pos < data.drawdata_stream.len() {
scene.extend_from_slice(&data.drawdata_stream[pos..])
}
} else {
scene.extend(&data.drawdata_stream);
}
let transform_base = size_to_words(scene.len());
scene.extend(bytemuck::cast_slice(&data.transform_stream));
let linewidth_base = size_to_words(scene.len());
scene.extend(bytemuck::cast_slice(&data.linewidth_stream));
let n_path = data.n_path;
// TODO: calculate for real when we do rectangles
let n_drawobj = n_path;
@ -159,11 +197,11 @@ pub fn render_full(scene: &Scene, shaders: &FullShaders) -> (Recording, BufProxy
linewidth_base,
};
println!("{:?}", config);
let scene_buf = recording.upload(scene);
let config_buf = recording.upload(bytemuck::bytes_of(&config).to_owned());
let scene_buf = ResourceProxy::Buf(recording.upload(scene));
let config_buf = ResourceProxy::Buf(recording.upload(bytemuck::bytes_of(&config).to_owned()));
let pathtag_wgs = pathtag_padded / (4 * shaders::PATHTAG_REDUCE_WG as usize);
let reduced_buf = BufProxy::new(pathtag_wgs as u64 * TAG_MONOID_FULL_SIZE);
let reduced_buf = ResourceProxy::new_buf(pathtag_wgs as u64 * TAG_MONOID_FULL_SIZE);
// TODO: really only need pathtag_wgs - 1
recording.dispatch(
shaders.pathtag_reduce,
@ -171,22 +209,24 @@ pub fn render_full(scene: &Scene, shaders: &FullShaders) -> (Recording, BufProxy
[config_buf, scene_buf, reduced_buf],
);
let tagmonoid_buf =
BufProxy::new(pathtag_wgs as u64 * shaders::PATHTAG_REDUCE_WG as u64 * TAG_MONOID_FULL_SIZE);
let tagmonoid_buf = ResourceProxy::new_buf(
pathtag_wgs as u64 * shaders::PATHTAG_REDUCE_WG as u64 * TAG_MONOID_FULL_SIZE,
);
recording.dispatch(
shaders.pathtag_scan,
(pathtag_wgs as u32, 1, 1),
[config_buf, scene_buf, reduced_buf, tagmonoid_buf],
);
let drawobj_wgs = (n_drawobj + shaders::PATH_BBOX_WG - 1) / shaders::PATH_BBOX_WG;
let path_bbox_buf = BufProxy::new(n_path as u64 * PATH_BBOX_SIZE);
let path_bbox_buf = ResourceProxy::new_buf(n_path as u64 * PATH_BBOX_SIZE);
recording.dispatch(
shaders.bbox_clear,
(drawobj_wgs, 1, 1),
[config_buf, path_bbox_buf],
);
let cubic_buf = BufProxy::new(n_pathtag as u64 * CUBIC_SIZE);
let path_coarse_wgs = (n_pathtag as u32 + shaders::PATH_COARSE_WG - 1) / shaders::PATH_COARSE_WG;
let cubic_buf = ResourceProxy::new_buf(n_pathtag as u64 * CUBIC_SIZE);
let path_coarse_wgs =
(n_pathtag as u32 + shaders::PATH_COARSE_WG - 1) / shaders::PATH_COARSE_WG;
recording.dispatch(
shaders.pathseg,
(path_coarse_wgs, 1, 1),
@ -198,14 +238,14 @@ pub fn render_full(scene: &Scene, shaders: &FullShaders) -> (Recording, BufProxy
cubic_buf,
],
);
let draw_reduced_buf = BufProxy::new(drawobj_wgs as u64 * DRAWMONOID_SIZE);
let draw_reduced_buf = ResourceProxy::new_buf(drawobj_wgs as u64 * DRAWMONOID_SIZE);
recording.dispatch(
shaders.draw_reduce,
(drawobj_wgs, 1, 1),
[config_buf, scene_buf, draw_reduced_buf],
);
let draw_monoid_buf = BufProxy::new(n_drawobj as u64 * DRAWMONOID_SIZE);
let info_buf = BufProxy::new(n_drawobj as u64 * MAX_DRAWINFO_SIZE);
let draw_monoid_buf = ResourceProxy::new_buf(n_drawobj as u64 * DRAWMONOID_SIZE);
let info_buf = ResourceProxy::new_buf(n_drawobj as u64 * MAX_DRAWINFO_SIZE);
recording.dispatch(
shaders.draw_leaf,
(drawobj_wgs, 1, 1),
@ -218,16 +258,17 @@ pub fn render_full(scene: &Scene, shaders: &FullShaders) -> (Recording, BufProxy
info_buf,
],
);
let draw_bbox_buf = BufProxy::new(n_path as u64 * DRAW_BBOX_SIZE);
let draw_bbox_buf = ResourceProxy::new_buf(n_path as u64 * DRAW_BBOX_SIZE);
let bump_buf = BufProxy::new(BUMP_SIZE);
// Not actually used yet.
let clip_bbox_buf = BufProxy::new(1024);
let bin_data_buf = BufProxy::new(1 << 20);
let clip_bbox_buf = ResourceProxy::new_buf(1024);
let bin_data_buf = ResourceProxy::new_buf(1 << 20);
let width_in_bins = (config.width_in_tiles + 15) / 16;
let height_in_bins = (config.height_in_tiles + 15) / 16;
let n_bins = width_in_bins * height_in_bins;
let bin_header_buf = BufProxy::new((n_bins * drawobj_wgs) as u64 * BIN_HEADER_SIZE);
let bin_header_buf = ResourceProxy::new_buf((n_bins * drawobj_wgs) as u64 * BIN_HEADER_SIZE);
recording.clear_all(bump_buf);
let bump_buf = ResourceProxy::Buf(bump_buf);
recording.dispatch(
shaders.binning,
(drawobj_wgs, 1, 1),
@ -242,8 +283,8 @@ pub fn render_full(scene: &Scene, shaders: &FullShaders) -> (Recording, BufProxy
bin_header_buf,
],
);
let path_buf = BufProxy::new(n_path as u64 * PATH_SIZE);
let tile_buf = BufProxy::new(1 << 20);
let path_buf = ResourceProxy::new_buf(n_path as u64 * PATH_SIZE);
let tile_buf = ResourceProxy::new_buf(1 << 20);
let path_wgs = (n_path + shaders::PATH_BBOX_WG - 1) / shaders::PATH_BBOX_WG;
recording.dispatch(
shaders.tile_alloc,
@ -258,7 +299,7 @@ pub fn render_full(scene: &Scene, shaders: &FullShaders) -> (Recording, BufProxy
],
);
let segments_buf = BufProxy::new(1 << 24);
let segments_buf = ResourceProxy::new_buf(1 << 24);
recording.dispatch(
shaders.path_coarse,
(path_coarse_wgs, 1, 1),
@ -278,7 +319,7 @@ pub fn render_full(scene: &Scene, shaders: &FullShaders) -> (Recording, BufProxy
(path_wgs, 1, 1),
[config_buf, path_buf, tile_buf],
);
let ptcl_buf = BufProxy::new(1 << 24);
let ptcl_buf = ResourceProxy::new_buf(1 << 24);
recording.dispatch(
shaders.coarse,
(width_in_bins, height_in_bins, 1),
@ -300,7 +341,14 @@ pub fn render_full(scene: &Scene, shaders: &FullShaders) -> (Recording, BufProxy
recording.dispatch(
shaders.fine,
(config.width_in_tiles, config.height_in_tiles, 1),
[config_buf, tile_buf, segments_buf, out_buf, ptcl_buf],
[
config_buf,
tile_buf,
segments_buf,
ResourceProxy::Buf(out_buf),
ptcl_buf,
gradient_image,
],
);
let download_buf = out_buf;

View file

@ -255,6 +255,7 @@ pub fn full_shaders(device: &Device, engine: &mut Engine) -> Result<FullShaders,
BindType::BufReadOnly,
BindType::Buffer,
BindType::BufReadOnly,
BindType::ImageRead,
],
)?;
Ok(FullShaders {

View file

@ -15,14 +15,16 @@
// Also licensed under MIT license, at your choice.
use kurbo::BezPath;
use piet_scene::{Affine, Brush, Color, Fill, PathElement, Point, Scene, SceneBuilder, Stroke};
use piet_scene::{Affine, Brush, Color, Fill, LinearGradient, PathElement, Point, Scene, SceneBuilder, Stroke, GradientStop};
use crate::pico_svg::PicoSvg;
pub fn gen_test_scene() -> Scene {
let mut scene = Scene::default();
let mut builder = SceneBuilder::for_scene(&mut scene);
if false {
let scene_ix = 1;
match scene_ix {
0 => {
let path = [
PathElement::MoveTo(Point::new(100.0, 100.0)),
PathElement::LineTo(Point::new(500.0, 120.0)),
@ -40,11 +42,44 @@ pub fn gen_test_scene() -> Scene {
let style = simple_stroke(1.0);
let brush = Brush::Solid(Color::rgb8(0xa0, 0x00, 0x00));
builder.stroke(&style, transform, &brush, None, &path);
} else {
let xml_str = std::str::from_utf8(include_bytes!("../../piet-gpu/Ghostscript_Tiger.svg")).unwrap();
}
1 => {
let path = [
PathElement::MoveTo(Point::new(100.0, 100.0)),
PathElement::LineTo(Point::new(300.0, 100.0)),
PathElement::LineTo(Point::new(300.0, 300.0)),
PathElement::LineTo(Point::new(100.0, 300.0)),
PathElement::Close,
];
let gradient = Brush::LinearGradient(LinearGradient {
start: Point::new(100.0, 100.0),
end: Point::new(300.0, 300.0),
extend: piet_scene::ExtendMode::Pad,
stops: vec![
GradientStop {
offset: 0.0,
color: Color::rgb8(255, 0, 0),
},
GradientStop {
offset: 0.5,
color: Color::rgb8(0, 255, 0),
},
GradientStop {
offset: 1.0,
color: Color::rgb8(0, 0, 255),
},
].into()
});
builder.fill(Fill::NonZero, Affine::scale(3.0, 3.0), &gradient, None, &path);
}
_ => {
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);
}
}
scene
}
@ -94,7 +129,6 @@ fn convert_bez_path<'a>(path: &'a BezPath) -> impl Iterator<Item = PathElement>
.map(|el| PathElement::from_kurbo(*el))
}
fn simple_stroke(width: f32) -> Stroke<[f32; 0]> {
Stroke {
width,