Skip to content
Merged
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
62 changes: 49 additions & 13 deletions crates/kas-core/src/draw/draw_shared.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

use super::color::Rgba;
use super::{DrawImpl, PassId};
use crate::cast::Cast;
use crate::ActionRedraw;
use crate::config::RasterConfig;
use crate::geom::{Quad, Size, Vec2};
use crate::text::{Effect, TextDisplay};
Expand Down Expand Up @@ -62,6 +62,17 @@ pub enum ImageFormat {
#[error("failed to allocate: size too large or zero-sized")]
pub struct AllocError;

/// Upload failed
#[derive(Error, Debug)]
pub enum UploadError {
/// No allocation found for the [`ImageId`] used
#[error("image_upload: allocation not found")]
Missing,
/// Size of the uploaded image is wrong
#[error("image_upload: size does not match the allocation")]
Size,
}

/// Shared draw state
///
/// A single [`SharedState`] instance is shared by all windows and draw contexts.
Expand Down Expand Up @@ -93,17 +104,28 @@ pub trait DrawShared {
/// Allocate an image
///
/// Use [`SharedState::image_upload`] to set contents of the new image.
fn image_alloc(&mut self, size: (u32, u32)) -> Result<ImageHandle, AllocError>;
fn image_alloc(&mut self, size: Size) -> Result<ImageHandle, AllocError>;

/// Upload an image to the GPU
///
/// This should be called at least once on each image before display. May be
/// called again to update the image contents.
///
/// `handle` must refer to an allocation of some size `(w, h)`, such that
/// `data.len() == b * w * h` where `b` is the number of bytes per pixel,
/// according to `format`. Data must be in row-major order.
fn image_upload(&mut self, handle: &ImageHandle, data: &[u8], format: ImageFormat);
/// `handle` must refer to an allocation of some size matching `size`.
///
/// The image `data` must have `data.len() == b * w * h` where
/// `(w, h) == size.cast()` and `b` is the number of bytes per pixel
/// (according to `format`). Data must be in row-major order.
///
/// On success, this returns an [`ActionRedraw`] to indicate that any
/// widgets using this image will require a redraw.
fn image_upload(
&mut self,
handle: &ImageHandle,
size: Size,
data: &[u8],
format: ImageFormat,
) -> Result<ActionRedraw, UploadError>;

/// Potentially free an image
///
Expand All @@ -117,15 +139,23 @@ pub trait DrawShared {

impl<DS: DrawSharedImpl> DrawShared for SharedState<DS> {
#[inline]
fn image_alloc(&mut self, size: (u32, u32)) -> Result<ImageHandle, AllocError> {
fn image_alloc(&mut self, size: Size) -> Result<ImageHandle, AllocError> {
self.draw
.image_alloc(size)
.map(|id| ImageHandle(id, Rc::new(())))
}

#[inline]
fn image_upload(&mut self, handle: &ImageHandle, data: &[u8], format: ImageFormat) {
self.draw.image_upload(handle.0, data, format);
fn image_upload(
&mut self,
handle: &ImageHandle,
size: Size,
data: &[u8],
format: ImageFormat,
) -> Result<ActionRedraw, UploadError> {
self.draw
.image_upload(handle.0, size, data, format)
.map(|_| ActionRedraw)
}

#[inline]
Expand All @@ -137,7 +167,7 @@ impl<DS: DrawSharedImpl> DrawShared for SharedState<DS> {

#[inline]
fn image_size(&self, handle: &ImageHandle) -> Option<Size> {
self.draw.image_size(handle.0).map(|size| size.cast())
self.draw.image_size(handle.0)
}
}

Expand All @@ -156,19 +186,25 @@ pub trait DrawSharedImpl: Any {
/// Allocate an image
///
/// Use [`DrawSharedImpl::image_upload`] to set contents of the new image.
fn image_alloc(&mut self, size: (u32, u32)) -> Result<ImageId, AllocError>;
fn image_alloc(&mut self, size: Size) -> Result<ImageId, AllocError>;

/// Upload an image to the GPU
///
/// This should be called at least once on each image before display. May be
/// called again to update the image contents.
fn image_upload(&mut self, id: ImageId, data: &[u8], format: ImageFormat);
fn image_upload(
&mut self,
id: ImageId,
size: Size,
data: &[u8],
format: ImageFormat,
) -> Result<(), UploadError>;

/// Free an image allocation
fn image_free(&mut self, id: ImageId);

/// Query an image's size
fn image_size(&self, id: ImageId) -> Option<(u32, u32)>;
fn image_size(&self, id: ImageId) -> Option<Size>;

/// Draw the image in the given `rect`
fn draw_image(&self, draw: &mut Self::Draw, pass: PassId, id: ImageId, rect: Quad);
Expand Down
6 changes: 3 additions & 3 deletions crates/kas-core/src/draw/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,12 @@ mod draw_rounded;
mod draw_shared;

use crate::cast::Cast;
use crate::geom::Quad;
use crate::geom::{Quad, Size};
#[allow(unused)] use crate::theme::DrawCx;

pub use draw::{Draw, DrawIface, DrawImpl};
pub use draw_rounded::{DrawRounded, DrawRoundedImpl};
pub use draw_shared::{AllocError, ImageFormat, ImageHandle, ImageId};
pub use draw_shared::{AllocError, ImageFormat, ImageHandle, ImageId, UploadError};
pub use draw_shared::{DrawShared, DrawSharedImpl, SharedState};
use std::time::{Duration, Instant};

Expand All @@ -73,7 +73,7 @@ pub struct Allocation {
#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
#[cfg_attr(docsrs, doc(cfg(internal_doc)))]
pub trait Allocator {
fn allocate(&mut self, size: (u32, u32)) -> Result<Allocation, AllocError>;
fn allocate(&mut self, size: Size) -> Result<Allocation, AllocError>;
fn deallocate(&mut self, atlas: u32, alloc: u32);
}

Expand Down
1 change: 1 addition & 0 deletions crates/kas-image/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ png = ["dep:image", "image/png"]
webp = ["dep:image", "image/webp"]

[dependencies]
log = "0.4"
tiny-skia = { version = "0.11.0" }
resvg = { version = "0.45.0", optional = true }
usvg = { version = "0.45.0", optional = true }
Expand Down
17 changes: 9 additions & 8 deletions crates/kas-image/src/canvas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,11 +208,11 @@ mod Canvas {
fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
if let Some((program, mut pixmap)) = cx.try_pop::<(P, Pixmap)>() {
debug_assert!(matches!(self.inner.get_mut(), State::Rendering));
let size = (pixmap.width(), pixmap.height());
let size = (pixmap.width(), pixmap.height()).cast();
let ds = cx.draw_shared();

if let Some(im_size) = self.image.as_ref().and_then(|h| ds.image_size(h))
&& im_size != Size::conv(size)
&& im_size != size
&& let Some(handle) = self.image.take()
{
ds.image_free(handle);
Expand All @@ -223,17 +223,18 @@ mod Canvas {
}

if let Some(handle) = self.image.as_ref() {
ds.image_upload(handle, pixmap.data(), ImageFormat::Rgba8);
match ds.image_upload(handle, size, pixmap.data(), ImageFormat::Rgba8) {
Ok(_) => cx.redraw(),
Err(err) => log::warn!("Canvas: image upload failed: {err}"),
}
}

cx.redraw();

let rect_size: (u32, u32) = self.rect().size.cast();
let own_size = self.rect().size;
let state = self.inner.get_mut();
if rect_size != size {
if own_size != size {
// Possible if a redraw was in progress when set_rect was called

pixmap = if let Some(px) = Pixmap::new(rect_size.0, rect_size.1) {
pixmap = if let Some(px) = Pixmap::new(own_size.0.cast(), own_size.1.cast()) {
px
} else {
*state = State::Initial(program);
Expand Down
20 changes: 12 additions & 8 deletions crates/kas-image/src/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,18 +168,22 @@ mod Image {
// TODO(opt): we converted to RGBA8 since this is the only format common
// to both the image and wgpu crates. It may not be optimal however.
// It also assumes that the image colour space is sRGB.
let size = image.dimensions();
let size = image.dimensions().cast();

let draw = cx.draw_shared();
match draw.image_alloc(size) {
Ok(handle) => {
draw.image_upload(&handle, &image, kas::draw::ImageFormat::Rgba8);
self.raw.set(cx, handle);
}
let handle = match draw.image_alloc(size) {
Ok(handle) => handle,
Err(err) => {
warn_about_error("Failed to allocate image", &err);
log::warn!("Image: allocate failed: {err}");
return;
}
}
};

match draw.image_upload(&handle, size, &image, kas::draw::ImageFormat::Rgba8) {
Ok(_) => cx.redraw(),
Err(err) => log::warn!("Image: image upload failed: {err}"),
};
self.raw.set(cx, handle);
} else {
self.raw.clear(cx);
}
Expand Down
18 changes: 18 additions & 0 deletions crates/kas-image/src/sprite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,24 @@ mod Sprite {
}
}

/// Access the current [`ImageHandle`], if any
///
/// This handle may be used with [`DrawShared`](kas::draw::DrawShared)
/// methods.
#[inline]
pub fn handle(&self) -> Option<&ImageHandle> {
self.handle.as_ref()
}

/// Get the image buffer size
///
/// This is the size of the image last assigned using [`Sprite::set`].
/// Initially it is [`Size::ZERO`].
#[inline]
pub fn image_size(&self) -> Size {
self.image_size
}

/// Remove image (set empty)
pub fn clear(&mut self, cx: &mut EventCx) {
if let Some(handle) = self.handle.take() {
Expand Down
15 changes: 8 additions & 7 deletions crates/kas-image/src/svg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,11 +299,11 @@ mod Svg {

fn handle_messages(&mut self, cx: &mut EventCx, _: &Self::Data) {
if let Some(pixmap) = cx.try_pop::<Pixmap>() {
let size = (pixmap.width(), pixmap.height());
let size = (pixmap.width(), pixmap.height()).cast();
let ds = cx.draw_shared();

if let Some(im_size) = self.image.as_ref().and_then(|h| ds.image_size(h))
&& im_size != Size::conv(size)
&& im_size != size
&& let Some(handle) = self.image.take()
{
ds.image_free(handle);
Expand All @@ -314,20 +314,21 @@ mod Svg {
}

if let Some(handle) = self.image.as_ref() {
ds.image_upload(handle, pixmap.data(), ImageFormat::Rgba8);
match ds.image_upload(handle, size, pixmap.data(), ImageFormat::Rgba8) {
Ok(_) => cx.redraw(),
Err(err) => log::warn!("Svg: image upload failed: {err}"),
}
}

cx.redraw();
self.inner = match std::mem::take(&mut self.inner) {
State::None => State::None,
State::Initial(source) | State::Rendering(source) | State::Ready(source, _) => {
State::Ready(source, pixmap)
}
};

let own_size: (u32, u32) = self.rect().size.cast();
if size != own_size
&& let Some(fut) = self.inner.resize(own_size)
if size != self.rect().size
&& let Some(fut) = self.inner.resize(self.rect().size.cast())
{
cx.send_spawn(self.id(), fut);
}
Expand Down
Loading