use std::{collections::hash_map::Entry, sync::Arc};
use rustc_hash::FxHashMap;
use webrender::{
RenderApi,
api::{
self as wr, DocumentId, ExternalImage, ExternalImageData, ExternalImageHandler, ExternalImageId, ExternalImageSource,
ExternalImageType, ImageKey,
units::{ImageDirtyRect, TexelRect},
},
};
use zng_unit::{Px, PxPoint, PxRect, PxSize};
use zng_view_api::{ImageRendering, image::ImageTextureId};
use crate::{
display_list::{DisplayListCache, SpaceAndClip},
px_wr::PxToWr as _,
};
use super::{Image, ImageData};
pub(crate) struct WrImageCache {
locked: Vec<Arc<ImageData>>,
}
impl WrImageCache {
pub fn new_boxed() -> Box<dyn ExternalImageHandler> {
Box::new(WrImageCache { locked: vec![] })
}
}
impl ExternalImageHandler for WrImageCache {
fn lock(&mut self, key: ExternalImageId, _channel_index: u8, _is_composited: bool) -> ExternalImage<'_> {
let img = unsafe {
let ptr = key.0 as *const ImageData;
Arc::increment_strong_count(ptr);
Arc::<ImageData>::from_raw(ptr)
};
self.locked.push(img);
match &**self.locked.last().unwrap() {
ImageData::RawData { pixels, range, .. } => {
ExternalImage {
uv: TexelRect::invalid(), source: ExternalImageSource::RawData(&pixels[range.clone()]),
}
}
ImageData::NativeTexture { uv, texture: id } => ExternalImage {
uv: *uv,
source: ExternalImageSource::NativeTexture(*id),
},
}
}
fn unlock(&mut self, key: ExternalImageId, _channel_index: u8) {
if let Some(i) = self.locked.iter().position(|d| ExternalImageId(Arc::as_ptr(d) as _) == key) {
self.locked.swap_remove(i);
} else {
debug_assert!(false);
}
}
}
impl Image {
fn external_id(&self) -> ExternalImageId {
ExternalImageId(Arc::as_ptr(&self.0) as u64)
}
fn data(&self) -> webrender::api::ImageData {
webrender::api::ImageData::External(ExternalImageData {
id: self.external_id(),
channel_index: 0,
image_type: ExternalImageType::Buffer,
normalized_uvs: false,
})
}
}
struct ImageUse {
image: Image,
texture_id: ImageTextureId,
stripes: Box<[ImageTextureId]>,
mipmap: Vec<ImageUse>,
}
pub(crate) struct ImageUseMap {
id_tex: FxHashMap<ExternalImageId, ImageUse>,
tex_id: FxHashMap<ImageTextureId, ExternalImageId>,
}
impl ImageUseMap {
pub fn new() -> Self {
Self {
id_tex: FxHashMap::default(),
tex_id: FxHashMap::default(),
}
}
pub fn new_use(&mut self, image: &Image, document_id: DocumentId, api: &mut RenderApi) -> ImageTextureId {
let id = image.external_id();
match self.id_tex.entry(id) {
Entry::Occupied(e) => e.get().texture_id,
Entry::Vacant(e) => {
let key = api.generate_image_key();
let tex_id = ImageTextureId::from_raw(key.1);
e.insert(ImageUse {
image: image.clone(),
texture_id: tex_id,
stripes: Box::new([]),
mipmap: vec![],
});
self.tex_id.insert(tex_id, id);
let mut txn = webrender::Transaction::new();
txn.add_image(key, image.descriptor(), image.data(), None);
api.send_transaction(document_id, txn);
tex_id
}
}
}
pub fn update_use(
&mut self,
texture_id: ImageTextureId,
image: &Image,
dirty_rect: Option<PxRect>,
document_id: DocumentId,
api: &mut RenderApi,
) -> bool {
if let Entry::Occupied(mut e) = self.tex_id.entry(texture_id) {
let id = image.external_id();
if *e.get() != id {
let prev_image = self.id_tex.get(&id).unwrap();
if prev_image.image.descriptor() != image.descriptor() {
tracing::error!("cannot update image use {texture_id:?}, new image has different dimensions");
return false;
}
let prev_id = e.insert(id);
let prev_image = self.id_tex.remove(&prev_id).unwrap();
let mut txn = webrender::Transaction::new();
txn.update_image(
ImageKey(api.get_namespace_id(), texture_id.get()),
image.descriptor(),
image.data(),
&match dirty_rect {
Some(r) => ImageDirtyRect::Partial(r.to_box2d().cast().cast_unit()),
None => ImageDirtyRect::All,
},
);
for stripe in prev_image.stripes {
txn.delete_image(ImageKey(api.get_namespace_id(), stripe.get()));
}
for mip in prev_image.mipmap {
txn.delete_image(ImageKey(api.get_namespace_id(), mip.texture_id.get()));
for stripe in mip.stripes {
txn.delete_image(ImageKey(api.get_namespace_id(), stripe.get()));
}
}
self.id_tex.insert(
id,
ImageUse {
image: image.clone(),
texture_id,
stripes: Box::new([]),
mipmap: vec![],
},
);
api.send_transaction(document_id, txn);
}
true
} else {
tracing::error!("cannot update image use, texture not found");
false
}
}
pub fn delete(&mut self, texture_id: ImageTextureId, document_id: DocumentId, api: &mut RenderApi) {
if let Some(id) = self.tex_id.remove(&texture_id) {
let image = self.id_tex.remove(&id).unwrap(); let mut txn = webrender::Transaction::new();
txn.delete_image(ImageKey(api.get_namespace_id(), image.texture_id.get()));
for stripe in image.stripes {
txn.delete_image(ImageKey(api.get_namespace_id(), stripe.get()));
}
for mip in image.mipmap {
txn.delete_image(ImageKey(api.get_namespace_id(), mip.texture_id.get()));
for stripe in mip.stripes {
txn.delete_image(ImageKey(api.get_namespace_id(), stripe.get()));
}
}
api.send_transaction(document_id, txn);
}
}
#[expect(clippy::too_many_arguments)]
pub fn push_display_list_img(
&mut self,
document_id: DocumentId,
api: &mut RenderApi,
wr_list: &mut wr::DisplayListBuilder,
sc: &mut SpaceAndClip,
cache: &DisplayListCache,
clip_rect: PxRect,
image_id: ImageTextureId,
image_size: PxSize,
rendering: ImageRendering,
tile_size: PxSize,
tile_spacing: PxSize,
) {
let img = match self.tex_id.get(&image_id) {
Some(id) => self.id_tex.get_mut(id).unwrap(),
None => return,
};
if tile_spacing.is_empty() && tile_size == image_size {
let full_size = img.image.size();
if !img.image.should_stripe() {
let bounds = clip_rect.to_wr();
let clip = sc.clip_chain_id(wr_list);
let props = wr::CommonItemProperties {
clip_rect: bounds,
clip_chain_id: clip,
spatial_id: sc.spatial_id(),
flags: sc.primitive_flags(),
};
wr_list.push_image(
&props,
PxRect::from_size(image_size).to_wr(),
rendering.to_wr(),
wr::AlphaType::PremultipliedAlpha,
wr::ImageKey(cache.id_namespace(), img.texture_id.get()),
wr::ColorF::WHITE,
);
} else {
if img.stripes.is_empty() {
let stripes = img.image.wr_stripes();
if stripes.is_empty() {
return;
}
let mut stripe_ids = Vec::with_capacity(stripes.len());
let mut txn = webrender::Transaction::new();
for stripe in stripes {
let key = api.generate_image_key();
let tex_id = ImageTextureId::from_raw(key.1);
stripe_ids.push(tex_id);
txn.add_image(key, stripe.descriptor(), stripe.data(), None);
}
api.send_transaction(document_id, txn);
img.stripes = stripe_ids.into_boxed_slice();
}
let scale_x = full_size.width.0 as f32 / image_size.width.0 as f32;
let scale_y = full_size.height.0 as f32 / image_size.height.0 as f32;
let mut scaled_y = Px(0);
for (stripe_id, img) in img.stripes.iter().zip(img.image.wr_stripes()) {
let scaled_stripe = {
let mut r = img.size();
r.width /= scale_x;
r.height /= scale_y;
r
};
if scaled_y != Px(0) {
let spatial_id = wr_list.push_reference_frame(
PxPoint::zero().to_wr(),
sc.spatial_id(),
wr::TransformStyle::Flat,
wr::PropertyBinding::Value(wr::units::LayoutTransform::translation(0.0, scaled_y.0 as f32, 0.0)),
wr::ReferenceFrameKind::Transform {
is_2d_scale_translation: true,
should_snap: false,
paired_with_perspective: false,
},
sc.next_view_process_frame_id().to_wr(),
);
sc.push_spatial(spatial_id);
}
let mut clip_rect = clip_rect;
clip_rect.origin.y -= scaled_y;
let clip = sc.clip_chain_id(wr_list);
let props = wr::CommonItemProperties {
clip_rect: clip_rect.to_wr(),
clip_chain_id: clip,
spatial_id: sc.spatial_id(),
flags: sc.primitive_flags(),
};
wr_list.push_image(
&props,
PxRect::from_size(scaled_stripe).to_wr(),
rendering.to_wr(),
wr::AlphaType::PremultipliedAlpha,
wr::ImageKey(cache.id_namespace(), stripe_id.get()),
wr::ColorF::WHITE,
);
if scaled_y != Px(0) {
wr_list.pop_reference_frame();
sc.pop_spatial();
}
scaled_y += scaled_stripe.height;
}
}
} else {
if img.image.overflows_wr() {
tracing::error!("cannot tile or repeat image, too large");
return;
}
let bounds = clip_rect.to_wr();
let clip = sc.clip_chain_id(wr_list);
let props = wr::CommonItemProperties {
clip_rect: bounds,
clip_chain_id: clip,
spatial_id: sc.spatial_id(),
flags: sc.primitive_flags(),
};
wr_list.push_repeating_image(
&props,
PxRect::from_size(image_size).to_wr(),
tile_size.to_wr(),
tile_spacing.to_wr(),
rendering.to_wr(),
wr::AlphaType::PremultipliedAlpha,
wr::ImageKey(cache.id_namespace(), img.texture_id.get()),
wr::ColorF::WHITE,
);
}
}
}