Gnimuc / CImGui.jl

Julia wrapper for cimgui
https://github.com/cimgui/cimgui
MIT License
251 stars 25 forks source link

Example of integrating a GLMakie plot in a CImGui window (or conversely) #114

Closed Benoit9 closed 1 month ago

Benoit9 commented 3 months ago

I ran across this nice example: https://gist.github.com/SteffenPL/88f65222bf786a6434bc2da3ae02ac74

Unfortunately, both Makie and CimGui have changed since then, and I can't figure out how to adapt the code to the new APIs.

Would there be another example somewhere, or could someone give me some hints?

Thanks a lot!

JamesWrigley commented 3 months ago

That's the only example I'm aware of I'm afraid :sweat_smile: What are the errors you get when you try running it? Note that that example is for embedding ImGui inside GLMakie, there's another discussion here about embedding GLMakie in ImGui: https://github.com/Gnimuc/CImGui.jl/discussions/112

Benoit9 commented 3 months ago

Well, there are changes to the API that are easily fixed, like using CImGui.GLFWBackend that becomes using CImGui.ImGuiGLFWBackend

But then, a line like: ImGui_ImplGlfw_InitForOpenGL(GLMakie.to_native(GLMakie.global_gl_screen()), true) I have no clue what to do with.

I have been told on Discourse to replace

screen = AbstractPlotting.backend_display(GLMakie.GLBackend(), scene)
set_window_config!(renderloop=imgui_renderloop)

with:

new_screen = display(scene, backend = GLMakie)
GLMakie.activate!(; config_kwargs...)

That helps.

GLMakie in Imgui or the other way around would work for me. If there is some WIP, I can try and contribute... Thanks!

JamesWrigley commented 3 months ago

I see :+1: I don't have the bandwidth to look into it more ATM (though it's on my todo list), but if you don't need Makie specifically you might be interested in ImPlot.jl as an alternative plotting library: https://github.com/wsphillips/ImPlot.jl

Benoit9 commented 3 months ago

OK, thanks! I I find something I will report back.

scls19fr commented 2 months ago

Hi, I was looking for something similar ie GLMakie plot inside CImGui window (for my use case). Any news?

JamesWrigley commented 2 months ago

Not from me unfortunately, though it's still on my todo list :see_no_evil: I think for now ImPlot.jl is probably your best bet for plotting.

JamesWrigley commented 1 month ago

Here's an extremely experimental demo: imgui-makie

Known bugs/things that don't work:

The code: ```julia # Experimental Makie-CImGui integration # # GLMakie mostly works around a Screen{T} object, where T is some # OpenGL-supporting window. GLMakie sets this to a GLFW.Window by default, but # here we make a MakieWindow to represent a single Figure to be drawn in # ImGui. We attempt to re-use GLMakie as much possible so we don't have to # worry about reimplementing rendering etc. # # What we get from GLMakie is a framebuffer with a color image texture # attachment that can be displayed by ImGui as an image: # https://github.com/ocornut/imgui/wiki/Image-Loading-and-Displaying-Examples#example-for-opengl-users using CImGui import CImGui as ig, ModernGL as gl, GLFW using GLMakie import ShaderAbstractions ig.set_backend(:GlfwOpenGL3) # Represents a single Figure to be shown as an ImGui image texture mutable struct MakieWindow glfw_window::GLFW.Window # Only needed for supporting GLMakie requirements end Base.isopen(window::MakieWindow) = isopen(window.glfw_window) # Specialization of Base.resize(::GLMakie.Screen, ::Int, ::Int) to not do GLFW things # See: https://github.com/MakieOrg/Makie.jl/blob/4c4eaa1f3a7f7b3777a4b8ab38388a48c0eee6ce/GLMakie/src/screen.jl#L664 function Base.resize!(screen::GLMakie.Screen{MakieWindow}, w::Int, h::Int) fbscale = screen.px_per_unit[] fbw, fbh = round.(Int, fbscale .* (w, h)) resize!(screen.framebuffer, fbw, fbh) end # Not sure if this is correct, it should probably be the figure size GLMakie.framebuffer_size(window::MakieWindow) = GLMakie.framebuffer_size(window.glfw_window) # ShaderAbstractions support ShaderAbstractions.native_switch_context!(x::MakieWindow) = (GLFW.MakeContextCurrent(x.glfw_window); nothing) ShaderAbstractions.native_context_alive(x::MakieWindow) = GLFW.is_initialized() && x.glfw_window != C_NULL # This is called by GLMakie.display() to set up connections to GLFW, we'll need # to implement this properly later to handle interactions. GLMakie.Makie.connect_screen(::GLMakie.Scene, ::GLMakie.Screen{MakieWindow}) = nothing # Modified copy of apply_config!() with all GLFW/renderloop things removed # See: https://github.com/MakieOrg/Makie.jl/blob/4c4eaa1f3a7f7b3777a4b8ab38388a48c0eee6ce/GLMakie/src/screen.jl#L343 function apply_config!(screen::GLMakie.Screen, config::GLMakie.ScreenConfig) screen.scalefactor[] = !isnothing(config.scalefactor) ? config.scalefactor : 1 screen.px_per_unit[] = !isnothing(config.px_per_unit) ? config.px_per_unit : screen.scalefactor[] function replace_processor!(postprocessor, idx) fb = screen.framebuffer shader_cache = screen.shader_cache post = screen.postprocessors[idx] if post.constructor !== postprocessor GLMakie.destroy!(screen.postprocessors[idx]) screen.postprocessors[idx] = postprocessor(fb, shader_cache) end nothing end replace_processor!(config.ssao ? GLMakie.ssao_postprocessor : GLMakie.empty_postprocessor, 1) replace_processor!(config.oit ? GLMakie.OIT_postprocessor : GLMakie.empty_postprocessor, 2) replace_processor!(config.fxaa ? GLMakie.fxaa_postprocessor : GLMakie.empty_postprocessor, 3) # Set the config screen.config = config end function generate_data(type::Symbol=:random, N=1000) if type === :random [Point2f(i, rand()) for i in 1:N] end end function imgui_test() # Create a plot f = Figure() ax = Axis(f[1, 1]; title="Random data") data = Observable(generate_data()) lines!(ax, data) # Variables we'll use later screen = nothing makie_window = nothing fb = nothing # Start the GUI ctx = ig.CreateContext() screen_created = false ig.render(ctx; window_size=(800, 600), window_title="ImGui Window") do window ig.Begin("Makie test") if !screen_created # The code in this block combines the screen creation code from # GLMakie.empty_screen() and the screen configuration code from # GLMakie.Screen(). # See: # - https://github.com/MakieOrg/Makie.jl/blob/4c4eaa1f3a7f7b3777a4b8ab38388a48c0eee6ce/GLMakie/src/screen.jl#L223 # - https://github.com/MakieOrg/Makie.jl/blob/4c4eaa1f3a7f7b3777a4b8ab38388a48c0eee6ce/GLMakie/src/screen.jl#L388 makie_window = MakieWindow(window) ShaderAbstractions.switch_context!(makie_window) shader_cache = GLMakie.GLAbstraction.ShaderCache(makie_window) fb = GLMakie.GLFramebuffer((100, 100)) gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0) # Have to unbind after creating the framebuffer postprocessors = [ GLMakie.empty_postprocessor(), GLMakie.empty_postprocessor(), GLMakie.empty_postprocessor(), GLMakie.to_screen_postprocessor(fb, shader_cache) ] screen = GLMakie.Screen(makie_window, shader_cache, fb, nothing, false, nothing, Dict{WeakRef, GLMakie.ScreenID}(), GLMakie.ScreenArea[], Tuple{GLMakie.ZIndex, GLMakie.ScreenID, GLMakie.RenderObject}[], postprocessors, Dict{UInt64, GLMakie.RenderObject}(), Dict{UInt32, GLMakie.AbstractPlot}(), true) config = GLMakie.Makie.merge_screen_config(GLMakie.ScreenConfig, Dict{Symbol, Any}()) apply_config!(screen, config) @show screen screen_created = true display(screen, f) end if ig.Button("Random data") data[] = generate_data() end region_avail = ig.GetContentRegionAvail() region_size = (Int(region_avail.x), Int(region_avail.y)) scene_size = size(GLMakie.Makie.get_scene(f)) if scene_size != region_size && all(region_size .> 0) @debug "resizing $(scene_size) -> $(region_size)" resize!(f, region_size[1], region_size[2]) end if GLMakie.requires_update(screen) @debug "rendering" GLMakie.render_frame(screen) end # The color texture is what we need to render as an image color_buffer = fb.buffers[:color] ig.Image(Ptr{Cvoid}(Int(color_buffer.id)), size(color_buffer), # GLMakie seems to use flipped Y coordinates compared to ImGui, # so we set these coordinates to flip the image. (0, 1), (1, 0)) ig.End() end end ```
JamesWrigley commented 1 month ago

This is now in v2.1, please give it a go and open an issue/PR if you find problems (which you almost certainly will :stuck_out_tongue: ).