Merge pull request #154 from SpaceManiac/patch/textures

Add custom texture support (both renderers)
This commit is contained in:
Joonas Javanainen 2018-10-05 20:43:17 +03:00 committed by GitHub
commit 6ccfbf0305
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 260 additions and 43 deletions

View File

@ -6,8 +6,9 @@ use gfx::handle::{Buffer, RenderTargetView};
use gfx::memory::Bind;
use gfx::texture::{FilterMethod, SamplerInfo, WrapMode};
use gfx::traits::FactoryExt;
use gfx::{Bundle, CommandBuffer, Encoder, Factory, IntoIndexBuffer, Rect, Resources, Slice};
use imgui::{DrawList, FrameSize, ImDrawIdx, ImDrawVert, ImGui, Ui};
use gfx::{CommandBuffer, Encoder, Factory, IntoIndexBuffer, Rect, Resources, Slice};
use gfx::pso::{PipelineData, PipelineState};
use imgui::{DrawList, FrameSize, ImDrawIdx, ImDrawVert, ImGui, Ui, Textures, ImTexture};
pub type RendererResult<T> = Result<T, RendererError>;
@ -17,6 +18,7 @@ pub enum RendererError {
Buffer(gfx::buffer::CreationError),
Pipeline(gfx::PipelineStateError<String>),
Combined(gfx::CombinedError),
BadTexture(ImTexture),
}
impl From<gfx::UpdateError<usize>> for RendererError {
@ -43,7 +45,42 @@ impl From<gfx::CombinedError> for RendererError {
}
}
gfx_defines!{
// Based on gfx_defines! / gfx_pipeline!, to allow for not having to clone Arcs
// every draw call when selecting which texture is going to be shown.
macro_rules! extended_defines {
(pipeline $module:ident { $( $field:ident: $ty:ty = $value:expr, )* }) => {
#[allow(missing_docs)]
mod $module {
#[allow(unused_imports)]
use super::*;
#[allow(unused_imports)]
use super::gfx;
gfx_pipeline_inner!{ $(
$field: $ty,
)*}
pub fn new() -> Init<'static> {
Init {
$( $field: $value, )*
}
}
pub struct BorrowedData<'a, R: Resources> {
$(pub $field: &'a <$ty as DataBind<R>>::Data,)*
}
impl<'a, R: Resources> gfx::pso::PipelineData<R> for BorrowedData<'a, R> {
type Meta = pipe::Meta;
fn bake_to(&self, out: &mut RawDataSet<R>, meta: &Self::Meta, man: &mut gfx::handle::Manager<R>, access: &mut AccessInfo<R>) {
$(meta.$field.bind_to(out, &self.$field, man, access);)*
}
}
}
}
}
extended_defines! {
pipeline pipe {
vertex_buffer: gfx::VertexBuffer<ImDrawVert> = (),
matrix: gfx::Global<[[f32; 4]; 4]> = "matrix",
@ -94,9 +131,12 @@ impl Shaders {
}
}
pub type Texture<R> = (gfx::handle::ShaderResourceView<R, [f32; 4]>, gfx::handle::Sampler<R>);
pub struct Renderer<R: Resources> {
bundle: Bundle<R, pipe::Data<R>>,
index_buffer: Buffer<R, u16>,
textures: Textures<Texture<R>>,
}
impl<R: Resources> Renderer<R> {
@ -131,26 +171,12 @@ impl<R: Resources> Renderer<R> {
&[handle.pixels],
)
})?;
// TODO: set texture id in imgui
let sampler =
factory.create_sampler(SamplerInfo::new(FilterMethod::Trilinear, WrapMode::Clamp));
let data = pipe::Data {
vertex_buffer: vertex_buffer,
matrix: [
[0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, 0.0, 0.0],
[0.0, 0.0, -1.0, 0.0],
[-1.0, 1.0, 0.0, 1.0],
],
tex: (texture, sampler),
out: out,
scissor: Rect {
x: 0,
y: 0,
w: 0,
h: 0,
},
};
let pair = (texture, sampler);
let mut textures = Textures::new();
imgui.set_font_texture_id(textures.insert(pair));
let slice = Slice {
start: 0,
end: 0,
@ -159,13 +185,25 @@ impl<R: Resources> Renderer<R> {
buffer: index_buffer.clone().into_index_buffer(factory),
};
Ok(Renderer {
bundle: Bundle::new(slice, pso, data),
bundle: Bundle {
slice: slice,
pso: pso,
vertex_buffer: vertex_buffer,
out: out,
},
index_buffer: index_buffer,
textures: textures,
})
}
pub fn update_render_target(&mut self, out: RenderTargetView<R, gfx::format::Rgba8>) {
self.bundle.data.out = out;
self.bundle.out = out;
}
pub fn textures(&mut self) -> &mut Textures<Texture<R>> {
&mut self.textures
}
pub fn render<'a, F: Factory<R>, C: CommandBuffer<R>>(
&mut self,
ui: Ui<'a>,
@ -185,7 +223,7 @@ impl<R: Resources> Renderer<R> {
(height * hidpi_factor) as f32,
);
self.bundle.data.matrix = [
let matrix = [
[(2.0 / width) as f32, 0.0, 0.0, 0.0],
[0.0, (2.0 / -height) as f32, 0.0, 0.0],
[0.0, 0.0, -1.0, 0.0],
@ -195,7 +233,7 @@ impl<R: Resources> Renderer<R> {
ui.render(|ui, mut draw_data| {
draw_data.scale_clip_rects(ui.imgui().display_framebuffer_scale());
for draw_list in &draw_data {
self.render_draw_list(factory, encoder, &draw_list, fb_size)?;
self.render_draw_list(factory, encoder, &draw_list, fb_size, &matrix)?;
}
Ok(())
})
@ -206,6 +244,7 @@ impl<R: Resources> Renderer<R> {
encoder: &mut Encoder<R, C>,
draw_list: &DrawList<'a>,
fb_size: (f32, f32),
matrix: &[[f32; 4]; 4],
) -> RendererResult<()> {
let (fb_width, fb_height) = fb_size;
@ -214,10 +253,12 @@ impl<R: Resources> Renderer<R> {
self.bundle.slice.start = 0;
for cmd in draw_list.cmd_buffer {
// TODO: check cmd.texture_id
let texture_id = cmd.texture_id.into();
let tex = self.textures.get(texture_id)
.ok_or_else(|| RendererError::BadTexture(texture_id))?;
self.bundle.slice.end = self.bundle.slice.start + cmd.elem_count;
self.bundle.data.scissor = Rect {
let scissor = Rect {
x: cmd.clip_rect.x.max(0.0).min(fb_width).round() as u16,
y: cmd.clip_rect.y.max(0.0).min(fb_height).round() as u16,
w: (cmd.clip_rect.z - cmd.clip_rect.x)
@ -229,7 +270,14 @@ impl<R: Resources> Renderer<R> {
.min(fb_height)
.round() as u16,
};
self.bundle.encode(encoder);
let data = pipe::BorrowedData {
vertex_buffer: &self.bundle.vertex_buffer,
matrix: matrix,
tex: tex,
out: &self.bundle.out,
scissor: &scissor,
};
encoder.draw(&self.bundle.slice, &self.bundle.pso, &data);
self.bundle.slice.start = self.bundle.slice.end;
}
Ok(())
@ -240,15 +288,15 @@ impl<R: Resources> Renderer<R> {
encoder: &mut Encoder<R, C>,
vtx_buffer: &[ImDrawVert],
) -> RendererResult<()> {
if self.bundle.data.vertex_buffer.len() < vtx_buffer.len() {
self.bundle.data.vertex_buffer = factory.create_buffer::<ImDrawVert>(
if self.bundle.vertex_buffer.len() < vtx_buffer.len() {
self.bundle.vertex_buffer = factory.create_buffer::<ImDrawVert>(
vtx_buffer.len(),
gfx::buffer::Role::Vertex,
gfx::memory::Usage::Dynamic,
Bind::empty(),
)?;
}
Ok(encoder.update_buffer(&self.bundle.data.vertex_buffer, vtx_buffer, 0)?)
Ok(encoder.update_buffer(&self.bundle.vertex_buffer, vtx_buffer, 0)?)
}
fn upload_index_buffer<F: Factory<R>, C: CommandBuffer<R>>(
&mut self,
@ -268,3 +316,10 @@ impl<R: Resources> Renderer<R> {
Ok(encoder.update_buffer(&self.index_buffer, idx_buffer, 0)?)
}
}
struct Bundle<R: Resources, Data: PipelineData<R>> {
slice: Slice<R>,
pso: PipelineState<R, Data::Meta>,
vertex_buffer: Buffer<R, ImDrawVert>,
out: RenderTargetView<R, gfx::format::Rgba8>,
}

View File

@ -7,8 +7,8 @@ use glium::index::{self, PrimitiveType};
use glium::program;
use glium::texture;
use glium::vertex;
use glium::{DrawError, GlObject, IndexBuffer, Program, Surface, Texture2d, VertexBuffer};
use imgui::{DrawList, FrameSize, ImDrawIdx, ImDrawVert, ImGui, Ui};
use glium::{DrawError, IndexBuffer, Program, Surface, Texture2d, VertexBuffer};
use imgui::{DrawList, FrameSize, ImDrawIdx, ImDrawVert, ImGui, Ui, Textures, ImTexture};
use std::borrow::Cow;
use std::fmt;
use std::rc::Rc;
@ -22,6 +22,7 @@ pub enum RendererError {
Program(program::ProgramChooserCreationError),
Texture(texture::TextureCreationError),
Draw(DrawError),
BadTexture(ImTexture),
}
impl fmt::Display for RendererError {
@ -33,6 +34,7 @@ impl fmt::Display for RendererError {
Program(ref e) => write!(f, "Program creation failed: {}", e),
Texture(_) => write!(f, "Texture creation failed"),
Draw(ref e) => write!(f, "Drawing failed: {}", e),
BadTexture(ref t) => write!(f, "Bad texture ID: {}", t.id()),
}
}
}
@ -81,6 +83,10 @@ impl Renderer {
})
}
pub fn textures(&mut self) -> &mut Textures<Texture2d> {
&mut self.device_objects.textures
}
pub fn render<'a, S: Surface>(&mut self, surface: &mut S, ui: Ui<'a>) -> RendererResult<()> {
let _ = self.ctx.insert_debug_marker("imgui-rs: starting rendering");
let FrameSize {
@ -128,12 +134,11 @@ impl Renderer {
self.device_objects
.upload_index_buffer(&self.ctx, draw_list.idx_buffer)?;
let font_texture_id = self.device_objects.texture.get_id() as usize;
let mut idx_start = 0;
for cmd in draw_list.cmd_buffer {
// We don't support custom textures...yet!
assert!(cmd.texture_id as usize == font_texture_id);
let texture_id = cmd.texture_id.into();
let texture = self.device_objects.textures.get(texture_id)
.ok_or_else(|| RendererError::BadTexture(texture_id))?;
let idx_end = idx_start + cmd.elem_count as usize;
@ -147,7 +152,7 @@ impl Renderer {
&self.device_objects.program,
&uniform! {
matrix: matrix,
tex: self.device_objects.texture.sampled()
tex: texture.sampled()
},
&DrawParameters {
blend: Blend::alpha_blending(),
@ -178,7 +183,7 @@ pub struct DeviceObjects {
vertex_buffer: VertexBuffer<ImDrawVert>,
index_buffer: IndexBuffer<ImDrawIdx>,
program: Program,
texture: Texture2d,
textures: Textures<Texture2d>,
}
fn compile_default_program<F: Facade>(
@ -231,13 +236,14 @@ impl DeviceObjects {
};
Texture2d::new(ctx, data)
})?;
im_gui.set_texture_id(texture.get_id() as usize);
let mut textures = Textures::new();
im_gui.set_font_texture_id(textures.insert(texture));
Ok(DeviceObjects {
vertex_buffer: vertex_buffer,
index_buffer: index_buffer,
program: program,
texture: texture,
textures: textures,
})
}
pub fn upload_vertex_buffer<F: Facade>(

144
src/image.rs Normal file
View File

@ -0,0 +1,144 @@
use super::{ImVec2, ImVec4, Ui};
use std::marker::PhantomData;
use std::collections::HashMap;
use std::os::raw::c_void;
use sys;
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct ImTexture(usize);
impl ImTexture {
pub fn id(&self) -> usize {
self.0
}
}
impl From<usize> for ImTexture {
fn from(id: usize) -> Self {
ImTexture(id)
}
}
impl From<*mut c_void> for ImTexture {
fn from(ptr: *mut c_void) -> Self {
ImTexture(ptr as usize)
}
}
/// Represent an image about to be drawn.
/// See [`Ui::image`].
///
/// Create your image using the builder pattern then [`Image::build`] it.
#[must_use]
pub struct Image<'ui> {
texture_id: ImTexture,
size: ImVec2,
uv0: ImVec2,
uv1: ImVec2,
tint_col: ImVec4,
border_col: ImVec4,
_phantom: PhantomData<&'ui Ui<'ui>>,
}
impl<'ui> Image<'ui> {
pub fn new<S>(_: &Ui<'ui>, texture_id: ImTexture, size: S) -> Self
where
S: Into<ImVec2>,
{
const DEFAULT_UV0: ImVec2 = ImVec2 { x: 0.0, y: 0.0 };
const DEFAULT_UV1: ImVec2 = ImVec2 { x: 1.0, y: 1.0 };
const DEFAULT_TINT_COL: ImVec4 = ImVec4 {
x: 1.0,
y: 1.0,
z: 1.0,
w: 1.0,
};
const DEFAULT_BORDER_COL: ImVec4 = ImVec4 {
x: 0.0,
y: 0.0,
z: 0.0,
w: 0.0,
};
Image {
texture_id: texture_id,
size: size.into(),
uv0: DEFAULT_UV0,
uv1: DEFAULT_UV1,
tint_col: DEFAULT_TINT_COL,
border_col: DEFAULT_BORDER_COL,
_phantom: PhantomData,
}
}
/// Set size (default based on texture)
pub fn size<T: Into<ImVec2>>(mut self, size: T) -> Self {
self.size = size.into();
self
}
/// Set uv0 (default `[0.0, 0.0]`)
pub fn uv0<T: Into<ImVec2>>(mut self, uv0: T) -> Self {
self.uv0 = uv0.into();
self
}
/// Set uv1 (default `[1.0, 1.0]`)
pub fn uv1<T: Into<ImVec2>>(mut self, uv1: T) -> Self {
self.uv1 = uv1.into();
self
}
/// Set tint color (default: no tint color)
pub fn tint_col<T: Into<ImVec4>>(mut self, tint_col: T) -> Self {
self.tint_col = tint_col.into();
self
}
/// Set border color (default: no border)
pub fn border_col<T: Into<ImVec4>>(mut self, border_col: T) -> Self {
self.border_col = border_col.into();
self
}
/// Draw image where the cursor currently is
pub fn build(self) {
unsafe {
sys::igImage(
self.texture_id.0 as *mut c_void,
self.size,
self.uv0,
self.uv1,
self.tint_col,
self.border_col,
);
}
}
}
/// Generic texture mapping for use by renderers.
pub struct Textures<T> {
textures: HashMap<usize, T>,
next: usize,
}
impl<T> Textures<T> {
pub fn new() -> Self {
Textures {
textures: HashMap::new(),
next: 0,
}
}
pub fn insert(&mut self, texture: T) -> ImTexture {
let id = self.next;
self.textures.insert(id, texture);
self.next += 1;
ImTexture(id)
}
pub fn replace(&mut self, id: ImTexture, texture: T) -> Option<T> {
self.textures.insert(id.0, texture)
}
pub fn remove(&mut self, id: ImTexture) -> Option<T> {
self.textures.remove(&id.0)
}
pub fn get(&self, id: ImTexture) -> Option<&T> {
self.textures.get(&id.0)
}
}

View File

@ -18,6 +18,7 @@ pub use drag::{
DragInt4, DragIntRange2,
};
pub use fonts::{FontGlyphRange, ImFont, ImFontAtlas, ImFontConfig};
pub use image::{ImTexture, Image, Textures};
pub use input::{
InputFloat, InputFloat2, InputFloat3, InputFloat4, InputInt, InputInt2, InputInt3, InputInt4,
InputText, InputTextMultiline,
@ -46,6 +47,7 @@ mod child_frame;
mod color_editors;
mod drag;
mod fonts;
mod image;
mod input;
mod menus;
mod plothistogram;
@ -172,8 +174,8 @@ impl ImGui {
})
}
}
pub fn set_texture_id(&mut self, value: usize) {
self.fonts().set_texture_id(value);
pub fn set_font_texture_id(&mut self, value: ImTexture) {
self.fonts().set_texture_id(value.id());
}
pub fn set_ini_filename(&mut self, value: Option<ImString>) {
{
@ -1337,6 +1339,16 @@ impl<'ui> Ui<'ui> {
}
}
// Image
impl<'ui> Ui<'ui> {
pub fn image<S>(&self, texture: ImTexture, size: S) -> Image
where
S: Into<ImVec2>,
{
Image::new(self, texture, size)
}
}
impl<'ui> Ui<'ui> {
/// Calculate the size required for a given text string.
///