Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 8 additions & 60 deletions desktop/src/cef.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,8 @@ use std::sync::{Arc, Mutex};
use std::time::Instant;

use crate::event::{AppEvent, AppEventScheduler};
use crate::render::FrameBufferRef;
use crate::window::Cursor;
use crate::wrapper::{WgpuContext, deserialize_editor_message};
use crate::wrapper::deserialize_editor_message;

mod consts;
mod context;
Expand All @@ -32,17 +31,14 @@ mod internal;
mod ipc;
mod platform;
mod utility;

#[cfg(feature = "accelerated_paint")]
use cef::osr_texture_import::SharedTextureHandle;
mod view;

pub(crate) use context::{CefContext, CefContextBuilder, InitError};
pub(crate) use view::View;

pub(crate) trait CefEventHandler: Send + Sync + 'static {
fn view_info(&self) -> ViewInfo;
fn draw<'a>(&self, frame_buffer: FrameBufferRef<'a>);
#[cfg(feature = "accelerated_paint")]
fn draw_gpu(&self, shared_texture: SharedTextureHandle);
fn draw(&self, view: &View);
fn load_resource(&self, path: PathBuf) -> Option<Resource>;
fn cursor_change(&self, cursor: Cursor);
/// Schedule the main event loop to run the CEF event loop after the timeout.
Expand Down Expand Up @@ -120,15 +116,13 @@ impl Read for ResourceReader {
}

pub(crate) struct CefHandler {
wgpu_context: WgpuContext,
app_event_scheduler: AppEventScheduler,
view_info_receiver: Arc<Mutex<ViewInfoReceiver>>,
}

impl CefHandler {
pub(crate) fn new(wgpu_context: WgpuContext, app_event_scheduler: AppEventScheduler, view_info_receiver: Receiver<ViewInfoUpdate>) -> Self {
pub(crate) fn new(app_event_scheduler: AppEventScheduler, view_info_receiver: Receiver<ViewInfoUpdate>) -> Self {
Self {
wgpu_context,
app_event_scheduler,
view_info_receiver: Arc::new(Mutex::new(ViewInfoReceiver::new(view_info_receiver))),
}
Expand All @@ -147,55 +141,10 @@ impl CefEventHandler for CefHandler {
}
*view_info
}
fn draw<'a>(&self, frame_buffer: FrameBufferRef<'a>) {
let width = frame_buffer.width() as u32;
let height = frame_buffer.height() as u32;
let texture = self.wgpu_context.device.create_texture(&wgpu::TextureDescriptor {
label: Some("CEF Texture"),
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Bgra8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
self.wgpu_context.queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture: &texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
frame_buffer.buffer(),
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(4 * width),
rows_per_image: Some(height),
},
wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
);

self.app_event_scheduler.schedule(AppEvent::UiUpdate(texture));
}

#[cfg(feature = "accelerated_paint")]
fn draw_gpu(&self, shared_texture: SharedTextureHandle) {
match shared_texture.import_texture(&self.wgpu_context.device) {
Ok(texture) => {
self.app_event_scheduler.schedule(AppEvent::UiUpdate(texture));
}
Err(e) => {
tracing::error!("Failed to import shared texture: {}", e);
}
fn draw(&self, view: &View) {
if let Some(texture) = view.texture() {
self.app_event_scheduler.schedule(AppEvent::UiUpdate(texture));
}
}

Expand Down Expand Up @@ -279,7 +228,6 @@ impl CefEventHandler for CefHandler {
Self: Sized,
{
Self {
wgpu_context: self.wgpu_context.clone(),
app_event_scheduler: self.app_event_scheduler.clone(),
view_info_receiver: self.view_info_receiver.clone(),
}
Expand Down
13 changes: 7 additions & 6 deletions desktop/src/cef/context/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use crate::cef::consts::{RESOURCE_DOMAIN, RESOURCE_SCHEME};
use crate::cef::input::InputState;
use crate::cef::internal::{BrowserProcessAppImpl, BrowserProcessClientImpl, RenderProcessAppImpl, SchemeHandlerFactoryImpl};
use crate::dirs::TempDir;
use crate::wrapper::WgpuContext;

pub(crate) struct CefContextBuilder<H: CefEventHandler> {
pub(crate) args: Args,
Expand Down Expand Up @@ -67,19 +68,19 @@ impl<H: CefEventHandler> CefContextBuilder<H> {
}

#[cfg(target_os = "macos")]
pub(crate) fn create(self, event_handler: H, disable_gpu_acceleration: bool) -> Result<impl CefContext, InitError> {
pub(crate) fn create(self, event_handler: H, wgpu_context: WgpuContext, disable_gpu_acceleration: bool) -> Result<impl CefContext, InitError> {
let instance_dir = TempDir::new().expect("Failed to create temporary directory for CEF instance");
let accelerated_paint = accelerated_paint(disable_gpu_acceleration);
self.build_inner(&event_handler, instance_dir.as_ref(), accelerated_paint)?;
create_browser(event_handler, instance_dir, accelerated_paint)
create_browser(event_handler, wgpu_context, instance_dir, accelerated_paint)
}

#[cfg(not(target_os = "macos"))]
pub(crate) fn create(self, event_handler: H, disable_gpu_acceleration: bool) -> Result<impl CefContext, InitError> {
pub(crate) fn create(self, event_handler: H, wgpu_context: WgpuContext, disable_gpu_acceleration: bool) -> Result<impl CefContext, InitError> {
let instance_dir = TempDir::new().expect("Failed to create temporary directory for CEF instance");
let accelerated_paint = accelerated_paint(disable_gpu_acceleration);
self.build_inner(&event_handler, instance_dir.as_ref(), accelerated_paint)?;
super::multithreaded::run_on_ui_thread(move || match create_browser(event_handler, instance_dir, accelerated_paint) {
super::multithreaded::run_on_ui_thread(move || match create_browser(event_handler, wgpu_context, instance_dir, accelerated_paint) {
Ok(context) => super::multithreaded::CONTEXT.with(|b| *b.borrow_mut() = Some(context)),
Err(e) => panic!("Failed to initialize CEF context: {:?}", e),
});
Expand Down Expand Up @@ -149,8 +150,8 @@ fn platform_settings(instance_dir: &Path) -> Settings {
}
}

fn create_browser<H: CefEventHandler>(event_handler: H, instance_dir: TempDir, accelerated_paint: bool) -> Result<SingleThreadedCefContext, InitError> {
let mut client = Client::new(BrowserProcessClientImpl::new(&event_handler));
fn create_browser<H: CefEventHandler>(event_handler: H, wgpu_context: WgpuContext, instance_dir: TempDir, accelerated_paint: bool) -> Result<SingleThreadedCefContext, InitError> {
let mut client = Client::new(BrowserProcessClientImpl::new(&event_handler, wgpu_context));

let window_info = WindowInfo {
windowless_rendering_enabled: 1,
Expand Down
5 changes: 3 additions & 2 deletions desktop/src/cef/internal/browser_process_client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use cef::{ContextMenuHandler, DisplayHandler, ImplClient, LifeSpanHandler, LoadH

use crate::cef::CefEventHandler;
use crate::cef::ipc::{MessageType, UnpackMessage, UnpackedMessage};
use crate::wrapper::WgpuContext;

use super::context_menu_handler::ContextMenuHandlerImpl;
use super::display_handler::DisplayHandlerImpl;
Expand All @@ -21,12 +22,12 @@ pub(crate) struct BrowserProcessClientImpl<H: CefEventHandler> {
request_handler: RequestHandler,
}
impl<H: CefEventHandler> BrowserProcessClientImpl<H> {
pub(crate) fn new(event_handler: &H) -> Self {
pub(crate) fn new(event_handler: &H, wgpu_context: WgpuContext) -> Self {
Self {
object: std::ptr::null_mut(),
event_handler: event_handler.duplicate(),
load_handler: LoadHandler::new(LoadHandlerImpl::new(event_handler.duplicate())),
render_handler: RenderHandler::new(RenderHandlerImpl::new(event_handler.duplicate())),
render_handler: RenderHandler::new(RenderHandlerImpl::new(event_handler.duplicate(), wgpu_context)),
display_handler: DisplayHandler::new(DisplayHandlerImpl::new(event_handler.duplicate())),
request_handler: RequestHandler::new(RequestHandlerImpl::new()),
}
Expand Down
30 changes: 15 additions & 15 deletions desktop/src/cef/internal/render_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,20 @@ use cef::rc::{Rc, RcImpl};
use cef::sys::{_cef_render_handler_t, cef_base_ref_counted_t};
use cef::{Browser, ImplRenderHandler, PaintElementType, Rect, WrapRenderHandler};

use crate::cef::CefEventHandler;
use crate::render::FrameBufferRef;
use crate::cef::{CefEventHandler, View};
use crate::wrapper::WgpuContext;

pub(crate) struct RenderHandlerImpl<H: CefEventHandler> {
object: *mut RcImpl<_cef_render_handler_t, Self>,
event_handler: H,
view: View,
}
impl<H: CefEventHandler> RenderHandlerImpl<H> {
pub(crate) fn new(event_handler: H) -> Self {
pub(crate) fn new(event_handler: H, wgpu_context: WgpuContext) -> Self {
Self {
object: std::ptr::null_mut(),
event_handler,
view: View::new(wgpu_context),
}
}
}
Expand All @@ -31,29 +33,26 @@ impl<H: CefEventHandler> ImplRenderHandler for RenderHandlerImpl<H> {
}
}

fn on_paint(&self, _browser: Option<&mut Browser>, _type_: PaintElementType, _dirty_rects: Option<&[Rect]>, buffer: *const u8, width: std::ffi::c_int, height: std::ffi::c_int) {
fn on_paint(&self, _browser: Option<&mut Browser>, type_: PaintElementType, dirty_rects: Option<&[Rect]>, buffer: *const u8, width: std::ffi::c_int, height: std::ffi::c_int) {
if type_ != PaintElementType::default() {
return;
}

let buffer_size = (width * height * 4) as usize;
let buffer_slice = unsafe { std::slice::from_raw_parts(buffer, buffer_size) };
let frame_buffer = FrameBufferRef::new(buffer_slice, width as usize, height as usize).expect("Failed to create frame buffer");

self.event_handler.draw(frame_buffer)
self.view.upload_frame_buffer(buffer_slice, width as u32, height as u32, dirty_rects.unwrap_or(&[]));
self.event_handler.draw(&self.view)
}

#[cfg(feature = "accelerated_paint")]
fn on_accelerated_paint(&self, _browser: Option<&mut Browser>, type_: PaintElementType, _dirty_rects: Option<&[Rect]>, info: Option<&cef::AcceleratedPaintInfo>) {
use cef::osr_texture_import::SharedTextureHandle;

if type_ != PaintElementType::default() {
return;
}

let shared_handle = SharedTextureHandle::new(info.unwrap());
if let SharedTextureHandle::Unsupported = shared_handle {
tracing::error!("Platform does not support accelerated painting");
return;
}

self.event_handler.draw_gpu(shared_handle);
self.view.import_shared_texture(info.unwrap());
self.event_handler.draw(&self.view)
Comment on lines +54 to +55

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Accelerated paint can crash the app when no texture info is provided, because info.unwrap() panics in a callback path. Handling None locally (log + return) keeps rendering resilient.

(Based on your team's feedback about avoiding panicking Option/Result handling in application code.)

View Feedback: 1 2

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At desktop/src/cef/internal/render_handler.rs, line 54:

<comment>Accelerated paint can crash the app when no texture info is provided, because `info.unwrap()` panics in a callback path. Handling `None` locally (log + return) keeps rendering resilient.

(Based on your team's feedback about avoiding panicking Option/Result handling in application code.) </comment>

<file context>
@@ -31,29 +33,26 @@ impl<H: CefEventHandler> ImplRenderHandler for RenderHandlerImpl<H> {
-		}
-
-		self.event_handler.draw_gpu(shared_handle);
+		self.view.import_shared_texture(info.unwrap());
+		self.event_handler.draw(&self.view)
 	}
</file context>
Suggested change
self.view.import_shared_texture(info.unwrap());
self.event_handler.draw(&self.view)
if let Some(info) = info {
self.view.import_shared_texture(info);
self.event_handler.draw(&self.view)
} else {
tracing::error!("Missing accelerated paint info");
}

}

fn get_raw(&self) -> *mut _cef_render_handler_t {
Expand All @@ -70,6 +69,7 @@ impl<H: CefEventHandler> Clone for RenderHandlerImpl<H> {
Self {
object: self.object,
event_handler: self.event_handler.duplicate(),
view: self.view.clone(),
}
}
}
Expand Down
108 changes: 108 additions & 0 deletions desktop/src/cef/view.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
use cef::Rect;
use std::sync::{Arc, Mutex};

use crate::wrapper::WgpuContext;

#[derive(Clone)]
pub(crate) struct View {
context: WgpuContext,
texture: Arc<Mutex<Option<wgpu::Texture>>>,
}

impl View {
pub(crate) fn new(context: WgpuContext) -> Self {
Self {
context,
texture: Arc::new(Mutex::new(None)),
}
}

pub(crate) fn texture(&self) -> Option<wgpu::Texture> {
let Ok(texture) = self.texture.lock() else {
tracing::error!("Failed to lock view texture");
return None;
};
texture.clone()
}

pub(super) fn upload_frame_buffer(&self, buffer: &[u8], width: u32, height: u32, dirty_rects: &[Rect]) {
debug_assert_eq!(buffer.len(), width as usize * height as usize * 4);

let Ok(mut slot) = self.texture.lock() else {
tracing::error!("Failed to lock view texture");
return;
};

let needs_new_texture = slot.as_ref().is_none_or(|texture| texture.width() != width || texture.height() != height);
if needs_new_texture {
*slot = Some(self.context.device.create_texture(&wgpu::TextureDescriptor {
label: Some("CEF Texture"),
size: wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: wgpu::TextureFormat::Bgra8Unorm,
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
}));
}
let texture = slot.as_ref().expect("Texture was just created");

let full_frame = [Rect {
x: 0,
y: 0,
width: width as i32,
height: height as i32,
}];
let rects = if needs_new_texture || dirty_rects.is_empty() { &full_frame } else { dirty_rects };

for rect in rects {
let x = (rect.x.max(0) as u32).min(width);
let y = (rect.y.max(0) as u32).min(height);
let rect_width = (rect.width.max(0) as u32).min(width - x);
let rect_height = (rect.height.max(0) as u32).min(height - y);
if rect_width == 0 || rect_height == 0 {
continue;
}
Comment on lines +64 to +70

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current clamping logic does not correctly handle cases where the dirty rectangle starts off-screen (e.g., rect.x < 0). If rect.x is negative, x is clamped to 0, but rect_width is not adjusted to account for the shifted start. This causes the copy operation to upload pixels outside the actual dirty region, and can even result in copying pixels when the dirty rect is entirely off-screen (e.g., rect.x = -100, rect.width = 50).

Using proper rectangle intersection ensures that only the visible portion of the dirty rectangle is uploaded, avoiding unnecessary GPU writes.

			let x1 = rect.x.max(0).min(width as i32);
			let y1 = rect.y.max(0).min(height as i32);
			let x2 = (rect.x + rect.width).max(0).min(width as i32);
			let y2 = (rect.y + rect.height).max(0).min(height as i32);

			let rect_width = (x2 - x1).max(0) as u32;
			let rect_height = (y2 - y1).max(0) as u32;
			if rect_width == 0 || rect_height == 0 {
				continue;
			}
			let x = x1 as u32;
			let y = y1 as u32;

self.context.queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture,
mip_level: 0,
origin: wgpu::Origin3d { x, y, z: 0 },
aspect: wgpu::TextureAspect::All,
},
buffer,
wgpu::TexelCopyBufferLayout {
offset: 4 * (y as u64 * width as u64 + x as u64),
bytes_per_row: Some(4 * width),
rows_per_image: None,
},
wgpu::Extent3d {
width: rect_width,
height: rect_height,
depth_or_array_layers: 1,
},
);
}
}

#[cfg(feature = "accelerated_paint")]
pub(super) fn import_shared_texture(&self, info: &cef::AcceleratedPaintInfo) {
let texture = match cef::osr_texture_import::SharedTextureHandle::new(info).import_texture(&self.context.device) {
Ok(texture) => texture,
Err(e) => {
tracing::error!("Failed to import shared texture: {e}");
return;
}
};
let Ok(mut slot) = self.texture.lock() else {
tracing::error!("Failed to lock view texture");
return;
};
*slot = Some(texture);
}
}
4 changes: 2 additions & 2 deletions desktop/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ pub fn start() {
println!("UI acceleration is disabled");
}

let cef_handler = cef::CefHandler::new(wgpu_context.clone(), app_event_scheduler.clone(), cef_view_info_receiver);
let cef_context = match cef_context_builder.create(cef_handler, prefs.disable_ui_acceleration) {
let cef_handler = cef::CefHandler::new(app_event_scheduler.clone(), cef_view_info_receiver);
let cef_context = match cef_context_builder.create(cef_handler, wgpu_context.clone(), prefs.disable_ui_acceleration) {
Ok(context) => {
tracing::info!("CEF initialized successfully");
context
Expand Down
3 changes: 0 additions & 3 deletions desktop/src/render.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,2 @@
mod frame_buffer_ref;
pub(crate) use frame_buffer_ref::FrameBufferRef;

mod state;
pub(crate) use state::{RenderError, RenderState};
Loading
Loading