mirror of
https://github.com/italicsjenga/vello.git
synced 2025-01-09 20:31:29 +11:00
wgsl port in realtime
* add writeable image support to engine * add external resources to engine * update fine to output to a texture * copy over original piet-gpu test scenes * put it all in a pretty (resizable!) window
This commit is contained in:
parent
fddd019567
commit
ff4f71ef3c
208
Cargo.lock
generated
208
Cargo.lock
generated
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
81
piet-wgsl/src/simple_text.rs
Normal file
81
piet-wgsl/src/simple_text.rs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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,
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue