Skip to content

Commit

Permalink
[web-wasm] Make SmelterRenderer safe to use asynchronously
Browse files Browse the repository at this point in the history
  • Loading branch information
noituri committed Feb 7, 2025
1 parent 2bfaa0d commit 3208136
Show file tree
Hide file tree
Showing 2 changed files with 158 additions and 81 deletions.
135 changes: 54 additions & 81 deletions compositor_web/src/wasm.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
use std::sync::Arc;
use std::sync::{Arc, Mutex};

use bytes::Bytes;
use compositor_api::types as api;
use compositor_render::{
image::{ImageSource, ImageType},
InputId, OutputFrameFormat, OutputId, RegistryType, Renderer, RendererId, RendererSpec,
};
use compositor_api::types::{Component, ImageSpec, Resolution};
use compositor_render::image::{ImageSource, ImageType};
use glyphon::fontdb::Source;
use input_uploader::InputUploader;
use output_downloader::OutputDownloader;
use types::to_js_error;
use wasm_bindgen::prelude::*;
use wgpu::create_wgpu_context;

mod input_uploader;
mod output_downloader;
mod renderer;
mod types;
mod wgpu;

Expand All @@ -34,125 +29,103 @@ pub async fn create_renderer(options: JsValue) -> Result<SmelterRenderer, JsValu
let _ = wasm_log::try_init(wasm_log::Config::new(options.logger_level.into()));

let (device, queue) = create_wgpu_context().await?;

let (renderer, _) = Renderer::new(compositor_render::RendererOptions {
wgpu_ctx: Some((device, queue)),
..options.into()
})
.map_err(to_js_error)?;
let input_uploader = InputUploader::default();
let output_downloader = OutputDownloader::default();

Ok(SmelterRenderer {
renderer,
input_uploader,
output_downloader,
})
let renderer = renderer::Renderer::new(device, queue, options.into())?;
Ok(SmelterRenderer(Mutex::new(renderer)))
}

#[wasm_bindgen]
pub struct SmelterRenderer {
renderer: Renderer,
input_uploader: InputUploader,
output_downloader: OutputDownloader,
}
pub struct SmelterRenderer(Mutex<renderer::Renderer>);

#[wasm_bindgen]
impl SmelterRenderer {
pub fn render(&mut self, input: types::FrameSet) -> Result<types::FrameSet, JsValue> {
let (device, queue) = self.renderer.wgpu_ctx();
let frame_set = self.input_uploader.upload(&device, &queue, input)?;

let outputs = self.renderer.render(frame_set).map_err(to_js_error)?;
self.output_downloader
.download_outputs(&device, &queue, outputs)
pub fn render(&self, input: types::FrameSet) -> Result<types::FrameSet, JsValue> {
let mut renderer = self.0.lock().unwrap();
renderer.render(input)
}

pub fn update_scene(
&mut self,
&self,
output_id: String,
resolution: JsValue,
scene: JsValue,
) -> Result<(), JsValue> {
let resolution = types::from_js_value::<api::Resolution>(resolution)?;
let scene = types::from_js_value::<api::Component>(scene)?;

self.renderer
.update_scene(
OutputId(output_id.into()),
resolution.into(),
OutputFrameFormat::RgbaWgpuTexture,
scene.try_into().map_err(to_js_error)?,
)
.map_err(to_js_error)
let resolution = types::from_js_value::<Resolution>(resolution)?;
let scene = types::from_js_value::<Component>(scene)?;

let mut renderer = self.0.lock().unwrap();
renderer.update_scene(output_id, resolution, scene)
}

pub fn register_input(&mut self, input_id: String) {
self.renderer.register_input(InputId(input_id.into()));
pub fn register_input(&self, input_id: String) {
let mut renderer = self.0.lock().unwrap();
renderer.register_input(input_id)
}

pub async fn register_image(
&mut self,
&self,
renderer_id: String,
image_spec: JsValue,
) -> Result<(), JsValue> {
let image_spec = types::from_js_value::<api::ImageSpec>(image_spec)?;
let image_spec = types::from_js_value::<ImageSpec>(image_spec)?;
let (url, image_type) = match image_spec {
api::ImageSpec::Png { url, .. } => (url, ImageType::Png),
api::ImageSpec::Jpeg { url, .. } => (url, ImageType::Jpeg),
api::ImageSpec::Svg {
ImageSpec::Png { url, .. } => (url, ImageType::Png),
ImageSpec::Jpeg { url, .. } => (url, ImageType::Jpeg),
ImageSpec::Svg {
url, resolution, ..
} => (
url,
ImageType::Svg {
resolution: resolution.map(Into::into),
},
),
api::ImageSpec::Gif { url, .. } => (url, ImageType::Gif),
ImageSpec::Gif { url, .. } => (url, ImageType::Gif),
};

let Some(url) = url else {
return Err(JsValue::from_str("Expected `url` field in image spec"));
};

let bytes = download(&url).await?;
let renderer_spec = RendererSpec::Image(compositor_render::image::ImageSpec {
src: ImageSource::Bytes { bytes },
image_type,
});
self.renderer
.register_renderer(RendererId(renderer_id.into()), renderer_spec)
.map_err(to_js_error)

let mut renderer = self.0.lock().unwrap();
renderer
.register_image(
renderer_id,
compositor_render::image::ImageSpec {
src: ImageSource::Bytes { bytes },
image_type,
},
)
.await
}

pub async fn register_font(&mut self, font_url: String) -> Result<(), JsValue> {
pub async fn register_font(&self, font_url: String) -> Result<(), JsValue> {
let bytes = download(&font_url).await?;
self.renderer
.register_font(Source::Binary(Arc::new(bytes.to_vec())));
let mut renderer = self.0.lock().unwrap();
renderer
.register_font(Source::Binary(Arc::new(bytes)))
.await;

Ok(())
}

pub fn unregister_input(&mut self, input_id: String) {
let input_id = InputId(input_id.into());
self.renderer.unregister_input(&input_id);
self.input_uploader.remove_input(&input_id);
pub fn unregister_input(&self, input_id: String) {
let mut renderer = self.0.lock().unwrap();
renderer.unregister_input(input_id)
}

pub fn unregister_output(&mut self, output_id: String) {
let output_id = OutputId(output_id.into());
self.renderer.unregister_output(&output_id);
self.output_downloader.remove_output(&output_id);
pub fn unregister_output(&self, output_id: String) {
let mut renderer = self.0.lock().unwrap();
renderer.unregister_output(output_id)
}

pub fn unregister_image(&mut self, renderer_id: String) -> Result<(), JsValue> {
self.renderer
.unregister_renderer(&RendererId(renderer_id.into()), RegistryType::Image)
.map_err(to_js_error)
pub fn unregister_image(&self, renderer_id: String) -> Result<(), JsValue> {
let mut renderer = self.0.lock().unwrap();
renderer.unregister_image(renderer_id)
}
}

async fn download(url: &str) -> Result<Bytes, JsValue> {
let resp = reqwest::get(url).await.map_err(to_js_error)?;
let resp = resp.error_for_status().map_err(to_js_error)?;
resp.bytes().await.map_err(to_js_error)
let resp = reqwest::get(url).await.map_err(types::to_js_error)?;
let resp = resp.error_for_status().map_err(types::to_js_error)?;
resp.bytes().await.map_err(types::to_js_error)
}
104 changes: 104 additions & 0 deletions compositor_web/src/wasm/renderer.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use std::sync::Arc;

use compositor_api::types as api;
use compositor_render::{
image::ImageSpec, InputId, OutputFrameFormat, OutputId, RegistryType, RendererId,
RendererOptions, RendererSpec,
};
use glyphon::fontdb::Source;
use wasm_bindgen::JsValue;

use super::{input_uploader::InputUploader, output_downloader::OutputDownloader, types};

pub(super) struct Renderer {
renderer: compositor_render::Renderer,
input_uploader: InputUploader,
output_downloader: OutputDownloader,
}

impl Renderer {
pub fn new(
device: Arc<wgpu::Device>,
queue: Arc<wgpu::Queue>,
options: RendererOptions,
) -> Result<Self, JsValue> {
let (renderer, _) = compositor_render::Renderer::new(RendererOptions {
wgpu_ctx: Some((device, queue)),
..options
})
.map_err(types::to_js_error)?;
let input_uploader = InputUploader::default();
let output_downloader = OutputDownloader::default();

Ok(Self {
renderer,
input_uploader,
output_downloader,
})
}

pub fn render(&mut self, input: types::FrameSet) -> Result<types::FrameSet, JsValue> {
let (device, queue) = self.renderer.wgpu_ctx();
let frame_set = self.input_uploader.upload(&device, &queue, input)?;

let outputs = self
.renderer
.render(frame_set)
.map_err(types::to_js_error)?;
self.output_downloader
.download_outputs(&device, &queue, outputs)
}

pub fn update_scene(
&mut self,
output_id: String,
resolution: api::Resolution,
scene: api::Component,
) -> Result<(), JsValue> {
self.renderer
.update_scene(
OutputId(output_id.into()),
resolution.into(),
OutputFrameFormat::RgbaWgpuTexture,
scene.try_into().map_err(types::to_js_error)?,
)
.map_err(types::to_js_error)
}

pub fn register_input(&mut self, input_id: String) {
self.renderer.register_input(InputId(input_id.into()));
}

pub async fn register_image(
&mut self,
renderer_id: String,
image_spec: ImageSpec,
) -> Result<(), JsValue> {
let renderer_spec = RendererSpec::Image(image_spec);
self.renderer
.register_renderer(RendererId(renderer_id.into()), renderer_spec)
.map_err(types::to_js_error)
}

pub async fn register_font(&mut self, font: Source) {
self.renderer.register_font(font);
}

pub fn unregister_input(&mut self, input_id: String) {
let input_id = InputId(input_id.into());
self.renderer.unregister_input(&input_id);
self.input_uploader.remove_input(&input_id);
}

pub fn unregister_output(&mut self, output_id: String) {
let output_id = OutputId(output_id.into());
self.renderer.unregister_output(&output_id);
self.output_downloader.remove_output(&output_id);
}

pub fn unregister_image(&mut self, renderer_id: String) -> Result<(), JsValue> {
self.renderer
.unregister_renderer(&RendererId(renderer_id.into()), RegistryType::Image)
.map_err(types::to_js_error)
}
}

0 comments on commit 3208136

Please sign in to comment.