mirror of
https://github.com/italicsjenga/vello.git
synced 2025-01-10 12:41:30 +11:00
finish gradient support
This commit is contained in:
parent
ef3ed3c9d7
commit
20f7b68514
|
@ -17,7 +17,7 @@
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
collections::{hash_map::Entry, HashMap},
|
collections::{hash_map::Entry, HashMap},
|
||||||
num::NonZeroU64,
|
num::{NonZeroU32, NonZeroU64},
|
||||||
sync::atomic::{AtomicU64, Ordering},
|
sync::atomic::{AtomicU64, Ordering},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -25,7 +25,8 @@ use futures_intrusive::channel::shared::GenericOneshotReceiver;
|
||||||
use parking_lot::RawMutex;
|
use parking_lot::RawMutex;
|
||||||
use wgpu::{
|
use wgpu::{
|
||||||
util::DeviceExt, BindGroup, BindGroupLayout, Buffer, BufferAsyncError, BufferSlice, BufferView,
|
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>;
|
pub type Error = Box<dyn std::error::Error>;
|
||||||
|
@ -58,12 +59,27 @@ pub struct BufProxy {
|
||||||
id: Id,
|
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 {
|
pub enum Command {
|
||||||
Upload(BufProxy, Vec<u8>),
|
Upload(BufProxy, Vec<u8>),
|
||||||
|
UploadImage(ImageProxy, Vec<u8>),
|
||||||
// Discussion question: third argument is vec of resources?
|
// Discussion question: third argument is vec of resources?
|
||||||
// Maybe use tricks to make more ergonomic?
|
// Maybe use tricks to make more ergonomic?
|
||||||
// Alternative: provide bufs & images as separate sequences, like piet-gpu.
|
// 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),
|
Download(BufProxy),
|
||||||
Clear(BufProxy, u64, Option<NonZeroU64>),
|
Clear(BufProxy, u64, Option<NonZeroU64>),
|
||||||
}
|
}
|
||||||
|
@ -92,6 +108,7 @@ pub enum BindType {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct BindMap {
|
struct BindMap {
|
||||||
buf_map: HashMap<Id, Buffer>,
|
buf_map: HashMap<Id, Buffer>,
|
||||||
|
image_map: HashMap<Id, (Texture, TextureView)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Engine {
|
impl Engine {
|
||||||
|
@ -132,6 +149,16 @@ impl Engine {
|
||||||
},
|
},
|
||||||
count: None,
|
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!(),
|
_ => todo!(),
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
@ -182,6 +209,58 @@ impl Engine {
|
||||||
});
|
});
|
||||||
bind_map.insert_buf(buf_proxy.id, buf);
|
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) => {
|
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 shader = &self.shaders[shader_id.0];
|
||||||
|
@ -226,13 +305,23 @@ impl Recording {
|
||||||
buf_proxy
|
buf_proxy
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn dispatch(
|
pub fn upload_image(&mut self, width: u32, height: u32, data: impl Into<Vec<u8>>) -> ImageProxy {
|
||||||
&mut self,
|
let data = data.into();
|
||||||
shader: ShaderId,
|
let image_proxy = ImageProxy::new(width, height);
|
||||||
wg_size: (u32, u32, u32),
|
self.push(Command::UploadImage(image_proxy, data));
|
||||||
resources: impl Into<Vec<BufProxy>>,
|
image_proxy
|
||||||
) {
|
}
|
||||||
self.push(Command::Dispatch(shader, wg_size, resources.into()));
|
|
||||||
|
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) {
|
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 {
|
impl Id {
|
||||||
pub fn next() -> Id {
|
pub fn next() -> Id {
|
||||||
let val = ID_COUNTER.fetch_add(1, Ordering::Relaxed);
|
let val = ID_COUNTER.fetch_add(1, Ordering::Relaxed);
|
||||||
|
@ -264,34 +382,79 @@ impl BindMap {
|
||||||
self.buf_map.insert(id, buf);
|
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(
|
fn create_bind_group(
|
||||||
&mut self,
|
&mut self,
|
||||||
device: &Device,
|
device: &Device,
|
||||||
layout: &BindGroupLayout,
|
layout: &BindGroupLayout,
|
||||||
bindings: &[BufProxy],
|
bindings: &[ResourceProxy],
|
||||||
) -> Result<BindGroup, Error> {
|
) -> Result<BindGroup, Error> {
|
||||||
for proxy in bindings {
|
for proxy in bindings {
|
||||||
if let Entry::Vacant(v) = self.buf_map.entry(proxy.id) {
|
match proxy {
|
||||||
let buf = device.create_buffer(&wgpu::BufferDescriptor {
|
ResourceProxy::Buf(proxy) => {
|
||||||
label: None,
|
if let Entry::Vacant(v) = self.buf_map.entry(proxy.id) {
|
||||||
size: proxy.size,
|
let buf = device.create_buffer(&wgpu::BufferDescriptor {
|
||||||
usage: wgpu::BufferUsages::STORAGE
|
label: None,
|
||||||
| wgpu::BufferUsages::COPY_DST
|
size: proxy.size,
|
||||||
| wgpu::BufferUsages::COPY_SRC,
|
usage: wgpu::BufferUsages::STORAGE
|
||||||
mapped_at_creation: false,
|
| wgpu::BufferUsages::COPY_DST
|
||||||
});
|
| wgpu::BufferUsages::COPY_SRC,
|
||||||
v.insert(buf);
|
mapped_at_creation: false,
|
||||||
|
});
|
||||||
|
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
|
let entries = bindings
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(i, proxy)| {
|
.map(|(i, proxy)| match proxy {
|
||||||
let buf = self.buf_map.get(&proxy.id).unwrap();
|
ResourceProxy::Buf(proxy) => {
|
||||||
Ok(wgpu::BindGroupEntry {
|
let buf = self.buf_map.get(&proxy.id).unwrap();
|
||||||
binding: i as u32,
|
Ok(wgpu::BindGroupEntry {
|
||||||
resource: buf.as_entire_binding(),
|
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>>()?;
|
.collect::<Result<Vec<_>, Error>>()?;
|
||||||
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
|
||||||
|
|
|
@ -24,6 +24,7 @@ use wgpu::{Device, Limits, Queue};
|
||||||
|
|
||||||
mod engine;
|
mod engine;
|
||||||
mod pico_svg;
|
mod pico_svg;
|
||||||
|
mod ramp;
|
||||||
mod render;
|
mod render;
|
||||||
mod shaders;
|
mod shaders;
|
||||||
mod test_scene;
|
mod test_scene;
|
||||||
|
@ -58,7 +59,6 @@ fn dump_buf(buf: &[u32]) {
|
||||||
println!("{}: {:x} {}", i, val, f32::from_bits(*val));
|
println!("{}: {:x} {}", i, val, f32::from_bits(*val));
|
||||||
} else {
|
} else {
|
||||||
println!("{}: {:x}", i, val);
|
println!("{}: {:x}", i, val);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
137
piet-wgsl/src/ramp.rs
Normal file
137
piet-wgsl/src/ramp.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ use bytemuck::{Pod, Zeroable};
|
||||||
use piet_scene::Scene;
|
use piet_scene::Scene;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
engine::{BufProxy, Recording},
|
engine::{BufProxy, Recording, ResourceProxy},
|
||||||
shaders::{self, FullShaders, Shaders},
|
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],
|
[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
|
// TODO: more principled size calc
|
||||||
let tiles_buf = BufProxy::new(4097 * 8);
|
let tiles_buf = BufProxy::new(4097 * 8);
|
||||||
let segments_buf = BufProxy::new(256 * 24);
|
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) {
|
pub fn render_full(scene: &Scene, shaders: &FullShaders) -> (Recording, BufProxy) {
|
||||||
let mut recording = Recording::default();
|
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 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 n_pathtag = data.tag_stream.len();
|
||||||
let pathtag_padded = align_up(n_pathtag, 4 * shaders::PATHTAG_REDUCE_WG);
|
let pathtag_padded = align_up(n_pathtag, 4 * shaders::PATHTAG_REDUCE_WG);
|
||||||
// TODO: can compute size accurately, avoid reallocation
|
// 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());
|
let drawtag_base = size_to_words(scene.len());
|
||||||
scene.extend(bytemuck::cast_slice(&data.drawtag_stream));
|
scene.extend(bytemuck::cast_slice(&data.drawtag_stream));
|
||||||
let drawdata_base = size_to_words(scene.len());
|
let drawdata_base = size_to_words(scene.len());
|
||||||
scene.extend(&data.drawdata_stream);
|
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());
|
let transform_base = size_to_words(scene.len());
|
||||||
scene.extend(bytemuck::cast_slice(&data.transform_stream));
|
scene.extend(bytemuck::cast_slice(&data.transform_stream));
|
||||||
let linewidth_base = size_to_words(scene.len());
|
let linewidth_base = size_to_words(scene.len());
|
||||||
scene.extend(bytemuck::cast_slice(&data.linewidth_stream));
|
scene.extend(bytemuck::cast_slice(&data.linewidth_stream));
|
||||||
|
|
||||||
let n_path = data.n_path;
|
let n_path = data.n_path;
|
||||||
// TODO: calculate for real when we do rectangles
|
// TODO: calculate for real when we do rectangles
|
||||||
let n_drawobj = n_path;
|
let n_drawobj = n_path;
|
||||||
|
@ -159,11 +197,11 @@ pub fn render_full(scene: &Scene, shaders: &FullShaders) -> (Recording, BufProxy
|
||||||
linewidth_base,
|
linewidth_base,
|
||||||
};
|
};
|
||||||
println!("{:?}", config);
|
println!("{:?}", config);
|
||||||
let scene_buf = recording.upload(scene);
|
let scene_buf = ResourceProxy::Buf(recording.upload(scene));
|
||||||
let config_buf = recording.upload(bytemuck::bytes_of(&config).to_owned());
|
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 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
|
// TODO: really only need pathtag_wgs - 1
|
||||||
recording.dispatch(
|
recording.dispatch(
|
||||||
shaders.pathtag_reduce,
|
shaders.pathtag_reduce,
|
||||||
|
@ -171,22 +209,24 @@ pub fn render_full(scene: &Scene, shaders: &FullShaders) -> (Recording, BufProxy
|
||||||
[config_buf, scene_buf, reduced_buf],
|
[config_buf, scene_buf, reduced_buf],
|
||||||
);
|
);
|
||||||
|
|
||||||
let tagmonoid_buf =
|
let tagmonoid_buf = ResourceProxy::new_buf(
|
||||||
BufProxy::new(pathtag_wgs as u64 * shaders::PATHTAG_REDUCE_WG as u64 * TAG_MONOID_FULL_SIZE);
|
pathtag_wgs as u64 * shaders::PATHTAG_REDUCE_WG as u64 * TAG_MONOID_FULL_SIZE,
|
||||||
|
);
|
||||||
recording.dispatch(
|
recording.dispatch(
|
||||||
shaders.pathtag_scan,
|
shaders.pathtag_scan,
|
||||||
(pathtag_wgs as u32, 1, 1),
|
(pathtag_wgs as u32, 1, 1),
|
||||||
[config_buf, scene_buf, reduced_buf, tagmonoid_buf],
|
[config_buf, scene_buf, reduced_buf, tagmonoid_buf],
|
||||||
);
|
);
|
||||||
let drawobj_wgs = (n_drawobj + shaders::PATH_BBOX_WG - 1) / shaders::PATH_BBOX_WG;
|
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(
|
recording.dispatch(
|
||||||
shaders.bbox_clear,
|
shaders.bbox_clear,
|
||||||
(drawobj_wgs, 1, 1),
|
(drawobj_wgs, 1, 1),
|
||||||
[config_buf, path_bbox_buf],
|
[config_buf, path_bbox_buf],
|
||||||
);
|
);
|
||||||
let cubic_buf = BufProxy::new(n_pathtag as u64 * CUBIC_SIZE);
|
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;
|
let path_coarse_wgs =
|
||||||
|
(n_pathtag as u32 + shaders::PATH_COARSE_WG - 1) / shaders::PATH_COARSE_WG;
|
||||||
recording.dispatch(
|
recording.dispatch(
|
||||||
shaders.pathseg,
|
shaders.pathseg,
|
||||||
(path_coarse_wgs, 1, 1),
|
(path_coarse_wgs, 1, 1),
|
||||||
|
@ -198,14 +238,14 @@ pub fn render_full(scene: &Scene, shaders: &FullShaders) -> (Recording, BufProxy
|
||||||
cubic_buf,
|
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(
|
recording.dispatch(
|
||||||
shaders.draw_reduce,
|
shaders.draw_reduce,
|
||||||
(drawobj_wgs, 1, 1),
|
(drawobj_wgs, 1, 1),
|
||||||
[config_buf, scene_buf, draw_reduced_buf],
|
[config_buf, scene_buf, draw_reduced_buf],
|
||||||
);
|
);
|
||||||
let draw_monoid_buf = BufProxy::new(n_drawobj as u64 * DRAWMONOID_SIZE);
|
let draw_monoid_buf = ResourceProxy::new_buf(n_drawobj as u64 * DRAWMONOID_SIZE);
|
||||||
let info_buf = BufProxy::new(n_drawobj as u64 * MAX_DRAWINFO_SIZE);
|
let info_buf = ResourceProxy::new_buf(n_drawobj as u64 * MAX_DRAWINFO_SIZE);
|
||||||
recording.dispatch(
|
recording.dispatch(
|
||||||
shaders.draw_leaf,
|
shaders.draw_leaf,
|
||||||
(drawobj_wgs, 1, 1),
|
(drawobj_wgs, 1, 1),
|
||||||
|
@ -218,16 +258,17 @@ pub fn render_full(scene: &Scene, shaders: &FullShaders) -> (Recording, BufProxy
|
||||||
info_buf,
|
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);
|
let bump_buf = BufProxy::new(BUMP_SIZE);
|
||||||
// Not actually used yet.
|
// Not actually used yet.
|
||||||
let clip_bbox_buf = BufProxy::new(1024);
|
let clip_bbox_buf = ResourceProxy::new_buf(1024);
|
||||||
let bin_data_buf = BufProxy::new(1 << 20);
|
let bin_data_buf = ResourceProxy::new_buf(1 << 20);
|
||||||
let width_in_bins = (config.width_in_tiles + 15) / 16;
|
let width_in_bins = (config.width_in_tiles + 15) / 16;
|
||||||
let height_in_bins = (config.height_in_tiles + 15) / 16;
|
let height_in_bins = (config.height_in_tiles + 15) / 16;
|
||||||
let n_bins = width_in_bins * height_in_bins;
|
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);
|
recording.clear_all(bump_buf);
|
||||||
|
let bump_buf = ResourceProxy::Buf(bump_buf);
|
||||||
recording.dispatch(
|
recording.dispatch(
|
||||||
shaders.binning,
|
shaders.binning,
|
||||||
(drawobj_wgs, 1, 1),
|
(drawobj_wgs, 1, 1),
|
||||||
|
@ -242,8 +283,8 @@ pub fn render_full(scene: &Scene, shaders: &FullShaders) -> (Recording, BufProxy
|
||||||
bin_header_buf,
|
bin_header_buf,
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
let path_buf = BufProxy::new(n_path as u64 * PATH_SIZE);
|
let path_buf = ResourceProxy::new_buf(n_path as u64 * PATH_SIZE);
|
||||||
let tile_buf = BufProxy::new(1 << 20);
|
let tile_buf = ResourceProxy::new_buf(1 << 20);
|
||||||
let path_wgs = (n_path + shaders::PATH_BBOX_WG - 1) / shaders::PATH_BBOX_WG;
|
let path_wgs = (n_path + shaders::PATH_BBOX_WG - 1) / shaders::PATH_BBOX_WG;
|
||||||
recording.dispatch(
|
recording.dispatch(
|
||||||
shaders.tile_alloc,
|
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(
|
recording.dispatch(
|
||||||
shaders.path_coarse,
|
shaders.path_coarse,
|
||||||
(path_coarse_wgs, 1, 1),
|
(path_coarse_wgs, 1, 1),
|
||||||
|
@ -278,7 +319,7 @@ pub fn render_full(scene: &Scene, shaders: &FullShaders) -> (Recording, BufProxy
|
||||||
(path_wgs, 1, 1),
|
(path_wgs, 1, 1),
|
||||||
[config_buf, path_buf, tile_buf],
|
[config_buf, path_buf, tile_buf],
|
||||||
);
|
);
|
||||||
let ptcl_buf = BufProxy::new(1 << 24);
|
let ptcl_buf = ResourceProxy::new_buf(1 << 24);
|
||||||
recording.dispatch(
|
recording.dispatch(
|
||||||
shaders.coarse,
|
shaders.coarse,
|
||||||
(width_in_bins, height_in_bins, 1),
|
(width_in_bins, height_in_bins, 1),
|
||||||
|
@ -300,7 +341,14 @@ pub fn render_full(scene: &Scene, shaders: &FullShaders) -> (Recording, BufProxy
|
||||||
recording.dispatch(
|
recording.dispatch(
|
||||||
shaders.fine,
|
shaders.fine,
|
||||||
(config.width_in_tiles, config.height_in_tiles, 1),
|
(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;
|
let download_buf = out_buf;
|
||||||
|
|
|
@ -255,6 +255,7 @@ pub fn full_shaders(device: &Device, engine: &mut Engine) -> Result<FullShaders,
|
||||||
BindType::BufReadOnly,
|
BindType::BufReadOnly,
|
||||||
BindType::Buffer,
|
BindType::Buffer,
|
||||||
BindType::BufReadOnly,
|
BindType::BufReadOnly,
|
||||||
|
BindType::ImageRead,
|
||||||
],
|
],
|
||||||
)?;
|
)?;
|
||||||
Ok(FullShaders {
|
Ok(FullShaders {
|
||||||
|
|
|
@ -15,35 +15,70 @@
|
||||||
// Also licensed under MIT license, at your choice.
|
// Also licensed under MIT license, at your choice.
|
||||||
|
|
||||||
use kurbo::BezPath;
|
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;
|
use crate::pico_svg::PicoSvg;
|
||||||
|
|
||||||
pub fn gen_test_scene() -> Scene {
|
pub fn gen_test_scene() -> Scene {
|
||||||
let mut scene = Scene::default();
|
let mut scene = Scene::default();
|
||||||
let mut builder = SceneBuilder::for_scene(&mut scene);
|
let mut builder = SceneBuilder::for_scene(&mut scene);
|
||||||
if false {
|
let scene_ix = 1;
|
||||||
let path = [
|
match scene_ix {
|
||||||
PathElement::MoveTo(Point::new(100.0, 100.0)),
|
0 => {
|
||||||
PathElement::LineTo(Point::new(500.0, 120.0)),
|
let path = [
|
||||||
PathElement::LineTo(Point::new(300.0, 150.0)),
|
PathElement::MoveTo(Point::new(100.0, 100.0)),
|
||||||
PathElement::LineTo(Point::new(200.0, 260.0)),
|
PathElement::LineTo(Point::new(500.0, 120.0)),
|
||||||
PathElement::LineTo(Point::new(150.0, 210.0)),
|
PathElement::LineTo(Point::new(300.0, 150.0)),
|
||||||
PathElement::Close,
|
PathElement::LineTo(Point::new(200.0, 260.0)),
|
||||||
];
|
PathElement::LineTo(Point::new(150.0, 210.0)),
|
||||||
let brush = Brush::Solid(Color::rgb8(0x40, 0x40, 0xff));
|
PathElement::Close,
|
||||||
builder.fill(Fill::NonZero, Affine::IDENTITY, &brush, None, &path);
|
];
|
||||||
let transform = Affine::translate(50.0, 50.0);
|
let brush = Brush::Solid(Color::rgb8(0x40, 0x40, 0xff));
|
||||||
let brush = Brush::Solid(Color::rgba8(0xff, 0xff, 0x00, 0x80));
|
builder.fill(Fill::NonZero, Affine::IDENTITY, &brush, None, &path);
|
||||||
builder.fill(Fill::NonZero, transform, &brush, None, &path);
|
let transform = Affine::translate(50.0, 50.0);
|
||||||
let transform = Affine::translate(100.0, 100.0);
|
let brush = Brush::Solid(Color::rgba8(0xff, 0xff, 0x00, 0x80));
|
||||||
let style = simple_stroke(1.0);
|
builder.fill(Fill::NonZero, transform, &brush, None, &path);
|
||||||
let brush = Brush::Solid(Color::rgb8(0xa0, 0x00, 0x00));
|
let transform = Affine::translate(100.0, 100.0);
|
||||||
builder.stroke(&style, transform, &brush, None, &path);
|
let style = simple_stroke(1.0);
|
||||||
} else {
|
let brush = Brush::Solid(Color::rgb8(0xa0, 0x00, 0x00));
|
||||||
let xml_str = std::str::from_utf8(include_bytes!("../../piet-gpu/Ghostscript_Tiger.svg")).unwrap();
|
builder.stroke(&style, transform, &brush, None, &path);
|
||||||
let svg = PicoSvg::load(xml_str, 6.0).unwrap();
|
}
|
||||||
render_svg(&mut builder, &svg, false);
|
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
|
scene
|
||||||
}
|
}
|
||||||
|
@ -94,7 +129,6 @@ fn convert_bez_path<'a>(path: &'a BezPath) -> impl Iterator<Item = PathElement>
|
||||||
.map(|el| PathElement::from_kurbo(*el))
|
.map(|el| PathElement::from_kurbo(*el))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn simple_stroke(width: f32) -> Stroke<[f32; 0]> {
|
fn simple_stroke(width: f32) -> Stroke<[f32; 0]> {
|
||||||
Stroke {
|
Stroke {
|
||||||
width,
|
width,
|
||||||
|
|
Loading…
Reference in a new issue