From 5a1c6403b9dd3a1d24d1386bb2462468783876f9 Mon Sep 17 00:00:00 2001 From: Antoni Spaanderman <56turtle56@gmail.com> Date: Wed, 26 Mar 2025 00:01:03 +0100 Subject: [PATCH 01/10] make `pixels::Color` have a stable representation (`repr(C)`) this allows for faster code and is also needed for SDL_RenderGeometry --- src/sdl2/pixels.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/sdl2/pixels.rs b/src/sdl2/pixels.rs index 867182cbc5..02a35ee07b 100644 --- a/src/sdl2/pixels.rs +++ b/src/sdl2/pixels.rs @@ -48,9 +48,14 @@ impl Palette { // Already validated, so don't check again let ncolors = colors.len() as ::libc::c_int; - let colors = colors.iter().map(|color| color.raw()).collect::>(); - - let result = unsafe { sys::SDL_SetPaletteColors(pal.raw, colors.as_ptr(), 0, ncolors) }; + let result = unsafe { + sys::SDL_SetPaletteColors( + pal.raw, + colors.as_ptr().cast::(), + 0, + ncolors, + ) + }; if result < 0 { Err(get_error()) @@ -88,6 +93,7 @@ fn create_palette() { assert!(palette.len() == 255); } +#[repr(C)] #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub struct Color { pub r: u8, From 674778d19ec75af1704c50a6eb43ed0a2b12b283 Mon Sep 17 00:00:00 2001 From: Antoni Spaanderman <56turtle56@gmail.com> Date: Wed, 26 Mar 2025 00:03:15 +0100 Subject: [PATCH 02/10] add binding for SDL_RenderGeometryRaw --- src/sdl2/render.rs | 355 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 355 insertions(+) diff --git a/src/sdl2/render.rs b/src/sdl2/render.rs index 40f8c5cf2e..8aab531fde 100644 --- a/src/sdl2/render.rs +++ b/src/sdl2/render.rs @@ -52,6 +52,7 @@ use std::mem::{transmute, MaybeUninit}; use std::ops::Deref; use std::ptr; use std::rc::Rc; +use std::slice; use crate::sys; use crate::sys::SDL_BlendMode; @@ -1964,6 +1965,360 @@ impl Canvas { } } +/// Get a reference to an [`FPoint`] inside `Self`. This is used by [`Canvas::render_geometry`]. +/// +/// # Safety +/// +/// The implementation of `as_vertex_position` must +/// - be pure (have no side effects) and +/// - return a reference to an [`FPoint`] that is borrowed from `self`. +pub unsafe trait AsVertexPosition { + fn as_vertex_position(&self) -> &FPoint; +} + +unsafe impl AsVertexPosition for FPoint { + fn as_vertex_position(&self) -> &FPoint { + self + } +} + +/// Get a reference to a [`Color`][pixels::Color] inside `Self`. This is used by +/// [`Canvas::render_geometry`]. +/// +/// # Safety +/// +/// The implementation of `as_vertex_color` must +/// - be pure (have no side effects) and +/// - return a reference to a [`Color`][pixels::Color] that is borrowed from `self`. +pub unsafe trait AsVertexColor { + fn as_vertex_color(&self) -> &pixels::Color; +} + +unsafe impl AsVertexColor for pixels::Color { + fn as_vertex_color(&self) -> &pixels::Color { + self + } +} + +/// Get a reference to an [`FPoint`] inside `Self`. This is used by [`Canvas::render_geometry`]. +/// +/// # Safety +/// +/// The implementation of `as_vertex_tex_coord` must +/// - be pure (have no side effects) and +/// - return a reference to an [`FPoint`] that is borrowed from `self`. +pub unsafe trait AsVertexTexCoord { + fn as_vertex_tex_coord(&self) -> &FPoint; +} + +unsafe impl AsVertexTexCoord for FPoint { + fn as_vertex_tex_coord(&self) -> &FPoint { + self + } +} + +/// Generates implementations of [`AsVertexPosition`], [`AsVertexColor`] and/or +/// [`AsVertexTexCoord`]. +/// +/// # Usage examples +/// +/// Generate all three trait impls for a [`Vertex`]-like struct. +/// +/// ``` +/// # use sdl2::impl_as_vertex_traits; +/// # use sdl2::pixels::Color; +/// # use sdl2::rect::FPoint; +/// +/// pub struct Vertex { +/// pub position: FPoint, +/// pub color: Color, +/// pub tex_coord: FPoint, +/// } +/// +/// impl_as_vertex_traits!( +/// impl +/// AsVertexPosition(self.position), +/// AsVertexColor(self.color), +/// AsVertexTexCoord(self.tex_coord), +/// for Vertex +/// ); +/// ``` +/// +/// [examples/render-geometry.rs](https://github.com/Rust-SDL2/rust-sdl2/blob/master/examples/render-geometry.rs) +/// contains an example for a different use case. +#[macro_export] +macro_rules! impl_as_vertex_traits { + ( + impl + $(AsVertexPosition(self.$position:ident) $(,)?)? + $(AsVertexColor(self.$color:ident) $(,)?)? + $(AsVertexTexCoord(self.$tex_coord:ident) $(,)?)? + for $self_ty:ident + ) => { + $( + unsafe impl $crate::render::AsVertexPosition for $self_ty { + fn as_vertex_position(&self) -> &$crate::rect::FPoint { + let $self_ty { $position: ret, .. } = self; + ret + } + } + )? + + $( + unsafe impl $crate::render::AsVertexColor for $self_ty { + fn as_vertex_color(&self) -> &$crate::pixels::Color { + let $self_ty { $color: ret, .. } = self; + ret + } + } + )? + + $( + unsafe impl $crate::render::AsVertexTexCoord for $self_ty { + fn as_vertex_tex_coord(&self) -> &$crate::rect::FPoint { + let $self_ty { $tex_coord: ret, .. } = self; + ret + } + } + )? + }; +} + +#[derive(Debug, Clone, Copy)] +pub struct Vertex { + pub position: FPoint, + pub color: pixels::Color, + pub tex_coord: FPoint, +} + +impl_as_vertex_traits!( + impl + AsVertexPosition(self.position), + AsVertexColor(self.color), + AsVertexTexCoord(self.tex_coord), + for Vertex +); + +#[derive(Debug, Clone, Copy)] +pub enum VertexIndices<'a> { + /// Vertices are rendered in sequential order. + Sequential, + /// [`u8`] vertex indices. + U8(&'a [[u8; 3]]), + /// [`u16`] vertex indices. + U16(&'a [[u16; 3]]), + /// [`u32`] vertex indices. If any number in the slice is larger than [`i32::MAX`], SDL will + /// return an error when using this in [`Canvas::render_geometry`]. + U32(&'a [[u32; 3]]), +} + +impl VertexIndices<'_> { + /// Returns (indices, num_indices, size_indices) + fn into_raw(self) -> (*const c_void, c_int, c_int) { + match self { + Self::Sequential => (ptr::null(), 0, 0), + Self::U8(indices) => ( + indices.as_ptr().cast::(), + (indices.len() * 3) as c_int, + 1, + ), + Self::U16(indices) => ( + indices.as_ptr().cast::(), + (indices.len() * 3) as c_int, + 2, + ), + Self::U32(indices) => ( + indices.as_ptr().cast::(), + (indices.len() * 3) as c_int, + 4, + ), + } + } +} + +impl<'a> From<&'a [[u8; 3]]> for VertexIndices<'a> { + fn from(value: &'a [[u8; 3]]) -> Self { + Self::U8(value) + } +} + +impl<'a> From<&'a [[u16; 3]]> for VertexIndices<'a> { + fn from(value: &'a [[u16; 3]]) -> Self { + Self::U16(value) + } +} + +impl<'a> From<&'a [[u32; 3]]> for VertexIndices<'a> { + fn from(value: &'a [[u32; 3]]) -> Self { + Self::U32(value) + } +} + +impl<'a> From<&'a [[i32; 3]]> for VertexIndices<'a> { + fn from(value: &'a [[i32; 3]]) -> Self { + Self::U32(unsafe { slice::from_raw_parts(value.as_ptr().cast::<[u32; 3]>(), value.len()) }) + } +} + +#[derive(Debug, Clone)] +pub struct InvalidLengthError(()); + +impl fmt::Display for InvalidLengthError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "slice length must be a multiple of 3") + } +} + +impl std::error::Error for InvalidLengthError {} + +macro_rules! vertex_indices_try_from { + ($($ty:ty)*) => { + $( + impl<'a> TryFrom<&'a [$ty]> for VertexIndices<'a> { + type Error = InvalidLengthError; + + fn try_from(value: &'a [$ty]) -> Result { + let new_len = value.len() / 3; + if value.len() % 3 == 0 { + Ok(Self::from(unsafe { + slice::from_raw_parts(value.as_ptr().cast::<[$ty; 3]>(), new_len) + })) + } else { + Err(InvalidLengthError(())) + } + } + } + )* + }; +} + +vertex_indices_try_from!(u8 u16 u32 i32); + +impl Canvas { + /// Render triangles optionally using a texture. + /// + /// If you have vertices or indices in a different format, [`Canvas::render_geometry`] (the + /// advanced version of this function) might allow you to do the same as this function without + /// converting the parameters. + /// + /// # Panics + /// + /// This function panics if `indices.len()` is not a multiple of 3 + #[doc(alias = "SDL_RenderGeometry")] + pub fn render_geometry_simple( + &mut self, + vertices: &[Vertex], + texture: Option<&Texture>, + indices: Option<&[u32]>, + ) -> Result<(), String> { + self.render_geometry( + vertices, + vertices, + texture.map(|texture| (texture, vertices)), + match indices { + None => VertexIndices::Sequential, + Some(indices) => VertexIndices::try_from(indices).unwrap(), + }, + ) + } + + /// Render triangles optionally using a texture. + /// + /// This function allows its parameters to be passed in a variety of ways including one list of + /// vertices (like [`Canvas::render_geometry_simple`] accepts) and separate lists of positions, + /// colors and texture coordinates. + /// + /// # Panics + /// + /// - This function panic if the slices `positions`, `colors` and if applicable, "texcoords" + /// (`texture.unwrap().1`) have different lengths. + /// - This function does its best to check if the unsafe trait contracts + /// (of [`AsVertexPosition`], [`AsVertexColor`] and [`AsVertexTexCoord`]) are upheld. These + /// checks should not affect performance in release builds and may panic if a contract is + /// violated. + #[doc(alias = "SDL_RenderGeometry")] + #[doc(alias = "SDL_RenderGeometryRaw")] + pub fn render_geometry( + &mut self, + positions: &[impl AsVertexPosition], + colors: &[impl AsVertexColor], + texture: Option<(&Texture, &[impl AsVertexTexCoord])>, + indices: VertexIndices<'_>, + ) -> Result<(), String> { + let num_vertices = positions.len(); + + assert!(num_vertices <= c_int::MAX as usize); + assert_eq!(num_vertices, colors.len()); + if let Some((_, texcoords)) = texture { + assert_eq!(num_vertices, texcoords.len()); + }; + + if num_vertices < 3 { + // SDL also does this, but now we know the slices have at least one element + return Ok(()); + } + + // If no unsafe trait contract is broken (then the assertions here do not fail) this + // function can be optimized to return (slice + constant, constant). + // The constants are dependent on type sizes and field offset. + #[inline(always)] + unsafe fn ptr_and_stride( + slice: *const [E], + element_field: *const F, + ) -> (*const R, c_int) { + let offset = element_field.cast::().offset_from(slice.cast::()); + + assert!(offset >= 0); + assert!(size_of::() <= size_of::()); + assert!(size_of::() <= c_int::MAX as usize); + assert!(offset as usize <= size_of::() - size_of::()); + + (slice.byte_offset(offset).cast(), size_of::() as c_int) + } + + let ret = unsafe { + let (xy, xy_stride) = + ptr_and_stride::<_, FPoint, f32>(positions, positions[0].as_vertex_position()); + let (color, color_stride) = ptr_and_stride::<_, pixels::Color, sys::SDL_Color>( + colors, + colors[0].as_vertex_color(), + ); + + let (texture, (uv, uv_stride)) = if let Some((texture, texcoords)) = texture { + ( + texture.raw, + ptr_and_stride::<_, FPoint, f32>(texcoords, texcoords[0].as_vertex_tex_coord()), + ) + } else { + (ptr::null_mut(), (ptr::null(), 0)) + }; + + let (indices, num_indices, size_indices) = indices.into_raw(); + + sys::SDL_RenderGeometryRaw( + self.context.raw, + texture, + xy, + xy_stride, + color, + color_stride, + uv, + uv_stride, + num_vertices as c_int, + indices, + num_indices, + size_indices, + ) + }; + + if ret == 0 { + Ok(()) + } else { + Err(get_error()) + } + } +} + #[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)] pub struct TextureQuery { pub format: pixels::PixelFormatEnum, From 28930cfd0f265d3083698e290005127a614d2f8f Mon Sep 17 00:00:00 2001 From: Antoni Spaanderman <56turtle56@gmail.com> Date: Wed, 26 Mar 2025 00:03:43 +0100 Subject: [PATCH 03/10] add example of advanced use of `Canvas::render_geometry` --- Cargo.toml | 3 + examples/render-geometry.rs | 109 ++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 examples/render-geometry.rs diff --git a/Cargo.toml b/Cargo.toml index 46bcc04f27..5053f7cbec 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -135,6 +135,9 @@ name = "renderer-target" [[example]] name = "events" +[[example]] +name = "render-geometry" + [[example]] name = "renderer-texture" diff --git a/examples/render-geometry.rs b/examples/render-geometry.rs new file mode 100644 index 0000000000..d83f369af1 --- /dev/null +++ b/examples/render-geometry.rs @@ -0,0 +1,109 @@ +extern crate sdl2; + +use sdl2::event::Event; +use sdl2::impl_as_vertex_traits; +use sdl2::keyboard::Keycode; +use sdl2::pixels::Color; +use sdl2::rect::FPoint; +use sdl2::render::{AsVertexColor, VertexIndices}; +use std::thread; +use std::time::Duration; + +fn main() { + let sdl_context = sdl2::init().unwrap(); + let video_subsystem = sdl_context.video().unwrap(); + + let window = video_subsystem + .window("Rust SDL2 render_geometry custom struct example", 800, 600) + .position_centered() + .opengl() + .build() + .unwrap(); + + let mut canvas = window.into_canvas().build().unwrap(); + + let mut event_pump = sdl_context.event_pump().unwrap(); + let mut running = true; + + while running { + for event in event_pump.poll_iter() { + match event { + Event::Quit { .. } + | Event::KeyDown { + keycode: Some(Keycode::Escape), + .. + } => { + running = false; + } + _ => {} + } + } + + // black background + canvas.set_draw_color(Color::BLACK); + canvas.clear(); + + // `render_geometry` supports any custom struct as long as it contains the needed data + // (or other layout compatible of the needed data). + // The struct does not need to be `repr(C)` or `Copy` for example. + struct MyVertex { + // For demonstration purposes color is `[u8; 4]` here. `[u8; 4]` is layout-compatible + // with `sdl2::pixels::Color` + color: [u8; 4], + // The struct may contain data not needed by SDL. + #[expect(dead_code)] + foo: Vec, + // When defining your own vertex struct, using `FPoint` for position and tex_coord + // (and `Color` for color) is the easiest way (see the trait impls below) + pos: FPoint, + } + + // The unsafe trait to get the vertex position can simply be generated with a macro. + // This macro makes sure the implementation is sound, this is only possible when the field + // has the exact right type. + impl_as_vertex_traits!(impl AsVertexPosition(self.pos) for MyVertex); + + // The unsafe trait to get the vertex color must be implementated manually because the type + // of the `color` field is not `sdl2::pixels::Color`. + // Also make sure to not violate the contract of this unsafe trait! + // SAFETY: `as_vertex_color` only returns a borrow of a field of `self`. + unsafe impl AsVertexColor for MyVertex { + fn as_vertex_color(&self) -> &Color { + // SAFETY: [u8; 4] has the same layout as Color + unsafe { &*(&self.color as *const [u8; 4] as *const Color) } + } + } + + // Define the triangles + let vertices = [ + MyVertex { + color: [0xff, 0, 0, 0xff], + foo: b"some".to_vec(), + pos: FPoint::new(100.0, 500.0), + }, + MyVertex { + color: [0, 0xff, 0, 0xff], + foo: b"unrelated".to_vec(), + pos: FPoint::new(700.0, 500.0), + }, + MyVertex { + color: [0, 0, 0xff, 0xff], + foo: b"data".to_vec(), + pos: FPoint::new(400.0, 100.0), + }, + ]; + + // Actually render + canvas + .render_geometry( + &vertices, + &vertices, + None::<(&sdl2::render::Texture<'_>, &[sdl2::render::Vertex])>, + VertexIndices::Sequential, + ) + .expect("render_geometry failed"); + + canvas.present(); + thread::sleep(Duration::from_millis(16)); + } +} From fcd05c4799ebbc48327559c6afb4b8c14c444622 Mon Sep 17 00:00:00 2001 From: Antoni Spaanderman <56turtle56@gmail.com> Date: Wed, 26 Mar 2025 09:46:37 +0100 Subject: [PATCH 04/10] update unsafe trait docs to include a needed guarantee and refer to one doc comment instead of duplicating similar ones --- src/sdl2/render.rs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/sdl2/render.rs b/src/sdl2/render.rs index 8aab531fde..cf07effe74 100644 --- a/src/sdl2/render.rs +++ b/src/sdl2/render.rs @@ -1971,7 +1971,8 @@ impl Canvas { /// /// The implementation of `as_vertex_position` must /// - be pure (have no side effects) and -/// - return a reference to an [`FPoint`] that is borrowed from `self`. +/// - return a reference to an [`FPoint`] that is borrowed from the same allocated object as `self` +/// and has an offset from `self` that is constant (does not change between calls). pub unsafe trait AsVertexPosition { fn as_vertex_position(&self) -> &FPoint; } @@ -1987,9 +1988,8 @@ unsafe impl AsVertexPosition for FPoint { /// /// # Safety /// -/// The implementation of `as_vertex_color` must -/// - be pure (have no side effects) and -/// - return a reference to a [`Color`][pixels::Color] that is borrowed from `self`. +/// See [`AsVertexPosition`], but read `as_vertex_position` as `as_vertex_color` and `FPoint` as +/// `Color`. pub unsafe trait AsVertexColor { fn as_vertex_color(&self) -> &pixels::Color; } @@ -2004,9 +2004,7 @@ unsafe impl AsVertexColor for pixels::Color { /// /// # Safety /// -/// The implementation of `as_vertex_tex_coord` must -/// - be pure (have no side effects) and -/// - return a reference to an [`FPoint`] that is borrowed from `self`. +/// See [`AsVertexPosition`], but read `as_vertex_position` as `as_vertex_tex_coord`. pub unsafe trait AsVertexTexCoord { fn as_vertex_tex_coord(&self) -> &FPoint; } From ea5e52bce396e7c204191ef6480df79a9d1400c4 Mon Sep 17 00:00:00 2001 From: Antoni Spaanderman <56turtle56@gmail.com> Date: Sun, 30 Mar 2025 18:13:41 +0200 Subject: [PATCH 05/10] rework almost everything - the macro was very hard to get sound, auto deref has to be prevented somehow - the unsafe traits could not be implemented for types defined in other crates the unsafety must be kept simple, users now only need to provide the right offsets, which core::mem::offset_of does update example --- examples/render-geometry.rs | 37 ++-- src/sdl2/render.rs | 415 ++++++++++++++---------------------- 2 files changed, 171 insertions(+), 281 deletions(-) diff --git a/examples/render-geometry.rs b/examples/render-geometry.rs index d83f369af1..6726ade55b 100644 --- a/examples/render-geometry.rs +++ b/examples/render-geometry.rs @@ -1,11 +1,11 @@ extern crate sdl2; use sdl2::event::Event; -use sdl2::impl_as_vertex_traits; use sdl2::keyboard::Keycode; use sdl2::pixels::Color; use sdl2::rect::FPoint; -use sdl2::render::{AsVertexColor, VertexIndices}; +use sdl2::render::{RenderGeometryTextureParams, VertexIndices}; +use std::mem::offset_of; use std::thread; use std::time::Duration; @@ -43,7 +43,7 @@ fn main() { canvas.set_draw_color(Color::BLACK); canvas.clear(); - // `render_geometry` supports any custom struct as long as it contains the needed data + // `render_geometry_raw` supports any custom struct as long as it contains the needed data // (or other layout compatible of the needed data). // The struct does not need to be `repr(C)` or `Copy` for example. struct MyVertex { @@ -54,26 +54,11 @@ fn main() { #[expect(dead_code)] foo: Vec, // When defining your own vertex struct, using `FPoint` for position and tex_coord - // (and `Color` for color) is the easiest way (see the trait impls below) + // (and `Color` for color) is the easiest way. These are obviously layout-compatible + // with `FPoint` and `Color`, respectively. pos: FPoint, } - // The unsafe trait to get the vertex position can simply be generated with a macro. - // This macro makes sure the implementation is sound, this is only possible when the field - // has the exact right type. - impl_as_vertex_traits!(impl AsVertexPosition(self.pos) for MyVertex); - - // The unsafe trait to get the vertex color must be implementated manually because the type - // of the `color` field is not `sdl2::pixels::Color`. - // Also make sure to not violate the contract of this unsafe trait! - // SAFETY: `as_vertex_color` only returns a borrow of a field of `self`. - unsafe impl AsVertexColor for MyVertex { - fn as_vertex_color(&self) -> &Color { - // SAFETY: [u8; 4] has the same layout as Color - unsafe { &*(&self.color as *const [u8; 4] as *const Color) } - } - } - // Define the triangles let vertices = [ MyVertex { @@ -94,14 +79,18 @@ fn main() { ]; // Actually render - canvas - .render_geometry( + // SAFETY: core::mem::offset_of makes sure the offsets are right. + unsafe { + canvas.render_geometry_raw( &vertices, + offset_of!(MyVertex, pos), &vertices, - None::<(&sdl2::render::Texture<'_>, &[sdl2::render::Vertex])>, + offset_of!(MyVertex, color), + None::>, VertexIndices::Sequential, ) - .expect("render_geometry failed"); + } + .expect("render_geometry failed (probably unsupported, see error message)"); canvas.present(); thread::sleep(Duration::from_millis(16)); diff --git a/src/sdl2/render.rs b/src/sdl2/render.rs index cf07effe74..8dd0612be1 100644 --- a/src/sdl2/render.rs +++ b/src/sdl2/render.rs @@ -41,7 +41,7 @@ use crate::surface::{Surface, SurfaceContext, SurfaceRef}; use crate::video::{Window, WindowContext}; use libc::c_void; use libc::{c_double, c_int}; -use std::convert::TryFrom; +use std::convert::{TryFrom, TryInto}; use std::error::Error; use std::ffi::CStr; use std::fmt; @@ -1965,123 +1965,9 @@ impl Canvas { } } -/// Get a reference to an [`FPoint`] inside `Self`. This is used by [`Canvas::render_geometry`]. -/// -/// # Safety -/// -/// The implementation of `as_vertex_position` must -/// - be pure (have no side effects) and -/// - return a reference to an [`FPoint`] that is borrowed from the same allocated object as `self` -/// and has an offset from `self` that is constant (does not change between calls). -pub unsafe trait AsVertexPosition { - fn as_vertex_position(&self) -> &FPoint; -} - -unsafe impl AsVertexPosition for FPoint { - fn as_vertex_position(&self) -> &FPoint { - self - } -} - -/// Get a reference to a [`Color`][pixels::Color] inside `Self`. This is used by -/// [`Canvas::render_geometry`]. -/// -/// # Safety -/// -/// See [`AsVertexPosition`], but read `as_vertex_position` as `as_vertex_color` and `FPoint` as -/// `Color`. -pub unsafe trait AsVertexColor { - fn as_vertex_color(&self) -> &pixels::Color; -} - -unsafe impl AsVertexColor for pixels::Color { - fn as_vertex_color(&self) -> &pixels::Color { - self - } -} - -/// Get a reference to an [`FPoint`] inside `Self`. This is used by [`Canvas::render_geometry`]. -/// -/// # Safety -/// -/// See [`AsVertexPosition`], but read `as_vertex_position` as `as_vertex_tex_coord`. -pub unsafe trait AsVertexTexCoord { - fn as_vertex_tex_coord(&self) -> &FPoint; -} - -unsafe impl AsVertexTexCoord for FPoint { - fn as_vertex_tex_coord(&self) -> &FPoint { - self - } -} - -/// Generates implementations of [`AsVertexPosition`], [`AsVertexColor`] and/or -/// [`AsVertexTexCoord`]. -/// -/// # Usage examples -/// -/// Generate all three trait impls for a [`Vertex`]-like struct. -/// -/// ``` -/// # use sdl2::impl_as_vertex_traits; -/// # use sdl2::pixels::Color; -/// # use sdl2::rect::FPoint; -/// -/// pub struct Vertex { -/// pub position: FPoint, -/// pub color: Color, -/// pub tex_coord: FPoint, -/// } -/// -/// impl_as_vertex_traits!( -/// impl -/// AsVertexPosition(self.position), -/// AsVertexColor(self.color), -/// AsVertexTexCoord(self.tex_coord), -/// for Vertex -/// ); -/// ``` -/// -/// [examples/render-geometry.rs](https://github.com/Rust-SDL2/rust-sdl2/blob/master/examples/render-geometry.rs) -/// contains an example for a different use case. -#[macro_export] -macro_rules! impl_as_vertex_traits { - ( - impl - $(AsVertexPosition(self.$position:ident) $(,)?)? - $(AsVertexColor(self.$color:ident) $(,)?)? - $(AsVertexTexCoord(self.$tex_coord:ident) $(,)?)? - for $self_ty:ident - ) => { - $( - unsafe impl $crate::render::AsVertexPosition for $self_ty { - fn as_vertex_position(&self) -> &$crate::rect::FPoint { - let $self_ty { $position: ret, .. } = self; - ret - } - } - )? - - $( - unsafe impl $crate::render::AsVertexColor for $self_ty { - fn as_vertex_color(&self) -> &$crate::pixels::Color { - let $self_ty { $color: ret, .. } = self; - ret - } - } - )? - - $( - unsafe impl $crate::render::AsVertexTexCoord for $self_ty { - fn as_vertex_tex_coord(&self) -> &$crate::rect::FPoint { - let $self_ty { $tex_coord: ret, .. } = self; - ret - } - } - )? - }; -} - +// `repr(C)` is not needed for soundness in this crate, but users may expect it to be +// layout-compatible with `sys::SDL_Vertex`. +#[repr(C)] #[derive(Debug, Clone, Copy)] pub struct Vertex { pub position: FPoint, @@ -2089,25 +1975,16 @@ pub struct Vertex { pub tex_coord: FPoint, } -impl_as_vertex_traits!( - impl - AsVertexPosition(self.position), - AsVertexColor(self.color), - AsVertexTexCoord(self.tex_coord), - for Vertex -); - #[derive(Debug, Clone, Copy)] pub enum VertexIndices<'a> { /// Vertices are rendered in sequential order. Sequential, /// [`u8`] vertex indices. - U8(&'a [[u8; 3]]), + U8(&'a [u8]), /// [`u16`] vertex indices. - U16(&'a [[u16; 3]]), - /// [`u32`] vertex indices. If any number in the slice is larger than [`i32::MAX`], SDL will - /// return an error when using this in [`Canvas::render_geometry`]. - U32(&'a [[u32; 3]]), + U16(&'a [u16]), + /// [`u32`] vertex indices. If any index is larger than [`i32::MAX`], SDL will return an error. + U32(&'a [u32]), } impl VertexIndices<'_> { @@ -2115,184 +1992,208 @@ impl VertexIndices<'_> { fn into_raw(self) -> (*const c_void, c_int, c_int) { match self { Self::Sequential => (ptr::null(), 0, 0), - Self::U8(indices) => ( - indices.as_ptr().cast::(), - (indices.len() * 3) as c_int, - 1, - ), - Self::U16(indices) => ( - indices.as_ptr().cast::(), - (indices.len() * 3) as c_int, - 2, - ), - Self::U32(indices) => ( - indices.as_ptr().cast::(), - (indices.len() * 3) as c_int, - 4, - ), + Self::U8(indices) => (indices.as_ptr().cast::(), indices.len() as c_int, 1), + Self::U16(indices) => (indices.as_ptr().cast::(), indices.len() as c_int, 2), + Self::U32(indices) => (indices.as_ptr().cast::(), indices.len() as c_int, 4), } } } -impl<'a> From<&'a [[u8; 3]]> for VertexIndices<'a> { - fn from(value: &'a [[u8; 3]]) -> Self { +impl<'a> From<&'a [u8]> for VertexIndices<'a> { + fn from(value: &'a [u8]) -> Self { Self::U8(value) } } -impl<'a> From<&'a [[u16; 3]]> for VertexIndices<'a> { - fn from(value: &'a [[u16; 3]]) -> Self { +impl<'a> From<&'a [u16]> for VertexIndices<'a> { + fn from(value: &'a [u16]) -> Self { Self::U16(value) } } -impl<'a> From<&'a [[u32; 3]]> for VertexIndices<'a> { - fn from(value: &'a [[u32; 3]]) -> Self { +impl<'a> From<&'a [u32]> for VertexIndices<'a> { + fn from(value: &'a [u32]) -> Self { Self::U32(value) } } -impl<'a> From<&'a [[i32; 3]]> for VertexIndices<'a> { - fn from(value: &'a [[i32; 3]]) -> Self { - Self::U32(unsafe { slice::from_raw_parts(value.as_ptr().cast::<[u32; 3]>(), value.len()) }) +impl<'a> From<&'a [i32]> for VertexIndices<'a> { + fn from(value: &'a [i32]) -> Self { + Self::U32(unsafe { slice::from_raw_parts(value.as_ptr().cast::(), value.len()) }) } } -#[derive(Debug, Clone)] -pub struct InvalidLengthError(()); - -impl fmt::Display for InvalidLengthError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "slice length must be a multiple of 3") +impl<'a> From<&'a [[u8; 3]]> for VertexIndices<'a> { + fn from(value: &'a [[u8; 3]]) -> Self { + Self::U8(unsafe { slice::from_raw_parts(value.as_ptr().cast::(), value.len() * 3) }) } } -impl std::error::Error for InvalidLengthError {} +impl<'a> From<&'a [[u16; 3]]> for VertexIndices<'a> { + fn from(value: &'a [[u16; 3]]) -> Self { + Self::U16(unsafe { slice::from_raw_parts(value.as_ptr().cast::(), value.len() * 3) }) + } +} -macro_rules! vertex_indices_try_from { - ($($ty:ty)*) => { - $( - impl<'a> TryFrom<&'a [$ty]> for VertexIndices<'a> { - type Error = InvalidLengthError; +impl<'a> From<&'a [[u32; 3]]> for VertexIndices<'a> { + fn from(value: &'a [[u32; 3]]) -> Self { + Self::U32(unsafe { slice::from_raw_parts(value.as_ptr().cast::(), value.len() * 3) }) + } +} - fn try_from(value: &'a [$ty]) -> Result { - let new_len = value.len() / 3; - if value.len() % 3 == 0 { - Ok(Self::from(unsafe { - slice::from_raw_parts(value.as_ptr().cast::<[$ty; 3]>(), new_len) - })) - } else { - Err(InvalidLengthError(())) - } - } - } - )* - }; +impl<'a> From<&'a [[i32; 3]]> for VertexIndices<'a> { + fn from(value: &'a [[i32; 3]]) -> Self { + Self::U32(unsafe { slice::from_raw_parts(value.as_ptr().cast::(), value.len() * 3) }) + } } -vertex_indices_try_from!(u8 u16 u32 i32); +#[derive(Clone, Copy)] +pub struct RenderGeometryTextureParams<'a, TexCoordVertex> { + #[cfg(not(feature = "unsafe_textures"))] + pub texture: &'a Texture<'a>, + #[cfg(feature = "unsafe_textures")] + pub texture: &'a Texture, + pub tex_coords: &'a [TexCoordVertex], + pub tex_coord_offset: usize, +} impl Canvas { - /// Render triangles optionally using a texture. + /// Render triangles, optionally using a texture. /// - /// If you have vertices or indices in a different format, [`Canvas::render_geometry`] (the - /// advanced version of this function) might allow you to do the same as this function without + /// If you have vertices in a different format, [`Canvas::render_geometry_raw`] (the advanced + /// version of this function) might allow you to do the same as this function without /// converting the parameters. /// - /// # Panics - /// - /// This function panics if `indices.len()` is not a multiple of 3 + /// `indices` can be of any of the following: + /// - [`VertexIndices::Sequential`]: Vertices are rendered in sequential order. The number of + /// *vertices* must be divisible by 3, when using a list of indices, the number of *indices* + /// must be divisible by 3. SDL will check this and return an error if this is not the case. + /// - `&[u8]`: Indices of the type [`u8`], note that this only works when using [`u8::MAX`] + /// (255) or less vertices. + /// - `&[u16]`: Indices of the type [`u16`], note that this only works when using [`u16::MAX`] + /// (65535) or less vertices. + /// - `&[u32]` or `&[i32]`: Indices of the type [`i32`], possibly represented using [`u32`]. + /// Note that regardless of the type, indices must be positive and fit in [`i32`]. + /// - `&[[u8; 3]]`, `&[[u16; 3]]`, `&[[u32; 3]]` or `&[[i32; 3]]`, which work like `&[u8]`, + /// `&[u16]`, `&[u32]` or `&[i32]` but ensure at compile time that the number of indices is + /// divisible by 3. + /// - Any other (possibly user defined) type that implements `Into>`. #[doc(alias = "SDL_RenderGeometry")] - pub fn render_geometry_simple( + pub fn render_geometry<'a>( &mut self, vertices: &[Vertex], texture: Option<&Texture>, - indices: Option<&[u32]>, + indices: impl Into>, ) -> Result<(), String> { - self.render_geometry( - vertices, - vertices, - texture.map(|texture| (texture, vertices)), - match indices { - None => VertexIndices::Sequential, - Some(indices) => VertexIndices::try_from(indices).unwrap(), - }, - ) + unsafe { + self.render_geometry_raw( + vertices, + mem::offset_of!(Vertex, position), + vertices, + mem::offset_of!(Vertex, color), + texture.map(|texture| RenderGeometryTextureParams { + texture, + tex_coords: vertices, + tex_coord_offset: mem::offset_of!(Vertex, tex_coord), + }), + indices, + ) + } } - /// Render triangles optionally using a texture. + /// Render triangles, optionally using a texture. + /// + /// In the documentation of this function field names of [`RenderGeometryTextureParams`] are + /// used as if they are parameters. When they are used it only applies when a texture is used + /// (`texture_params` is [`Some(_)`][Some]). + /// + /// Vertices may be passed as up to 3 separate lists. Elements of the lists may contain + /// anything, but must contain the valid values at the given offset (in bytes). + /// Using [`offset_of`][core::mem::offset_of] is advised to get the right offset of a field, + /// especially when the element type does not have a stable layout. + /// + /// See the documentation of [`Canvas::render_geometry`] for usage of the `indices` parameter. /// - /// This function allows its parameters to be passed in a variety of ways including one list of - /// vertices (like [`Canvas::render_geometry_simple`] accepts) and separate lists of positions, - /// colors and texture coordinates. + /// # Safety + /// + /// It must be sound to [create references to](core::ptr#pointer-to-reference-conversion): + /// - `positions` offset by `position_offset` bytes, of type [`FPoint`] (or `[f32; 2]`), + /// - `colors` offset by `color_offset` bytes, of type [`Color`][pixels::Color] (or `[u8; 4]`) + /// - `tex_coords` offset by `tex_coord_offset` bytes, of type [`FPoint`] (or `[f32; 2]`). + /// + /// For the above to hold, make sure that the hypothetical references are properly aligned and + /// point to valid values ([`f32`] and [`u8`] allow any bit pattern, but not + /// uninitialized/padding bytes). /// /// # Panics /// - /// - This function panic if the slices `positions`, `colors` and if applicable, "texcoords" - /// (`texture.unwrap().1`) have different lengths. - /// - This function does its best to check if the unsafe trait contracts - /// (of [`AsVertexPosition`], [`AsVertexColor`] and [`AsVertexTexCoord`]) are upheld. These - /// checks should not affect performance in release builds and may panic if a contract is - /// violated. - #[doc(alias = "SDL_RenderGeometry")] + /// The following conditions are checked. All must hold or the function panics. + /// + /// Vertices are passed as separate lists, all lists must have the same length. + /// - `positions.len() == colors.len()` + /// - `positions.len() == tex_coords.len()` + /// + /// Offsets must be correct. + /// - `position_offset + size_of::() <= size_of::()`: an [`FPoint`] must fit in `PosVertex` at `position_offset` bytes from the start. + /// - `color_offset + size_of::() <= size_of::()`: a [`Color`][pixels::Color] must fit in `ColorVertex` at `color_offset` bytes from the start. + /// - `tex_coord_offset + size_of::() <= size_of::()`: an [`FPoint`] must fit in `TexCoordVertex` at `tex_coord_offset` bytes from the start. + /// + /// Various sizes must fit in a [C `int`][c_int]. + /// - `positions.len() <= c_int::MAX` + /// - `size_of::() <= c_int::MAX` + /// - `size_of::() <= c_int::MAX` + /// - `size_of::() <= c_int::MAX` + #[inline] #[doc(alias = "SDL_RenderGeometryRaw")] - pub fn render_geometry( + pub unsafe fn render_geometry_raw<'a, PosVertex, ColorVertex, TexCoordVertex>( &mut self, - positions: &[impl AsVertexPosition], - colors: &[impl AsVertexColor], - texture: Option<(&Texture, &[impl AsVertexTexCoord])>, - indices: VertexIndices<'_>, + positions: &[PosVertex], + position_offset: usize, + colors: &[ColorVertex], + color_offset: usize, + texture_params: Option>, + indices: impl Into>, ) -> Result<(), String> { let num_vertices = positions.len(); - - assert!(num_vertices <= c_int::MAX as usize); assert_eq!(num_vertices, colors.len()); - if let Some((_, texcoords)) = texture { - assert_eq!(num_vertices, texcoords.len()); - }; - if num_vertices < 3 { - // SDL also does this, but now we know the slices have at least one element - return Ok(()); - } + assert!(position_offset + size_of::() <= size_of::()); + assert!(color_offset + size_of::() <= size_of::()); + let (texture, uv, uv_stride) = if let Some(texture_params) = texture_params { + assert_eq!(num_vertices, texture_params.tex_coords.len()); + assert!( + texture_params.tex_coord_offset + size_of::() + <= size_of::(), + ); - // If no unsafe trait contract is broken (then the assertions here do not fail) this - // function can be optimized to return (slice + constant, constant). - // The constants are dependent on type sizes and field offset. - #[inline(always)] - unsafe fn ptr_and_stride( - slice: *const [E], - element_field: *const F, - ) -> (*const R, c_int) { - let offset = element_field.cast::().offset_from(slice.cast::()); + ( + texture_params.texture.raw, + texture_params + .tex_coords + .as_ptr() + .cast::() + .byte_offset(texture_params.tex_coord_offset.try_into().unwrap()), + size_of::().try_into().unwrap(), + ) + } else { + (ptr::null_mut(), ptr::null(), 0) + }; - assert!(offset >= 0); - assert!(size_of::() <= size_of::()); - assert!(size_of::() <= c_int::MAX as usize); - assert!(offset as usize <= size_of::() - size_of::()); + let xy = positions + .as_ptr() + .cast::() + .byte_offset(position_offset.try_into().unwrap()); + let xy_stride = size_of::().try_into().unwrap(); - (slice.byte_offset(offset).cast(), size_of::() as c_int) - } + let color = colors + .as_ptr() + .cast::() + .byte_offset(color_offset.try_into().unwrap()); + let color_stride = size_of::().try_into().unwrap(); - let ret = unsafe { - let (xy, xy_stride) = - ptr_and_stride::<_, FPoint, f32>(positions, positions[0].as_vertex_position()); - let (color, color_stride) = ptr_and_stride::<_, pixels::Color, sys::SDL_Color>( - colors, - colors[0].as_vertex_color(), - ); - - let (texture, (uv, uv_stride)) = if let Some((texture, texcoords)) = texture { - ( - texture.raw, - ptr_and_stride::<_, FPoint, f32>(texcoords, texcoords[0].as_vertex_tex_coord()), - ) - } else { - (ptr::null_mut(), (ptr::null(), 0)) - }; - - let (indices, num_indices, size_indices) = indices.into_raw(); + let (indices, num_indices, size_indices) = indices.into().into_raw(); + let ret = unsafe { sys::SDL_RenderGeometryRaw( self.context.raw, texture, @@ -2302,7 +2203,7 @@ impl Canvas { color_stride, uv, uv_stride, - num_vertices as c_int, + num_vertices.try_into().unwrap(), indices, num_indices, size_indices, From 05a62ed7b4b43433d9698cf3a8fd0a390e0be1dd Mon Sep 17 00:00:00 2001 From: Antoni Spaanderman <56turtle56@gmail.com> Date: Sun, 30 Mar 2025 18:26:19 +0200 Subject: [PATCH 06/10] add convenience impls for &Vec<_> to VertexIndices --- src/sdl2/render.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/sdl2/render.rs b/src/sdl2/render.rs index 8dd0612be1..cccb71c652 100644 --- a/src/sdl2/render.rs +++ b/src/sdl2/render.rs @@ -2047,6 +2047,20 @@ impl<'a> From<&'a [[i32; 3]]> for VertexIndices<'a> { } } +macro_rules! impl_vec_ref_into_vertex_indices { + ($($ty:ty)*) => { + $( + impl<'a> From<&'a Vec<$ty>> for VertexIndices<'a> { + fn from(value: &'a Vec<$ty>) -> Self { + Self::from(value.as_slice()) + } + } + )* + }; +} + +impl_vec_ref_into_vertex_indices!(u8 u16 u32 i32 [u8; 3] [u16; 3] [u32; 3] [i32; 3]); + #[derive(Clone, Copy)] pub struct RenderGeometryTextureParams<'a, TexCoordVertex> { #[cfg(not(feature = "unsafe_textures"))] From c0c18ca2746121f0be75ad54f658cf109ad84243 Mon Sep 17 00:00:00 2001 From: Antoni Spaanderman <56turtle56@gmail.com> Date: Sun, 30 Mar 2025 20:41:04 +0200 Subject: [PATCH 07/10] update example to use 2 ways of using render_geometry_raw, and use render_geometry --- examples/render-geometry.rs | 68 +++++++++++++++++++++++++++++++------ src/sdl2/render.rs | 10 ++++-- 2 files changed, 66 insertions(+), 12 deletions(-) diff --git a/examples/render-geometry.rs b/examples/render-geometry.rs index 6726ade55b..419fe7cc8a 100644 --- a/examples/render-geometry.rs +++ b/examples/render-geometry.rs @@ -4,7 +4,7 @@ use sdl2::event::Event; use sdl2::keyboard::Keycode; use sdl2::pixels::Color; use sdl2::rect::FPoint; -use sdl2::render::{RenderGeometryTextureParams, VertexIndices}; +use sdl2::render::{RenderGeometryTextureParams, Vertex, VertexIndices}; use std::mem::offset_of; use std::thread; use std::time::Duration; @@ -43,6 +43,29 @@ fn main() { canvas.set_draw_color(Color::BLACK); canvas.clear(); + // First, draw a triangle using `render_geometry`. The `tex_coord` fields are unused but + // must be provided, `render_geometry` only supports `sdl2::render::Vertex`. + let vertices = [ + Vertex { + position: FPoint::new(100.0, 200.0), + color: Color::RED, + tex_coord: FPoint::new(0.0, 0.0), + }, + Vertex { + position: FPoint::new(200.0, 200.0), + color: Color::GREEN, + tex_coord: FPoint::new(0.0, 0.0), + }, + Vertex { + position: FPoint::new(150.0, 100.0), + color: Color::BLUE, + tex_coord: FPoint::new(0.0, 0.0), + }, + ]; + canvas + .render_geometry(&vertices, None, VertexIndices::Sequential) + .expect("render_geometry failed (probably unsupported, see error message)"); + // `render_geometry_raw` supports any custom struct as long as it contains the needed data // (or other layout compatible of the needed data). // The struct does not need to be `repr(C)` or `Copy` for example. @@ -59,26 +82,31 @@ fn main() { pos: FPoint, } - // Define the triangles + // Define the vertices of a square let vertices = [ MyVertex { - color: [0xff, 0, 0, 0xff], + color: [0, 0, 0, 0xff], foo: b"some".to_vec(), - pos: FPoint::new(100.0, 500.0), + pos: FPoint::new(300.0, 100.0), }, MyVertex { color: [0, 0xff, 0, 0xff], foo: b"unrelated".to_vec(), - pos: FPoint::new(700.0, 500.0), + pos: FPoint::new(400.0, 100.0), }, MyVertex { - color: [0, 0, 0xff, 0xff], + color: [0xff, 0, 0, 0xff], foo: b"data".to_vec(), - pos: FPoint::new(400.0, 100.0), + pos: FPoint::new(300.0, 200.0), + }, + MyVertex { + color: [0xff, 0xff, 0, 0xff], + foo: b"!".to_vec(), + pos: FPoint::new(400.0, 200.0), }, ]; - // Actually render + // A square is rendered as two triangles (see indices) // SAFETY: core::mem::offset_of makes sure the offsets are right. unsafe { canvas.render_geometry_raw( @@ -87,10 +115,30 @@ fn main() { &vertices, offset_of!(MyVertex, color), None::>, - VertexIndices::Sequential, + &[[0, 1, 2], [1, 2, 3]], + ) + } + .expect("render_geometry_raw failed (probably unsupported, see error message)"); + + // Parameters can be reused, here only the positions are swapped out for new ones. + // SAFETY: core::mem::offset_of makes sure the offsets are right. + // The offset 0 is correct because the element type of positions is `[f32; 2]`. + unsafe { + canvas.render_geometry_raw( + &[ + [500.0f32, 100.0], + [600.0, 100.0], + [500.0, 200.0], + [600.0, 200.0], + ], + 0, + &vertices, + offset_of!(MyVertex, color), + None::>, + &[[0, 1, 2], [1, 2, 3]], ) } - .expect("render_geometry failed (probably unsupported, see error message)"); + .expect("render_geometry_raw failed (probably unsupported, see error message)"); canvas.present(); thread::sleep(Duration::from_millis(16)); diff --git a/src/sdl2/render.rs b/src/sdl2/render.rs index cccb71c652..e998f5d765 100644 --- a/src/sdl2/render.rs +++ b/src/sdl2/render.rs @@ -2047,7 +2047,7 @@ impl<'a> From<&'a [[i32; 3]]> for VertexIndices<'a> { } } -macro_rules! impl_vec_ref_into_vertex_indices { +macro_rules! impl_into_vertex_indices_forward { ($($ty:ty)*) => { $( impl<'a> From<&'a Vec<$ty>> for VertexIndices<'a> { @@ -2055,11 +2055,17 @@ macro_rules! impl_vec_ref_into_vertex_indices { Self::from(value.as_slice()) } } + + impl<'a, const N: usize> From<&'a [$ty; N]> for VertexIndices<'a> { + fn from(value: &'a [$ty; N]) -> Self { + Self::from(value.as_slice()) + } + } )* }; } -impl_vec_ref_into_vertex_indices!(u8 u16 u32 i32 [u8; 3] [u16; 3] [u32; 3] [i32; 3]); +impl_into_vertex_indices_forward!(u8 u16 u32 i32 [u8; 3] [u16; 3] [u32; 3] [i32; 3]); #[derive(Clone, Copy)] pub struct RenderGeometryTextureParams<'a, TexCoordVertex> { From 48441c1a0f32592116abc18b5f7813700cefc0fc Mon Sep 17 00:00:00 2001 From: Antoni Spaanderman <56turtle56@gmail.com> Date: Sun, 30 Mar 2025 21:23:10 +0200 Subject: [PATCH 08/10] mention alignment in safety comments in render_geometry example --- examples/render-geometry.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/render-geometry.rs b/examples/render-geometry.rs index 419fe7cc8a..072e97d357 100644 --- a/examples/render-geometry.rs +++ b/examples/render-geometry.rs @@ -107,7 +107,7 @@ fn main() { ]; // A square is rendered as two triangles (see indices) - // SAFETY: core::mem::offset_of makes sure the offsets are right. + // SAFETY: core::mem::offset_of makes sure the offsets are right and alignment is respected. unsafe { canvas.render_geometry_raw( &vertices, @@ -121,7 +121,7 @@ fn main() { .expect("render_geometry_raw failed (probably unsupported, see error message)"); // Parameters can be reused, here only the positions are swapped out for new ones. - // SAFETY: core::mem::offset_of makes sure the offsets are right. + // SAFETY: core::mem::offset_of makes sure the offsets are right and alignment is respected. // The offset 0 is correct because the element type of positions is `[f32; 2]`. unsafe { canvas.render_geometry_raw( From 48e7c8044ecb096e402c68456f3543aae1466b5e Mon Sep 17 00:00:00 2001 From: Antoni Spaanderman <56turtle56@gmail.com> Date: Sun, 30 Mar 2025 22:50:10 +0200 Subject: [PATCH 09/10] fix and improve docs --- src/sdl2/render.rs | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/src/sdl2/render.rs b/src/sdl2/render.rs index e998f5d765..6d48ff3941 100644 --- a/src/sdl2/render.rs +++ b/src/sdl2/render.rs @@ -2081,8 +2081,8 @@ impl Canvas { /// Render triangles, optionally using a texture. /// /// If you have vertices in a different format, [`Canvas::render_geometry_raw`] (the advanced - /// version of this function) might allow you to do the same as this function without - /// converting the parameters. + /// version of this function) might allow you to do the same as this function without having to + /// convert the parameters to use [`Vertex`]. /// /// `indices` can be of any of the following: /// - [`VertexIndices::Sequential`]: Vertices are rendered in sequential order. The number of @@ -2097,7 +2097,8 @@ impl Canvas { /// - `&[[u8; 3]]`, `&[[u16; 3]]`, `&[[u32; 3]]` or `&[[i32; 3]]`, which work like `&[u8]`, /// `&[u16]`, `&[u32]` or `&[i32]` but ensure at compile time that the number of indices is /// divisible by 3. - /// - Any other (possibly user defined) type that implements `Into>`. + /// - Any other (possibly user defined) type that implements + /// [`Into>`][VertexIndices#trait-implementations]. #[doc(alias = "SDL_RenderGeometry")] pub fn render_geometry<'a>( &mut self, @@ -2134,6 +2135,10 @@ impl Canvas { /// /// See the documentation of [`Canvas::render_geometry`] for usage of the `indices` parameter. /// + /// When a texture is not used, the vertex type must still be specified (because of a Rust + /// limitation, more info [here](https://github.com/rust-lang/rust/issues/36887)). This can be + /// done with turbofish syntax after `None` like so: `None::>`. + /// /// # Safety /// /// It must be sound to [create references to](core::ptr#pointer-to-reference-conversion): @@ -2154,9 +2159,13 @@ impl Canvas { /// - `positions.len() == tex_coords.len()` /// /// Offsets must be correct. - /// - `position_offset + size_of::() <= size_of::()`: an [`FPoint`] must fit in `PosVertex` at `position_offset` bytes from the start. - /// - `color_offset + size_of::() <= size_of::()`: a [`Color`][pixels::Color] must fit in `ColorVertex` at `color_offset` bytes from the start. - /// - `tex_coord_offset + size_of::() <= size_of::()`: an [`FPoint`] must fit in `TexCoordVertex` at `tex_coord_offset` bytes from the start. + /// - `position_offset + size_of::() <= size_of::()`: an [`FPoint`] must fit + /// in `PosVertex` at `position_offset` bytes from the start. + /// - `color_offset + size_of::() <= size_of::()`: a + /// [`Color`][pixels::Color] must fit in `ColorVertex` at `color_offset` bytes from the + /// start. + /// - `tex_coord_offset + size_of::() <= size_of::()`: an [`FPoint`] + /// must fit in `TexCoordVertex` at `tex_coord_offset` bytes from the start. /// /// Various sizes must fit in a [C `int`][c_int]. /// - `positions.len() <= c_int::MAX` From f0cc990a2a4f40c8dc2c339829ccce18ff67bb26 Mon Sep 17 00:00:00 2001 From: Antoni Spaanderman <56turtle56@gmail.com> Date: Sun, 30 Mar 2025 22:55:01 +0200 Subject: [PATCH 10/10] update changelog --- changelog.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog.md b/changelog.md index 7dcbfaaace..80c052d7b4 100644 --- a/changelog.md +++ b/changelog.md @@ -3,6 +3,8 @@ when upgrading from a version of rust-sdl2 to another. ### Next +[PR #1472](https://github.com/Rust-SDL2/rust-sdl2/pull/1472) Add `Canvas::render_geometry`, `Canvas::render_geometry_raw` and an example that uses them both. Both these bindings use `SDL_RenderGeometryRaw`. + [PR #1473](https://github.com/Rust-SDL2/rust-sdl2/pull/1473) **BREAKING CHANGE** Fix UB in `ToColor` implementations and remove implementation for `isize`. [PR #1414](https://github.com/Rust-SDL2/rust-sdl2/pull/1414) Use `TTF_GlyphIsProvided32` and `TTF_GlyphMetrics32` instead of the 16 bit ones.