Libify piet-wgsl

This creates a new Renderer type that offers fairly simple render_to_texture and render_to_surface methods, the latter of which handles the final blit internally. Also adds a util module with some helpers for device and surface creation.

There is a new winit example in piet-wgsl/examples/winit that shows how to make use of it all. This should be fairly trivial to adapt to glazier/xilem.
This commit is contained in:
Chad Brokaw 2022-11-26 14:27:19 -05:00
parent 30af6704ed
commit 016f9de05f
21 changed files with 710 additions and 810 deletions

64
Cargo.lock generated
View file

@ -19,15 +19,6 @@ dependencies = [
"version_check",
]
[[package]]
name = "aho-corasick"
version = "0.7.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4f55bd91a0978cbfd91c457a164bab8b4001c833b7f323132c0a4e1922dd44e"
dependencies = [
"memchr",
]
[[package]]
name = "android_system_properties"
version = "0.1.5"
@ -506,19 +497,6 @@ dependencies = [
"wio",
]
[[package]]
name = "env_logger"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c90bf5f19754d10198ccb95b70664fc925bd1fc090a0fd9a6ebc54acc8cd6272"
dependencies = [
"atty",
"humantime",
"log",
"regex",
"termcolor",
]
[[package]]
name = "expat-sys"
version = "2.1.6"
@ -756,12 +734,6 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
[[package]]
name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
[[package]]
name = "ident_case"
version = "1.0.1"
@ -1236,7 +1208,7 @@ dependencies = [
"raw-window-handle 0.3.4",
"raw-window-handle 0.5.0",
"roxmltree",
"winit",
"winit 0.27.5",
]
[[package]]
@ -1303,15 +1275,11 @@ name = "piet-wgsl"
version = "0.1.0"
dependencies = [
"bytemuck",
"env_logger",
"futures-intrusive",
"parking_lot",
"piet-scene",
"png",
"pollster",
"roxmltree",
"raw-window-handle 0.5.0",
"wgpu",
"winit",
]
[[package]]
@ -1520,23 +1488,6 @@ dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c4eb3267174b8c6c2f654116623910a0fef09c4753f8dd83db29c48a0df988b"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244"
[[package]]
name = "remove_dir_all"
version = "0.5.3"
@ -2251,6 +2202,17 @@ version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40009d85759725a34da6d89a94e63d7bdc50a862acf0dbc7c8e488f1edcb6f5"
[[package]]
name = "winit"
version = "0.1.0"
dependencies = [
"piet-scene",
"piet-wgsl",
"pollster",
"roxmltree",
"winit 0.27.5",
]
[[package]]
name = "winit"
version = "0.27.5"

View file

@ -9,6 +9,7 @@ members = [
"piet-gpu-types",
"piet-scene",
"piet-wgsl",
"piet-wgsl/examples/winit",
"tests",
]

View file

@ -82,7 +82,7 @@ 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!("../Ghostscript_Tiger.svg")).unwrap();
let xml_str = std::str::from_utf8(include_bytes!("../../piet-wgsl/examples/assets/Ghostscript_Tiger.svg")).unwrap();
let start = std::time::Instant::now();
let svg = PicoSvg::load(xml_str, 8.0).unwrap();
if print_stats {

View file

@ -22,7 +22,7 @@ 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!("../third-party/Roboto-Regular.ttf");
const FONT_DATA: &[u8] = include_bytes!("../../piet-wgsl/examples/assets/third-party/Roboto-Regular.ttf");
pub struct SimpleText {
gcx: GlyphContext,

View file

@ -7,17 +7,8 @@ edition = "2021"
[dependencies]
wgpu = "0.14"
env_logger = "0.9.1"
pollster = "0.2.5"
raw-window-handle = "0.5"
futures-intrusive = "0.5.0"
parking_lot = "0.12"
bytemuck = { version = "1.12.1", features = ["derive"] }
png = "0.17.6"
piet-scene = { path = "../piet-scene" }
# for picosvg, should be split out
roxmltree = "0.13"
# move this to an example
winit = "0.27.5"

View file

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

View file

@ -0,0 +1,14 @@
[package]
name = "winit"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
piet-wgsl = { path = "../../../piet-wgsl" }
piet-scene = { path = "../../../piet-scene" }
winit = "0.27.5"
pollster = "0.2.5"
# for picosvg
roxmltree = "0.13"

View file

@ -0,0 +1,106 @@
// 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.
mod pico_svg;
mod simple_text;
mod test_scene;
use piet_scene::{Scene, SceneBuilder};
use piet_wgsl::{util::RenderContext, Renderer, Result};
async fn run() -> Result<()> {
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 render_cx = RenderContext::new().await?;
let size = window.inner_size();
let mut surface = render_cx.create_surface(&window, size.width, size.height);
let mut renderer = Renderer::new(&render_cx.device)?;
let mut simple_text = simple_text::SimpleText::new();
let mut current_frame = 0usize;
let mut scene_ix = 0usize;
let mut scene = Scene::default();
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) => {
render_cx.resize_surface(&mut surface, size.width, size.height);
window.request_redraw();
}
_ => {}
},
Event::MainEventsCleared => {
window.request_redraw();
}
Event::RedrawRequested(_) => {
current_frame += 1;
let width = surface.config.width;
let height = surface.config.height;
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 surface_texture = surface
.surface
.get_current_texture()
.expect("failed to get surface texture");
renderer
.render_to_surface(
&render_cx.device,
&render_cx.queue,
&scene,
&surface_texture,
width,
height,
)
.expect("failed to render to surface");
surface_texture.present();
}
_ => {}
});
}
fn main() {
pollster::block_on(run()).unwrap();
}

View file

@ -22,7 +22,7 @@ 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");
const FONT_DATA: &[u8] = include_bytes!("../../assets/third-party/Roboto-Regular.ttf");
pub struct SimpleText {
gcx: GlyphContext,

View file

@ -1,9 +1,8 @@
use super::PicoSvg;
use crate::pico_svg::PicoSvg;
use crate::simple_text::SimpleText;
use piet_scene::kurbo::{Affine, BezPath, Ellipse, PathEl, Point, Rect};
use piet_scene::*;
use crate::SimpleText;
pub fn render_funky_paths(sb: &mut SceneBuilder) {
use PathEl::*;
let missing_movetos = [
@ -83,7 +82,7 @@ pub fn render_svg(sb: &mut SceneBuilder, svg: &PicoSvg, print_stats: bool) {
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();
std::str::from_utf8(include_bytes!("../../assets/Ghostscript_Tiger.svg")).unwrap();
let start = std::time::Instant::now();
let svg = PicoSvg::load(xml_str, 6.0).unwrap();
if print_stats {

View file

@ -1,5 +0,0 @@
#![allow(dead_code)]
pub mod clip;
pub mod draw;
pub mod fine;

View file

@ -1,13 +0,0 @@
use bytemuck::{Pod, Zeroable};
#[derive(Copy, Clone, Debug, Zeroable, Pod)]
#[repr(C)]
pub struct ClipEl {
pub parent_ix: u32,
pub pad: [u32; 3],
pub bbox: [f32; 4],
}
pub fn parse_clip_els(data: &[u8]) -> Vec<ClipEl> {
Vec::from(bytemuck::cast_slice(data))
}

View file

@ -1,14 +0,0 @@
use bytemuck::{Pod, Zeroable};
#[derive(Copy, Clone, Debug, Zeroable, Pod)]
#[repr(C)]
pub struct DrawMonoid {
pub path_ix: u32,
pub clip_ix: u32,
pub scene_offset: u32,
pub info_offset: u32,
}
pub fn parse_draw_monoids(data: &[u8]) -> Vec<DrawMonoid> {
Vec::from(bytemuck::cast_slice(data))
}

View file

@ -1,153 +0,0 @@
#[derive(Copy, Clone, Debug)]
#[repr(C)]
pub struct Fill {
pub tile: u32,
pub backdrop: i32,
}
#[derive(Copy, Clone, Debug)]
#[repr(C)]
pub struct Stroke {
pub tile: u32,
pub half_width: f32,
}
#[derive(Copy, Clone, Debug)]
#[repr(C)]
pub struct Color {
abgr: [u8; 4],
}
#[derive(Copy, Clone, Debug)]
#[repr(C)]
pub struct LinGrad {
pub index: u32,
pub line_x: f32,
pub line_y: f32,
pub line_c: f32,
}
#[derive(Copy, Clone, Debug)]
#[repr(C)]
pub struct RadGrad {
pub index: u32,
pub matrix: [f32; 4],
pub xlat: [f32; 2],
pub c1: [f32; 2],
pub ra: f32,
pub roff: f32,
}
#[derive(Copy, Clone, Debug)]
pub enum Command {
Fill(Fill),
Stroke(Stroke),
Solid,
Color(Color),
LinGrad(LinGrad),
RadGrad(RadGrad),
BeginClip,
EndClip(u32),
End,
}
const PTCL_INITIAL_ALLOC: usize = 64;
#[derive(Debug)]
pub struct CommandList {
pub tiles: Vec<(u32, u32, Vec<Command>)>,
}
impl CommandList {
pub fn parse(width: usize, height: usize, ptcl: &[u8]) -> Self {
let mut tiles = vec![];
let width_tiles = width / 16;
let height_tiles = height / 16;
for y in 0..height_tiles {
for x in 0..width_tiles {
let tile_ix = y * width_tiles + x;
let ix = tile_ix * PTCL_INITIAL_ALLOC;
let commands = parse_commands(ptcl, ix);
if !commands.is_empty() {
tiles.push((x as u32, y as u32, commands));
}
}
}
Self { tiles }
}
}
fn parse_commands(ptcl: &[u8], mut ix: usize) -> Vec<Command> {
let mut commands = vec![];
let words: &[u32] = bytemuck::cast_slice(ptcl);
while ix < words.len() {
let tag = words[ix];
ix += 1;
match tag {
0 => break,
1 => {
commands.push(Command::Fill(Fill {
tile: words[ix],
backdrop: words[ix + 1] as i32,
}));
ix += 2;
}
2 => {
commands.push(Command::Stroke(Stroke {
tile: words[ix],
half_width: bytemuck::cast(words[ix + 1]),
}));
ix += 2;
}
3 => {
commands.push(Command::Solid);
}
5 => {
commands.push(Command::Color(Color {
abgr: bytemuck::cast(words[ix]),
}));
ix += 1;
}
6 => {
commands.push(Command::LinGrad(LinGrad {
index: words[ix],
line_x: bytemuck::cast(words[ix + 1]),
line_y: bytemuck::cast(words[ix + 2]),
line_c: bytemuck::cast(words[ix + 3]),
}));
ix += 4;
}
7 => {
let matrix = [
bytemuck::cast(words[ix + 1]),
bytemuck::cast(words[ix + 2]),
bytemuck::cast(words[ix + 3]),
bytemuck::cast(words[ix + 4]),
];
let xlat = [bytemuck::cast(words[ix + 5]), bytemuck::cast(words[ix + 6])];
let c1 = [bytemuck::cast(words[ix + 7]), bytemuck::cast(words[ix + 8])];
commands.push(Command::RadGrad(RadGrad {
index: words[ix],
matrix,
xlat,
c1,
ra: bytemuck::cast(words[ix + 9]),
roff: bytemuck::cast(words[ix + 10]),
}));
ix += 11;
}
9 => {
commands.push(Command::BeginClip);
}
10 => {
commands.push(Command::EndClip(words[ix]));
ix += 1;
}
11 => {
ix = words[ix] as usize;
}
_ => {}
}
}
commands
}

274
piet-wgsl/src/lib.rs Normal file
View file

@ -0,0 +1,274 @@
// 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.
mod engine;
mod ramp;
mod render;
mod shaders;
pub mod util;
use engine::{Engine, ExternalResource};
use shaders::FullShaders;
use piet_scene::Scene;
use wgpu::{Device, Queue, SurfaceTexture, Texture, TextureFormat, TextureView};
/// Catch-all error type.
pub type Error = Box<dyn std::error::Error>;
/// Specialization of `Result` for our catch-all error type.
pub type Result<T> = std::result::Result<T, Error>;
/// Renders a scene into a texture or surface.
pub struct Renderer {
engine: Engine,
shaders: FullShaders,
blit: BlitPipeline,
target: Option<TargetTexture>,
}
impl Renderer {
/// Creates a new renderer for the specified device.
pub fn new(device: &Device) -> Result<Self> {
let mut engine = Engine::new();
let shaders = shaders::full_shaders(device, &mut engine)?;
let blit = BlitPipeline::new(device, TextureFormat::Bgra8Unorm);
Ok(Self {
engine,
shaders,
blit,
target: None,
})
}
/// Renders a scene to the target texture.
///
/// The texture is assumed to be of the specified dimensions and have been created with
/// the [wgpu::TextureFormat::Rgba8Unorm] format and the [wgpu::TextureUsages::STORAGE_BINDING]
/// flag set.
pub fn render_to_texture(
&mut self,
device: &Device,
queue: &Queue,
scene: &Scene,
texture: &TextureView,
width: u32,
height: u32,
) -> Result<()> {
let (recording, target) = render::render_full(&scene, &self.shaders, width, height);
let external_resources = [ExternalResource::Image(
*target.as_image().unwrap(),
texture,
)];
let _ = self
.engine
.run_recording(device, queue, &recording, &external_resources)?;
Ok(())
}
/// Renders a scene to the target surface.
///
/// This renders to an intermediate texture and then runs a render pass to blit to the
/// specified surface texture.
///
/// The surface is assumed to be of the specified dimensions and have been created with the
/// [wgpu::TextureFormat::Bgra8Unorm] format.
pub fn render_to_surface(
&mut self,
device: &Device,
queue: &Queue,
scene: &Scene,
surface: &SurfaceTexture,
width: u32,
height: u32,
) -> Result<()> {
let mut target = self
.target
.take()
.unwrap_or_else(|| TargetTexture::new(device, width, height));
// TODO: implement clever resizing semantics here to avoid thrashing the memory allocator
// during resize, specifically on metal.
if target.width != width || target.height != height {
target = TargetTexture::new(device, width, height);
}
self.render_to_texture(device, queue, scene, &target.view, width, height)?;
let mut encoder =
device.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
{
let surface_view = surface
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: &&self.blit.bind_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(&self.blit.pipeline);
render_pass.set_bind_group(0, &bind_group, &[]);
render_pass.draw(0..6, 0..1);
}
queue.submit(Some(encoder.finish()));
self.target = Some(target);
Ok(())
}
}
struct TargetTexture {
texture: Texture,
view: TextureView,
width: u32,
height: u32,
}
impl TargetTexture {
pub fn new(device: &Device, width: u32, height: u32) -> Self {
let texture = device.create_texture(&wgpu::TextureDescriptor {
label: None,
size: wgpu::Extent3d {
width,
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());
Self {
texture,
view,
width,
height,
}
}
}
struct BlitPipeline {
bind_layout: wgpu::BindGroupLayout,
pipeline: wgpu::RenderPipeline,
}
impl BlitPipeline {
fn new(device: &Device, format: TextureFormat) -> Self {
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 = device.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("blit shaders"),
source: wgpu::ShaderSource::Wgsl(SHADERS.into()),
});
let bind_layout = 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 = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
bind_group_layouts: &[&bind_layout],
push_constant_ranges: &[],
});
let pipeline = 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,
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,
});
Self {
bind_layout,
pipeline,
}
}
}

View file

@ -1,348 +0,0 @@
// 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.
//! A simple application to run a compute shader.
use engine::{Engine, Error, ExternalResource};
use piet_scene::{Scene, SceneBuilder};
use wgpu::{Device, Instance, Limits, Queue, Surface, SurfaceConfiguration};
use winit::window::Window;
mod debug;
mod engine;
mod pico_svg;
mod ramp;
mod render;
mod shaders;
mod simple_text;
mod test_scene;
use pico_svg::PicoSvg;
use simple_text::SimpleText;
pub struct Dimensions {
width: u32,
height: u32,
}
pub struct WgpuState {
pub instance: Instance,
pub device: Device,
pub queue: Queue,
pub surface: Option<Surface>,
pub surface_config: SurfaceConfiguration,
}
impl WgpuState {
pub async fn new(window: Option<&Window>) -> Result<Self, Box<dyn std::error::Error>> {
let instance = Instance::new(wgpu::Backends::PRIMARY);
let adapter = instance.request_adapter(&Default::default()).await.unwrap();
let features = adapter.features();
let mut limits = Limits::default();
limits.max_storage_buffers_per_shader_stage = 16;
let (device, queue) = adapter
.request_device(
&wgpu::DeviceDescriptor {
label: None,
features: features
& (wgpu::Features::TIMESTAMP_QUERY | wgpu::Features::CLEAR_TEXTURE),
limits,
},
None,
)
.await?;
let (surface, surface_config) = if let Some(window) = window {
let surface = unsafe { instance.create_surface(window) };
let size = window.inner_size();
// let format = surface.get_supported_formats(&adapter)[0];
let format = wgpu::TextureFormat::Bgra8Unorm;
println!("surface: {:?} {:?}", size, format);
let surface_config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format,
width: size.width,
height: size.height,
present_mode: wgpu::PresentMode::Fifo,
alpha_mode: wgpu::CompositeAlphaMode::Auto,
};
surface.configure(&device, &surface_config);
(Some(surface), surface_config)
} else {
let surface_config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::empty(),
format: wgpu::TextureFormat::Bgra8Unorm,
width: 0,
height: 0,
present_mode: wgpu::PresentMode::Fifo,
alpha_mode: wgpu::CompositeAlphaMode::Auto,
};
(None, surface_config)
};
Ok(Self {
instance,
device,
queue,
surface,
surface_config,
})
}
pub fn create_target_texture(&self) -> (wgpu::Texture, wgpu::TextureView) {
let texture = self.device.create_texture(&wgpu::TextureDescriptor {
label: None,
size: wgpu::Extent3d {
width: self.surface_config.width,
height: self.surface_config.height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING,
format: wgpu::TextureFormat::Rgba8Unorm,
});
let view = texture.create_view(&wgpu::TextureViewDescriptor::default());
(texture, view)
}
}
async fn run_interactive() -> Result<(), Error> {
use winit::{
dpi::LogicalSize,
event::*,
event_loop::{ControlFlow, EventLoop},
window::WindowBuilder,
};
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_inner_size(LogicalSize::new(1044, 800))
.with_resizable(true)
.build(&event_loop)
.unwrap();
let mut state = WgpuState::new(Some(&window)).await?;
let mut engine = Engine::new();
let full_shaders = shaders::full_shaders(&state.device, &mut engine)?;
let (blit_layout, blit_pipeline) = create_blit_pipeline(&state);
let mut simple_text = SimpleText::new();
let mut current_frame = 0usize;
let mut scene_ix = 0usize;
let (mut _target_texture, mut target_view) = state.create_target_texture();
event_loop.run(move |event, _, control_flow| match event {
Event::WindowEvent {
ref event,
window_id,
} if window_id == window.id() => match event {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::KeyboardInput { input, .. } => {
if input.state == ElementState::Pressed {
match input.virtual_keycode {
Some(VirtualKeyCode::Left) => scene_ix = scene_ix.saturating_sub(1),
Some(VirtualKeyCode::Right) => scene_ix = scene_ix.saturating_add(1),
_ => {}
}
}
}
WindowEvent::Resized(size) => {
state.surface_config.width = size.width;
state.surface_config.height = size.height;
state
.surface
.as_ref()
.unwrap()
.configure(&state.device, &state.surface_config);
let (t, v) = state.create_target_texture();
_target_texture = t;
target_view = v;
window.request_redraw();
}
_ => {}
},
Event::MainEventsCleared => {
window.request_redraw();
}
Event::RedrawRequested(_) => {
current_frame += 1;
let surface_texture = state
.surface
.as_ref()
.unwrap()
.get_current_texture()
.unwrap();
let dimensions = Dimensions {
width: state.surface_config.width,
height: state.surface_config.height,
};
let mut scene = Scene::default();
let mut builder = SceneBuilder::for_scene(&mut scene);
const N_SCENES: usize = 6;
match scene_ix % N_SCENES {
0 => test_scene::render_anim_frame(&mut builder, &mut simple_text, current_frame),
1 => test_scene::render_blend_grid(&mut builder),
2 => test_scene::render_tiger(&mut builder, false),
3 => test_scene::render_brush_transform(&mut builder, current_frame),
4 => test_scene::render_funky_paths(&mut builder),
_ => test_scene::render_scene(&mut builder),
}
builder.finish();
let (recording, target) = render::render_full(&scene, &full_shaders, &dimensions);
let external_resources = [ExternalResource::Image(
*target.as_image().unwrap(),
&target_view,
)];
let _ = engine
.run_recording(&state.device, &state.queue, &recording, &external_resources)
.unwrap();
let mut encoder = state
.device
.create_command_encoder(&wgpu::CommandEncoderDescriptor { label: None });
{
let surface_view = surface_texture
.texture
.create_view(&wgpu::TextureViewDescriptor::default());
let bind_group = state.device.create_bind_group(&wgpu::BindGroupDescriptor {
label: None,
layout: &blit_layout,
entries: &[wgpu::BindGroupEntry {
binding: 0,
resource: wgpu::BindingResource::TextureView(&target_view),
}],
});
let mut render_pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
label: None,
color_attachments: &[Some(wgpu::RenderPassColorAttachment {
view: &surface_view,
resolve_target: None,
ops: wgpu::Operations {
load: wgpu::LoadOp::Clear(wgpu::Color::default()),
store: true,
},
})],
depth_stencil_attachment: None,
});
render_pass.set_pipeline(&blit_pipeline);
render_pass.set_bind_group(0, &bind_group, &[]);
render_pass.draw(0..6, 0..1);
}
state.queue.submit(Some(encoder.finish()));
surface_texture.present();
}
_ => {}
});
}
fn main() {
pollster::block_on(run_interactive()).unwrap();
}
// Fit this into the recording code somehow?
fn create_blit_pipeline(state: &WgpuState) -> (wgpu::BindGroupLayout, wgpu::RenderPipeline) {
const SHADERS: &str = r#"
@vertex
fn vs_main(@builtin(vertex_index) ix: u32) -> @builtin(position) vec4<f32> {
// Generate a full screen quad in NDCs
var vertex = vec2<f32>(-1.0, 1.0);
switch ix {
case 1u: {
vertex = vec2<f32>(-1.0, -1.0);
}
case 2u, 4u: {
vertex = vec2<f32>(1.0, -1.0);
}
case 5u: {
vertex = vec2<f32>(1.0, 1.0);
}
default: {}
}
return vec4<f32>(vertex, 0.0, 1.0);
}
@group(0) @binding(0)
var fine_output: texture_2d<f32>;
@fragment
fn fs_main(@builtin(position) pos: vec4<f32>) -> @location(0) vec4<f32> {
return textureLoad(fine_output, vec2<i32>(pos.xy), 0);
}
"#;
let shader = state
.device
.create_shader_module(wgpu::ShaderModuleDescriptor {
label: Some("blit shaders"),
source: wgpu::ShaderSource::Wgsl(SHADERS.into()),
});
let bind_group_layout =
state
.device
.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
label: None,
entries: &[wgpu::BindGroupLayoutEntry {
visibility: wgpu::ShaderStages::FRAGMENT,
binding: 0,
ty: wgpu::BindingType::Texture {
sample_type: wgpu::TextureSampleType::Float { filterable: true },
view_dimension: wgpu::TextureViewDimension::D2,
multisampled: false,
},
count: None,
}],
});
let pipeline_layout = state
.device
.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
label: None,
bind_group_layouts: &[&bind_group_layout],
push_constant_ranges: &[],
});
let pipeline = state
.device
.create_render_pipeline(&wgpu::RenderPipelineDescriptor {
label: None,
layout: Some(&pipeline_layout),
vertex: wgpu::VertexState {
module: &shader,
entry_point: "vs_main",
buffers: &[],
},
fragment: Some(wgpu::FragmentState {
module: &shader,
entry_point: "fs_main",
targets: &[Some(wgpu::ColorTargetState {
format: state.surface_config.format,
blend: None,
write_mask: wgpu::ColorWrites::ALL,
})],
}),
primitive: wgpu::PrimitiveState {
topology: wgpu::PrimitiveTopology::TriangleList,
strip_index_format: None,
front_face: wgpu::FrontFace::Ccw,
cull_mode: Some(wgpu::Face::Back),
polygon_mode: wgpu::PolygonMode::Fill,
unclipped_depth: false,
conservative: false,
},
depth_stencil: None,
multisample: wgpu::MultisampleState {
count: 1,
mask: !0,
alpha_to_coverage_enabled: false,
},
multiview: None,
});
(bind_group_layout, pipeline)
}

View file

@ -6,7 +6,6 @@ use piet_scene::Scene;
use crate::{
engine::{BufProxy, ImageFormat, ImageProxy, Recording, ResourceProxy},
shaders::{self, FullShaders, Shaders},
Dimensions,
};
const TAG_MONOID_SIZE: u64 = 12;
@ -62,6 +61,7 @@ pub const fn next_multiple_of(val: u32, rhs: u32) -> u32 {
}
}
#[allow(unused)]
fn render(scene: &Scene, shaders: &Shaders) -> (Recording, BufProxy) {
let mut recording = Recording::default();
let data = scene.data();
@ -140,7 +140,8 @@ fn render(scene: &Scene, shaders: &Shaders) -> (Recording, BufProxy) {
pub fn render_full(
scene: &Scene,
shaders: &FullShaders,
dimensions: &Dimensions,
width: u32,
height: u32,
) -> (Recording, ResourceProxy) {
let mut recording = Recording::default();
let mut ramps = crate::ramp::RampCache::default();
@ -209,15 +210,15 @@ pub fn render_full(
let n_drawobj = n_path;
let n_clip = data.n_clip;
let new_width = next_multiple_of(dimensions.width, 16);
let new_height = next_multiple_of(dimensions.height, 16);
let new_width = next_multiple_of(width, 16);
let new_height = next_multiple_of(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,
target_width: width,
target_height: height,
n_drawobj,
n_path,
n_clip,
@ -402,7 +403,7 @@ pub fn render_full(
ptcl_buf,
],
);
let out_image = ImageProxy::new(dimensions.width, dimensions.height, ImageFormat::Rgba8);
let out_image = ImageProxy::new(width, height, ImageFormat::Rgba8);
recording.dispatch(
shaders.fine,
(config.width_in_tiles, config.height_in_tiles, 1),

85
piet-wgsl/src/util.rs Normal file
View file

@ -0,0 +1,85 @@
// 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.
//! Simple helpers for managing wgpu state and surfaces.
use raw_window_handle::{HasRawDisplayHandle, HasRawWindowHandle};
use wgpu::{Device, Instance, Limits, Queue, Surface, SurfaceConfiguration};
/// Simple render context that maintains wgpu state for rendering the pipeline.
pub struct RenderContext {
pub instance: Instance,
pub device: Device,
pub queue: Queue,
}
impl RenderContext {
pub async fn new() -> 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?;
Ok(Self {
instance,
device,
queue,
})
}
/// Creates a new surface for the specified window and dimensions.
pub fn create_surface<W>(&self, window: &W, width: u32, height: u32) -> RenderSurface
where
W: HasRawWindowHandle + HasRawDisplayHandle,
{
let surface = unsafe { self.instance.create_surface(window) };
let format = wgpu::TextureFormat::Bgra8Unorm;
let config = wgpu::SurfaceConfiguration {
usage: wgpu::TextureUsages::RENDER_ATTACHMENT,
format,
width,
height,
present_mode: wgpu::PresentMode::Fifo,
alpha_mode: wgpu::CompositeAlphaMode::Auto,
};
surface.configure(&self.device, &config);
RenderSurface { surface, config }
}
/// Resizes the surface to the new dimensions.
pub fn resize_surface(&self, surface: &mut RenderSurface, width: u32, height: u32) {
surface.config.width = width;
surface.config.height = height;
surface.surface.configure(&self.device, &surface.config);
}
}
/// Combination of surface and its configuration.
pub struct RenderSurface {
pub surface: Surface,
pub config: SurfaceConfiguration,
}