use crate::drawing_context_impl::{CreateInfo, DrawingContextImpl, VelloBackdrop};
use crate::gpu::{log_render_failure, VelloDeviceTexture, VelloGpuTexture, VelloTextureAlpha, VelloWgpuDevice};
use crate::helpers::image_saving_helper;
use crate::helpers::pixel_format_helper::{scene_size, to_image};
use crate::i_drawable_bitmap_impl::IDrawableBitmapImpl;
use crate::immutable_bitmap::ImmutableBitmap;
use crate::scene::{VelloSceneBrush, VelloSceneImage, VelloSceneTexture};
use crate::vello_options::VelloRenderingMode;
use ferroui_base::media::imaging::BitmapEncoderOptions;
use ferroui_base::platform::{
IBitmapImpl, IDrawingContextImpl, IDrawingContextLayerImpl, IDrawingContextLayerWithRenderContextAffinityImpl,
};
use ferroui_base::{PixelSize, Vector};
use peniko::{BlendMode, Extend, ImageData, ImageQuality};
use std::any::Any;
use std::cell::{Cell, RefCell};
use std::io::{self, Write};
use std::rc::Rc;
use std::sync::Arc;
struct DeviceSurface {
texture: Option<Arc<VelloDeviceTexture>>,
spare: Option<Arc<VelloDeviceTexture>>,
has_content: bool,
image: Option<ImageData>,
}
pub struct DeviceSurfaceRenderTarget {
device: Arc<VelloWgpuDevice>,
mode: VelloRenderingMode,
surface: Rc<RefCell<DeviceSurface>>,
rendering_modes: Vec<VelloRenderingMode>,
dpi: Vector,
pixel_size: PixelSize,
version: Rc<Cell<i32>>,
use_scaled_drawing: bool,
}
impl DeviceSurfaceRenderTarget {
pub fn is_available(mode: VelloRenderingMode, device: &VelloWgpuDevice) -> bool {
match mode {
VelloRenderingMode::Cpu => false,
VelloRenderingMode::Hybrid => cfg!(feature = "hybrid"),
VelloRenderingMode::Gpu => cfg!(feature = "gpu") && device.supports_compute(),
}
}
fn alpha(mode: VelloRenderingMode) -> VelloTextureAlpha {
match mode {
VelloRenderingMode::Gpu => VelloTextureAlpha::Straight,
_ => VelloTextureAlpha::Premultiplied,
}
}
pub fn new(
device: Arc<VelloWgpuDevice>,
mode: VelloRenderingMode,
pixel_size: PixelSize,
dpi: Vector,
rendering_modes: Vec<VelloRenderingMode>,
use_scaled_drawing: bool,
) -> Self {
let pixel_size = PixelSize::new(pixel_size.width.max(1), pixel_size.height.max(1));
let (width, height) = scene_size(pixel_size);
let texture = VelloDeviceTexture::new(&device, u32::from(width), u32::from(height), Self::alpha(mode));
Self {
device,
mode,
surface: Rc::new(RefCell::new(DeviceSurface {
texture: Some(texture),
spare: None,
has_content: false,
image: None,
})),
rendering_modes,
dpi,
pixel_size,
version: Rc::new(Cell::new(1)),
use_scaled_drawing,
}
}
pub fn texture(&self) -> Option<Arc<VelloDeviceTexture>> {
self.surface.borrow().texture.clone()
}
pub fn device(&self) -> &Arc<VelloWgpuDevice> {
&self.device
}
fn read_back(surface: &RefCell<DeviceSurface>, pixel_size: PixelSize) -> Option<ImageData> {
let mut surface = surface.borrow_mut();
if surface.image.is_none() {
let texture = surface.texture.clone()?;
let pixels = if surface.has_content {
texture.read_pixels()?
} else {
vec![0u8; texture.width() as usize * texture.height() as usize * 4]
};
surface.image = Some(to_image(pixels, pixel_size));
}
surface.image.clone()
}
pub(crate) fn brush(
&self,
context: &mut DrawingContextImpl,
(x_extend, y_extend): (Extend, Extend),
quality: ImageQuality,
alpha: f32,
) -> Option<VelloSceneBrush> {
if context.draws_on_device(&self.device) {
let texture = self.texture()?;
return Some(VelloSceneBrush::Texture(VelloSceneTexture { texture, x_extend, y_extend, quality, alpha }));
}
let image = self.image()?;
Some(VelloSceneBrush::Image(VelloSceneImage { image, x_extend, y_extend, quality, alpha }))
}
}
impl IBitmapImpl for DeviceSurfaceRenderTarget {
fn dpi(&self) -> Vector {
self.dpi
}
fn pixel_size(&self) -> PixelSize {
self.pixel_size
}
fn version(&self) -> i32 {
self.version.get()
}
fn save(&self, stream: &mut dyn Write, options: &BitmapEncoderOptions) -> io::Result<()> {
let image = self.image().ok_or_else(|| io::Error::other("DeviceSurfaceRenderTarget has been disposed"))?;
image_saving_helper::save_image(&image, stream, options)
}
fn dispose(&self) {
let mut surface = self.surface.borrow_mut();
surface.texture = None;
surface.spare = None;
surface.image = None;
}
fn as_any(&self) -> &dyn Any {
self
}
}
impl IDrawingContextLayerImpl for DeviceSurfaceRenderTarget {
fn blit(&self, context: &mut dyn IDrawingContextImpl) {
let context = context
.as_any_mut()
.downcast_mut::<DrawingContextImpl>()
.unwrap_or_else(|| panic!("The layer can only be blitted onto a drawing context of the Vello backend"));
if let Some(brush) = self.brush(context, (Extend::Pad, Extend::Pad), ImageQuality::Low, 1.0) {
let (width, height) = (self.pixel_size.width as f64, self.pixel_size.height as f64);
context.draw_brush_in_device_space(brush, width, height, BlendMode::default());
}
}
fn can_blit(&self) -> bool {
true
}
fn is_corrupted(&self) -> bool {
self.device.is_lost()
}
fn create_shared_snapshot(&self) -> Arc<ferroui_base::platform::SharedBitmapImpl> {
let image = self.image().unwrap_or_else(|| panic!("DeviceSurfaceRenderTarget has been disposed or its device lost"));
Arc::new(ImmutableBitmap::from_image(image))
}
fn create_drawing_context(&self) -> Box<dyn IDrawingContextImpl> {
Box::new(self.create_drawing_context_with(Vec::new()))
}
fn as_layer_with_render_context_affinity(&self) -> Option<&dyn IDrawingContextLayerWithRenderContextAffinityImpl> {
Some(self)
}
}
impl DeviceSurfaceRenderTarget {
pub fn has_content(&self) -> bool {
self.surface.borrow().has_content
}
pub fn create_drawing_context_with(&self, disposables: Vec<Box<dyn FnOnce()>>) -> DrawingContextImpl {
let texture = self.texture().unwrap_or_else(|| panic!("DeviceSurfaceRenderTarget has been disposed"));
let (width, height) = scene_size(self.pixel_size);
let sink =
crate::gpu::create_window_scene_sink(&self.device, &[self.mode], width, height, texture.texture().format());
let composes_over_target = sink.capabilities().retained_targets;
let has_content = self.surface.borrow().has_content;
let target = if composes_over_target {
texture.clone()
} else {
let mut surface = self.surface.borrow_mut();
surface
.spare
.get_or_insert_with(|| {
VelloDeviceTexture::new(&self.device, u32::from(width), u32::from(height), Self::alpha(self.mode))
})
.clone()
};
let backdrop = has_content.then(|| {
if composes_over_target {
let (surface, pixel_size) = (self.surface.clone(), self.pixel_size);
VelloBackdrop {
composes_over_target: true,
brush: Box::new(move || {
Self::read_back(&surface, pixel_size).map(|image| {
VelloSceneBrush::Image(VelloSceneImage {
image,
x_extend: Extend::Pad,
y_extend: Extend::Pad,
quality: ImageQuality::Low,
alpha: 1.0,
})
})
}),
}
} else {
let content = texture.clone();
VelloBackdrop {
composes_over_target: false,
brush: Box::new(move || {
Some(VelloSceneBrush::Texture(VelloSceneTexture {
texture: content,
x_extend: Extend::Pad,
y_extend: Extend::Pad,
quality: ImageQuality::Low,
alpha: 1.0,
}))
}),
}
}
});
let surface = self.surface.clone();
let mode = self.mode;
let create_info = CreateInfo {
sink,
backdrop,
on_finished: Box::new(move |sink| {
let rendered = sink.render_to_texture(&VelloGpuTexture { texture: target.texture() });
if let Err(error) = &rendered {
log_render_failure(if mode == VelloRenderingMode::Gpu { "GPU" } else { "hybrid" }, error);
}
let mut surface = surface.borrow_mut();
surface.image = None;
if rendered.is_ok() && surface.texture.is_some() {
surface.has_content = true;
if !composes_over_target {
surface.spare = surface.texture.replace(target);
}
}
}),
scale_drawing_to_dpi: self.use_scaled_drawing,
dpi: self.dpi,
rendering_modes: self.rendering_modes.clone(),
};
let version = self.version.clone();
let mut all: Vec<Box<dyn FnOnce()>> = vec![Box::new(move || version.set(version.get() + 1))];
all.extend(disposables);
DrawingContextImpl::new(create_info, all)
}
}
impl IDrawingContextLayerWithRenderContextAffinityImpl for DeviceSurfaceRenderTarget {
fn has_render_context_affinity(&self) -> bool {
true
}
fn create_non_affined_snapshot(&self) -> Arc<ferroui_base::platform::SharedBitmapImpl> {
self.create_shared_snapshot()
}
}
impl IDrawableBitmapImpl for DeviceSurfaceRenderTarget {
fn image(&self) -> Option<ImageData> {
Self::read_back(&self.surface, self.pixel_size)
}
}