Gnimuc / CImGui.jl

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

Create a CImGui sample application ie running outside of Julia CLI (from PowerShell commandline) #124

Closed scls19fr closed 2 months ago

scls19fr commented 2 months ago

Hello,

I'm looking for a way to create an application using CImGui.jl and run it outside of Julia CLI.

What is appropriate workflow for that purpose?

I did

$ julia
julia> ] add PkgTemplates
julia> using PkgTemplates

julia> Template(interactive=true)("SampleCImGui")
Template keywords to customize:
[press: Enter=toggle, a=all, n=none, d=done, q=abort]
 > [ ] user ("")
   [ ] authors ("... <....@mail.com> and contributors")
   [ ] dir ("C:\\Users\\Admin\\.julia\\dev")
   [ ] host ("github.com")
   [ ] julia (v"1.6.7")
   [ ] plugins (PkgTemplates.Plugin[])
Enter value for 'user' (required): me
[ Info: Running prehooks
[ Info: Running hooks
  Activating project at `C:\Users\Admin\.julia\dev\SampleCImGui`
    Updating registry at `C:\Users\Admin\.julia\registries\General.toml`
  No Changes to `C:\Users\Admin\.julia\dev\SampleCImGui\Project.toml`
  No Changes to `C:\Users\Admin\.julia\dev\SampleCImGui\Manifest.toml`
Precompiling project...
  1 dependency successfully precompiled in 1 seconds
  Activating project at `C:\Users\Admin\.julia\environments\v1.10`
[ Info: Running posthooks
[ Info: New package is at C:\Users\Admin\.julia\dev\SampleCImGui
"C:\\Users\\Admin\\.julia\\dev\\SampleCImGui"

Run my editor

code .\.julia\dev\SampleCImGui\

Create src/Renderer.jl with https://raw.githubusercontent.com/Gnimuc/CImGui.jl/master/examples/Renderer.jl content

Modify SampleCImGui.jl from

module SampleCImGui

# Write your package code here.

end

to

module SampleCImGui

using CImGui

include("Renderer.jl")
using .Renderer

Renderer.render(width = 360, height = 480, title = "IMGUI Window") do
    CImGui.Begin("Hello ImGui")
    CImGui.Button("My Button") && @show "triggered"
    CImGui.End()
end

end

Running

julia C:\Users\Admin\.julia\dev\SampleCImGui\src\SampleCImGui.jl

raises

ERROR: LoadError: UndefVarError: `glsl_version` not defined
Stacktrace:
 [1] init_renderer(width::Int64, height::Int64, title::String)
   @ Main.SampleCImGui.Renderer C:\Users\Admin\.julia\dev\SampleCImGui\src\Renderer.jl:50
 [2] #render#3
   @ C:\Users\Admin\.julia\dev\SampleCImGui\src\Renderer.jl:93 [inlined]
 [3] top-level scope
   @ C:\Users\Admin\.julia\dev\SampleCImGui\src\SampleCImGui.jl:8
in expression starting at C:\Users\Admin\.julia\dev\SampleCImGui\src\SampleCImGui.jl:1

so adding

Renderer.__init__()

before

Renderer.render(width = 360, height = 480, title = "IMGUI Window") do

but now

PS C:\Users\Admin\.julia\dev\SampleCImGui> julia .\src\SampleCImGui.jl

opens a windows which exits instantaneously.

Any idea what is wrong with this workflow?

I also did...

PS C:\Users\Admin> cd C:\Users\Admin\.julia\dev\SampleCImGui\
PS C:\Users\Admin\.julia\dev\SampleCImGui> julia
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.10.4 (2024-06-04)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

(@v1.10) pkg> activate .
  Activating project at `C:\Users\Admin\.julia\dev\SampleCImGui`

(SampleCImGui) pkg> add CImGUI
ERROR: The following package names could not be resolved:
 * CImGUI (not found in project, manifest or registry)
   Suggestions: CImGui CImGui_jll CImGuiPack_jll LibCImGui DecisionMakingUtils AbstractImageReconstruction VLConstraintBasedModelGenerationUtilities

(SampleCImGui) pkg> add CImGui
   Resolving package versions...
    Updating `C:\Users\Admin\.julia\dev\SampleCImGui\Project.toml`
  [5d785b6c] + CImGui v1.89.1
    Updating `C:\Users\Admin\.julia\dev\SampleCImGui\Manifest.toml`
⌅ [fa961155] + CEnum v0.4.2
  [5d785b6c] + CImGui v1.89.1
(...)
  [3f19e933] + p7zip_jll v17.4.0+2
        Info Packages marked with ⌅ have new versions available but compatibility constraints restrict them from upgrading. To see why use `status --outdated -m`
Precompiling project...
  1 dependency successfully precompiled in 8 seconds. 32 already precompiled.

in this case Window opens and doesn't exits instantaneously as previously.

My Project.toml looks like

name = "SampleCImGui"
uuid = "45e96454-f604-4cbc-b785-565ff795104d"
authors = ["scls19fr <s.celles@gmail.com> and contributors"]
version = "1.0.0-DEV"

[deps]
CImGui = "5d785b6c-b76f-510e-a07c-3070796c7e87"

[compat]
julia = "1.6.7"

[extras]
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"

[targets]
test = ["Test"]

Thanks for your help

Kind regards

PS : I also tried running using

PS C:\Users\Admin\.julia\dev\SampleCImGui> julia --project=. .\src\SampleCImGui.jl

but window exits quickly also

JamesWrigley commented 2 months ago

I also did...

PS C:\Users\Admin> cd C:\Users\Admin\.julia\dev\SampleCImGui\
PS C:\Users\Admin\.julia\dev\SampleCImGui> julia
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.10.4 (2024-06-04)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

(@v1.10) pkg> activate .
  Activating project at `C:\Users\Admin\.julia\dev\SampleCImGui`

(SampleCImGui) pkg> add CImGUI
ERROR: The following package names could not be resolved:
 * CImGUI (not found in project, manifest or registry)
   Suggestions: CImGui CImGui_jll CImGuiPack_jll LibCImGui DecisionMakingUtils AbstractImageReconstruction VLConstraintBasedModelGenerationUtilities

(SampleCImGui) pkg> add CImGui
   Resolving package versions...
    Updating `C:\Users\Admin\.julia\dev\SampleCImGui\Project.toml`
  [5d785b6c] + CImGui v1.89.1
    Updating `C:\Users\Admin\.julia\dev\SampleCImGui\Manifest.toml`
⌅ [fa961155] + CEnum v0.4.2
  [5d785b6c] + CImGui v1.89.1
(...)
  [3f19e933] + p7zip_jll v17.4.0+2
        Info Packages marked with ⌅ have new versions available but compatibility constraints restrict them from upgrading. To see why use `status --outdated -m`
Precompiling project...
  1 dependency successfully precompiled in 8 seconds. 32 already precompiled.

in this case Window opens and doesn't exits instantaneously as previously.

I didn't quite understand this bit, what exactly did you run that worked?

scls19fr commented 2 months ago

When precompiling the project... the window appears (and didn't quit). It happened only once

JamesWrigley commented 2 months ago

That is... quite weird :stuck_out_tongue: Anyway, I think there's two possibilities:

Could you try using the rendering code from demo.jl? If I remember correctly the demo is working on your machine so that rendering code should be doing the right thing.

FWIW this whole renderer situation should improve once I finish the update to 1.90.8, because then we'll include the renderloop in CImGui itself and the demo should look like this: https://github.com/Gnimuc/CImGui.jl/blob/1.90.8/demo/demo.jl

scls19fr commented 2 months ago

demo.jl includes a lot of other files... that's not really a minimal working example. I will wait next release. 😴 Please let me know when it will be available. Thanks

JamesWrigley commented 2 months ago

Oh, no I don't mean you have to run the whole demo, I mean you can delete all the demo-specific stuff and replace it with your code just to see if it works.

scls19fr commented 2 months ago

Ok something like

using CImGui
using CImGui.ImGuiGLFWBackend
using CImGui.ImGuiGLFWBackend.LibCImGui
using CImGui.ImGuiGLFWBackend.LibGLFW
using CImGui.ImGuiOpenGLBackend
using CImGui.ImGuiOpenGLBackend.ModernGL
# using CImGui.ImGuiGLFWBackend.GLFW
using CImGui.CSyntax

function ShowJuliaDemoWindow(p_open::Ref{Bool})
    CImGui.Begin("Hello ImGui")
    CImGui.Button("My Button") && @show "triggered"
    CImGui.End()
end

glfwDefaultWindowHints()
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3)
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2)
if Sys.isapple()
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE) # 3.2+ only
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE) # required on Mac
end

# create window
window = glfwCreateWindow(1280, 720, "Demo", C_NULL, C_NULL)
@assert window != C_NULL
glfwMakeContextCurrent(window)
glfwSwapInterval(1)  # enable vsync

# create OpenGL and GLFW context
window_ctx = ImGuiGLFWBackend.create_context(window)
gl_ctx = ImGuiOpenGLBackend.create_context()

# setup Dear ImGui context
ctx = CImGui.CreateContext()

# enable docking and multi-viewport
io = CImGui.GetIO()
io.ConfigFlags = unsafe_load(io.ConfigFlags) | CImGui.ImGuiConfigFlags_DockingEnable
io.ConfigFlags = unsafe_load(io.ConfigFlags) | CImGui.ImGuiConfigFlags_ViewportsEnable

# setup Dear ImGui style
CImGui.StyleColorsDark()
# CImGui.StyleColorsClassic()
# CImGui.StyleColorsLight()

# When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones.
style = Ptr{ImGuiStyle}(CImGui.GetStyle())
if unsafe_load(io.ConfigFlags) & ImGuiConfigFlags_ViewportsEnable == ImGuiConfigFlags_ViewportsEnable
    style.WindowRounding = 5.0f0
    col = CImGui.c_get(style.Colors, CImGui.ImGuiCol_WindowBg)
    CImGui.c_set!(style.Colors, CImGui.ImGuiCol_WindowBg, ImVec4(col.x, col.y, col.z, 1.0f0))
end

# load Fonts
# - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use `CImGui.PushFont/PopFont` to select them.
# - `CImGui.AddFontFromFileTTF` will return the `Ptr{ImFont}` so you can store it if you need to select the font among multiple.
# - If the file cannot be loaded, the function will return C_NULL. Please handle those errors in your application (e.g. use an assertion, or display an error and quit).
# - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling `CImGui.Build()`/`GetTexDataAsXXXX()``, which `ImGui_ImplXXXX_NewFrame` below will call.
# - Read 'fonts/README.txt' for more instructions and details.
fonts_dir = joinpath(@__DIR__, "..", "fonts")
fonts = unsafe_load(CImGui.GetIO().Fonts)
# default_font = CImGui.AddFontDefault(fonts)
# CImGui.AddFontFromFileTTF(fonts, joinpath(fonts_dir, "Cousine-Regular.ttf"), 15)
# CImGui.AddFontFromFileTTF(fonts, joinpath(fonts_dir, "DroidSans.ttf"), 16)
# CImGui.AddFontFromFileTTF(fonts, joinpath(fonts_dir, "Karla-Regular.ttf"), 10)
# CImGui.AddFontFromFileTTF(fonts, joinpath(fonts_dir, "ProggyTiny.ttf"), 10)
# CImGui.AddFontFromFileTTF(fonts, joinpath(fonts_dir, "Roboto-Medium.ttf"), 16)
CImGui.AddFontFromFileTTF(fonts, joinpath(fonts_dir, "Recursive Mono Casual-Regular.ttf"), 16)
CImGui.AddFontFromFileTTF(fonts, joinpath(fonts_dir, "Recursive Mono Linear-Regular.ttf"), 16)
CImGui.AddFontFromFileTTF(fonts, joinpath(fonts_dir, "Recursive Sans Casual-Regular.ttf"), 16)
CImGui.AddFontFromFileTTF(fonts, joinpath(fonts_dir, "Recursive Sans Linear-Regular.ttf"), 16)
# @assert default_font != C_NULL

# create texture for image drawing
img_width, img_height = 256, 256
image_id = ImGuiOpenGLBackend.ImGui_ImplOpenGL3_CreateImageTexture(img_width, img_height)

# setup Platform/Renderer bindings
ImGuiGLFWBackend.init(window_ctx)
ImGuiOpenGLBackend.init(gl_ctx)

try
    demo_open = true
    clear_color = Cfloat[0.45, 0.55, 0.60, 1.00]
    while glfwWindowShouldClose(window) == 0
        glfwPollEvents()
        # start the Dear ImGui frame
        ImGuiOpenGLBackend.new_frame(gl_ctx)
        ImGuiGLFWBackend.new_frame(window_ctx)
        CImGui.NewFrame()

        demo_open && @c ShowJuliaDemoWindow(&demo_open)

        # rendering
        CImGui.Render()
        glfwMakeContextCurrent(window)

        width, height = Ref{Cint}(), Ref{Cint}() #! need helper fcn
        glfwGetFramebufferSize(window, width, height)
        display_w = width[]
        display_h = height[]

        glViewport(0, 0, display_w, display_h)
        glClearColor(clear_color...)
        glClear(GL_COLOR_BUFFER_BIT)
        ImGuiOpenGLBackend.render(gl_ctx)

        if unsafe_load(igGetIO().ConfigFlags) & ImGuiConfigFlags_ViewportsEnable == ImGuiConfigFlags_ViewportsEnable
            backup_current_context = glfwGetCurrentContext()
            igUpdatePlatformWindows()
            GC.@preserve gl_ctx igRenderPlatformWindowsDefault(C_NULL, pointer_from_objref(gl_ctx))
            glfwMakeContextCurrent(backup_current_context)
        end

        glfwSwapBuffers(window)
    end
catch e
    @error "Error in renderloop!" exception=e
    Base.show_backtrace(stderr, catch_backtrace())
finally
    ImGuiOpenGLBackend.shutdown(gl_ctx)
    ImGuiGLFWBackend.shutdown(window_ctx)
    CImGui.DestroyContext(ctx)
    glfwDestroyWindow(window)
end

I'm very eager to hide all the rendering loop code.

PS: maybe some code is still useless here?

JamesWrigley commented 2 months ago

Yeah exactly, does that work for you? Once we know what's working it should be possible to move it into your copy of Renderer.jl

scls19fr commented 2 months ago

Ok. It works fine.

Here is now SampleCImGui.jl

module SampleCImGui

using  CImGui
include("Renderer.jl")

function ShowJuliaDemoWindow(p_open::Ref{Bool})
    CImGui.Begin("Hello ImGui")
    CImGui.Button("My Button") && @show "triggered"
    CImGui.End()
end

Renderer.__init__()
window, ctx, gl_ctx, window_ctx = Renderer.init_renderer(1280, 720, "Demo")
Renderer.render(window, ctx, gl_ctx, window_ctx, ShowJuliaDemoWindow)

end

and Renderer.jl

module Renderer

using CImGui
using CImGui.ImGuiGLFWBackend
using CImGui.ImGuiGLFWBackend.LibCImGui
using CImGui.ImGuiGLFWBackend.LibGLFW
using CImGui.ImGuiOpenGLBackend
using CImGui.ImGuiOpenGLBackend.ModernGL
# using CImGui.ImGuiGLFWBackend.GLFW
using CImGui.CSyntax

function __init__()
    glfwDefaultWindowHints()
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3)
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2)
    if Sys.isapple()
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE) # 3.2+ only
        glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE) # required on Mac
    end
end

# create window
function init_renderer(width, height, title::AbstractString)
    window = glfwCreateWindow(width, height, title, C_NULL, C_NULL)
    @assert window != C_NULL
    glfwMakeContextCurrent(window)
    glfwSwapInterval(1)  # enable vsync

    # create OpenGL and GLFW context
    window_ctx = ImGuiGLFWBackend.create_context(window)
    gl_ctx = ImGuiOpenGLBackend.create_context()

    # setup Dear ImGui context
    ctx = CImGui.CreateContext()

    # enable docking and multi-viewport
    io = CImGui.GetIO()
    io.ConfigFlags = unsafe_load(io.ConfigFlags) | CImGui.ImGuiConfigFlags_DockingEnable
    io.ConfigFlags = unsafe_load(io.ConfigFlags) | CImGui.ImGuiConfigFlags_ViewportsEnable

    # setup Dear ImGui style
    CImGui.StyleColorsDark()
    # CImGui.StyleColorsClassic()
    # CImGui.StyleColorsLight()

    # When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones.
    style = Ptr{ImGuiStyle}(CImGui.GetStyle())
    if unsafe_load(io.ConfigFlags) & ImGuiConfigFlags_ViewportsEnable == ImGuiConfigFlags_ViewportsEnable
        style.WindowRounding = 5.0f0
        col = CImGui.c_get(style.Colors, CImGui.ImGuiCol_WindowBg)
        CImGui.c_set!(style.Colors, CImGui.ImGuiCol_WindowBg, ImVec4(col.x, col.y, col.z, 1.0f0))
    end

    # load Fonts
    # - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use `CImGui.PushFont/PopFont` to select them.
    # - `CImGui.AddFontFromFileTTF` will return the `Ptr{ImFont}` so you can store it if you need to select the font among multiple.
    # - If the file cannot be loaded, the function will return C_NULL. Please handle those errors in your application (e.g. use an assertion, or display an error and quit).
    # - The fonts will be rasterized at a given size (w/ oversampling) and stored into a texture when calling `CImGui.Build()`/`GetTexDataAsXXXX()``, which `ImGui_ImplXXXX_NewFrame` below will call.
    # - Read 'fonts/README.txt' for more instructions and details.
    fonts_dir = joinpath(@__DIR__, "..", "fonts")
    fonts = unsafe_load(CImGui.GetIO().Fonts)
    # default_font = CImGui.AddFontDefault(fonts)
    # CImGui.AddFontFromFileTTF(fonts, joinpath(fonts_dir, "Cousine-Regular.ttf"), 15)
    # CImGui.AddFontFromFileTTF(fonts, joinpath(fonts_dir, "DroidSans.ttf"), 16)
    # CImGui.AddFontFromFileTTF(fonts, joinpath(fonts_dir, "Karla-Regular.ttf"), 10)
    # CImGui.AddFontFromFileTTF(fonts, joinpath(fonts_dir, "ProggyTiny.ttf"), 10)
    # CImGui.AddFontFromFileTTF(fonts, joinpath(fonts_dir, "Roboto-Medium.ttf"), 16)
    CImGui.AddFontFromFileTTF(fonts, joinpath(fonts_dir, "Recursive Mono Casual-Regular.ttf"), 16)
    CImGui.AddFontFromFileTTF(fonts, joinpath(fonts_dir, "Recursive Mono Linear-Regular.ttf"), 16)
    CImGui.AddFontFromFileTTF(fonts, joinpath(fonts_dir, "Recursive Sans Casual-Regular.ttf"), 16)
    CImGui.AddFontFromFileTTF(fonts, joinpath(fonts_dir, "Recursive Sans Linear-Regular.ttf"), 16)
    # @assert default_font != C_NULL

    # create texture for image drawing
    img_width, img_height = 256, 256
    image_id = ImGuiOpenGLBackend.ImGui_ImplOpenGL3_CreateImageTexture(img_width, img_height)

    # setup Platform/Renderer bindings
    ImGuiGLFWBackend.init(window_ctx)
    ImGuiOpenGLBackend.init(gl_ctx)

    return window, ctx, gl_ctx, window_ctx
end

function render(window, ctx, gl_ctx, window_ctx, ShowJuliaWindow)
    try
        demo_open = true
        clear_color = Cfloat[0.45, 0.55, 0.60, 1.00]
        while glfwWindowShouldClose(window) == 0
            glfwPollEvents()
            # start the Dear ImGui frame
            ImGuiOpenGLBackend.new_frame(gl_ctx)
            ImGuiGLFWBackend.new_frame(window_ctx)
            CImGui.NewFrame()

            demo_open && @c ShowJuliaWindow(&demo_open)

            # rendering
            CImGui.Render()
            glfwMakeContextCurrent(window)

            width, height = Ref{Cint}(), Ref{Cint}() #! need helper fcn
            glfwGetFramebufferSize(window, width, height)
            display_w = width[]
            display_h = height[]

            glViewport(0, 0, display_w, display_h)
            glClearColor(clear_color...)
            glClear(GL_COLOR_BUFFER_BIT)
            ImGuiOpenGLBackend.render(gl_ctx)

            if unsafe_load(igGetIO().ConfigFlags) & ImGuiConfigFlags_ViewportsEnable == ImGuiConfigFlags_ViewportsEnable
                backup_current_context = glfwGetCurrentContext()
                igUpdatePlatformWindows()
                GC.@preserve gl_ctx igRenderPlatformWindowsDefault(C_NULL, pointer_from_objref(gl_ctx))
                glfwMakeContextCurrent(backup_current_context)
            end

            glfwSwapBuffers(window)
        end
    catch e
        @error "Error in renderloop!" exception=e
        Base.show_backtrace(stderr, catch_backtrace())
    finally
        ImGuiOpenGLBackend.shutdown(gl_ctx)
        ImGuiGLFWBackend.shutdown(window_ctx)
        CImGui.DestroyContext(ctx)
        glfwDestroyWindow(window)
    end
end

end

Great to know this part will be easier in a near future.

JamesWrigley commented 2 months ago

Noice noice, I'll close this then. BTW, I would recommend moving these lines out of global scope in the module and putting them in a function:

Renderer.__init__()
window, ctx, gl_ctx, window_ctx = Renderer.init_renderer(1280, 720, "Demo")
Renderer.render(window, ctx, gl_ctx, window_ctx, ShowJuliaDemoWindow)

That way they won't be executed during precompilation.