Files
Graphite/desktop/ui/src/frames/surface.rs
Timon ba0a97aefe Desktop: Isolate CEF-rendered UI into separate crate and process (#4321)
* Extract CEF rendered UI into a separate process and crate

* Review

* Review

* Review

* Review

* Remove necessary workarounds

* Block on frame copy ack

* Crop and resample frames correctly

* Skip blank frames

* Fix deps

* Fix fmt

* Fix clippy warning

* Review

* Fix todo
2026-07-14 14:51:20 -07:00

264 lines
7.0 KiB
Rust

use std::sync::{Arc, Mutex};
#[cfg(feature = "accelerated_paint")]
use super::import::ContentMapping;
#[cfg(feature = "accelerated_paint")]
use super::resample::Resampler;
#[derive(Clone)]
pub(crate) struct FrameSurface {
device: wgpu::Device,
queue: wgpu_sync::Queue,
slot: Arc<Mutex<Option<wgpu::Texture>>>,
#[cfg(feature = "accelerated_paint")]
resampler: Resampler,
}
impl FrameSurface {
pub(crate) fn new(device: wgpu::Device, queue: wgpu_sync::Queue) -> Self {
Self {
#[cfg(feature = "accelerated_paint")]
resampler: Resampler::new(device.clone()),
device,
queue,
slot: Arc::new(Mutex::new(None)),
}
}
pub(crate) fn upload_buffer(&self, buffer: &[u8], width: u32, height: u32) -> Option<wgpu::Texture> {
debug_assert_eq!(buffer.len(), width as usize * height as usize * 4);
let Ok(mut slot) = self.slot.lock() else {
tracing::error!("Failed to lock the frame surface");
return None;
};
if buffer.chunks_exact(4).take(width as usize).all(|pixel| pixel[3] == 0) {
tracing::debug!("Skipping fully transparent frame");
return None;
}
if slot.as_ref().is_none_or(|texture| texture.width() != width || texture.height() != height) {
*slot = Some(self.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()?;
self.queue.write_texture(
wgpu::TexelCopyTextureInfo {
texture,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
buffer,
wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(4 * width),
rows_per_image: None,
},
wgpu::Extent3d {
width,
height,
depth_or_array_layers: 1,
},
);
Some(texture.clone())
}
#[cfg(feature = "accelerated_paint")]
pub(crate) fn import_texture(&self, importer: impl crate::frames::import::TextureImporter, content_rect: crate::frames::import::ContentRect) -> Option<wgpu::Texture> {
let imported = match importer.import_to_wgpu(&self.device) {
Ok(texture) => texture,
Err(e) => {
tracing::error!("Failed to import remote accelerated frame: {e}");
return None;
}
};
let mut encoder = self.device.create_command_encoder(&wgpu::CommandEncoderDescriptor {
label: Some("CEF Frame Copy Encoder"),
});
let output = match content_rect.mapping(imported.width(), imported.height()) {
ContentMapping::Identity => {
let output = self.device.create_texture(&wgpu::TextureDescriptor {
label: Some("CEF Imported Frame Copy"),
size: imported.size(),
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: imported.format(),
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::COPY_DST,
view_formats: &[],
});
encoder.copy_texture_to_texture(
wgpu::TexelCopyTextureInfo {
texture: &imported,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
wgpu::TexelCopyTextureInfo {
texture: &output,
mip_level: 0,
origin: wgpu::Origin3d::ZERO,
aspect: wgpu::TextureAspect::All,
},
imported.size(),
);
output
}
ContentMapping::Scaled(content_rect) => {
let output = self.device.create_texture(&wgpu::TextureDescriptor {
label: Some("CEF Imported Scaled Frame Copy"),
size: wgpu::Extent3d {
width: content_rect.source_width,
height: content_rect.source_height,
depth_or_array_layers: 1,
},
mip_level_count: 1,
sample_count: 1,
dimension: wgpu::TextureDimension::D2,
format: imported.format(),
usage: wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_SRC | wgpu::TextureUsages::RENDER_ATTACHMENT,
view_formats: &[],
});
let size = wgpu::Extent3d {
width: content_rect.width,
height: content_rect.height,
depth_or_array_layers: 1,
};
self.resampler.encode(
&mut encoder,
&imported,
wgpu::Origin3d {
x: content_rect.x,
y: content_rect.y,
z: 0,
},
size,
&output,
);
output
}
};
let blank_check = blank_check::encode_readback(&self.device, &mut encoder, &output);
let submission = self.queue.submit([encoder.finish()]);
let blank_check = blank_check.map();
let _ = self.device.poll(wgpu::PollType::Wait {
submission_index: Some(submission),
timeout: None,
});
if blank_check.check_is_blank() {
tracing::debug!("Skipping fully transparent accelerated frame");
return None;
}
let Ok(mut slot) = self.slot.lock() else {
tracing::error!("Failed to lock the frame surface");
return None;
};
*slot = Some(output.clone());
Some(output)
}
}
#[cfg(feature = "accelerated_paint")]
mod blank_check {
use std::sync::mpsc;
const STRIP_BYTES: u32 = wgpu::COPY_BYTES_PER_ROW_ALIGNMENT;
const STRIP_TEXELS: u32 = STRIP_BYTES / 4;
pub(super) fn encode_readback(device: &wgpu::Device, encoder: &mut wgpu::CommandEncoder, texture: &wgpu::Texture) -> PendingBlankCheck {
let buffer = device.create_buffer(&wgpu::BufferDescriptor {
label: Some("CEF Blank Check"),
size: STRIP_BYTES as u64,
usage: wgpu::BufferUsages::COPY_DST | wgpu::BufferUsages::MAP_READ,
mapped_at_creation: false,
});
let width = texture.width().min(STRIP_TEXELS);
encoder.copy_texture_to_buffer(
wgpu::TexelCopyTextureInfo {
texture,
mip_level: 0,
origin: wgpu::Origin3d {
x: (texture.width() - width) / 2,
y: 0,
z: 0,
},
aspect: wgpu::TextureAspect::All,
},
wgpu::TexelCopyBufferInfo {
buffer: &buffer,
layout: wgpu::TexelCopyBufferLayout {
offset: 0,
bytes_per_row: Some(STRIP_BYTES),
rows_per_image: None,
},
},
wgpu::Extent3d {
width,
height: 1,
depth_or_array_layers: 1,
},
);
PendingBlankCheck { buffer, width }
}
pub(super) struct PendingBlankCheck {
buffer: wgpu::Buffer,
width: u32,
}
impl PendingBlankCheck {
pub(super) fn map(self) -> MappedBlankCheck {
let (sender, receiver) = mpsc::channel();
self.buffer.slice(..u64::from(self.width) * 4).map_async(wgpu::MapMode::Read, move |result| {
let _ = sender.send(result);
});
MappedBlankCheck {
buffer: self.buffer,
width: self.width,
receiver,
}
}
}
pub(super) struct MappedBlankCheck {
buffer: wgpu::Buffer,
width: u32,
receiver: mpsc::Receiver<Result<(), wgpu::BufferAsyncError>>,
}
impl MappedBlankCheck {
pub(super) fn check_is_blank(self) -> bool {
match self.receiver.try_recv() {
Ok(Ok(())) => {
let slice = self.buffer.slice(..u64::from(self.width) * 4);
slice.get_mapped_range().chunks_exact(4).all(|texel| texel[3] == 0)
}
_ => false,
}
}
}
}