#[macro_use] pub extern crate gfx; pub extern crate imgui; use gfx::format::BlendFormat; use gfx::handle::{Buffer, RenderTargetView}; use gfx::memory::Bind; use gfx::pso::PipelineState; use gfx::texture::{FilterMethod, SamplerInfo, WrapMode}; use gfx::traits::FactoryExt; use gfx::{CommandBuffer, Encoder, Factory, IntoIndexBuffer, Rect, Resources, Slice}; use imgui::internal::RawWrapper; use imgui::{ BackendFlags, DrawCmd, DrawCmdParams, DrawData, DrawIdx, ImString, TextureId, Textures, }; use std::error::Error; use std::fmt; use std::usize; #[derive(Clone, Debug)] pub enum RendererError { Update(gfx::UpdateError), Buffer(gfx::buffer::CreationError), Pipeline(gfx::PipelineStateError), Combined(gfx::CombinedError), BadTexture(TextureId), } impl fmt::Display for RendererError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::RendererError::*; match *self { Update(ref e) => write!(f, "{}", e), Buffer(ref e) => write!(f, "{}", e), Pipeline(ref e) => write!(f, "{}", e), Combined(ref e) => write!(f, "{}", e), BadTexture(ref t) => write!(f, "Bad texture ID: {}", t.id()), } } } impl Error for RendererError { fn source(&self) -> Option<&(dyn Error + 'static)> { use self::RendererError::*; match *self { Update(ref e) => Some(e), Buffer(ref e) => Some(e), Pipeline(ref e) => Some(e), Combined(ref e) => Some(e), BadTexture(_) => None, } } } impl From> for RendererError { fn from(e: gfx::UpdateError) -> RendererError { RendererError::Update(e) } } impl From for RendererError { fn from(e: gfx::buffer::CreationError) -> RendererError { RendererError::Buffer(e) } } impl From> for RendererError { fn from(e: gfx::PipelineStateError) -> RendererError { RendererError::Pipeline(e) } } impl From for RendererError { fn from(e: gfx::CombinedError) -> RendererError { RendererError::Combined(e) } } #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum Shaders { /// OpenGL 4.0+ GlSl400, /// OpenGL 3.2+ GlSl150, /// OpenGL 3.0+ GlSl130, /// OpenGL 2.0+ GlSl110, /// OpenGL ES 3.0+ GlSlEs300, /// OpenGL ES 2.0+ GlSlEs100, /// HLSL Shader Model 4.0+ HlslSm40, } impl Shaders { fn get_program_code(self) -> (&'static [u8], &'static [u8]) { use self::Shaders::*; match self { GlSl400 => ( include_bytes!("shader/glsl_400.vert"), include_bytes!("shader/glsl_400.frag"), ), GlSl150 => ( include_bytes!("shader/glsl_150.vert"), include_bytes!("shader/glsl_150.frag"), ), GlSl130 => ( include_bytes!("shader/glsl_130.vert"), include_bytes!("shader/glsl_130.frag"), ), GlSl110 => ( include_bytes!("shader/glsl_110.vert"), include_bytes!("shader/glsl_110.frag"), ), GlSlEs300 => ( include_bytes!("shader/glsles_300.vert"), include_bytes!("shader/glsles_300.frag"), ), GlSlEs100 => ( include_bytes!("shader/glsles_100.vert"), include_bytes!("shader/glsles_100.frag"), ), HlslSm40 => ( include_bytes!("data/vertex.fx"), include_bytes!("data/pixel.fx"), ), } } } pub type Texture = ( gfx::handle::ShaderResourceView, gfx::handle::Sampler, ); pub struct Renderer { vertex_buffer: Buffer, index_buffer: Buffer, slice: Slice, pso: PipelineState>, font_texture: Texture, textures: Textures>, #[cfg(feature = "directx")] constants: Buffer, } impl Renderer where Cf: BlendFormat, R: Resources, { pub fn init>( ctx: &mut imgui::Context, factory: &mut F, shaders: Shaders, ) -> Result, RendererError> { let (vs_code, ps_code) = shaders.get_program_code(); let pso = factory.create_pipeline_simple(vs_code, ps_code, pipeline::new::())?; let vertex_buffer = factory.create_buffer::( 256, gfx::buffer::Role::Vertex, gfx::memory::Usage::Dynamic, Bind::empty(), )?; let index_buffer = factory.create_buffer::( 256, gfx::buffer::Role::Index, gfx::memory::Usage::Dynamic, Bind::empty(), )?; let font_texture = upload_font_texture(ctx.fonts(), factory)?; let slice = Slice { start: 0, end: 0, base_vertex: 0, instances: None, buffer: index_buffer.clone().into_index_buffer(factory), }; ctx.set_renderer_name(Some(ImString::from(format!( "imgui-gfx-renderer {}", env!("CARGO_PKG_VERSION") )))); ctx.io_mut() .backend_flags .insert(BackendFlags::RENDERER_HAS_VTX_OFFSET); Ok(Renderer { vertex_buffer, index_buffer, slice, pso, font_texture, textures: Textures::new(), #[cfg(feature = "directx")] constants: factory.create_constant_buffer(1), }) } pub fn reload_font_texture>( &mut self, ctx: &mut imgui::Context, factory: &mut F, ) -> Result<(), RendererError> { self.font_texture = upload_font_texture(ctx.fonts(), factory)?; Ok(()) } pub fn textures(&mut self) -> &mut Textures> { &mut self.textures } pub fn render, C: CommandBuffer>( &mut self, factory: &mut F, encoder: &mut Encoder, target: &mut RenderTargetView, draw_data: &DrawData, ) -> Result<(), RendererError> { let fb_width = draw_data.display_size[0] * draw_data.framebuffer_scale[0]; let fb_height = draw_data.display_size[1] * draw_data.framebuffer_scale[1]; if !(fb_width > 0.0 && fb_height > 0.0) { return Ok(()); } let left = draw_data.display_pos[0]; let right = draw_data.display_pos[0] + draw_data.display_size[0]; let top = draw_data.display_pos[1]; let bottom = draw_data.display_pos[1] + draw_data.display_size[1]; let matrix = [ [(2.0 / (right - left)), 0.0, 0.0, 0.0], [0.0, (2.0 / (top - bottom)), 0.0, 0.0], [0.0, 0.0, -1.0, 0.0], [ (right + left) / (left - right), (top + bottom) / (bottom - top), 0.0, 1.0, ], ]; let clip_off = draw_data.display_pos; let clip_scale = draw_data.framebuffer_scale; for draw_list in draw_data.draw_lists() { self.upload_vertex_buffer(factory, encoder, unsafe { draw_list.transmute_vtx_buffer::() })?; self.upload_index_buffer(factory, encoder, draw_list.idx_buffer())?; self.slice.start = 0; for cmd in draw_list.commands() { match cmd { DrawCmd::Elements { count, cmd_params: DrawCmdParams { clip_rect, texture_id, vtx_offset, idx_offset, .. }, } => { let clip_rect = [ (clip_rect[0] - clip_off[0]) * clip_scale[0], (clip_rect[1] - clip_off[1]) * clip_scale[1], (clip_rect[2] - clip_off[0]) * clip_scale[0], (clip_rect[3] - clip_off[1]) * clip_scale[1], ]; self.slice.start = idx_offset as u32; self.slice.end = self.slice.start + count as u32; self.slice.base_vertex = vtx_offset as u32; if clip_rect[0] < fb_width && clip_rect[1] < fb_height && clip_rect[2] >= 0.0 && clip_rect[3] >= 0.0 { let scissor = Rect { x: f32::max(0.0, clip_rect[0]).floor() as u16, y: f32::max(0.0, clip_rect[1]).floor() as u16, w: (clip_rect[2] - clip_rect[0]).abs().ceil() as u16, h: (clip_rect[3] - clip_rect[1]).abs().ceil() as u16, }; let tex = self.lookup_texture(texture_id)?; #[cfg(feature = "directx")] { let constants = constants::Constants { matrix }; encoder.update_constant_buffer(&self.constants, &constants); } let data = pipeline::Data { vertex_buffer: &self.vertex_buffer, #[cfg(not(feature = "directx"))] matrix: &matrix, #[cfg(feature = "directx")] constants: &self.constants, tex, scissor: &scissor, target, }; encoder.draw(&self.slice, &self.pso, &data); } } DrawCmd::ResetRenderState => (), // TODO DrawCmd::RawCallback { callback, raw_cmd } => unsafe { callback(draw_list.raw(), raw_cmd) }, } } } Ok(()) } fn upload_vertex_buffer, C: CommandBuffer>( &mut self, factory: &mut F, encoder: &mut Encoder, vtx_buffer: &[GfxDrawVert], ) -> Result<(), RendererError> { if self.vertex_buffer.len() < vtx_buffer.len() { self.vertex_buffer = factory.create_buffer::( vtx_buffer.len(), gfx::buffer::Role::Vertex, gfx::memory::Usage::Dynamic, Bind::empty(), )?; } encoder.update_buffer(&self.vertex_buffer, vtx_buffer, 0)?; Ok(()) } fn upload_index_buffer, C: CommandBuffer>( &mut self, factory: &mut F, encoder: &mut Encoder, idx_buffer: &[DrawIdx], ) -> Result<(), RendererError> { if self.index_buffer.len() < idx_buffer.len() { self.index_buffer = factory.create_buffer::( idx_buffer.len(), gfx::buffer::Role::Index, gfx::memory::Usage::Dynamic, Bind::empty(), )?; self.slice.buffer = self.index_buffer.clone().into_index_buffer(factory); } encoder.update_buffer(&self.index_buffer, idx_buffer, 0)?; Ok(()) } fn lookup_texture(&self, texture_id: TextureId) -> Result<&Texture, RendererError> { if texture_id.id() == usize::MAX { Ok(&self.font_texture) } else if let Some(texture) = self.textures.get(texture_id) { Ok(texture) } else { Err(RendererError::BadTexture(texture_id)) } } } fn upload_font_texture>( mut fonts: imgui::FontAtlasRefMut, factory: &mut F, ) -> Result, RendererError> { let texture = fonts.build_rgba32_texture(); let (_, texture_view) = factory.create_texture_immutable_u8::( gfx::texture::Kind::D2( texture.width as u16, texture.height as u16, gfx::texture::AaMode::Single, ), gfx::texture::Mipmap::Provided, &[texture.data], )?; fonts.tex_id = TextureId::from(usize::MAX); let sampler = factory.create_sampler(SamplerInfo::new(FilterMethod::Bilinear, WrapMode::Tile)); let font_texture = (texture_view, sampler); Ok(font_texture) } #[cfg(feature = "directx")] mod constants { use gfx::gfx_constant_struct_meta; use gfx::gfx_impl_struct_meta; gfx::gfx_constant_struct! { Constants { // `matrix` is a reserved keyword in HLSL matrix: [[f32; 4]; 4] = "matrix_", } } } // This is basically what gfx_pipeline generates, but with some changes: // // * Data struct contains references to avoid copying // * Everything is parameterized with BlendFormat // * Pipeline init is specialized for our structs mod pipeline { use super::*; use gfx::format::BlendFormat; use gfx::handle::Manager; use gfx::preset::blend; use gfx::pso::{ AccessInfo, DataBind, DataLink, Descriptor, InitError, PipelineData, PipelineInit, RawDataSet, }; use gfx::state::ColorMask; use gfx::{ProgramInfo, Resources}; #[derive(Clone, Debug, PartialEq)] pub struct Data<'a, R: Resources, Cf: BlendFormat + 'a> { pub vertex_buffer: &'a as DataBind>::Data, #[cfg(not(feature = "directx"))] pub matrix: &'a as DataBind>::Data, #[cfg(feature = "directx")] pub constants: &'a as DataBind>::Data, pub tex: &'a as DataBind>::Data, pub target: &'a as DataBind>::Data, pub scissor: &'a >::Data, } #[derive(Clone, Debug, Hash, PartialEq)] pub struct Meta { vertex_buffer: gfx::VertexBuffer, #[cfg(not(feature = "directx"))] matrix: gfx::Global<[[f32; 4]; 4]>, #[cfg(feature = "directx")] constants: gfx::ConstantBuffer, tex: gfx::TextureSampler<[f32; 4]>, target: gfx::BlendTarget, scissor: gfx::Scissor, } #[derive(Clone, Debug, PartialEq)] pub struct Init<'a, Cf: BlendFormat> { vertex_buffer: as DataLink<'a>>::Init, #[cfg(not(feature = "directx"))] matrix: as DataLink<'a>>::Init, #[cfg(feature = "directx")] constants: as DataLink<'a>>::Init, tex: as DataLink<'a>>::Init, target: as DataLink<'a>>::Init, scissor: >::Init, } impl<'a, Cf: BlendFormat> PipelineInit for Init<'a, Cf> { type Meta = Meta; fn link_to<'s>( &self, desc: &mut Descriptor, info: &'s ProgramInfo, ) -> Result, InitError<&'s str>> { let mut meta = Meta { vertex_buffer: DataLink::new(), #[cfg(not(feature = "directx"))] matrix: DataLink::new(), #[cfg(feature = "directx")] constants: DataLink::new(), tex: DataLink::new(), target: DataLink::new(), scissor: DataLink::new(), }; if let Some(d) = meta .vertex_buffer .link_vertex_buffer(0, &self.vertex_buffer) { assert!(meta.vertex_buffer.is_active()); desc.vertex_buffers[0] = Some(d); } for at in &info.vertex_attributes { match meta.vertex_buffer.link_input(at, &self.vertex_buffer) { Some(Ok(d)) => { assert!(meta.vertex_buffer.is_active()); desc.attributes[at.slot as usize] = Some(d); continue; } Some(Err(fm)) => return Err(InitError::VertexImport(&at.name, Some(fm))), None => return Err(InitError::VertexImport(&at.name, None)), } } #[cfg(feature = "directx")] for cb in &info.constant_buffers { match meta.constants.link_constant_buffer(cb, &self.constants) { Some(Ok(d)) => { assert!(meta.constants.is_active()); desc.constant_buffers[cb.slot as usize] = Some(d); } Some(Err(e)) => return Err(InitError::ConstantBuffer(&cb.name, Some(e))), None => return Err(InitError::ConstantBuffer(&cb.name, None)), } } #[cfg(not(feature = "directx"))] for gc in &info.globals { match meta.matrix.link_global_constant(gc, &self.matrix) { Some(Ok(())) => assert!(meta.matrix.is_active()), Some(Err(e)) => return Err(InitError::GlobalConstant(&gc.name, Some(e))), None => return Err(InitError::GlobalConstant(&gc.name, None)), } } for srv in &info.textures { match meta.tex.link_resource_view(srv, &self.tex) { Some(Ok(d)) => { assert!(meta.tex.is_active()); desc.resource_views[srv.slot as usize] = Some(d); } Some(Err(_)) => return Err(InitError::ResourceView(&srv.name, Some(()))), None => return Err(InitError::ResourceView(&srv.name, None)), } } for sm in &info.samplers { match meta.tex.link_sampler(sm, &self.tex) { Some(d) => { assert!(meta.tex.is_active()); desc.samplers[sm.slot as usize] = Some(d); } None => return Err(InitError::Sampler(&sm.name, None)), } } for out in &info.outputs { match meta.target.link_output(out, &self.target) { Some(Ok(d)) => { assert!(meta.target.is_active()); desc.color_targets[out.slot as usize] = Some(d); } Some(Err(fm)) => return Err(InitError::PixelExport(&out.name, Some(fm))), None => return Err(InitError::PixelExport(&out.name, None)), } } if !info.knows_outputs { use gfx::shade::core::*; let mut out = OutputVar { name: String::new(), slot: 0, base_type: BaseType::F32, container: ContainerType::Vector(4), }; match meta.target.link_output(&out, &self.target) { Some(Ok(d)) => { assert!(meta.target.is_active()); desc.color_targets[out.slot as usize] = Some(d); out.slot += 1; } Some(Err(fm)) => return Err(InitError::PixelExport(&"!known", Some(fm))), None => (), } } if meta.scissor.link_scissor() { assert!(meta.scissor.is_active()); desc.scissor = true; } Ok(meta) } } impl<'a, R: Resources, Cf: BlendFormat> PipelineData for Data<'a, R, Cf> { type Meta = Meta; fn bake_to( &self, out: &mut RawDataSet, meta: &Meta, man: &mut Manager, access: &mut AccessInfo, ) { meta.vertex_buffer .bind_to(out, self.vertex_buffer, man, access); #[cfg(not(feature = "directx"))] { meta.matrix.bind_to(out, self.matrix, man, access); } #[cfg(feature = "directx")] { meta.constants.bind_to(out, self.constants, man, access); } meta.tex.bind_to(out, self.tex, man, access); meta.target.bind_to(out, self.target, man, access); meta.scissor.bind_to(out, self.scissor, man, access); } } pub fn new() -> Init<'static, Cf> { Init { vertex_buffer: (), #[cfg(not(feature = "directx"))] matrix: "matrix", #[cfg(feature = "directx")] constants: "Constants", tex: "tex", target: ("Target0", ColorMask::all(), blend::ALPHA), scissor: (), } } } gfx_vertex_struct! { GfxDrawVert { pos: [f32; 2] = "pos", uv: [f32; 2] = "uv", col: [gfx::format::U8Norm; 4] = "col", } }