diff --git a/.github/workflows/package_for_release.yml b/.github/workflows/package_for_release.yml index 40c45a89c..73cecffee 100644 --- a/.github/workflows/package_for_release.yml +++ b/.github/workflows/package_for_release.yml @@ -25,6 +25,11 @@ jobs: name: video_compositor_linux_x86_64.tar.gz path: video_compositor_linux_x86_64.tar.gz + - uses: actions/upload-artifact@v3 + with: + name: video_compositor_with_web_renderer_linux_x86_64.tar.gz + path: video_compositor_with_web_renderer_linux_x86_64.tar.gz + macos_x86_64: runs-on: macos-latest steps: @@ -44,6 +49,11 @@ jobs: with: name: video_compositor_darwin_x86_64.tar.gz path: video_compositor_darwin_x86_64.tar.gz + + - uses: actions/upload-artifact@v3 + with: + name: video_compositor_with_web_renderer_darwin_x86_64.tar.gz + path: video_compositor_with_web_renderer_darwin_x86_64.tar.gz macos-aarch64: runs-on: macos-latest-xlarge @@ -64,3 +74,8 @@ jobs: with: name: video_compositor_darwin_aarch64.tar.gz path: video_compositor_darwin_aarch64.tar.gz + + - uses: actions/upload-artifact@v3 + with: + name: video_compositor_with_web_renderer_darwin_aarch64.tar.gz + path: video_compositor_with_web_renderer_darwin_aarch64.tar.gz diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml index b1cf7940f..6c704b54b 100644 --- a/.github/workflows/shellcheck.yml +++ b/.github/workflows/shellcheck.yml @@ -25,4 +25,4 @@ jobs: - name: shellcheck run: | - shellcheck ./scripts/release.sh ./scripts/compositor_runtime_wrapper.sh + shellcheck ./scripts/release.sh ./src/bin/package_for_release/linux_runtime_wrapper.sh diff --git a/Cargo.lock b/Cargo.lock index 78be6b4e1..c6b757bbc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -370,7 +370,6 @@ version = "0.1.0" dependencies = [ "anyhow", "bytes", - "compositor_chromium", "compositor_render", "crossbeam-channel", "ffmpeg-next", diff --git a/Cargo.toml b/Cargo.toml index 5d10998d2..8963a8409 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,13 +6,13 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [workspace] -members = [ - "compositor_render", - "compositor_pipeline", - "compositor_chromium", -] +members = ["compositor_render", "compositor_pipeline", "compositor_chromium"] resolver = "2" +[features] +default = ["web_renderer"] +web_renderer = ["dep:compositor_chromium", "compositor_render/web_renderer"] + [workspace.dependencies] bytes = "1.4.0" serde_json = { version = "1.0.99", features = ["preserve_order"] } @@ -31,7 +31,7 @@ rtp = "0.9.0" [dependencies] compositor_render = { path = "compositor_render" } compositor_pipeline = { path = "compositor_pipeline" } -compositor_chromium = { path = "compositor_chromium" } +compositor_chromium = { path = "compositor_chromium", optional = true } serde = { workspace = true } serde_json = { workspace = true } anyhow = { workspace = true } @@ -57,3 +57,8 @@ libc = "0.2.151" [dev-dependencies] reqwest = { workspace = true } + +[[bin]] +name = "process_helper" +path = "src/bin/process_helper/main.rs" +required-features = ["web_renderer"] diff --git a/compositor_pipeline/Cargo.toml b/compositor_pipeline/Cargo.toml index 260aac303..4a4ca9bdc 100644 --- a/compositor_pipeline/Cargo.toml +++ b/compositor_pipeline/Cargo.toml @@ -4,10 +4,8 @@ version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - [dependencies] compositor_render = { path = "../compositor_render" } -compositor_chromium = { path = "../compositor_chromium" } bytes = { workspace = true } crossbeam-channel = { workspace = true } anyhow = "1.0.71" diff --git a/compositor_pipeline/src/pipeline.rs b/compositor_pipeline/src/pipeline.rs index 9aa1056da..09416c5d2 100644 --- a/compositor_pipeline/src/pipeline.rs +++ b/compositor_pipeline/src/pipeline.rs @@ -77,7 +77,7 @@ pub struct Options { } impl Pipeline { - pub fn new(opts: Options) -> Result<(Self, EventLoop), InitRendererEngineError> { + pub fn new(opts: Options) -> Result<(Self, Arc), InitRendererEngineError> { let (renderer, event_loop) = Renderer::new(RendererOptions { web_renderer: opts.web_renderer, framerate: opts.framerate, diff --git a/compositor_render/Cargo.toml b/compositor_render/Cargo.toml index 1fc3c40bf..eb7765f30 100644 --- a/compositor_render/Cargo.toml +++ b/compositor_render/Cargo.toml @@ -4,12 +4,14 @@ version = "0.1.0" edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +web_renderer = ["dep:compositor_chromium"] [dependencies] pollster = "0.3.0" thiserror = { workspace = true } wgpu = { version = "0.16.1", features = ["naga"] } -compositor_chromium = { path = "../compositor_chromium" } +compositor_chromium = { path = "../compositor_chromium", optional = true } image = { workspace = true } reqwest = { workspace = true } bytes = { workspace = true } diff --git a/compositor_render/src/event_loop.rs b/compositor_render/src/event_loop.rs index 5e1d33f2e..8803f721b 100644 --- a/compositor_render/src/event_loop.rs +++ b/compositor_render/src/event_loop.rs @@ -1,36 +1,8 @@ -use std::sync::Arc; - -use compositor_chromium::cef; - -pub struct EventLoop { - cef_ctx: Option>, -} - -impl EventLoop { - pub fn new(cef_ctx: Option>) -> Self { - Self { cef_ctx } - } - +pub trait EventLoop { /// Runs the event loop. It must run on the main thread. /// `fallback` is used when web rendering is disabled. /// Blocks the thread indefinitely. - pub fn run_with_fallback(&self, fallback: impl FnOnce()) -> Result<(), EventLoopRunError> { - match &self.cef_ctx { - Some(ctx) => self.cef_event_loop(ctx)?, - None => fallback(), - } - - Ok(()) - } - - fn cef_event_loop(&self, ctx: &cef::Context) -> Result<(), EventLoopRunError> { - if !ctx.currently_on_thread(cef::ThreadId::UI) { - return Err(EventLoopRunError::WrongThread); - } - - ctx.run_message_loop(); - Ok(()) - } + fn run_with_fallback(&self, fallback: &dyn Fn()) -> Result<(), EventLoopRunError>; } #[derive(Debug, thiserror::Error)] diff --git a/compositor_render/src/state.rs b/compositor_render/src/state.rs index e1f1de6fd..ad950825b 100644 --- a/compositor_render/src/state.rs +++ b/compositor_render/src/state.rs @@ -6,7 +6,7 @@ use crate::image; use crate::scene::OutputScene; use crate::transformations::image_renderer::Image; use crate::transformations::shader::Shader; -use crate::transformations::web_renderer::WebRenderer; +use crate::transformations::web_renderer::{self, WebRenderer}; use crate::{ error::{InitRendererEngineError, RenderSceneError, UpdateSceneError}, transformations::{ @@ -19,7 +19,7 @@ use crate::{ scene::{self, SceneState}, wgpu::{WgpuCtx, WgpuErrorScope}, }; -use crate::{shader, web_renderer, RegistryType, RendererId}; +use crate::{shader, RegistryType, RendererId}; use self::{ render_graph::RenderGraph, @@ -79,9 +79,11 @@ pub enum RendererSpec { } impl Renderer { - pub fn new(opts: RendererOptions) -> Result<(Self, EventLoop), InitRendererEngineError> { + pub fn new( + opts: RendererOptions, + ) -> Result<(Self, Arc), InitRendererEngineError> { let renderer = InnerRenderer::new(opts)?; - let event_loop = EventLoop::new(renderer.chromium_context.cef_context()); + let event_loop = renderer.chromium_context.event_loop(); Ok((Self(Arc::new(Mutex::new(renderer))), event_loop)) } diff --git a/compositor_render/src/transformations/web_renderer.rs b/compositor_render/src/transformations/web_renderer.rs index 5780ccb48..52837df24 100644 --- a/compositor_render/src/transformations/web_renderer.rs +++ b/compositor_render/src/transformations/web_renderer.rs @@ -1,30 +1,31 @@ +use crate::{FallbackStrategy, RendererId, Resolution}; use bytes::Bytes; use nalgebra_glm::Mat4; -use std::env; -use std::path::PathBuf; use std::sync::{Arc, Mutex}; -use crate::state::render_graph::NodeId; -use crate::state::{RegisterCtx, RenderCtx}; -use crate::transformations::web_renderer::shader::WebRendererShader; -use crate::wgpu::common_pipeline::CreateShaderError; -use crate::wgpu::texture::{BGRATexture, NodeTexture, Texture}; -use crate::{FallbackStrategy, RendererId, Resolution}; +#[cfg(feature = "web_renderer")] +mod renderer; -use crate::transformations::web_renderer::browser_client::BrowserClient; -use crate::transformations::web_renderer::chromium_sender::ChromiumSender; -use crate::transformations::web_renderer::embedder::{EmbedError, EmbeddingHelper}; -use log::{error, info}; +#[cfg(not(feature = "web_renderer"))] +#[path = "web_renderer/disabled_renderer.rs"] +mod renderer; -use self::embedder::RenderInfo; +pub use renderer::*; -pub mod browser_client; pub mod chromium_context; +pub(crate) mod node; + +#[cfg(feature = "web_renderer")] +pub mod browser_client; +#[cfg(feature = "web_renderer")] mod chromium_sender; +#[cfg(feature = "web_renderer")] mod chromium_sender_thread; +#[cfg(feature = "web_renderer")] mod embedder; -pub(crate) mod node; +#[cfg(feature = "web_renderer")] mod shader; +#[cfg(feature = "web_renderer")] mod shared_memory; pub const EMBED_SOURCE_FRAMES_MESSAGE: &str = "EMBED_SOURCE_FRAMES"; @@ -42,7 +43,7 @@ pub struct WebRendererInitOptions { impl Default for WebRendererInitOptions { fn default() -> Self { Self { - init: true, + init: cfg!(feature = "web_renderer"), disable_gpu: false, } } @@ -69,143 +70,3 @@ pub enum WebEmbeddingMethod { /// The website's background has to be transparent NativeEmbeddingUnderContent, } - -#[derive(Debug)] -pub struct WebRenderer { - spec: WebRendererSpec, - frame_data: FrameData, - source_transforms: SourceTransforms, - embedding_helper: EmbeddingHelper, - - website_texture: BGRATexture, - render_website_shader: WebRendererShader, -} - -impl WebRenderer { - pub fn new(ctx: &RegisterCtx, spec: WebRendererSpec) -> Result { - if ctx.chromium.cef_context().is_none() { - return Err(CreateWebRendererError::WebRendererDisabled); - } - - info!("Starting web renderer for {}", &spec.url); - - let frame_data = Arc::new(Mutex::new(Bytes::new())); - let source_transforms = Arc::new(Mutex::new(Vec::new())); - - let client = BrowserClient::new( - frame_data.clone(), - source_transforms.clone(), - spec.resolution, - ); - let chromium_sender = ChromiumSender::new(ctx, spec.url.clone(), client); - let embedding_helper = EmbeddingHelper::new(ctx, chromium_sender, spec.embedding_method); - let render_website_shader = WebRendererShader::new(&ctx.wgpu_ctx)?; - let website_texture = BGRATexture::new(&ctx.wgpu_ctx, spec.resolution); - - Ok(Self { - spec, - frame_data, - source_transforms, - embedding_helper, - website_texture, - render_website_shader, - }) - } - - pub fn render( - &self, - ctx: &RenderCtx, - node_id: &NodeId, - sources: &[(&NodeId, &NodeTexture)], - buffers: &[Arc], - target: &mut NodeTexture, - ) -> Result<(), RenderWebsiteError> { - self.embedding_helper - .prepare_embedding(node_id, sources, buffers) - .map_err(|err| RenderWebsiteError::EmbeddingFailed(self.spec.url.clone(), err))?; - - if let Some(frame) = self.retrieve_frame() { - let target = target.ensure_size(ctx.wgpu_ctx, self.spec.resolution); - self.website_texture.upload(ctx.wgpu_ctx, &frame); - - let render_textures = self.prepare_textures(sources); - - self.render_website_shader - .render(ctx.wgpu_ctx, &render_textures, target); - } - - Ok(()) - } - - fn prepare_textures<'a>( - &'a self, - sources: &'a [(&NodeId, &NodeTexture)], - ) -> Vec<(Option<&Texture>, RenderInfo)> { - let mut source_info = sources - .iter() - .zip(self.source_transforms.lock().unwrap().iter()) - .map(|((_node_id, node_texture), transform)| { - ( - node_texture.texture(), - RenderInfo::source_transform(transform), - ) - }) - .collect(); - - let website_info = (Some(self.website_texture.texture()), RenderInfo::website()); - - let mut result = Vec::new(); - match self.spec.embedding_method { - WebEmbeddingMethod::NativeEmbeddingOverContent => { - result.push(website_info); - result.append(&mut source_info); - } - WebEmbeddingMethod::NativeEmbeddingUnderContent => { - result.append(&mut source_info); - result.push(website_info); - } - WebEmbeddingMethod::ChromiumEmbedding => { - result.push(website_info); - } - }; - - result - } - - fn retrieve_frame(&self) -> Option { - let frame_data = self.frame_data.lock().unwrap(); - if frame_data.is_empty() { - return None; - } - Some(frame_data.clone()) - } - - pub fn resolution(&self) -> Resolution { - self.spec.resolution - } - - pub fn shared_memory_root_path(renderer_id: &str) -> PathBuf { - env::temp_dir() - .join("video_compositor") - .join(format!("instance_{}", renderer_id)) - } - - pub fn fallback_strategy(&self) -> FallbackStrategy { - self.spec.fallback_strategy - } -} - -#[derive(Debug, thiserror::Error)] -pub enum CreateWebRendererError { - #[error(transparent)] - CreateShaderFailed(#[from] CreateShaderError), - - #[error("Web rendering is disabled")] - WebRendererDisabled, -} - -#[derive(Debug, thiserror::Error)] -pub enum RenderWebsiteError { - #[error("Failed to embed source on the website \"{0}\": {1}")] - EmbeddingFailed(String, #[source] EmbedError), -} diff --git a/compositor_render/src/transformations/web_renderer/chromium_context.rs b/compositor_render/src/transformations/web_renderer/chromium_context.rs index 92e6b528c..ac7f76bd1 100644 --- a/compositor_render/src/transformations/web_renderer/chromium_context.rs +++ b/compositor_render/src/transformations/web_renderer/chromium_context.rs @@ -1,15 +1,21 @@ use std::sync::Arc; -use crate::{types::Framerate, utils::random_string}; +use crate::{ + event_loop::{EventLoop, EventLoopRunError}, + types::Framerate, + utils::random_string, +}; +#[cfg(feature = "web_renderer")] use compositor_chromium::cef; use crossbeam_channel::RecvError; use log::info; -use super::{browser_client::BrowserClient, WebRendererInitOptions}; +use super::WebRendererInitOptions; pub struct ChromiumContext { instance_id: String, - context: Option>, + #[cfg(feature = "web_renderer")] + pub(super) context: Option>, framerate: Framerate, } @@ -19,42 +25,57 @@ impl ChromiumContext { framerate: Framerate, ) -> Result { let instance_id = random_string(30); - - if !opts.init { - info!("Chromium context disabled"); + #[cfg(not(feature = "web_renderer"))] + { + if opts.init { + return Err(WebRendererContextError::WebRenderingNotAvailable); + } return Ok(Self { instance_id, framerate, - context: None, }); } - info!("Init chromium context"); - - let app = ChromiumApp { - show_fps: false, - disable_gpu: opts.disable_gpu, - }; - let settings = cef::Settings { - windowless_rendering_enabled: true, - log_severity: cef::LogSeverity::Info, - ..Default::default() - }; - - let context = Arc::new( - cef::Context::new(app, settings).map_err(WebRendererContextError::ContextFailure)?, - ); - Ok(Self { - instance_id, - framerate, - context: Some(context), - }) + #[cfg(feature = "web_renderer")] + { + if !opts.init { + info!("Chromium context disabled"); + return Ok(Self { + instance_id, + framerate, + context: None, + }); + } + + info!("Init chromium context"); + + let app = ChromiumApp { + show_fps: false, + disable_gpu: opts.disable_gpu, + }; + let settings = cef::Settings { + windowless_rendering_enabled: true, + log_severity: cef::LogSeverity::Info, + ..Default::default() + }; + + let context = Arc::new( + cef::Context::new(app, settings) + .map_err(WebRendererContextError::ContextFailure)?, + ); + Ok(Self { + instance_id, + framerate, + context: Some(context), + }) + } } + #[cfg(feature = "web_renderer")] pub(super) fn start_browser( &self, url: &str, - state: BrowserClient, + state: super::browser_client::BrowserClient, ) -> Result { let context = self .context @@ -79,8 +100,15 @@ impl ChromiumContext { rx.recv()?.map_err(WebRendererContextError::ContextFailure) } - pub fn cef_context(&self) -> Option> { - self.context.clone() + pub fn event_loop(&self) -> Arc { + #[cfg(feature = "web_renderer")] + return self + .context + .clone() + .map(|ctx| ctx as Arc) + .unwrap_or_else(|| Arc::new(FallbackEventLoop)); + #[cfg(not(feature = "web_renderer"))] + return Arc::new(FallbackEventLoop); } pub fn instance_id(&self) -> &str { @@ -93,6 +121,7 @@ struct ChromiumApp { disable_gpu: bool, } +#[cfg(feature = "web_renderer")] impl cef::App for ChromiumApp { type RenderProcessHandlerType = (); @@ -122,8 +151,30 @@ impl cef::App for ChromiumApp { } } +#[cfg(feature = "web_renderer")] +impl EventLoop for cef::Context { + fn run_with_fallback(&self, _fallback: &dyn Fn()) -> Result<(), EventLoopRunError> { + if !self.currently_on_thread(cef::ThreadId::UI) { + return Err(EventLoopRunError::WrongThread); + } + + self.run_message_loop(); + Ok(()) + } +} + +struct FallbackEventLoop; + +impl EventLoop for FallbackEventLoop { + fn run_with_fallback(&self, fallback: &dyn Fn()) -> Result<(), EventLoopRunError> { + fallback(); + Ok(()) + } +} + #[derive(Debug, thiserror::Error)] pub enum WebRendererContextError { + #[cfg(feature = "web_renderer")] #[error("Chromium context failed: {0}")] ContextFailure(cef::ContextError), @@ -135,4 +186,7 @@ pub enum WebRendererContextError { #[error("Chromium message loop can only run on the main thread.")] WrongThreadForMessageLoop, + + #[error("Web rendering feature is not available")] + WebRenderingNotAvailable, } diff --git a/compositor_render/src/transformations/web_renderer/disabled_renderer.rs b/compositor_render/src/transformations/web_renderer/disabled_renderer.rs new file mode 100644 index 000000000..96357dc04 --- /dev/null +++ b/compositor_render/src/transformations/web_renderer/disabled_renderer.rs @@ -0,0 +1,48 @@ +use std::sync::Arc; + +use crate::{ + state::{render_graph::NodeId, RegisterCtx, RenderCtx}, + wgpu::texture::NodeTexture, + FallbackStrategy, Resolution, +}; + +use super::WebRendererSpec; + +#[derive(Debug)] +pub struct WebRenderer { + spec: WebRendererSpec, +} + +impl WebRenderer { + pub fn new(_ctx: &RegisterCtx, _spec: WebRendererSpec) -> Result { + return Err(CreateWebRendererError::WebRenderingNotAvailable); + } + + pub fn render( + &self, + _ctx: &RenderCtx, + _node_id: &NodeId, + _sources: &[(&NodeId, &NodeTexture)], + _buffers: &[Arc], + _target: &mut NodeTexture, + ) -> Result<(), RenderWebsiteError> { + Ok(()) + } + + pub fn resolution(&self) -> Resolution { + self.spec.resolution + } + + pub fn fallback_strategy(&self) -> FallbackStrategy { + self.spec.fallback_strategy + } +} + +#[derive(Debug, thiserror::Error)] +pub enum CreateWebRendererError { + #[error("Web rendering feature is not available")] + WebRenderingNotAvailable, +} + +#[derive(Debug, thiserror::Error)] +pub enum RenderWebsiteError {} diff --git a/compositor_render/src/transformations/web_renderer/renderer.rs b/compositor_render/src/transformations/web_renderer/renderer.rs new file mode 100644 index 000000000..bbf85c648 --- /dev/null +++ b/compositor_render/src/transformations/web_renderer/renderer.rs @@ -0,0 +1,166 @@ +use std::{ + env, + path::PathBuf, + sync::{Arc, Mutex}, +}; + +use bytes::Bytes; +use log::info; + +use crate::{ + state::{render_graph::NodeId, RegisterCtx, RenderCtx}, + transformations::web_renderer::{ + browser_client::BrowserClient, chromium_sender::ChromiumSender, + }, + wgpu::{ + common_pipeline::CreateShaderError, + texture::{BGRATexture, NodeTexture, Texture}, + }, + FallbackStrategy, Resolution, +}; + +use super::{ + embedder::{EmbedError, EmbeddingHelper, RenderInfo}, + shader::WebRendererShader, + FrameData, SourceTransforms, WebEmbeddingMethod, WebRendererSpec, +}; + +#[derive(Debug)] +pub struct WebRenderer { + spec: WebRendererSpec, + frame_data: FrameData, + source_transforms: SourceTransforms, + embedding_helper: EmbeddingHelper, + + website_texture: BGRATexture, + render_website_shader: WebRendererShader, +} + +impl WebRenderer { + pub fn new(ctx: &RegisterCtx, spec: WebRendererSpec) -> Result { + if ctx.chromium.context.is_none() { + return Err(CreateWebRendererError::WebRendererDisabled); + } + + info!("Starting web renderer for {}", &spec.url); + + let frame_data = Arc::new(Mutex::new(Bytes::new())); + let source_transforms = Arc::new(Mutex::new(Vec::new())); + + let client = BrowserClient::new( + frame_data.clone(), + source_transforms.clone(), + spec.resolution, + ); + let chromium_sender = ChromiumSender::new(ctx, spec.url.clone(), client); + let embedding_helper = EmbeddingHelper::new(ctx, chromium_sender, spec.embedding_method); + let render_website_shader = WebRendererShader::new(&ctx.wgpu_ctx)?; + let website_texture = BGRATexture::new(&ctx.wgpu_ctx, spec.resolution); + + Ok(Self { + spec, + frame_data, + source_transforms, + embedding_helper, + website_texture, + render_website_shader, + }) + } + + pub fn render( + &self, + ctx: &RenderCtx, + node_id: &NodeId, + sources: &[(&NodeId, &NodeTexture)], + buffers: &[Arc], + target: &mut NodeTexture, + ) -> Result<(), RenderWebsiteError> { + self.embedding_helper + .prepare_embedding(node_id, sources, buffers) + .map_err(|err| RenderWebsiteError::EmbeddingFailed(self.spec.url.clone(), err))?; + + if let Some(frame) = self.retrieve_frame() { + let target = target.ensure_size(ctx.wgpu_ctx, self.spec.resolution); + self.website_texture.upload(ctx.wgpu_ctx, &frame); + + let render_textures = self.prepare_textures(sources); + + self.render_website_shader + .render(ctx.wgpu_ctx, &render_textures, target); + } + + Ok(()) + } + + fn prepare_textures<'a>( + &'a self, + sources: &'a [(&NodeId, &NodeTexture)], + ) -> Vec<(Option<&Texture>, RenderInfo)> { + let mut source_info = sources + .iter() + .zip(self.source_transforms.lock().unwrap().iter()) + .map(|((_node_id, node_texture), transform)| { + ( + node_texture.texture(), + RenderInfo::source_transform(transform), + ) + }) + .collect(); + + let website_info = (Some(self.website_texture.texture()), RenderInfo::website()); + + let mut result = Vec::new(); + match self.spec.embedding_method { + WebEmbeddingMethod::NativeEmbeddingOverContent => { + result.push(website_info); + result.append(&mut source_info); + } + WebEmbeddingMethod::NativeEmbeddingUnderContent => { + result.append(&mut source_info); + result.push(website_info); + } + WebEmbeddingMethod::ChromiumEmbedding => { + result.push(website_info); + } + }; + + result + } + + fn retrieve_frame(&self) -> Option { + let frame_data = self.frame_data.lock().unwrap(); + if frame_data.is_empty() { + return None; + } + Some(frame_data.clone()) + } + + pub fn resolution(&self) -> Resolution { + self.spec.resolution + } + + pub fn shared_memory_root_path(renderer_id: &str) -> PathBuf { + env::temp_dir() + .join("video_compositor") + .join(format!("instance_{}", renderer_id)) + } + + pub fn fallback_strategy(&self) -> FallbackStrategy { + self.spec.fallback_strategy + } +} + +#[derive(Debug, thiserror::Error)] +pub enum CreateWebRendererError { + #[error(transparent)] + CreateShaderFailed(#[from] CreateShaderError), + + #[error("Web rendering can not be used because it was disabled in the init request")] + WebRendererDisabled, +} + +#[derive(Debug, thiserror::Error)] +pub enum RenderWebsiteError { + #[error("Failed to embed source on the website \"{0}\": {1}")] + EmbeddingFailed(String, #[source] EmbedError), +} diff --git a/examples/web_view.rs b/examples/web_view.rs index d07da062a..9304ba87d 100644 --- a/examples/web_view.rs +++ b/examples/web_view.rs @@ -1,5 +1,4 @@ use anyhow::Result; -use compositor_chromium::cef::bundle_for_development; use log::{error, info}; use serde_json::json; use std::{ @@ -30,17 +29,21 @@ fn main() { ffmpeg_next::format::network::init(); logger::init_logger(); - let target_path = &std::env::current_exe() - .unwrap() - .parent() - .unwrap() - .join(".."); - - if let Err(err) = bundle_for_development(target_path) { - panic!( - "Build process helper first. For release profile use: cargo build -r --bin process_helper. {:?}", - err - ); + #[cfg(feature = "web_renderer")] + { + use compositor_chromium::cef::bundle_for_development; + + let target_path = &std::env::current_exe() + .unwrap() + .parent() + .unwrap() + .join(".."); + if let Err(err) = bundle_for_development(target_path) { + panic!( + "Build process helper first. For release profile use: cargo build -r --bin process_helper. {:?}", + err + ); + } } thread::spawn(|| { if let Err(err) = start_example_client_code() { diff --git a/scripts/release.sh b/scripts/release.sh index a079f463b..6bca49001 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -25,9 +25,16 @@ cd "$ROOT_DIR/release_tmp" gh run download "$WORKFLOW_RUN_ID" -n video_compositor_linux_x86_64.tar.gz gh run download "$WORKFLOW_RUN_ID" -n video_compositor_darwin_x86_64.tar.gz gh run download "$WORKFLOW_RUN_ID" -n video_compositor_darwin_aarch64.tar.gz +gh run download "$WORKFLOW_RUN_ID" -n video_compositor_with_web_renderer_linux_x86_64.tar.gz +gh run download "$WORKFLOW_RUN_ID" -n video_compositor_with_web_renderer_darwin_x86_64.tar.gz +gh run download "$WORKFLOW_RUN_ID" -n video_compositor_with_web_renderer_darwin_aarch64.tar.gz + gh release create "$RELEASE_TAG" gh release upload "$RELEASE_TAG" video_compositor_linux_x86_64.tar.gz gh release upload "$RELEASE_TAG" video_compositor_darwin_x86_64.tar.gz gh release upload "$RELEASE_TAG" video_compositor_darwin_aarch64.tar.gz +gh release upload "$RELEASE_TAG" video_compositor_with_web_renderer_linux_x86_64.tar.gz +gh release upload "$RELEASE_TAG" video_compositor_with_web_renderer_darwin_x86_64.tar.gz +gh release upload "$RELEASE_TAG" video_compositor_with_web_renderer_darwin_aarch64.tar.gz rm -rf "$ROOT_DIR/release_tmp" diff --git a/src/api.rs b/src/api.rs index e99482004..9f58eb87e 100644 --- a/src/api.rs +++ b/src/api.rs @@ -92,7 +92,7 @@ pub struct Api { } impl Api { - pub fn new(opts: pipeline::Options) -> Result<(Api, EventLoop), ApiError> { + pub fn new(opts: pipeline::Options) -> Result<(Api, Arc), ApiError> { let (pipeline, event_loop) = Pipeline::new(opts)?; Ok((Api { pipeline }, event_loop)) } diff --git a/src/bin/package_for_release/bundle_linux.rs b/src/bin/package_for_release/bundle_linux.rs index 6dedb72b3..c4b14ba9d 100644 --- a/src/bin/package_for_release/bundle_linux.rs +++ b/src/bin/package_for_release/bundle_linux.rs @@ -8,55 +8,83 @@ use std::process::Command; use crate::utils; const X86_TARGET: &str = "x86_64-unknown-linux-gnu"; +const X86_OUTPUT_FILE: &str = "video_compositor_linux_x86_64.tar.gz"; +const X86_WITH_WEB_RENDERER_OUTPUT_FILE: &str = + "video_compositor_with_web_renderer_linux_x86_64.tar.gz"; pub fn bundle_linux_app() -> Result<()> { tracing_subscriber::fmt().init(); + info!("Bundling compositor without web rendering"); + bundle_app(false)?; + + info!("Bundling compositor with web rendering"); + bundle_app(true)?; + + Ok(()) +} + +fn bundle_app(enable_web_rendering: bool) -> Result<()> { let root_dir_str = env!("CARGO_MANIFEST_DIR"); let root_dir: PathBuf = root_dir_str.into(); let release_dir = root_dir.join("target/x86_64-unknown-linux-gnu/release"); let tmp_dir = root_dir.join("video_compositor"); + utils::setup_bundle_dir(&tmp_dir)?; info!("Build main_process binary."); - utils::cargo_build("main_process", X86_TARGET)?; - info!("Build process_helper binary."); - utils::cargo_build("process_helper", X86_TARGET)?; - - info!("Create {} directory", tmp_dir.display()); - fs::create_dir_all(tmp_dir.clone())?; - - info!("Copy main_process binary."); - fs::copy( - release_dir.join("main_process"), - tmp_dir.join("video_compositor_main"), - )?; - info!("Copy process_helper binary."); - fs::copy( - release_dir.join("process_helper"), - tmp_dir.join("video_compositor_process_helper"), - )?; - - info!("Copy wrapper script."); - fs::copy( - root_dir.join("scripts/compositor_runtime_wrapper.sh"), - tmp_dir.join("video_compositor"), - )?; - - info!( - "Copy lib directory. {:?} {:?}", - release_dir.join("lib"), - tmp_dir.join("lib"), - ); - - dir::copy(release_dir.join("lib"), tmp_dir, &CopyOptions::default())?; + utils::cargo_build("main_process", X86_TARGET, !enable_web_rendering)?; + + if enable_web_rendering { + info!("Build process_helper binary."); + utils::cargo_build("process_helper", X86_TARGET, false)?; + + info!("Create {} directory", tmp_dir.display()); + fs::create_dir_all(tmp_dir.clone())?; + + info!("Copy main_process binary."); + fs::copy( + release_dir.join("main_process"), + tmp_dir.join("video_compositor_main"), + )?; + + info!("Copy process_helper binary."); + fs::copy( + release_dir.join("process_helper"), + tmp_dir.join("video_compositor_process_helper"), + )?; + + info!("Copy wrapper script."); + fs::copy( + root_dir.join("src/bin/package_for_release/linux_runtime_wrapper.sh"), + tmp_dir.join("video_compositor"), + )?; + + info!( + "Copy lib directory. {:?} {:?}", + release_dir.join("lib"), + tmp_dir.join("lib"), + ); + + dir::copy(release_dir.join("lib"), tmp_dir, &CopyOptions::default())?; + } else { + info!("Copy main_process binary."); + fs::copy( + release_dir.join("main_process"), + tmp_dir.join("video_compositor"), + )?; + } info!("Create tar.gz archive."); + let archive_name = match enable_web_rendering { + true => X86_WITH_WEB_RENDERER_OUTPUT_FILE, + false => X86_OUTPUT_FILE, + }; let exit_code = Command::new("tar") .args([ "-C", root_dir_str, "-czvf", - "video_compositor_linux_x86_64.tar.gz", + archive_name, "video_compositor", ]) .spawn()? diff --git a/src/bin/package_for_release/bundle_macos.rs b/src/bin/package_for_release/bundle_macos.rs index 0b52ef8f2..2a50f3fff 100644 --- a/src/bin/package_for_release/bundle_macos.rs +++ b/src/bin/package_for_release/bundle_macos.rs @@ -7,39 +7,56 @@ use anyhow::{anyhow, Result}; use log::info; use crate::utils; -use compositor_chromium::cef; const ARM_MAC_TARGET: &str = "aarch64-apple-darwin"; const ARM_OUTPUT_FILE: &str = "video_compositor_darwin_aarch64.tar.gz"; +const ARM_WITH_WEB_RENDERER_OUTPUT_FILE: &str = + "video_compositor_with_web_renderer_darwin_aarch64.tar.gz"; + const INTEL_MAC_TARGET: &str = "x86_64-apple-darwin"; const INTEL_OUTPUT_FILE: &str = "video_compositor_darwin_x86_64.tar.gz"; +const INTEL_WITH_WEB_RENDERER_OUTPUT_FILE: &str = + "video_compositor_with_web_renderer_darwin_x86_64.tar.gz"; pub fn bundle_macos_app() -> Result<()> { + tracing_subscriber::fmt().init(); + if cfg!(target_arch = "x86_64") { - bundle_app(INTEL_MAC_TARGET, INTEL_OUTPUT_FILE)?; + bundle_app(INTEL_MAC_TARGET, INTEL_OUTPUT_FILE, false)?; + bundle_app(INTEL_MAC_TARGET, INTEL_WITH_WEB_RENDERER_OUTPUT_FILE, true)?; } else if cfg!(target_arch = "aarch64") { - bundle_app(ARM_MAC_TARGET, ARM_OUTPUT_FILE)?; + bundle_app(ARM_MAC_TARGET, ARM_OUTPUT_FILE, false)?; + bundle_app(ARM_MAC_TARGET, ARM_WITH_WEB_RENDERER_OUTPUT_FILE, true)?; } else { panic!("Unknown architecture") } Ok(()) } -fn bundle_app(target: &'static str, output_name: &'static str) -> Result<()> { - tracing_subscriber::fmt().init(); +fn bundle_app(target: &'static str, output_name: &str, enable_web_rendering: bool) -> Result<()> { + if enable_web_rendering { + info!("Bundling compositor with web rendering"); + } else { + info!("Bundling compositor without web rendering"); + } let root_dir_str = env!("CARGO_MANIFEST_DIR"); let root_dir: PathBuf = root_dir_str.into(); let build_dir = root_dir.join(format!("target/{target}/release")); let tmp_dir = root_dir.join("video_compositor"); + utils::setup_bundle_dir(&tmp_dir)?; info!("Build main_process binary."); - utils::cargo_build("main_process", target)?; - info!("Build process_helper binary."); - utils::cargo_build("process_helper", target)?; + utils::cargo_build("main_process", target, !enable_web_rendering)?; info!("Create macOS bundle."); - cef::bundle_app(&build_dir, &tmp_dir.join("video_compositor.app"))?; + if enable_web_rendering { + use compositor_chromium::cef; + + info!("Build process_helper binary."); + utils::cargo_build("process_helper", target, false)?; + cef::bundle_app(&build_dir, &tmp_dir.join("video_compositor.app"))?; + } fs::copy( build_dir.join("main_process"), diff --git a/scripts/compositor_runtime_wrapper.sh b/src/bin/package_for_release/linux_runtime_wrapper.sh similarity index 77% rename from scripts/compositor_runtime_wrapper.sh rename to src/bin/package_for_release/linux_runtime_wrapper.sh index a2b1a2879..5feff2682 100755 --- a/scripts/compositor_runtime_wrapper.sh +++ b/src/bin/package_for_release/linux_runtime_wrapper.sh @@ -1,5 +1,9 @@ #!/usr/bin/env bash +# +# Runtime wrapper which provides paths to native libs used by the web renderer +# + set -eo pipefail SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" diff --git a/src/bin/package_for_release/utils.rs b/src/bin/package_for_release/utils.rs index b111ffdba..cbf8dc4a4 100644 --- a/src/bin/package_for_release/utils.rs +++ b/src/bin/package_for_release/utils.rs @@ -1,9 +1,13 @@ use anyhow::{anyhow, Result}; use log::{info, warn}; -use std::{process::Command, str::from_utf8}; +use std::{fs, path::PathBuf, process::Command, str::from_utf8}; -pub fn cargo_build(bin: &'static str, target: &'static str) -> Result<()> { - let args = vec![ +pub fn cargo_build( + bin: &'static str, + target: &'static str, + disable_default_features: bool, +) -> Result<()> { + let mut args = vec![ "build", "--release", "--target", @@ -12,6 +16,10 @@ pub fn cargo_build(bin: &'static str, target: &'static str) -> Result<()> { "--bin", bin, ]; + if disable_default_features { + args.extend(["--no-default-features"]); + } + info!("Running command \"cargo {}\"", args.join(" ")); let output = Command::new("cargo") .args(args) @@ -24,3 +32,19 @@ pub fn cargo_build(bin: &'static str, target: &'static str) -> Result<()> { } Ok(()) } + +pub fn setup_bundle_dir(dir: &PathBuf) -> Result<()> { + if dir.exists() { + if !dir.is_dir() { + return Err(anyhow!("Expected directory path")); + } + + info!("Bundle directory already exists. Removing..."); + fs::remove_dir_all(dir)?; + } + + info!("Creating new bundle directory"); + fs::create_dir_all(dir)?; + + Ok(()) +} diff --git a/src/http.rs b/src/http.rs index 02f14c04a..71f1039a4 100644 --- a/src/http.rs +++ b/src/http.rs @@ -108,7 +108,7 @@ impl Server { let mut signals = Signals::new([consts::SIGINT]).unwrap(); signals.forever().next(); }; - if let Err(err) = event_loop.run_with_fallback(event_loop_fallback) { + if let Err(err) = event_loop.run_with_fallback(&event_loop_fallback) { error!( "Failed to start event loop.\n{}", ErrorStack::new(&err).into_string() @@ -116,7 +116,7 @@ impl Server { } } - fn handle_init(&self) -> (Api, EventLoop) { + fn handle_init(&self) -> (Api, Arc) { for mut raw_request in self.server.incoming_requests() { let result = self .handle_request_before_init(&mut raw_request) diff --git a/src/main.rs b/src/main.rs index f161d49e6..3845d41c1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,5 @@ use std::env; -use compositor_chromium::cef::bundle_for_development; use log::info; use crate::http::API_PORT_ENV; @@ -19,13 +18,18 @@ mod snapshot_tests; fn main() { logger::init_logger(); - let target_path = std::env::current_exe() - .unwrap() - .parent() - .unwrap() - .to_owned(); - if bundle_for_development(&target_path).is_err() { - panic!("Build process helper first. For release profile use: cargo build -r --bin process_helper"); + #[cfg(feature = "web_renderer")] + { + use compositor_chromium::cef::bundle_for_development; + + let target_path = std::env::current_exe() + .unwrap() + .parent() + .unwrap() + .to_owned(); + if bundle_for_development(&target_path).is_err() { + panic!("Build process helper first. For release profile use: cargo build -r --bin process_helper"); + } } ffmpeg_next::format::network::init();