mirror of
https://github.com/eliasstepanik/imgui-rs.git
synced 2026-01-13 22:48:34 +00:00
Fix sRGB support and comments
Fix sRGB support, now when initialising a Renderer you can explicitly choose whether to output colors in linear or sRGB color spaces. Fix examples to show how to render these properly. Fix comments in examples
This commit is contained in:
parent
9de800da48
commit
2ab0878f88
@ -13,15 +13,13 @@ type Window = WindowedContext<glutin::PossiblyCurrent>;
|
||||
|
||||
fn main() {
|
||||
// Common setup for creating a winit window and imgui context, not specifc
|
||||
// to this renderer at all ecept that glutin is used to create the window
|
||||
// to this renderer at all except that glutin is used to create the window
|
||||
// since it will give us access to a GL context
|
||||
let (event_loop, window) = create_window();
|
||||
let (mut winit_platform, mut imgui_context) = imgui_init(&window);
|
||||
|
||||
// OpenGL context from glow
|
||||
let gl = glow_context(&window);
|
||||
// Outputting to screen, we want an sRGB framebuffer
|
||||
unsafe { gl.enable(glow::FRAMEBUFFER_SRGB) };
|
||||
|
||||
// OpenGL renderer from this crate
|
||||
let mut ig_renderer = imgui_glow_renderer::AutoRenderer::initialize(gl, &mut imgui_context)
|
||||
|
||||
@ -1,8 +1,4 @@
|
||||
//! A basic example showing imgui rendering together with some custom rendering.
|
||||
//!
|
||||
//! Note this example uses `RendererBuilder` rather than `auto_renderer` and
|
||||
//! (because we're using the default "trivial" `ContextStateManager`)
|
||||
//! therefore does not attempt to backup/restore OpenGL state.
|
||||
//! A basic example showing imgui rendering on top of a simple custom scene.
|
||||
|
||||
use std::time::Instant;
|
||||
|
||||
@ -40,21 +36,17 @@ fn main() {
|
||||
window.window().request_redraw();
|
||||
}
|
||||
glutin::event::Event::RedrawRequested(_) => {
|
||||
{
|
||||
let gl = ig_renderer.gl_context();
|
||||
// This is required because, without the `StateBackupCsm`
|
||||
// (which is provided by `auto_renderer` but not
|
||||
// `RendererBuilder` by default), the OpenGL context is left
|
||||
// in an arbitrary, dirty state
|
||||
unsafe { gl.disable(glow::SCISSOR_TEST) };
|
||||
tri_renderer.render(gl);
|
||||
}
|
||||
// Render your custom scene, note we need to borrow the OpenGL
|
||||
// context from the `AutoRenderer`, which takes ownership of it.
|
||||
tri_renderer.render(ig_renderer.gl_context());
|
||||
|
||||
let ui = imgui_context.frame();
|
||||
ui.show_demo_window(&mut true);
|
||||
|
||||
winit_platform.prepare_render(&ui, window.window());
|
||||
let draw_data = ui.render();
|
||||
|
||||
// Render imgui on top of it
|
||||
ig_renderer
|
||||
.render(&draw_data)
|
||||
.expect("error rendering imgui");
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
//! A basic example showing imgui rendering together with some custom rendering
|
||||
//! A basic example showing imgui rendering on top of a simple custom scene
|
||||
//! using OpenGL ES, rather than full-fat OpenGL.
|
||||
//!
|
||||
//! Note this example uses `Renderer` rather than `OwningRenderer` and
|
||||
//! Note this example uses `Renderer` rather than `AutoRenderer` and
|
||||
//! therefore requries more lifetime-management of the OpenGL context.
|
||||
|
||||
use std::time::Instant;
|
||||
@ -18,15 +18,17 @@ fn main() {
|
||||
let (mut winit_platform, mut imgui_context) = utils::imgui_init(&window);
|
||||
let gl = utils::glow_context(&window);
|
||||
|
||||
// When using `Renderer`, we need to create a texture map
|
||||
let mut texture_map = imgui_glow_renderer::SimpleTextureMap::default();
|
||||
|
||||
// When using `Renderer`, we specify whether or not to output sRGB colors.
|
||||
// Since we're drawing to screen and using OpenGL ES (which doesn't support
|
||||
// `GL_FRAMEBUFFER_SRGB`) then we do need to convert to sRGB in the shader.
|
||||
let mut ig_renderer =
|
||||
imgui_glow_renderer::Renderer::initialize(&gl, &mut imgui_context, &mut texture_map)
|
||||
imgui_glow_renderer::Renderer::initialize(&gl, &mut imgui_context, &mut texture_map, true)
|
||||
.expect("failed to create renderer");
|
||||
// Note the shader header now needs a precision specifier
|
||||
let tri_renderer = Triangler::new(
|
||||
&gl,
|
||||
"#version 300 es\nprecision mediump float;\n#define IS_GLES",
|
||||
);
|
||||
let tri_renderer = Triangler::new(&gl, "#version 300 es\nprecision mediump float;");
|
||||
|
||||
let mut last_frame = Instant::now();
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
@ -47,6 +49,7 @@ fn main() {
|
||||
window.window().request_redraw();
|
||||
}
|
||||
glutin::event::Event::RedrawRequested(_) => {
|
||||
// Draw custom scene
|
||||
tri_renderer.render(&gl);
|
||||
|
||||
let ui = imgui_context.frame();
|
||||
@ -54,6 +57,8 @@ fn main() {
|
||||
|
||||
winit_platform.prepare_render(&ui, window.window());
|
||||
let draw_data = ui.render();
|
||||
|
||||
// Render imgui on top
|
||||
ig_renderer
|
||||
.render(&gl, &texture_map, &draw_data)
|
||||
.expect("error rendering imgui");
|
||||
|
||||
@ -1,5 +1,9 @@
|
||||
//! Example showing the same functionality as
|
||||
//! `imgui-examples/examples/custom_textures.rs`
|
||||
//!
|
||||
//! Not that the texture uses the internal format `glow::SRGB`, so that
|
||||
//! OpenGL automatically converts colors to linear space before the shaders.
|
||||
//! The renderer assumes you set this internal format correctly like this.
|
||||
|
||||
use std::{io::Cursor, time::Instant};
|
||||
|
||||
@ -18,9 +22,15 @@ fn main() {
|
||||
let (event_loop, window) = utils::create_window("Custom textures", glutin::GlRequest::Latest);
|
||||
let (mut winit_platform, mut imgui_context) = utils::imgui_init(&window);
|
||||
let gl = utils::glow_context(&window);
|
||||
// This time, we tell OpenGL this is an sRGB framebuffer and OpenGL will
|
||||
// do the conversion to sSGB space for us after the fragment shader.
|
||||
unsafe { gl.enable(glow::FRAMEBUFFER_SRGB) };
|
||||
|
||||
let mut textures = imgui::Textures::<glow::Texture>::default();
|
||||
let mut ig_renderer = Renderer::initialize(&gl, &mut imgui_context, &mut textures)
|
||||
// Note that `output_srgb` is `false`. This is because we set
|
||||
// `glow::FRAMEBUFFER_SRGB` so we don't have to manually do the conversion
|
||||
// in the shader.
|
||||
let mut ig_renderer = Renderer::initialize(&gl, &mut imgui_context, &mut textures, false)
|
||||
.expect("failed to create renderer");
|
||||
let textures_ui = TexturesUi::new(&gl, &mut textures);
|
||||
|
||||
@ -120,7 +130,7 @@ impl TexturesUi {
|
||||
gl.tex_image_2d(
|
||||
glow::TEXTURE_2D,
|
||||
0,
|
||||
glow::RGB as _,
|
||||
glow::RGB as _, // When generating a texture like this, you're probably working in linear color space
|
||||
WIDTH as _,
|
||||
HEIGHT as _,
|
||||
0,
|
||||
@ -262,7 +272,7 @@ impl Lenna {
|
||||
gl.tex_image_2d(
|
||||
glow::TEXTURE_2D,
|
||||
0,
|
||||
glow::SRGB as _,
|
||||
glow::SRGB as _, // image file has sRGB encoded colors
|
||||
width as _,
|
||||
height as _,
|
||||
0,
|
||||
|
||||
@ -23,11 +23,7 @@ pub fn create_window(title: &str, gl_request: GlRequest) -> (EventLoop<()>, Wind
|
||||
}
|
||||
|
||||
pub fn glow_context(window: &Window) -> glow::Context {
|
||||
unsafe {
|
||||
let gl = glow::Context::from_loader_function(|s| window.get_proc_address(s).cast());
|
||||
gl.enable(glow::FRAMEBUFFER_SRGB);
|
||||
gl
|
||||
}
|
||||
unsafe { glow::Context::from_loader_function(|s| window.get_proc_address(s).cast()) }
|
||||
}
|
||||
|
||||
pub fn imgui_init(window: &Window) -> (WinitPlatform, imgui::Context) {
|
||||
@ -53,7 +49,6 @@ pub fn imgui_init(window: &Window) -> (WinitPlatform, imgui::Context) {
|
||||
pub struct Triangler {
|
||||
pub program: <glow::Context as HasContext>::Program,
|
||||
pub vertex_array: <glow::Context as HasContext>::VertexArray,
|
||||
is_gles: bool,
|
||||
}
|
||||
|
||||
impl Triangler {
|
||||
@ -93,7 +88,6 @@ in vec4 color;
|
||||
out vec4 frag_color;
|
||||
|
||||
vec4 linear_to_srgb(vec4 linear_color) {
|
||||
#ifdef IS_GLES
|
||||
vec3 linear = linear_color.rgb;
|
||||
vec3 selector = ceil(linear - 0.0031308);
|
||||
vec3 less_than_branch = linear * 12.92;
|
||||
@ -102,10 +96,6 @@ vec4 linear_to_srgb(vec4 linear_color) {
|
||||
mix(less_than_branch, greater_than_branch, selector),
|
||||
linear_color.a
|
||||
);
|
||||
#else
|
||||
// For non-GLES, GL_FRAMEBUFFER_SRGB handles this for free
|
||||
return linear_color;
|
||||
#endif
|
||||
}
|
||||
|
||||
void main() {
|
||||
@ -149,21 +139,13 @@ void main() {
|
||||
Self {
|
||||
program,
|
||||
vertex_array,
|
||||
is_gles: imgui_glow_renderer::versions::GlVersion::read(gl).is_gles,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&self, gl: &glow::Context) {
|
||||
unsafe {
|
||||
if self.is_gles {
|
||||
// Specify clear color in sRGB space, since GL_FRAMEBUFFER_SRGB
|
||||
// is not supported
|
||||
gl.clear_color(0.05, 0.05, 0.1, 1.0);
|
||||
} else {
|
||||
// Specify clear color in linear space
|
||||
gl.clear_color(0.004, 0.004, 0.01, 1.0);
|
||||
}
|
||||
gl.clear_color(0.05, 0.05, 0.1, 1.0);
|
||||
gl.clear(glow::COLOR_BUFFER_BIT);
|
||||
gl.use_program(Some(self.program));
|
||||
gl.bind_vertex_array(Some(self.vertex_array));
|
||||
|
||||
@ -1,3 +1,45 @@
|
||||
//! Renderer for `[imgui-rs]` using the `[glow]` library for OpenGL.
|
||||
//!
|
||||
//! This is heavily influenced by the
|
||||
//! [example from upstream](https://github.com/ocornut/imgui/blob/fe245914114588f272b0924538fdd43f6c127a26/backends/imgui_impl_opengl3.cpp).
|
||||
//!
|
||||
//! # Basic usage
|
||||
//!
|
||||
//! A few code examples are provided in the source.
|
||||
//!
|
||||
//! In short, create either an `[AutoRenderer]` (for basic usage) or `[Renderer]`
|
||||
//! (for slightly more customizable operation), then call the `render(...)`
|
||||
//! method with draw data from `imgui-rs`.
|
||||
//!
|
||||
//! # OpenGL (ES) versions
|
||||
//!
|
||||
//! This renderer is expected to work with OpenGL version 3.3 and above, and
|
||||
//! OpenGL ES version 3.0 or above. This should cover the vast majority of even
|
||||
//! fairly dated hardware. Please submit an issue for any incompatibilities
|
||||
//! found with these OpenGL versions, pull requests to extend support to earlier
|
||||
//! versions are welcomed.
|
||||
//!
|
||||
//! # Scope
|
||||
//!
|
||||
//! Consider this an example renderer. It is intended to be sufficent for simple
|
||||
//! applications running imgui-rs as the final rendering step. If your application
|
||||
//! has more specific needs, it's probably best to write your own renderer, in
|
||||
//! which case this can be a useful starting point.
|
||||
//!
|
||||
//! # sRGB
|
||||
//!
|
||||
//! When outputting colors to a screen, colors need to be converted from a
|
||||
//! linear color space to a non-linear space matching the monitor (e.g. sRGB).
|
||||
//! When using the `[AutoRenderer]`, this library will convert colors to sRGB
|
||||
//! as a step in the shader. When initialising a `[Renderer]`, you can choose
|
||||
//! whether or not to include this step in the shader or not when calling
|
||||
//! `[Renderer::initialize]`.
|
||||
//!
|
||||
//! This library also assumes that textures have their internal format
|
||||
//! set appropriately when uploaded to OpenGL. That is, assuming your texture
|
||||
//! is sRGB (if you don't know, it probably is) the `internal_format` is
|
||||
//! one of the `SRGB*` values.
|
||||
|
||||
use std::{borrow::Cow, error::Error, fmt::Display, mem::size_of};
|
||||
|
||||
use imgui::internal::RawWrapper;
|
||||
@ -38,9 +80,10 @@ impl<
|
||||
{
|
||||
}
|
||||
|
||||
/// Renderer which owns the OpenGL context and handles textures itself.
|
||||
/// Useful for simple applications, but more complicated applications may prefer
|
||||
/// to use `[Renderer]`.
|
||||
/// Renderer which owns the OpenGL context and handles textures itself. Also
|
||||
/// converts all output colors to sRGB for display. Useful for simple applications,
|
||||
/// but more complicated applications may prefer to use `[Renderer]`, or even
|
||||
/// write their own renderer based on this code.
|
||||
///
|
||||
/// OpenGL context is still available to the rest of the application through
|
||||
/// the `[gl_context]` method.
|
||||
@ -56,7 +99,7 @@ impl<G: Gl> AutoRenderer<G> {
|
||||
/// result in an error.
|
||||
pub fn initialize(gl: G, imgui_context: &mut imgui::Context) -> Result<Self, InitError> {
|
||||
let mut texture_map = SimpleTextureMap::default();
|
||||
let renderer = Renderer::initialize(&gl, imgui_context, &mut texture_map)?;
|
||||
let renderer = Renderer::initialize(&gl, imgui_context, &mut texture_map, true)?;
|
||||
Ok(Self {
|
||||
gl,
|
||||
texture_map,
|
||||
@ -101,6 +144,8 @@ impl<G: Gl> Drop for AutoRenderer<G> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Main renderer. Borrows the OpenGL context and [texture map](TextureMap)
|
||||
/// when required.
|
||||
pub struct Renderer<G: Gl> {
|
||||
shaders: Shaders<G>,
|
||||
state_backup: GlStateBackup,
|
||||
@ -115,6 +160,22 @@ pub struct Renderer<G: Gl> {
|
||||
}
|
||||
|
||||
impl<G: Gl> Renderer<G> {
|
||||
/// Create the renderer, initialising OpenGL objects and shaders.
|
||||
///
|
||||
/// `output_srgb` controls whether the shader outputs sRGB colors, or linear
|
||||
/// RGB colors. In short:
|
||||
/// - If you're outputting to the screen and haven't specified the framebuffer
|
||||
/// is sRGB (e.g. with `gl.enable(glow::FRAMEBUFFER_SRGB)`), then you probably
|
||||
/// want `output_srgb=true`.
|
||||
/// - If you're outputting to a screen with an sRGB framebuffer (e.g. with
|
||||
/// `gl.enable(glow::FRAMEBUFFER_SRGB)`), then you probably want
|
||||
/// `output_srgb=false`, as OpenGL will convert to sRGB itself.
|
||||
/// - If you're not outputting to some intermediate framebuffer, then you
|
||||
/// probably want `output_srgb=false` to keep the colours in linear
|
||||
/// color space, and then convert them to sRGB at some later stage.
|
||||
/// - OpenGL ES doesn't support sRGB framebuffers, so you almost always
|
||||
/// want `output_srgb=true`.
|
||||
///
|
||||
/// # Errors
|
||||
/// Any error initialising the OpenGL objects (including shaders) will
|
||||
/// result in an error.
|
||||
@ -122,6 +183,7 @@ impl<G: Gl> Renderer<G> {
|
||||
gl: &G,
|
||||
imgui_context: &mut imgui::Context,
|
||||
texture_map: &mut T,
|
||||
output_srgb: bool,
|
||||
) -> Result<Self, InitError> {
|
||||
#![allow(
|
||||
clippy::similar_names,
|
||||
@ -157,7 +219,7 @@ impl<G: Gl> Renderer<G> {
|
||||
|
||||
let font_atlas_texture = prepare_font_atlas(gl, imgui_context.fonts(), texture_map)?;
|
||||
|
||||
let shaders = Shaders::new(gl, gl_version)?;
|
||||
let shaders = Shaders::new(gl, gl_version, output_srgb)?;
|
||||
let vbo_handle = unsafe { gl.create_buffer() }.map_err(InitError::CreateBufferObject)?;
|
||||
let ebo_handle = unsafe { gl.create_buffer() }.map_err(InitError::CreateBufferObject)?;
|
||||
|
||||
@ -183,6 +245,8 @@ impl<G: Gl> Renderer<G> {
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// This must be called before being dropped to properly free OpenGL
|
||||
/// resources.
|
||||
pub fn destroy(&mut self, gl: &G) {
|
||||
if self.is_destroyed {
|
||||
return;
|
||||
@ -487,38 +551,46 @@ impl<G: Gl> Renderer<G> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for mapping imgui texture IDs to OpenGL textures.
|
||||
///
|
||||
/// `[register]` should be called after uploading a texture to OpenGL to get a
|
||||
/// `[imgui::TextureId]` corresponding to it.
|
||||
///
|
||||
/// Then `[gl_texture]` can be called to find the OpenGL texture corresponding to
|
||||
/// that `[imgui::TextureId]`.
|
||||
pub trait TextureMap {
|
||||
fn gl_texture(&self, imgui_texture: imgui::TextureId) -> Option<glow::Texture>;
|
||||
|
||||
fn register(&mut self, gl_texture: glow::Texture) -> Option<imgui::TextureId>;
|
||||
|
||||
fn gl_texture(&self, imgui_texture: imgui::TextureId) -> Option<glow::Texture>;
|
||||
}
|
||||
|
||||
/// Texture map where the imgui texture ID is simply the OpenGL texture ID
|
||||
/// Texture map where the imgui texture ID is simply numerically equal to the
|
||||
/// OpenGL texture ID.
|
||||
#[derive(Default)]
|
||||
pub struct SimpleTextureMap();
|
||||
|
||||
impl TextureMap for SimpleTextureMap {
|
||||
#[inline(always)]
|
||||
fn register(&mut self, gl_texture: glow::Texture) -> Option<imgui::TextureId> {
|
||||
Some(imgui::TextureId::new(gl_texture as _))
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn gl_texture(&self, imgui_texture: imgui::TextureId) -> Option<glow::Texture> {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
Some(imgui_texture.id() as _)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn register(&mut self, gl_texture: glow::Texture) -> Option<imgui::TextureId> {
|
||||
Some(imgui::TextureId::new(gl_texture as _))
|
||||
}
|
||||
}
|
||||
|
||||
/// `Textures` from the `imgui` crate is a simple choice for a texture map
|
||||
/// `[imgui::Textures]` is a simple choice for a texture map.
|
||||
impl TextureMap for imgui::Textures<glow::Texture> {
|
||||
fn gl_texture(&self, imgui_texture: imgui::TextureId) -> Option<glow::Texture> {
|
||||
self.get(imgui_texture).copied()
|
||||
}
|
||||
|
||||
fn register(&mut self, gl_texture: glow::Texture) -> Option<imgui::TextureId> {
|
||||
Some(self.insert(gl_texture))
|
||||
}
|
||||
|
||||
fn gl_texture(&self, imgui_texture: imgui::TextureId) -> Option<glow::Texture> {
|
||||
self.get(imgui_texture).copied()
|
||||
}
|
||||
}
|
||||
|
||||
/// This OpenGL state backup is based on the upstream OpenGL example from
|
||||
@ -718,8 +790,9 @@ struct Shaders<G: Gl> {
|
||||
}
|
||||
|
||||
impl<G: Gl> Shaders<G> {
|
||||
fn new(gl: &G, gl_version: GlVersion) -> Result<Self, ShaderError> {
|
||||
let (vertex_source, fragment_source) = Self::get_shader_sources(gl, gl_version)?;
|
||||
fn new(gl: &G, gl_version: GlVersion, output_srgb: bool) -> Result<Self, ShaderError> {
|
||||
let (vertex_source, fragment_source) =
|
||||
Self::get_shader_sources(gl, gl_version, output_srgb)?;
|
||||
|
||||
let vertex_shader =
|
||||
unsafe { gl.create_shader(glow::VERTEX_SHADER) }.map_err(ShaderError::CreateShader)?;
|
||||
@ -783,7 +856,11 @@ impl<G: Gl> Shaders<G> {
|
||||
})
|
||||
}
|
||||
|
||||
fn get_shader_sources(gl: &G, gl_version: GlVersion) -> Result<(String, String), ShaderError> {
|
||||
fn get_shader_sources(
|
||||
gl: &G,
|
||||
gl_version: GlVersion,
|
||||
output_srgb: bool,
|
||||
) -> Result<(String, String), ShaderError> {
|
||||
const VERTEX_BODY: &str = r#"
|
||||
layout (location = 0) in vec2 position;
|
||||
layout (location = 1) in vec2 uv;
|
||||
@ -820,7 +897,6 @@ uniform sampler2D tex;
|
||||
layout (location = 0) out vec4 out_color;
|
||||
|
||||
vec4 linear_to_srgb(vec4 linear_color) {
|
||||
#ifdef IS_GLES
|
||||
vec3 linear = linear_color.rgb;
|
||||
vec3 selector = ceil(linear - 0.0031308);
|
||||
vec3 less_than_branch = linear * 12.92;
|
||||
@ -829,14 +905,15 @@ vec4 linear_to_srgb(vec4 linear_color) {
|
||||
mix(less_than_branch, greater_than_branch, selector),
|
||||
linear_color.a
|
||||
);
|
||||
#else
|
||||
// For non-GLES, GL_FRAMEBUFFER_SRGB handles this for free
|
||||
return linear_color;
|
||||
#endif
|
||||
}
|
||||
|
||||
void main() {
|
||||
out_color = linear_to_srgb(fragment_color * texture(tex, fragment_uv.st));
|
||||
vec4 linear_color = fragment_color * texture(tex, fragment_uv.st);
|
||||
#ifdef OUTPUT_SRGB
|
||||
out_color = linear_to_srgb(linear_color);
|
||||
#else
|
||||
out_color = linear_color;
|
||||
#endif
|
||||
}
|
||||
"#;
|
||||
|
||||
@ -879,11 +956,16 @@ void main() {
|
||||
body = VERTEX_BODY,
|
||||
);
|
||||
let fragment_source = format!(
|
||||
"#version {major}{minor}{es_extras}\n{body}",
|
||||
"#version {major}{minor}{es_extras}{defines}\n{body}",
|
||||
major = major,
|
||||
minor = minor * 10,
|
||||
es_extras = if is_gles {
|
||||
" es\nprecision mediump float;\n#define IS_GLES"
|
||||
" es\nprecision mediump float;"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
defines = if output_srgb {
|
||||
"\n#define OUTPUT_SRGB"
|
||||
} else {
|
||||
""
|
||||
},
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user