Skip to content

Commit

Permalink
Add rasterize()
Browse files Browse the repository at this point in the history
  • Loading branch information
yutannihilation committed Oct 27, 2024
1 parent b2e30fe commit 95fa5e5
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 56 deletions.
10 changes: 9 additions & 1 deletion src/rust/vellogd-shared/src/winit_app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -720,8 +720,16 @@ impl<'a, T: AppResponseRelay> ApplicationHandler<Request> for VelloApp<'a, T> {

self.needs_redraw.store(true, Ordering::Relaxed);
}

// Note: this doesn't relates to window, so it might be possible to
// do this off-screen rendering outside of VelloApp. I'm not sure if
// it's feasible, though.
Request::SaveAsPng { filename } => {
self.save_as_png(filename);
let width = self.width.load(Ordering::Relaxed);
let height = self.height.load(Ordering::Relaxed);

// TODO: handle error
let _ = self.save_as_png(filename, width, height);
}

// ignore other events
Expand Down
119 changes: 64 additions & 55 deletions src/rust/vellogd-shared/src/winit_app/wgpu_util.rs
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
use std::sync::atomic::Ordering;
use std::{num::NonZeroUsize, sync::atomic::Ordering};

use crate::protocol::AppResponseRelay;

use super::{RenderState, VelloApp};
use super::VelloApp;
use peniko::Color;
use vello::{
util::RenderSurface,
wgpu::{
Buffer, Device, Extent3d, ImageCopyBuffer, ImageDataLayout, Queue, Texture,
TextureDescriptor, TextureDimension, TextureFormat, TextureUsages, TextureView,
TextureViewDescriptor,
Buffer, Device, Extent3d, ImageCopyBuffer, ImageDataLayout, Texture, TextureDescriptor,
TextureDimension, TextureFormat, TextureUsages, TextureViewDescriptor,
},
Renderer, Scene,
RenderParams, RendererOptions, Scene,
};

pub fn create_texture(device: &Device, size: Extent3d) -> Texture {
Expand All @@ -27,34 +25,7 @@ pub fn create_texture(device: &Device, size: Extent3d) -> Texture {
})
}

pub fn render_to_texture(
device: &Device,
queue: &Queue,
surface: &RenderSurface,
view: &TextureView,
renderer: &mut Renderer,
scene: &Scene,
base_color: Color,
) {
// TODO: handle error
let _ = renderer.render_to_texture(
device,
queue,
scene,
view,
&vello::RenderParams {
base_color,
width: surface.config.width,
height: surface.config.height,
antialiasing_method: vello::AaConfig::Area,
},
);
}

pub fn create_buffer(device: &Device, surface: &RenderSurface) -> (Buffer, u32) {
let width = surface.config.width;
let height = surface.config.height;

pub fn create_buffer(device: &Device, width: u32, height: u32) -> (Buffer, u32) {
let padded_byte_width = (width * 4).next_multiple_of(256);
let buffer_size = padded_byte_width as u64 * height as u64;

Expand All @@ -71,19 +42,26 @@ pub fn create_buffer(device: &Device, surface: &RenderSurface) -> (Buffer, u32)
impl<'a, T: AppResponseRelay> VelloApp<'a, T> {
// This implementation is is based on
// https://github.com/linebender/vello/blob/main/examples/headless/src/main.rs
pub fn save_as_png(&mut self, filename: String) {
let RenderState::Active(render_state) = &mut self.state else {
return;
};

let surface = &render_state.surface;
let width = surface.config.width;
let height = surface.config.height;

let Some(renderer) = self.renderers[surface.dev_id].as_mut() else {
return;
};
let device_handle = &self.context.devices[surface.dev_id];
pub fn rasterize(
&mut self,
scene: &Scene,
width: u32,
height: u32,
) -> Result<Vec<u8>, vello::Error> {
let dev_id = pollster::block_on(async { self.context.device(None).await }).unwrap();
let device_handle = &self.context.devices[dev_id];

// TODO: move to app's field
let mut renderer = vello::Renderer::new(
&device_handle.device,
RendererOptions {
surface_format: None,
use_cpu: false,
antialiasing_support: vello::AaSupport::area_only(),
num_init_threads: NonZeroUsize::new(1),
},
)
.unwrap();

let size = Extent3d {
width,
Expand All @@ -101,17 +79,20 @@ impl<'a, T: AppResponseRelay> VelloApp<'a, T> {
Color::rgba8(r, g, b, a)
};

render_to_texture(
renderer.render_to_texture(
&device_handle.device,
&device_handle.queue,
surface,
scene,
&view,
renderer,
&self.scene.scene(),
base_color,
);
&RenderParams {
base_color,
width,
height,
antialiasing_method: vello::AaConfig::Area,
},
)?;

let (buffer, padded_byte_width) = create_buffer(&device_handle.device, surface);
let (buffer, padded_byte_width) = create_buffer(&device_handle.device, width, height);
let mut encoder =
device_handle
.device
Expand Down Expand Up @@ -151,12 +132,40 @@ impl<'a, T: AppResponseRelay> VelloApp<'a, T> {
let start = (row * padded_byte_width).try_into().unwrap();
result_unpadded.extend(&data[start..start + (width * 4) as usize]);
}

Ok(result_unpadded)
}

pub fn save_as_png(
&mut self,
filename: String,
width: u32,
height: u32,
) -> Result<(), vello::Error> {
// TODO: in theory, this doesn't need clone(). However, if I put the
// scene directly to self.rasterize(), the borrow checker gives the
// following error:
//
// error[E0502]: cannot borrow `*self` as mutable because it is also borrowed as immutable
//
// Assuming writing to PNG is not called so frequently, I think this
// won't affect the performance much. Also, probably it's a good idea
// not to block the main rendering to screen by this.
//
// cf.
// https://smallcultfollowing.com/babysteps/blog/2018/11/01/after-nll-interprocedural-conflicts/
let scene = self.scene.scene().clone();

let result_unpadded = self.rasterize(&scene, width, height)?;

let mut file = std::fs::File::create(&filename).unwrap();
let mut encoder = png::Encoder::new(&mut file, width, height);
encoder.set_color(png::ColorType::Rgba);
encoder.set_depth(png::BitDepth::Eight);
let mut writer = encoder.write_header().unwrap();
writer.write_image_data(&result_unpadded).unwrap();
writer.finish().unwrap();

Ok(())
}
}

0 comments on commit 95fa5e5

Please sign in to comment.