imgui_gfx_renderer/
lib.rs

1#[macro_use]
2pub extern crate gfx;
3pub extern crate imgui;
4
5use gfx::format::BlendFormat;
6use gfx::handle::{Buffer, RenderTargetView};
7use gfx::memory::Bind;
8use gfx::pso::PipelineState;
9use gfx::texture::{FilterMethod, SamplerInfo, WrapMode};
10use gfx::traits::FactoryExt;
11use gfx::{CommandBuffer, Encoder, Factory, IntoIndexBuffer, Rect, Resources, Slice};
12use imgui::internal::RawWrapper;
13use imgui::{BackendFlags, DrawCmd, DrawCmdParams, DrawData, DrawIdx, TextureId, Textures};
14use std::error::Error;
15use std::fmt;
16use std::usize;
17
18#[derive(Clone, Debug)]
19pub enum RendererError {
20    Update(gfx::UpdateError<usize>),
21    Buffer(gfx::buffer::CreationError),
22    Pipeline(gfx::PipelineStateError<String>),
23    Combined(gfx::CombinedError),
24    BadTexture(TextureId),
25}
26
27impl fmt::Display for RendererError {
28    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
29        use self::RendererError::*;
30        match *self {
31            Update(ref e) => write!(f, "{}", e),
32            Buffer(ref e) => write!(f, "{}", e),
33            Pipeline(ref e) => write!(f, "{}", e),
34            Combined(ref e) => write!(f, "{}", e),
35            BadTexture(ref t) => write!(f, "Bad texture ID: {}", t.id()),
36        }
37    }
38}
39
40impl Error for RendererError {
41    fn source(&self) -> Option<&(dyn Error + 'static)> {
42        use self::RendererError::*;
43        match *self {
44            Update(ref e) => Some(e),
45            Buffer(ref e) => Some(e),
46            Pipeline(ref e) => Some(e),
47            Combined(ref e) => Some(e),
48            BadTexture(_) => None,
49        }
50    }
51}
52
53impl From<gfx::UpdateError<usize>> for RendererError {
54    fn from(e: gfx::UpdateError<usize>) -> RendererError {
55        RendererError::Update(e)
56    }
57}
58
59impl From<gfx::buffer::CreationError> for RendererError {
60    fn from(e: gfx::buffer::CreationError) -> RendererError {
61        RendererError::Buffer(e)
62    }
63}
64
65impl From<gfx::PipelineStateError<String>> for RendererError {
66    fn from(e: gfx::PipelineStateError<String>) -> RendererError {
67        RendererError::Pipeline(e)
68    }
69}
70
71impl From<gfx::CombinedError> for RendererError {
72    fn from(e: gfx::CombinedError) -> RendererError {
73        RendererError::Combined(e)
74    }
75}
76
77#[derive(Copy, Clone, Debug, PartialEq, Eq)]
78pub enum Shaders {
79    /// OpenGL 4.0+
80    GlSl400,
81    /// OpenGL 3.2+
82    GlSl150,
83    /// OpenGL 3.0+
84    GlSl130,
85    /// OpenGL 2.0+
86    GlSl110,
87    /// OpenGL ES 3.0+
88    GlSlEs300,
89    /// OpenGL ES 2.0+
90    GlSlEs100,
91    /// HLSL Shader Model 4.0+
92    HlslSm40,
93}
94
95impl Shaders {
96    fn get_program_code(self) -> (&'static [u8], &'static [u8]) {
97        use self::Shaders::*;
98        match self {
99            GlSl400 => (
100                include_bytes!("shader/glsl_400.vert"),
101                include_bytes!("shader/glsl_400.frag"),
102            ),
103            GlSl150 => (
104                include_bytes!("shader/glsl_150.vert"),
105                include_bytes!("shader/glsl_150.frag"),
106            ),
107            GlSl130 => (
108                include_bytes!("shader/glsl_130.vert"),
109                include_bytes!("shader/glsl_130.frag"),
110            ),
111            GlSl110 => (
112                include_bytes!("shader/glsl_110.vert"),
113                include_bytes!("shader/glsl_110.frag"),
114            ),
115            GlSlEs300 => (
116                include_bytes!("shader/glsles_300.vert"),
117                include_bytes!("shader/glsles_300.frag"),
118            ),
119            GlSlEs100 => (
120                include_bytes!("shader/glsles_100.vert"),
121                include_bytes!("shader/glsles_100.frag"),
122            ),
123            HlslSm40 => (
124                include_bytes!("data/vertex.fx"),
125                include_bytes!("data/pixel.fx"),
126            ),
127        }
128    }
129}
130
131pub type Texture<R> = (
132    gfx::handle::ShaderResourceView<R, [f32; 4]>,
133    gfx::handle::Sampler<R>,
134);
135
136pub struct Renderer<Cf: BlendFormat, R: Resources> {
137    vertex_buffer: Buffer<R, GfxDrawVert>,
138    index_buffer: Buffer<R, DrawIdx>,
139    slice: Slice<R>,
140    pso: PipelineState<R, pipeline::Meta<Cf>>,
141    font_texture: Texture<R>,
142    textures: Textures<Texture<R>>,
143    #[cfg(feature = "directx")]
144    constants: Buffer<R, constants::Constants>,
145}
146
147impl<Cf, R> Renderer<Cf, R>
148where
149    Cf: BlendFormat,
150    R: Resources,
151{
152    pub fn init<F: Factory<R>>(
153        ctx: &mut imgui::Context,
154        factory: &mut F,
155        shaders: Shaders,
156    ) -> Result<Renderer<Cf, R>, RendererError> {
157        let (vs_code, ps_code) = shaders.get_program_code();
158        let pso = factory.create_pipeline_simple(vs_code, ps_code, pipeline::new::<Cf>())?;
159        let vertex_buffer = factory.create_buffer::<GfxDrawVert>(
160            256,
161            gfx::buffer::Role::Vertex,
162            gfx::memory::Usage::Dynamic,
163            Bind::empty(),
164        )?;
165        let index_buffer = factory.create_buffer::<DrawIdx>(
166            256,
167            gfx::buffer::Role::Index,
168            gfx::memory::Usage::Dynamic,
169            Bind::empty(),
170        )?;
171        let font_texture = upload_font_texture(ctx.fonts(), factory)?;
172        let slice = Slice {
173            start: 0,
174            end: 0,
175            base_vertex: 0,
176            instances: None,
177            buffer: index_buffer.clone().into_index_buffer(factory),
178        };
179        ctx.set_renderer_name(Some(format!(
180            "imgui-gfx-renderer {}",
181            env!("CARGO_PKG_VERSION")
182        )));
183        ctx.io_mut()
184            .backend_flags
185            .insert(BackendFlags::RENDERER_HAS_VTX_OFFSET);
186        Ok(Renderer {
187            vertex_buffer,
188            index_buffer,
189            slice,
190            pso,
191            font_texture,
192            textures: Textures::new(),
193            #[cfg(feature = "directx")]
194            constants: factory.create_constant_buffer(1),
195        })
196    }
197    pub fn reload_font_texture<F: Factory<R>>(
198        &mut self,
199        ctx: &mut imgui::Context,
200        factory: &mut F,
201    ) -> Result<(), RendererError> {
202        self.font_texture = upload_font_texture(ctx.fonts(), factory)?;
203        Ok(())
204    }
205    pub fn textures(&mut self) -> &mut Textures<Texture<R>> {
206        &mut self.textures
207    }
208    pub fn render<F: Factory<R>, C: CommandBuffer<R>>(
209        &mut self,
210        factory: &mut F,
211        encoder: &mut Encoder<R, C>,
212        target: &mut RenderTargetView<R, Cf>,
213        draw_data: &DrawData,
214    ) -> Result<(), RendererError> {
215        let fb_width = draw_data.display_size[0] * draw_data.framebuffer_scale[0];
216        let fb_height = draw_data.display_size[1] * draw_data.framebuffer_scale[1];
217        if !(fb_width > 0.0 && fb_height > 0.0) {
218            return Ok(());
219        }
220        let left = draw_data.display_pos[0];
221        let right = draw_data.display_pos[0] + draw_data.display_size[0];
222        let top = draw_data.display_pos[1];
223        let bottom = draw_data.display_pos[1] + draw_data.display_size[1];
224        let matrix = [
225            [(2.0 / (right - left)), 0.0, 0.0, 0.0],
226            [0.0, (2.0 / (top - bottom)), 0.0, 0.0],
227            [0.0, 0.0, -1.0, 0.0],
228            [
229                (right + left) / (left - right),
230                (top + bottom) / (bottom - top),
231                0.0,
232                1.0,
233            ],
234        ];
235        let clip_off = draw_data.display_pos;
236        let clip_scale = draw_data.framebuffer_scale;
237        for draw_list in draw_data.draw_lists() {
238            self.upload_vertex_buffer(factory, encoder, unsafe {
239                draw_list.transmute_vtx_buffer::<GfxDrawVert>()
240            })?;
241            self.upload_index_buffer(factory, encoder, draw_list.idx_buffer())?;
242            self.slice.start = 0;
243            for cmd in draw_list.commands() {
244                match cmd {
245                    DrawCmd::Elements {
246                        count,
247                        cmd_params:
248                            DrawCmdParams {
249                                clip_rect,
250                                texture_id,
251                                vtx_offset,
252                                idx_offset,
253                                ..
254                            },
255                    } => {
256                        let clip_rect = [
257                            (clip_rect[0] - clip_off[0]) * clip_scale[0],
258                            (clip_rect[1] - clip_off[1]) * clip_scale[1],
259                            (clip_rect[2] - clip_off[0]) * clip_scale[0],
260                            (clip_rect[3] - clip_off[1]) * clip_scale[1],
261                        ];
262
263                        self.slice.start = idx_offset as u32;
264                        self.slice.end = self.slice.start + count as u32;
265                        self.slice.base_vertex = vtx_offset as u32;
266
267                        if clip_rect[0] < fb_width
268                            && clip_rect[1] < fb_height
269                            && clip_rect[2] >= 0.0
270                            && clip_rect[3] >= 0.0
271                        {
272                            let scissor = Rect {
273                                x: f32::max(0.0, clip_rect[0]).floor() as u16,
274                                y: f32::max(0.0, clip_rect[1]).floor() as u16,
275                                w: (clip_rect[2] - clip_rect[0]).abs().ceil() as u16,
276                                h: (clip_rect[3] - clip_rect[1]).abs().ceil() as u16,
277                            };
278                            let tex = self.lookup_texture(texture_id)?;
279                            #[cfg(feature = "directx")]
280                            {
281                                let constants = constants::Constants { matrix };
282                                encoder.update_constant_buffer(&self.constants, &constants);
283                            }
284                            let data = pipeline::Data {
285                                vertex_buffer: &self.vertex_buffer,
286                                #[cfg(not(feature = "directx"))]
287                                matrix: &matrix,
288                                #[cfg(feature = "directx")]
289                                constants: &self.constants,
290                                tex,
291                                scissor: &scissor,
292                                target,
293                            };
294                            encoder.draw(&self.slice, &self.pso, &data);
295                        }
296                    }
297                    DrawCmd::ResetRenderState => (), // TODO
298                    DrawCmd::RawCallback { callback, raw_cmd } => unsafe {
299                        callback(draw_list.raw(), raw_cmd)
300                    },
301                }
302            }
303        }
304        Ok(())
305    }
306    fn upload_vertex_buffer<F: Factory<R>, C: CommandBuffer<R>>(
307        &mut self,
308        factory: &mut F,
309        encoder: &mut Encoder<R, C>,
310        vtx_buffer: &[GfxDrawVert],
311    ) -> Result<(), RendererError> {
312        if self.vertex_buffer.len() < vtx_buffer.len() {
313            self.vertex_buffer = factory.create_buffer::<GfxDrawVert>(
314                vtx_buffer.len(),
315                gfx::buffer::Role::Vertex,
316                gfx::memory::Usage::Dynamic,
317                Bind::empty(),
318            )?;
319        }
320        encoder.update_buffer(&self.vertex_buffer, vtx_buffer, 0)?;
321        Ok(())
322    }
323    fn upload_index_buffer<F: Factory<R>, C: CommandBuffer<R>>(
324        &mut self,
325        factory: &mut F,
326        encoder: &mut Encoder<R, C>,
327        idx_buffer: &[DrawIdx],
328    ) -> Result<(), RendererError> {
329        if self.index_buffer.len() < idx_buffer.len() {
330            self.index_buffer = factory.create_buffer::<DrawIdx>(
331                idx_buffer.len(),
332                gfx::buffer::Role::Index,
333                gfx::memory::Usage::Dynamic,
334                Bind::empty(),
335            )?;
336            self.slice.buffer = self.index_buffer.clone().into_index_buffer(factory);
337        }
338        encoder.update_buffer(&self.index_buffer, idx_buffer, 0)?;
339        Ok(())
340    }
341    fn lookup_texture(&self, texture_id: TextureId) -> Result<&Texture<R>, RendererError> {
342        if texture_id.id() == usize::MAX {
343            Ok(&self.font_texture)
344        } else if let Some(texture) = self.textures.get(texture_id) {
345            Ok(texture)
346        } else {
347            Err(RendererError::BadTexture(texture_id))
348        }
349    }
350}
351
352fn upload_font_texture<R: Resources, F: Factory<R>>(
353    mut fonts: imgui::FontAtlasRefMut,
354    factory: &mut F,
355) -> Result<Texture<R>, RendererError> {
356    let texture = fonts.build_rgba32_texture();
357    let (_, texture_view) = factory.create_texture_immutable_u8::<gfx::format::Srgba8>(
358        gfx::texture::Kind::D2(
359            texture.width as u16,
360            texture.height as u16,
361            gfx::texture::AaMode::Single,
362        ),
363        gfx::texture::Mipmap::Provided,
364        &[texture.data],
365    )?;
366    fonts.tex_id = TextureId::from(usize::MAX);
367    let sampler = factory.create_sampler(SamplerInfo::new(FilterMethod::Bilinear, WrapMode::Tile));
368    let font_texture = (texture_view, sampler);
369    Ok(font_texture)
370}
371
372#[cfg(feature = "directx")]
373mod constants {
374    use gfx::gfx_constant_struct_meta;
375    use gfx::gfx_impl_struct_meta;
376
377    gfx::gfx_constant_struct! {
378        Constants {
379            // `matrix` is a reserved keyword in HLSL
380            matrix: [[f32; 4]; 4] = "matrix_",
381        }
382    }
383}
384
385// This is basically what gfx_pipeline generates, but with some changes:
386//
387// * Data struct contains references to avoid copying
388// * Everything is parameterized with BlendFormat
389// * Pipeline init is specialized for our structs
390mod pipeline {
391    use super::*;
392    use gfx::format::BlendFormat;
393    use gfx::handle::Manager;
394    use gfx::preset::blend;
395    use gfx::pso::{
396        AccessInfo, DataBind, DataLink, Descriptor, InitError, PipelineData, PipelineInit,
397        RawDataSet,
398    };
399    use gfx::state::ColorMask;
400    use gfx::{ProgramInfo, Resources};
401
402    #[derive(Clone, Debug, PartialEq)]
403    pub struct Data<'a, R: Resources, Cf: BlendFormat + 'a> {
404        pub vertex_buffer: &'a <gfx::VertexBuffer<GfxDrawVert> as DataBind<R>>::Data,
405        #[cfg(not(feature = "directx"))]
406        pub matrix: &'a <gfx::Global<[[f32; 4]; 4]> as DataBind<R>>::Data,
407        #[cfg(feature = "directx")]
408        pub constants: &'a <gfx::ConstantBuffer<super::constants::Constants> as DataBind<R>>::Data,
409        pub tex: &'a <gfx::TextureSampler<[f32; 4]> as DataBind<R>>::Data,
410        pub target: &'a <gfx::BlendTarget<Cf> as DataBind<R>>::Data,
411        pub scissor: &'a <gfx::Scissor as DataBind<R>>::Data,
412    }
413
414    #[derive(Clone, Debug, Hash, PartialEq)]
415    pub struct Meta<Cf: BlendFormat> {
416        vertex_buffer: gfx::VertexBuffer<GfxDrawVert>,
417        #[cfg(not(feature = "directx"))]
418        matrix: gfx::Global<[[f32; 4]; 4]>,
419        #[cfg(feature = "directx")]
420        constants: gfx::ConstantBuffer<super::constants::Constants>,
421        tex: gfx::TextureSampler<[f32; 4]>,
422        target: gfx::BlendTarget<Cf>,
423        scissor: gfx::Scissor,
424    }
425
426    #[derive(Clone, Debug, PartialEq)]
427    pub struct Init<'a, Cf: BlendFormat> {
428        vertex_buffer: <gfx::VertexBuffer<GfxDrawVert> as DataLink<'a>>::Init,
429        #[cfg(not(feature = "directx"))]
430        matrix: <gfx::Global<[[f32; 4]; 4]> as DataLink<'a>>::Init,
431        #[cfg(feature = "directx")]
432        constants: <gfx::ConstantBuffer<super::constants::Constants> as DataLink<'a>>::Init,
433        tex: <gfx::TextureSampler<[f32; 4]> as DataLink<'a>>::Init,
434        target: <gfx::BlendTarget<Cf> as DataLink<'a>>::Init,
435        scissor: <gfx::Scissor as DataLink<'a>>::Init,
436    }
437
438    impl<'a, Cf: BlendFormat> PipelineInit for Init<'a, Cf> {
439        type Meta = Meta<Cf>;
440        fn link_to<'s>(
441            &self,
442            desc: &mut Descriptor,
443            info: &'s ProgramInfo,
444        ) -> Result<Meta<Cf>, InitError<&'s str>> {
445            let mut meta = Meta {
446                vertex_buffer: DataLink::new(),
447                #[cfg(not(feature = "directx"))]
448                matrix: DataLink::new(),
449                #[cfg(feature = "directx")]
450                constants: DataLink::new(),
451                tex: DataLink::new(),
452                target: DataLink::new(),
453                scissor: DataLink::new(),
454            };
455            if let Some(d) = meta
456                .vertex_buffer
457                .link_vertex_buffer(0, &self.vertex_buffer)
458            {
459                assert!(meta.vertex_buffer.is_active());
460                desc.vertex_buffers[0] = Some(d);
461            }
462            for at in &info.vertex_attributes {
463                match meta.vertex_buffer.link_input(at, &self.vertex_buffer) {
464                    Some(Ok(d)) => {
465                        assert!(meta.vertex_buffer.is_active());
466                        desc.attributes[at.slot as usize] = Some(d);
467                        continue;
468                    }
469                    Some(Err(fm)) => return Err(InitError::VertexImport(&at.name, Some(fm))),
470                    None => return Err(InitError::VertexImport(&at.name, None)),
471                }
472            }
473            #[cfg(feature = "directx")]
474            for cb in &info.constant_buffers {
475                match meta.constants.link_constant_buffer(cb, &self.constants) {
476                    Some(Ok(d)) => {
477                        assert!(meta.constants.is_active());
478                        desc.constant_buffers[cb.slot as usize] = Some(d);
479                    }
480                    Some(Err(e)) => return Err(InitError::ConstantBuffer(&cb.name, Some(e))),
481                    None => return Err(InitError::ConstantBuffer(&cb.name, None)),
482                }
483            }
484            #[cfg(not(feature = "directx"))]
485            for gc in &info.globals {
486                match meta.matrix.link_global_constant(gc, &self.matrix) {
487                    Some(Ok(())) => assert!(meta.matrix.is_active()),
488                    Some(Err(e)) => return Err(InitError::GlobalConstant(&gc.name, Some(e))),
489                    None => return Err(InitError::GlobalConstant(&gc.name, None)),
490                }
491            }
492            for srv in &info.textures {
493                match meta.tex.link_resource_view(srv, &self.tex) {
494                    Some(Ok(d)) => {
495                        assert!(meta.tex.is_active());
496                        desc.resource_views[srv.slot as usize] = Some(d);
497                    }
498                    Some(Err(_)) => return Err(InitError::ResourceView(&srv.name, Some(()))),
499                    None => return Err(InitError::ResourceView(&srv.name, None)),
500                }
501            }
502            for sm in &info.samplers {
503                match meta.tex.link_sampler(sm, &self.tex) {
504                    Some(d) => {
505                        assert!(meta.tex.is_active());
506                        desc.samplers[sm.slot as usize] = Some(d);
507                    }
508                    None => return Err(InitError::Sampler(&sm.name, None)),
509                }
510            }
511            for out in &info.outputs {
512                match meta.target.link_output(out, &self.target) {
513                    Some(Ok(d)) => {
514                        assert!(meta.target.is_active());
515                        desc.color_targets[out.slot as usize] = Some(d);
516                    }
517                    Some(Err(fm)) => return Err(InitError::PixelExport(&out.name, Some(fm))),
518                    None => return Err(InitError::PixelExport(&out.name, None)),
519                }
520            }
521            if !info.knows_outputs {
522                use gfx::shade::core::*;
523                let mut out = OutputVar {
524                    name: String::new(),
525                    slot: 0,
526                    base_type: BaseType::F32,
527                    container: ContainerType::Vector(4),
528                };
529                match meta.target.link_output(&out, &self.target) {
530                    Some(Ok(d)) => {
531                        assert!(meta.target.is_active());
532                        desc.color_targets[out.slot as usize] = Some(d);
533                        out.slot += 1;
534                    }
535                    Some(Err(fm)) => return Err(InitError::PixelExport("!known", Some(fm))),
536                    None => (),
537                }
538            }
539            if meta.scissor.link_scissor() {
540                assert!(meta.scissor.is_active());
541                desc.scissor = true;
542            }
543            Ok(meta)
544        }
545    }
546
547    impl<'a, R: Resources, Cf: BlendFormat> PipelineData<R> for Data<'a, R, Cf> {
548        type Meta = Meta<Cf>;
549        fn bake_to(
550            &self,
551            out: &mut RawDataSet<R>,
552            meta: &Meta<Cf>,
553            man: &mut Manager<R>,
554            access: &mut AccessInfo<R>,
555        ) {
556            meta.vertex_buffer
557                .bind_to(out, self.vertex_buffer, man, access);
558            #[cfg(not(feature = "directx"))]
559            {
560                meta.matrix.bind_to(out, self.matrix, man, access);
561            }
562            #[cfg(feature = "directx")]
563            {
564                meta.constants.bind_to(out, self.constants, man, access);
565            }
566            meta.tex.bind_to(out, self.tex, man, access);
567            meta.target.bind_to(out, self.target, man, access);
568            meta.scissor.bind_to(out, self.scissor, man, access);
569        }
570    }
571
572    pub fn new<Cf: BlendFormat>() -> Init<'static, Cf> {
573        Init {
574            vertex_buffer: (),
575            #[cfg(not(feature = "directx"))]
576            matrix: "matrix",
577            #[cfg(feature = "directx")]
578            constants: "Constants",
579            tex: "tex",
580            target: ("Target0", ColorMask::all(), blend::ALPHA),
581            scissor: (),
582        }
583    }
584}
585
586gfx_vertex_struct! {
587    GfxDrawVert {
588        pos: [f32; 2] = "pos",
589        uv: [f32; 2] = "uv",
590        col: [gfx::format::U8Norm; 4] = "col",
591    }
592}