1use glium::backend::{Context, Facade};
2use glium::index::{self, PrimitiveType};
3use glium::program::ProgramChooserCreationError;
4use glium::texture::{ClientFormat, MipmapsOption, RawImage2d, TextureCreationError};
5use glium::uniforms::{
6 MagnifySamplerFilter, MinifySamplerFilter, Sampler, SamplerBehavior, SamplerWrapFunction,
7};
8use glium::{
9 program, uniform, vertex, Blend, BlendingFunction, DrawError, DrawParameters, IndexBuffer,
10 LinearBlendingFactor, Program, Rect, Surface, Texture2d, VertexBuffer,
11};
12
13use imgui::internal::RawWrapper;
14use imgui::{BackendFlags, DrawCmd, DrawCmdParams, DrawData, TextureId, Textures};
15use std::borrow::Cow;
16use std::error::Error;
17use std::fmt;
18use std::rc::Rc;
19
20#[derive(Clone, Debug)]
21pub enum RendererError {
22 Vertex(vertex::BufferCreationError),
23 Index(index::BufferCreationError),
24 Program(ProgramChooserCreationError),
25 Texture(TextureCreationError),
26 Draw(DrawError),
27 BadTexture(TextureId),
28}
29
30impl Error for RendererError {
31 fn source(&self) -> Option<&(dyn Error + 'static)> {
32 use self::RendererError::*;
33 match *self {
34 Vertex(ref e) => Some(e),
35 Index(ref e) => Some(e),
36 Program(ref e) => Some(e),
37 Texture(ref e) => Some(e),
38 Draw(ref e) => Some(e),
39 BadTexture(_) => None,
40 }
41 }
42}
43
44impl fmt::Display for RendererError {
45 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
46 use self::RendererError::*;
47 match *self {
48 Vertex(_) => write!(f, "Vertex buffer creation failed"),
49 Index(_) => write!(f, "Index buffer creation failed"),
50 Program(ref e) => write!(f, "Program creation failed: {}", e),
51 Texture(_) => write!(f, "Texture creation failed"),
52 Draw(ref e) => write!(f, "Drawing failed: {}", e),
53 BadTexture(ref t) => write!(f, "Bad texture ID: {}", t.id()),
54 }
55 }
56}
57
58impl From<vertex::BufferCreationError> for RendererError {
59 fn from(e: vertex::BufferCreationError) -> RendererError {
60 RendererError::Vertex(e)
61 }
62}
63
64impl From<index::BufferCreationError> for RendererError {
65 fn from(e: index::BufferCreationError) -> RendererError {
66 RendererError::Index(e)
67 }
68}
69
70impl From<ProgramChooserCreationError> for RendererError {
71 fn from(e: ProgramChooserCreationError) -> RendererError {
72 RendererError::Program(e)
73 }
74}
75
76impl From<TextureCreationError> for RendererError {
77 fn from(e: TextureCreationError) -> RendererError {
78 RendererError::Texture(e)
79 }
80}
81
82impl From<DrawError> for RendererError {
83 fn from(e: DrawError) -> RendererError {
84 RendererError::Draw(e)
85 }
86}
87
88pub struct Texture {
89 pub texture: Rc<Texture2d>,
90 pub sampler: SamplerBehavior,
91}
92
93pub struct Renderer {
94 ctx: Rc<Context>,
95 program: Program,
96 font_texture: Texture,
97 textures: Textures<Texture>,
98}
99
100#[repr(C)]
101#[derive(Copy, Clone, Debug, PartialEq)]
102pub struct GliumDrawVert {
103 pub pos: [f32; 2],
104 pub uv: [f32; 2],
105 pub col: [u8; 4],
106}
107
108impl glium::vertex::Vertex for GliumDrawVert {
110 #[inline]
111 fn build_bindings() -> glium::vertex::VertexFormat {
112 use std::borrow::Cow::*;
113 &[
114 (
115 Borrowed("pos"),
116 0,
117 -1,
118 glium::vertex::AttributeType::F32F32,
119 false,
120 ),
121 (
122 Borrowed("uv"),
123 8,
124 -1,
125 glium::vertex::AttributeType::F32F32,
126 false,
127 ),
128 (
129 Borrowed("col"),
130 16,
131 -1,
132 glium::vertex::AttributeType::U8U8U8U8,
133 false,
134 ),
135 ]
136 }
137}
138
139impl Renderer {
140 pub fn new<F: Facade>(ctx: &mut imgui::Context, facade: &F) -> Result<Renderer, RendererError> {
142 let program = compile_default_program(facade)?;
143 let font_texture = upload_font_texture(ctx.fonts(), facade.get_context())?;
144 ctx.set_renderer_name(Some(format!(
145 "imgui-glium-renderer {}",
146 env!("CARGO_PKG_VERSION")
147 )));
148 ctx.io_mut()
149 .backend_flags
150 .insert(BackendFlags::RENDERER_HAS_VTX_OFFSET);
151 Ok(Renderer {
152 ctx: Rc::clone(facade.get_context()),
153 program,
154 font_texture,
155 textures: Textures::new(),
156 })
157 }
158
159 #[deprecated(since = "0.13.0", note = "use `new` instead")]
161 pub fn init<F: Facade>(
162 ctx: &mut imgui::Context,
163 facade: &F,
164 ) -> Result<Renderer, RendererError> {
165 Self::new(ctx, facade)
166 }
167
168 pub fn reload_font_texture(&mut self, ctx: &mut imgui::Context) -> Result<(), RendererError> {
169 self.font_texture = upload_font_texture(ctx.fonts(), &self.ctx)?;
170 Ok(())
171 }
172 pub fn textures(&mut self) -> &mut Textures<Texture> {
173 &mut self.textures
174 }
175 fn lookup_texture(&self, texture_id: TextureId) -> Result<&Texture, RendererError> {
176 if texture_id.id() == usize::MAX {
177 Ok(&self.font_texture)
178 } else if let Some(texture) = self.textures.get(texture_id) {
179 Ok(texture)
180 } else {
181 Err(RendererError::BadTexture(texture_id))
182 }
183 }
184 pub fn render<T: Surface>(
185 &mut self,
186 target: &mut T,
187 draw_data: &DrawData,
188 ) -> Result<(), RendererError> {
189 let fb_width = draw_data.display_size[0] * draw_data.framebuffer_scale[0];
190 let fb_height = draw_data.display_size[1] * draw_data.framebuffer_scale[1];
191 if !(fb_width > 0.0 && fb_height > 0.0) {
192 return Ok(());
193 }
194 let _ = self.ctx.insert_debug_marker("imgui-rs: starting rendering");
195 let left = draw_data.display_pos[0];
196 let right = draw_data.display_pos[0] + draw_data.display_size[0];
197 let top = draw_data.display_pos[1];
198 let bottom = draw_data.display_pos[1] + draw_data.display_size[1];
199 let matrix = [
200 [(2.0 / (right - left)), 0.0, 0.0, 0.0],
201 [0.0, (2.0 / (top - bottom)), 0.0, 0.0],
202 [0.0, 0.0, -1.0, 0.0],
203 [
204 (right + left) / (left - right),
205 (top + bottom) / (bottom - top),
206 0.0,
207 1.0,
208 ],
209 ];
210 let clip_off = draw_data.display_pos;
211 let clip_scale = draw_data.framebuffer_scale;
212 for draw_list in draw_data.draw_lists() {
213 let vtx_buffer = VertexBuffer::immutable(&self.ctx, unsafe {
214 draw_list.transmute_vtx_buffer::<GliumDrawVert>()
215 })?;
216 let idx_buffer = IndexBuffer::immutable(
217 &self.ctx,
218 PrimitiveType::TrianglesList,
219 draw_list.idx_buffer(),
220 )?;
221 for cmd in draw_list.commands() {
222 match cmd {
223 DrawCmd::Elements {
224 count,
225 cmd_params:
226 DrawCmdParams {
227 clip_rect,
228 texture_id,
229 vtx_offset,
230 idx_offset,
231 ..
232 },
233 } => {
234 let clip_rect = [
235 (clip_rect[0] - clip_off[0]) * clip_scale[0],
236 (clip_rect[1] - clip_off[1]) * clip_scale[1],
237 (clip_rect[2] - clip_off[0]) * clip_scale[0],
238 (clip_rect[3] - clip_off[1]) * clip_scale[1],
239 ];
240
241 if clip_rect[0] < fb_width
242 && clip_rect[1] < fb_height
243 && clip_rect[2] >= 0.0
244 && clip_rect[3] >= 0.0
245 {
246 let texture = self.lookup_texture(texture_id)?;
247
248 target.draw(
249 vtx_buffer
250 .slice(vtx_offset..)
251 .expect("Invalid vertex buffer range"),
252 idx_buffer
253 .slice(idx_offset..(idx_offset + count))
254 .expect("Invalid index buffer range"),
255 &self.program,
256 &uniform! {
257 matrix: matrix,
258 tex: Sampler(texture.texture.as_ref(), texture.sampler)
259 },
260 &DrawParameters {
261 blend: Blend {
262 alpha: BlendingFunction::Addition {
263 source: LinearBlendingFactor::One,
264 destination: LinearBlendingFactor::OneMinusSourceAlpha,
265 },
266 ..Blend::alpha_blending()
267 },
268 scissor: Some(Rect {
269 left: f32::max(0.0, clip_rect[0]).floor() as u32,
270 bottom: f32::max(0.0, fb_height - clip_rect[3]).floor()
271 as u32,
272 width: (clip_rect[2] - clip_rect[0]).abs().ceil() as u32,
273 height: (clip_rect[3] - clip_rect[1]).abs().ceil() as u32,
274 }),
275 ..DrawParameters::default()
276 },
277 )?;
278 }
279 }
280 DrawCmd::ResetRenderState => (), DrawCmd::RawCallback { callback, raw_cmd } => unsafe {
282 callback(draw_list.raw(), raw_cmd)
283 },
284 }
285 }
286 }
287 let _ = self.ctx.insert_debug_marker("imgui-rs: rendering finished");
288 Ok(())
289 }
290}
291
292fn upload_font_texture(
293 fonts: &mut imgui::FontAtlas,
294 ctx: &Rc<Context>,
295) -> Result<Texture, RendererError> {
296 let texture = fonts.build_rgba32_texture();
297 let data = RawImage2d {
298 data: Cow::Borrowed(texture.data),
299 width: texture.width,
300 height: texture.height,
301 format: ClientFormat::U8U8U8U8,
302 };
303 let font_texture = Texture2d::with_mipmaps(ctx, data, MipmapsOption::NoMipmap)?;
304 fonts.tex_id = TextureId::from(usize::MAX);
305 Ok(Texture {
306 texture: Rc::new(font_texture),
307 sampler: SamplerBehavior {
308 minify_filter: MinifySamplerFilter::Linear,
309 magnify_filter: MagnifySamplerFilter::Linear,
310 wrap_function: (
311 SamplerWrapFunction::BorderClamp,
312 SamplerWrapFunction::BorderClamp,
313 SamplerWrapFunction::BorderClamp,
314 ),
315 ..Default::default()
316 },
317 })
318}
319
320fn compile_default_program<F: Facade>(facade: &F) -> Result<Program, ProgramChooserCreationError> {
321 program!(
322 facade,
323 400 => {
324 vertex: include_str!("shader/glsl_400.vert"),
325 fragment: include_str!("shader/glsl_400.frag"),
326 outputs_srgb: true,
327 },
328 150 => {
329 vertex: include_str!("shader/glsl_150.vert"),
330 fragment: include_str!("shader/glsl_150.frag"),
331 outputs_srgb: true,
332 },
333 130 => {
334 vertex: include_str!("shader/glsl_130.vert"),
335 fragment: include_str!("shader/glsl_130.frag"),
336 outputs_srgb: true,
337 },
338 110 => {
339 vertex: include_str!("shader/glsl_110.vert"),
340 fragment: include_str!("shader/glsl_110.frag"),
341 outputs_srgb: true,
342 },
343 300 es => {
344 vertex: include_str!("shader/glsles_300.vert"),
345 fragment: include_str!("shader/glsles_300.frag"),
346 outputs_srgb: true,
347 },
348 100 es => {
349 vertex: include_str!("shader/glsles_100.vert"),
350 fragment: include_str!("shader/glsles_100.frag"),
351 outputs_srgb: true,
352 },
353 )
354}