Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
scls19fr opened this issue Jun 20, 2024 · 9 comments

Comments

@scls19fr
Copy link

scls19fr commented Jun 20, 2024

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 ("... <[email protected]> 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 <[email protected]> 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
Copy link
Collaborator

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
Copy link
Author

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

@JamesWrigley
Copy link
Collaborator

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

  • Renderer.jl is incorrect, it hasn't been updated for a few years and I don't know if it's doing the right thing since I updated the library for 1.89.
  • There's some platform incompatibility with GLFW, which seems unlikely.

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
Copy link
Author

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
Copy link
Collaborator

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
Copy link
Author

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
Copy link
Collaborator

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
Copy link
Author

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
Copy link
Collaborator

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants