use std::cell::Cell;
use std::ptr::NonNull;
use std::rc::Rc;
use cef::{AcceleratedPaintInfo, ColorType, PaintElementType, Rect};
use waterui_graphics::gpu_surface::{GpuContext, GpuFrame, GpuView};
use wgpu_external_frame::io_surface::IoSurfaceFrame;
use super::presenter::{OwnedFrameMailbox, TexturePresenter, copy_source_texture};
use super::{request_browser_frame, sync_browser_viewport};
use crate::{AcceleratedFrameSink, CefPageHandle, CefPopupRect};
struct CefIoSurface {
surface: IoSurfaceFrame,
visible_width: u32,
visible_height: u32,
}
impl CefIoSurface {
unsafe fn retain(frame: &AcceleratedPaintInfo) -> Self {
let size = &frame.extra.coded_size;
let width = u32::try_from(size.width).expect("CEF IOSurface width must be positive");
let height = u32::try_from(size.height).expect("CEF IOSurface height must be positive");
let visible = &frame.extra.visible_rect;
let visible_width = u32::try_from(visible.width).unwrap_or(width).min(width);
let visible_height = u32::try_from(visible.height).unwrap_or(height).min(height);
let pointer = NonNull::new(frame.shared_texture_io_surface)
.expect("CEF accelerated paint returned a null IOSurface");
let format = if frame.format == ColorType::BGRA_8888 {
wgpu::TextureFormat::Bgra8Unorm
} else if frame.format == ColorType::RGBA_8888 {
wgpu::TextureFormat::Rgba8Unorm
} else {
panic!("CEF returned unsupported macOS accelerated color format")
};
let surface = unsafe { IoSurfaceFrame::retain(pointer, width, height, format) };
Self {
surface,
visible_width,
visible_height,
}
}
}
struct MacFrameSink {
device: wgpu::Device,
queue: wgpu::Queue,
mailbox: Rc<OwnedFrameMailbox>,
imported_size: Cell<(u32, u32)>,
}
impl AcceleratedFrameSink for MacFrameSink {
fn import(
&self,
element: PaintElementType,
_dirty_rects: &[Rect],
frame: &AcceleratedPaintInfo,
) {
let surface = unsafe { CefIoSurface::retain(frame) };
let size = (surface.surface.width(), surface.surface.height());
if self.imported_size.replace(size) != size {
tracing::debug!(
width = size.0,
height = size.1,
format = ?surface.surface.format(),
"Importing a resized accelerated CEF IOSurface"
);
}
let source = surface.surface.import(&self.device);
let owned = copy_source_texture(
&self.device,
&self.queue,
&source,
wgpu::Extent3d {
width: surface.visible_width,
height: surface.visible_height,
depth_or_array_layers: 1,
},
surface.surface.format(),
);
self.mailbox.publish(element, owned);
}
fn set_popup_rect(&self, rect: Option<CefPopupRect>) {
self.mailbox.set_popup_rect(rect);
}
}
pub(super) struct CefGpuView {
page: CefPageHandle,
mailbox: Rc<OwnedFrameMailbox>,
presenter: Option<TexturePresenter>,
}
impl CefGpuView {
pub(super) fn new(page: CefPageHandle) -> Self {
Self {
page,
mailbox: Rc::new(OwnedFrameMailbox::new()),
presenter: None,
}
}
}
impl GpuView for CefGpuView {
#[expect(
clippy::future_not_send,
reason = "CEF, Metal, and WaterUI view state are confined to the UI thread"
)]
async fn setup(&mut self, context: &GpuContext<'_>, _env: &mut waterui_core::Environment) {
assert_eq!(
context.adapter.get_info().backend,
wgpu::Backend::Metal,
"CEF IOSurface composition requires WaterUI's Metal backend"
);
let redraw = context.redraw_handle.clone();
self.mailbox
.set_waker(Rc::new(move || redraw.request_redraw()));
self.page.set_frame_sink(MacFrameSink {
device: context.device.clone(),
queue: context.queue.clone(),
mailbox: Rc::clone(&self.mailbox),
imported_size: Cell::new((0, 0)),
});
tracing::debug!("Installed the accelerated CEF frame sink");
self.presenter = Some(TexturePresenter::new(context));
}
fn render(&mut self, frame: &mut GpuFrame<'_>) {
self.page.pump();
request_browser_frame(&self.page, frame);
let scale = sync_browser_viewport(&self.page, frame);
let presenter = self
.presenter
.as_mut()
.expect("CEF GPU view rendered before setup");
if let Some(texture) = self.mailbox.take_view() {
presenter.set_source(texture);
}
if let Some(texture) = self.mailbox.take_popup() {
presenter.set_popup_source(texture);
}
presenter.set_popup_rect(self.mailbox.popup_rect());
presenter.render(frame, scale);
}
}