pix_engine/texture.rs
1//! `Texture` methods.
2//!
3//! Provides texture creation and rendering methods on [`PixState`].
4//!
5//! Provided methods:
6//!
7//! - [`PixState::texture`]: Render a portion of a texture to the current canvas.
8//! - [`PixState::texture_transformed`]: Render a transformed portion of a texture to the current
9//! canvas.
10//! - [`PixState::create_texture`]: Creates a new texture to render to.
11//! - [`PixState::delete_texture`]: Delete a texture.
12//! - [`PixState::update_texture`]: Update texture with [u8] [slice] of pixel data.
13//! - [`PixState::set_texture_target`]: Target a texture for rendering.
14//! - [`PixState::clear_texture_target`]: Clear texture target back to primary canvas for rendering.
15//!
16//! # Example
17//!
18//! ```
19//! # use pix_engine::prelude::*;
20//! # struct App;
21//! # impl PixEngine for App {
22//! fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
23//! let texture_id1 = s.create_texture(500, 600, PixelFormat::Rgb)?;
24//! // Does not actually render to the current canvas
25//! s.set_texture_target(texture_id1)?;
26//! s.background(Color::random());
27//! s.text("Rendered texture!")?;
28//! s.clear_texture_target();
29//!
30//! // `None` uses PixelFormat::default() which defaults to PixelFormat::Rgba
31//! let texture_id2 = s.create_texture(500, 600, None)?;
32//!
33//! // `None` updates the entire texture, pass a Rect<i32> to update a sub-rectangle area
34//! let image = Image::from_file("./some_image.png")?;
35//! let pitch = image.width() as usize;
36//! s.update_texture(texture_id2, None, image.as_bytes(), pitch)?;
37//!
38//! // Draw both textures to the current canvas
39//! s.texture(texture_id1, None, rect![0, 0, 500, 600])?;
40//! s.texture(texture_id2, None, rect![500, 0, 500, 600])?;
41//!
42//! // These could be stored in `self` to avoid re-creating every frame
43//! s.delete_texture(texture_id1)?;
44//! s.delete_texture(texture_id2)?;
45//! Ok(())
46//! }
47//! # }
48//! ```
49
50use crate::prelude::*;
51use std::{
52 fmt,
53 ops::{Deref, DerefMut},
54};
55
56/// `Texture` identifier used to reference and target an internally managed texture.
57#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
58pub struct TextureId(pub(crate) usize);
59
60impl fmt::Display for TextureId {
61 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
62 write!(f, "{}", self.0)
63 }
64}
65
66impl Deref for TextureId {
67 type Target = usize;
68 fn deref(&self) -> &Self::Target {
69 &self.0
70 }
71}
72
73impl DerefMut for TextureId {
74 fn deref_mut(&mut self) -> &mut Self::Target {
75 &mut self.0
76 }
77}
78
79impl PixState {
80 /// Draw a portion `src` of a texture to the current render target translated and resized to
81 /// the target `dst`. Passing `None` for `src` renders the entire texture. Passing `None` for
82 /// `dst` renders to the maximum size of the render target.
83 ///
84 /// # Note
85 ///
86 /// It's possible to render one texture onto another texture, but currently they both have to
87 /// have been created in the same window. Attempting to render to a texture created with
88 /// another window will result in a [`Error::InvalidTexture`]. This restriction may be
89 /// lifted in the future.
90 ///
91 /// # Errors
92 ///
93 /// Returns an error for any of the following:
94 /// - The current render target is closed or dropped.
95 /// - The texture being rendered has been dropped.
96 /// - The target texture is the same as the texture being rendered.
97 /// - The renderer fails to draw to the texture.
98 ///
99 /// # Example
100 ///
101 /// ```
102 /// # use pix_engine::prelude::*;
103 /// # struct App { texture_id: TextureId };
104 /// # impl PixEngine for App {
105 /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
106 /// s.set_texture_target(self.texture_id)?;
107 /// s.background(Color::random());
108 /// s.text("Rendered texture!")?;
109 /// s.clear_texture_target();
110 ///
111 /// let src = rect![10, 10, 100, 100]; // Render a sub-section of the texture
112 /// // translate and scale texture
113 /// let dst = rect![200, 200, 200, 200];
114 /// s.texture(self.texture_id, src, dst)?;
115 /// Ok(())
116 /// }
117 /// # }
118 /// ```
119 pub fn texture<R1, R2>(&mut self, texture_id: TextureId, src: R1, dst: R2) -> PixResult<()>
120 where
121 R1: Into<Option<Rect<i32>>>,
122 R2: Into<Option<Rect<i32>>>,
123 {
124 self.renderer
125 .texture(texture_id, src.into(), dst.into(), 0.0, None, None, None)
126 }
127
128 /// Draw a transformed portion `src` of a texture to the current render target translated and
129 /// resized to the target `dst`, optionally rotated by an `angle` about a `center` point or
130 /// `flipped`. `angle` can be in either radians or degrees based on [`AngleMode`]. Passing
131 /// `None` for `src` renders the entire texture. Passing `None` for `dst` renders to the
132 /// maximum size of the render target. [`PixState::image_tint`] can optionally add a tint color
133 /// to the rendered texture.
134 ///
135 /// # Note
136 ///
137 /// It's possible to render one texture onto another texture, but currently they both have to
138 /// have been created in the same window. Attempting to render to a texture created with
139 /// another window will result in a [`Error::InvalidTexture`]. This restriction may be
140 /// lifted in the future.
141 ///
142 /// # Errors
143 ///
144 /// Returns an error for any of the following:
145 /// - The current render target is closed or dropped.
146 /// - The texture being rendered has been dropped.
147 /// - The target texture is the same as the texture being rendered.
148 /// - The renderer fails to draw to the texture.
149 ///
150 /// # Example
151 ///
152 /// ```
153 /// # use pix_engine::prelude::*;
154 /// # struct App { texture_id: TextureId };
155 /// # impl PixEngine for App {
156 /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
157 /// s.set_texture_target(self.texture_id)?;
158 /// s.background(Color::random());
159 /// s.text("Rendered texture!")?;
160 /// s.clear_texture_target();
161 ///
162 /// let src = None;
163 /// // translate and scale texture
164 /// let dst = rect![200, 200, 200, 200];
165 /// let angle = 10.0;
166 /// let center = point!(10, 10);
167 /// s.texture_transformed(
168 /// self.texture_id,
169 /// src,
170 /// dst,
171 /// angle,
172 /// center,
173 /// Flipped::Horizontal,
174 /// )?;
175 /// Ok(())
176 /// }
177 /// # }
178 /// ```
179 pub fn texture_transformed<R1, R2, C, F>(
180 &mut self,
181 texture_id: TextureId,
182 src: R1,
183 dst: R2,
184 mut angle: f64,
185 center: C,
186 flipped: F,
187 ) -> PixResult<()>
188 where
189 R1: Into<Option<Rect<i32>>>,
190 R2: Into<Option<Rect<i32>>>,
191 C: Into<Option<Point<i32>>>,
192 F: Into<Option<Flipped>>,
193 {
194 let s = &self.settings;
195 if s.angle_mode == AngleMode::Radians {
196 angle = angle.to_degrees();
197 };
198 self.renderer.texture(
199 texture_id,
200 src.into(),
201 dst.into(),
202 angle,
203 center.into(),
204 flipped.into(),
205 s.image_tint,
206 )
207 }
208
209 /// Constructs a `Texture` to render to. Passing `None` for [`PixelFormat`] will use
210 /// [`PixelFormat::default`]. The texture will be created and tied to the current window
211 /// target. To create a texture for a window other than the primary window, call
212 /// [`PixState::set_window`].
213 ///
214 /// # Errors
215 ///
216 /// If the current window target is closed or invalid, or the texture dimensions are invalid,
217 /// then an error is returned.
218 ///
219 /// # Note
220 ///
221 /// Textures are automatically dropped when the window they were created in is closed due to an
222 /// implicit lifetime that the texture can not outlive the window it was created for. Calling
223 /// this method will create a texture for the current `window_target`, which can only be
224 /// changed using the [`PixState::set_window_target`] method. It is the responsibility of the
225 /// caller to manage created textures and call [`PixState::delete_texture`] when a texture
226 /// resource is no longer needed and to ensure that texture methods are not called for a given
227 /// window after it has been closed, otherwise an error will be returned.
228 ///
229 /// This constraint arises due to lifetime issues with SDL textures, See
230 /// <https://github.com/Rust-SDL2/rust-sdl2/issues/1107> for more details.
231 ///
232 /// # Example
233 ///
234 /// ```
235 /// # use pix_engine::prelude::*;
236 /// # struct App { texture_id: TextureId };
237 /// # impl PixEngine for App {
238 /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
239 /// fn on_start(&mut self, s: &mut PixState) -> PixResult<()> {
240 /// self.texture_id = s.create_texture(
241 /// s.width()? / 2,
242 /// s.height()? / 2,
243 /// PixelFormat::Rgb,
244 /// )?;
245 /// Ok(())
246 /// }
247 /// # }
248 /// ```
249 pub fn create_texture<F>(&mut self, width: u32, height: u32, format: F) -> PixResult<TextureId>
250 where
251 F: Into<Option<PixelFormat>>,
252 {
253 self.renderer.create_texture(width, height, format.into())
254 }
255
256 /// Delete a `Texture`.
257 ///
258 /// # Errors
259 ///
260 /// If the current window target is closed or invalid, or the texture has already been dropped,
261 /// then an error is returned.
262 ///
263 /// # Note
264 ///
265 /// Currently, it is up to the caller to manage valid textures. Textures become invalid
266 /// whenever the `window_target` they were created in has been closed. Calling any texture
267 /// methods with an invalid `TextureId` will result in an error.
268 ///
269 /// This constraint arises due to lifetime issues with SDL textures, See
270 /// <https://github.com/Rust-SDL2/rust-sdl2/issues/1107> for more details.
271 ///
272 /// # Example
273 ///
274 /// ```
275 /// # use pix_engine::prelude::*;
276 /// # struct App;
277 /// # impl PixEngine for App {
278 /// fn on_update(&mut self, s: &mut PixState) -> PixResult<()> {
279 /// // A more polished implementation would manage state in `self` to avoid re-creating and
280 /// // destroying textures every frame
281 /// let texture_id = s.create_texture(500, 600, PixelFormat::Rgb)?;
282 /// // Render things
283 /// s.delete_texture(texture_id)?;
284 /// Ok(())
285 /// }
286 /// # }
287 /// ```
288 pub fn delete_texture(&mut self, texture_id: TextureId) -> PixResult<()> {
289 self.renderer.delete_texture(texture_id)
290 }
291
292 /// Update the `Texture` with a [u8] [slice] of pixel data. Passing `None` for `rect` updates
293 /// the entire texture. `pitch` is the number of bytes in a row of pixels data including
294 /// padding between lines.
295 /// # Errors
296 ///
297 /// If the window in which the texture was created is closed, or the renderer fails
298 /// to update to the texture, then an error is returned.
299 ///
300 /// # Example
301 ///
302 /// ```
303 /// # use pix_engine::prelude::*;
304 /// # struct App { texture_id: TextureId };
305 /// # impl PixEngine for App {
306 /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
307 /// fn on_start(&mut self, s: &mut PixState) -> PixResult<()> {
308 /// self.texture_id = s.create_texture(500, 600, None)?;
309 /// let image = Image::from_file("./some_image.png")?;
310 /// s.update_texture(self.texture_id, None, image.as_bytes(), image.pitch())?;
311 /// Ok(())
312 /// }
313 /// # }
314 /// ```
315 pub fn update_texture<R, P>(
316 &mut self,
317 texture_id: TextureId,
318 rect: R,
319 pixels: P,
320 pitch: usize,
321 ) -> PixResult<()>
322 where
323 R: Into<Option<Rect<i32>>>,
324 P: AsRef<[u8]>,
325 {
326 let rect = rect.into();
327 let pixels = pixels.as_ref();
328 self.renderer
329 .update_texture(texture_id, rect, pixels, pitch)
330 }
331
332 /// Set a `Texture` as the priamry target for drawing operations. Pushes current settings and UI
333 /// cursor to the stack, so any changes made while a texture target is set will be in effect
334 /// until [`PixState::reset_texture_target`] is called.
335 ///
336 /// # Errors
337 ///
338 /// If the target has been dropped or is invalid, then an error is returned.
339 ///
340 /// # Example
341 ///
342 /// ```
343 /// # use pix_engine::prelude::*;
344 /// # struct App { texture_id: TextureId };
345 /// # impl PixEngine for App {
346 /// # fn on_update(&mut self, s: &mut PixState) -> PixResult<()> { Ok(()) }
347 /// fn on_start(&mut self, s: &mut PixState) -> PixResult<()> {
348 /// self.texture_id = s.create_texture(500, 600, None)?;
349 /// s.set_texture_target(self.texture_id)?;
350 /// s.background(Color::random());
351 /// s.text("Rendered texture!")?;
352 /// s.clear_texture_target();
353 /// Ok(())
354 /// }
355 /// # }
356 /// ```
357 pub fn set_texture_target(&mut self, id: TextureId) -> PixResult<()> {
358 if self.renderer.texture_target().is_none() {
359 self.push();
360 self.ui.push_cursor();
361 self.set_cursor_pos(self.theme.spacing.frame_pad);
362 self.renderer.set_texture_target(id)
363 } else {
364 Ok(())
365 }
366 }
367
368 /// Clears `Texture` target back to the primary canvas for drawing operations. Pops previous
369 /// settings and UI cursor off the stack, so that changes made while texture target was set are
370 /// reverted.
371 pub fn clear_texture_target(&mut self) {
372 if self.renderer.texture_target().is_some() {
373 self.renderer.clear_texture_target();
374 self.ui.pop_cursor();
375 self.pop();
376 }
377 }
378}
379
380/// Trait for texture operations on the underlying `Renderer`.
381pub(crate) trait TextureRenderer {
382 /// Create a `Texture` to draw to.
383 ///
384 /// # Errors
385 ///
386 /// If the current window target is closed or invalid, or the texture dimensions are invalid,
387 /// then an error is returned.
388 fn create_texture(
389 &mut self,
390 width: u32,
391 height: u32,
392 format: Option<PixelFormat>,
393 ) -> PixResult<TextureId>;
394
395 /// Delete a `Texture`.
396 ///
397 /// # Errors
398 ///
399 /// If the current window target is closed or invalid, or the texture has already been dropped,
400 /// then an error is returned.
401 ///
402 fn delete_texture(&mut self, texture_id: TextureId) -> PixResult<()>;
403
404 /// Update texture with pixel data.
405 ///
406 /// # Errors
407 ///
408 /// If the current window target is closed or invalid, or the renderer fails to update to the
409 /// texture, then an error is returned.
410 fn update_texture<P: AsRef<[u8]>>(
411 &mut self,
412 texture_id: TextureId,
413 rect: Option<Rect<i32>>,
414 pixels: P,
415 pitch: usize,
416 ) -> PixResult<()>;
417
418 /// Draw texture to the curent canvas.
419 ///
420 /// # Errors
421 ///
422 /// Returns an error for any of the following:
423 /// - The current render target is closed or dropped.
424 /// - The texture being rendered has been dropped.
425 /// - The target texture is the same as the texture being rendered.
426 /// - The renderer fails to draw to the texture.
427 ///
428 #[allow(clippy::too_many_arguments)]
429 fn texture(
430 &mut self,
431 texture_id: TextureId,
432 src: Option<Rect<i32>>,
433 dst: Option<Rect<i32>>,
434 angle: f64,
435 center: Option<Point<i32>>,
436 flipped: Option<Flipped>,
437 tint: Option<Color>,
438 ) -> PixResult<()>;
439
440 /// Returns texture used as the target for drawing operations, if set.
441 fn texture_target(&self) -> Option<TextureId>;
442
443 /// Set a `Texture` as the primary target for drawing operations instead of the window target
444 /// canvas.
445 ///
446 /// # Errors
447 ///
448 /// If the texture has been dropped or is invalid, then an error is returned.
449 fn set_texture_target(&mut self, texture_id: TextureId) -> PixResult<()>;
450
451 /// Clear `Texture` target back to the window target canvas for drawing operations.
452 fn clear_texture_target(&mut self);
453
454 /// Returns whether a texture is set as the target for drawing operations.
455 fn has_texture_target(&self) -> bool;
456
457 /// Clear internal texture cache.
458 fn clear_texture_cache(&mut self);
459}