kas_core/draw/draw_shared.rs
1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License in the LICENSE-APACHE file or at:
4// https://www.apache.org/licenses/LICENSE-2.0
5
6//! Drawing APIs — shared draw state
7
8use super::color::Rgba;
9use super::{DrawImpl, PassId};
10use crate::ActionRedraw;
11use crate::config::RasterConfig;
12use crate::geom::{Quad, Size, Vec2};
13use crate::text::{Effect, TextDisplay};
14use std::any::Any;
15use std::num::NonZeroU32;
16use std::rc::Rc;
17use thiserror::Error;
18
19/// Identifier for an image allocation
20#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
21pub struct ImageId(NonZeroU32);
22
23/// Handle for an image
24///
25/// Serves both to identify an allocated image and to track the number of users
26/// via reference counting.
27#[derive(Clone, Debug, Hash, PartialEq, Eq, PartialOrd, Ord)]
28pub struct ImageHandle(ImageId, Rc<()>);
29
30impl ImageHandle {
31 /// Convert to an [`ImageId`]
32 #[inline]
33 pub fn id(&self) -> ImageId {
34 self.0
35 }
36}
37
38impl ImageId {
39 /// Construct a new identifier from `u32` value not equal to 0
40 #[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
41 #[cfg_attr(docsrs, doc(cfg(internal_doc)))]
42 #[inline]
43 pub const fn try_new(n: u32) -> Option<Self> {
44 // We can't use ? or .map in a const fn so do it the tedious way:
45 if let Some(nz) = NonZeroU32::new(n) {
46 Some(ImageId(nz))
47 } else {
48 None
49 }
50 }
51}
52
53/// Image formats available for upload
54#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
55pub enum ImageFormat {
56 /// 8-bit unsigned RGBA values (4 bytes per pixel)
57 Rgba8,
58}
59
60/// Allocation failed: too large or zero sized
61#[derive(Error, Debug)]
62#[error("failed to allocate: size too large or zero-sized")]
63pub struct AllocError;
64
65/// Upload failed
66#[derive(Error, Debug)]
67pub enum UploadError {
68 /// Image atlas not found
69 #[error("image_upload: unknown atlas {0}")]
70 AtlasIndex(u32),
71 /// No allocation found for the [`ImageId`] used
72 #[error("image_upload: allocation not found: {0:?}")]
73 ImageId(ImageId),
74 /// Image not within bounds of texture
75 #[error("image_upload: texture coordinates not within bounds")]
76 TextureCoordinates,
77 /// Wrong data length
78 #[error("image_upload: bad data length (received {0} bytes)")]
79 DataLen(u32),
80}
81
82/// Shared draw state
83///
84/// A single [`SharedState`] instance is shared by all windows and draw contexts.
85/// This struct is built over a [`DrawSharedImpl`] object provided by the graphics backend,
86/// which may be accessed directly for a lower-level API (though most methods
87/// are available through [`SharedState`] directly).
88///
89/// Note: all functionality is implemented through the [`DrawShared`] trait to
90/// allow usage where the `DS` type parameter is unknown. Some functionality is
91/// also implemented directly to avoid the need for downcasting.
92pub struct SharedState<DS: DrawSharedImpl> {
93 /// The graphics backend's [`DrawSharedImpl`] object
94 pub draw: DS,
95}
96
97#[cfg_attr(not(feature = "internal_doc"), doc(hidden))]
98#[cfg_attr(docsrs, doc(cfg(internal_doc)))]
99impl<DS: DrawSharedImpl> SharedState<DS> {
100 /// Construct (this is only called by the graphics backend)
101 pub fn new(draw: DS) -> Self {
102 SharedState { draw }
103 }
104}
105
106/// Interface over [`SharedState`]
107///
108/// All methods concern management of resources for drawing.
109pub trait DrawShared {
110 /// Allocate an image
111 ///
112 /// Use [`SharedState::image_upload`] to set contents of the new image.
113 fn image_alloc(&mut self, format: ImageFormat, size: Size) -> Result<ImageHandle, AllocError>;
114
115 /// Upload an image to the GPU
116 ///
117 /// This should be called at least once on each image before display. May be
118 /// called again to update the image contents.
119 ///
120 /// The `handle` must point to an existing allocation of size `(w, h)` and
121 /// with image format `format` with `b` bytes-per-pixel such that
122 /// `data.len() == b * w * h`. Data must be in row-major order.
123 ///
124 /// On success, this returns an [`ActionRedraw`] to indicate that any
125 /// widgets using this image will require a redraw.
126 fn image_upload(
127 &mut self,
128 handle: &ImageHandle,
129 data: &[u8],
130 ) -> Result<ActionRedraw, UploadError>;
131
132 /// Potentially free an image
133 ///
134 /// The input `handle` is consumed. If this reduces its reference count to
135 /// zero, then the image is freed.
136 fn image_free(&mut self, handle: ImageHandle);
137
138 /// Get the size of an image
139 fn image_size(&self, handle: &ImageHandle) -> Option<Size>;
140}
141
142impl<DS: DrawSharedImpl> DrawShared for SharedState<DS> {
143 #[inline]
144 fn image_alloc(&mut self, format: ImageFormat, size: Size) -> Result<ImageHandle, AllocError> {
145 self.draw
146 .image_alloc(format, size)
147 .map(|id| ImageHandle(id, Rc::new(())))
148 }
149
150 #[inline]
151 fn image_upload(
152 &mut self,
153 handle: &ImageHandle,
154 data: &[u8],
155 ) -> Result<ActionRedraw, UploadError> {
156 self.draw.image_upload(handle.0, data).map(|_| ActionRedraw)
157 }
158
159 #[inline]
160 fn image_free(&mut self, handle: ImageHandle) {
161 if let Ok(()) = Rc::try_unwrap(handle.1) {
162 self.draw.image_free(handle.0);
163 }
164 }
165
166 #[inline]
167 fn image_size(&self, handle: &ImageHandle) -> Option<Size> {
168 self.draw.image_size(handle.0)
169 }
170}
171
172/// Implementation target for [`DrawShared`]
173///
174/// This is typically used via [`SharedState`].
175pub trait DrawSharedImpl: Any {
176 type Draw: DrawImpl;
177
178 /// Get the maximum 2D texture size
179 fn max_texture_dimension_2d(&self) -> u32;
180
181 /// Set font raster config
182 fn set_raster_config(&mut self, config: &RasterConfig);
183
184 /// Allocate an image
185 ///
186 /// Use [`DrawSharedImpl::image_upload`] to set contents of the new image.
187 fn image_alloc(&mut self, format: ImageFormat, size: Size) -> Result<ImageId, AllocError>;
188
189 /// Upload an image to the GPU
190 ///
191 /// This should be called at least once on each image before display. May be
192 /// called again to update the image contents.
193 fn image_upload(&mut self, id: ImageId, data: &[u8]) -> Result<(), UploadError>;
194
195 /// Free an image allocation
196 fn image_free(&mut self, id: ImageId);
197
198 /// Query an image's size
199 fn image_size(&self, id: ImageId) -> Option<Size>;
200
201 /// Draw the image in the given `rect`
202 fn draw_image(&self, draw: &mut Self::Draw, pass: PassId, id: ImageId, rect: Quad);
203
204 /// Draw text with a colour
205 fn draw_text(
206 &mut self,
207 draw: &mut Self::Draw,
208 pass: PassId,
209 pos: Vec2,
210 bb: Quad,
211 text: &TextDisplay,
212 col: Rgba,
213 );
214
215 /// Draw text with effects
216 ///
217 /// The `effects` list provides underlining/strikethrough information via
218 /// [`Effect::flags`] and an index [`Effect::e`]. If `effects` is empty,
219 /// this is equivalent to [`Self::draw_text`].
220 ///
221 /// Text colour lookup uses index `e` and is essentially:
222 /// `colors.get(e).unwrap_or(Rgba::BLACK)`.
223 fn draw_text_effects(
224 &mut self,
225 draw: &mut Self::Draw,
226 pass: PassId,
227 pos: Vec2,
228 bb: Quad,
229 text: &TextDisplay,
230 colors: &[Rgba],
231 effects: &[Effect],
232 );
233}