1use imgui::internal::RawWrapper as _;
4use std::{
5 error::Error,
6 ffi::{c_float, c_int, c_void},
7 fmt::Display,
8 mem::offset_of,
9};
10
11type RenderResult = std::result::Result<(), RenderError>;
12
13#[derive(Debug)]
15pub enum RenderError {
16 UpdateTexture(sdl3::render::UpdateTextureError),
17 TextureValue(sdl3::render::TextureValueError),
18 GenericSDL(sdl3::Error),
19}
20
21impl From<sdl3::render::UpdateTextureError> for RenderError {
22 fn from(value: sdl3::render::UpdateTextureError) -> Self {
23 Self::UpdateTexture(value)
24 }
25}
26
27impl From<sdl3::render::TextureValueError> for RenderError {
28 fn from(value: sdl3::render::TextureValueError) -> Self {
29 Self::TextureValue(value)
30 }
31}
32
33impl From<sdl3::Error> for RenderError {
34 fn from(value: sdl3::Error) -> Self {
35 Self::GenericSDL(value)
36 }
37}
38
39impl Display for RenderError {
40 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
41 match &self {
42 Self::UpdateTexture(e) => {
43 write!(f, "{}", e)
44 }
45 Self::TextureValue(e) => {
46 write!(f, "{}", e)
47 }
48 Self::GenericSDL(e) => {
49 write!(f, "{}", e)
50 }
51 }
52 }
53}
54
55impl Error for RenderError {
56 fn source(&self) -> Option<&(dyn Error + 'static)> {
57 match &self {
58 Self::UpdateTexture(e) => Some(e),
59 Self::TextureValue(e) => Some(e),
60 Self::GenericSDL(e) => Some(e),
61 }
62 }
63}
64
65pub struct Renderer<'a> {
67 texture_map: imgui::Textures<sdl3::render::Texture<'a>>,
68 color_buffer: Vec<sdl3_sys::pixels::SDL_FColor>,
69}
70
71impl<'a> Renderer<'a> {
72 pub fn new(
98 texture_creator: &'a sdl3::render::TextureCreator<impl std::any::Any>,
99 imgui_context: &mut imgui::Context,
100 ) -> Result<Self, RenderError> {
101 let mut texture_map = imgui::Textures::new();
102 Self::prepare_font_atlas(texture_creator, imgui_context, &mut texture_map)?;
103
104 imgui_context.set_renderer_name(Some(format!(
105 "imgui-rs-sdl3-renderer {}",
106 env!("CARGO_PKG_VERSION")
107 )));
108
109 imgui_context
110 .io_mut()
111 .backend_flags
112 .insert(imgui::BackendFlags::RENDERER_HAS_VTX_OFFSET);
113
114 Ok(Self {
115 texture_map,
116 color_buffer: Vec::new(),
117 })
118 }
119
120 pub fn render(
149 &mut self,
150 draw_data: &imgui::DrawData,
151 canvas: &mut sdl3::render::Canvas<impl sdl3::render::RenderTarget>,
152 ) -> RenderResult {
153 struct CanvasBackup {
154 viewport: sdl3::rect::Rect,
155 clip: sdl3::render::ClippingRect,
156 }
157
158 let backup = CanvasBackup {
159 viewport: canvas.viewport(),
160 clip: canvas.clip_rect(),
161 };
162
163 Self::set_up_render_state(canvas);
164
165 let fb_width = draw_data.display_size[0] * draw_data.framebuffer_scale[0];
167 let fb_height = draw_data.display_size[1] * draw_data.framebuffer_scale[1];
168
169 if fb_width == 0_f32 || fb_height == 0_f32 {
171 return Ok(());
172 }
173
174 for draw_list in draw_data.draw_lists() {
175 for command in draw_list.commands() {
176 match command {
177 imgui::DrawCmd::Elements { count, cmd_params } => {
178 Self::render_elements(
179 &self.texture_map,
180 &mut self.color_buffer,
181 canvas,
182 draw_list.vtx_buffer(),
183 draw_list.idx_buffer(),
184 count,
185 &cmd_params,
186 &draw_data.display_pos,
187 &draw_data.framebuffer_scale,
188 (fb_width, fb_height),
189 )?;
190 }
191 imgui::DrawCmd::ResetRenderState => {
192 Self::set_up_render_state(canvas);
193 }
194 imgui::DrawCmd::RawCallback { callback, raw_cmd } => unsafe {
195 callback(draw_list.raw(), raw_cmd);
196 },
197 }
198 }
199 }
200
201 canvas.set_viewport(backup.viewport);
202 canvas.set_clip_rect(backup.clip);
203 Ok(())
204 }
205
206 #[allow(clippy::too_many_arguments)]
207 fn render_elements(
208 texture_map: &imgui::Textures<sdl3::render::Texture<'a>>,
209 color_buffer: &mut Vec<sdl3_sys::pixels::SDL_FColor>,
210 canvas: &mut sdl3::render::Canvas<impl sdl3::render::RenderTarget>,
211 vertex_buffer: &[imgui::DrawVert],
212 index_buffer: &[imgui::DrawIdx],
213 elem_count: usize,
214 elem_params: &imgui::DrawCmdParams,
215 pos: &[f32; 2],
216 scale: &[f32; 2],
217 fb_size: (f32, f32),
218 ) -> RenderResult {
219 let imgui::DrawCmdParams {
220 clip_rect,
221 texture_id,
222 vtx_offset,
223 idx_offset,
224 } = elem_params;
225
226 let clip_min = (
227 (clip_rect[0] - pos[0]) * scale[0],
228 (clip_rect[1] - pos[1]) * scale[1],
229 );
230 let clip_max = (
231 (clip_rect[2] - pos[0]) * scale[0],
232 (clip_rect[3] - pos[1]) * scale[1],
233 );
234 if clip_min.0 >= fb_size.0
235 || clip_min.1 >= fb_size.1
236 || clip_max.0 < 0.0
237 || clip_max.1 < 0.0
238 {
239 return Ok(());
240 }
241
242 let rect = sdl3::rect::Rect::new(
243 clip_min.0 as i32,
244 clip_min.1 as i32,
245 (clip_max.0 - clip_min.0) as u32,
246 (clip_max.1 - clip_min.1) as u32,
247 );
248 canvas.set_clip_rect(rect);
249
250 let texture = texture_map.get(*texture_id);
251 Self::render_raw_geometry(
252 canvas,
253 color_buffer,
254 texture,
255 &vertex_buffer[*vtx_offset..],
256 &index_buffer[*idx_offset..idx_offset + elem_count],
257 )
258 }
259
260 fn render_raw_geometry(
261 canvas: &mut sdl3::render::Canvas<impl sdl3::render::RenderTarget>,
262 color_buffer: &mut Vec<sdl3_sys::pixels::SDL_FColor>,
263 texture: Option<&sdl3::render::Texture>,
264 vertices: &[imgui::DrawVert],
265 indices: &[imgui::DrawIdx],
266 ) -> RenderResult {
267 let vert_stride = size_of::<imgui::DrawVert>() as c_int;
268 color_buffer.clear();
269 color_buffer.extend(vertices.iter().map(|vert| sdl3_sys::pixels::SDL_FColor {
271 r: vert.col[0] as f32 / 255_f32,
272 g: vert.col[1] as f32 / 255_f32,
273 b: vert.col[2] as f32 / 255_f32,
274 a: vert.col[3] as f32 / 255_f32,
275 }));
276
277 let renderer = canvas.raw();
278 let texture = texture.map_or(std::ptr::null_mut(), |texture| texture.raw());
279
280 let xy = unsafe {
281 vertices.as_ptr().byte_add(offset_of!(imgui::DrawVert, pos)) as *const c_float
282 };
283 let uv = unsafe {
284 vertices.as_ptr().byte_add(offset_of!(imgui::DrawVert, uv)) as *const c_float
285 };
286 let idx = indices.as_ptr() as *const c_void;
287 let colors = color_buffer.as_ptr();
288
289 unsafe {
290 sdl3_sys::render::SDL_RenderGeometryRaw(
291 renderer,
292 texture,
293 xy,
294 vert_stride,
295 colors,
296 size_of::<sdl3_sys::pixels::SDL_FColor>() as c_int,
297 uv,
298 vert_stride,
299 vertices.len() as c_int,
300 idx,
301 indices.len() as c_int,
302 size_of::<imgui::DrawIdx>() as c_int,
303 )
304 }
305 .then_some(())
306 .ok_or_else(|| sdl3::get_error().into())
307 }
308
309 fn set_up_render_state(canvas: &mut sdl3::render::Canvas<impl sdl3::render::RenderTarget>) {
310 canvas.set_clip_rect(None);
311 canvas.set_viewport(None);
312 }
313
314 fn prepare_font_atlas(
315 creator: &'a sdl3::render::TextureCreator<impl std::any::Any>,
316 imgui_context: &mut imgui::Context,
317 texture_map: &mut imgui::Textures<sdl3::render::Texture<'a>>,
318 ) -> RenderResult {
319 let font_atlas = imgui_context.fonts().build_rgba32_texture();
320 let rgba32_format: sdl3::pixels::PixelFormat =
321 sdl3_sys::pixels::SDL_PixelFormat::RGBA32.try_into()?;
322 let mut font_texture =
323 creator.create_texture_static(rgba32_format, font_atlas.width, font_atlas.height)?;
324
325 font_texture.update(
326 None,
327 font_atlas.data,
328 rgba32_format.byte_size_of_pixels(font_atlas.width as usize),
329 )?;
330
331 font_texture.set_blend_mode(sdl3::render::BlendMode::Blend);
332 font_texture.set_scale_mode(sdl3::render::ScaleMode::Linear);
333
334 let id = texture_map.insert(font_texture);
335 imgui_context.fonts().tex_id = id;
336 Ok(())
337 }
338}
339