Open sam20908 opened 1 year ago
You aren't doing anything wrong or missing something, the SDL_render
backend does not support multi-viewports.
It may be a relatively easy PR to work on, if anyone is i interested in investigating it.
I'd love to see this being addressed, because SDL_Renderer is hardware accelerated and I'd get imgui's (awesome) viewports at the same time!
(I originally commented this on https://github.com/ocornut/imgui/issues/5874 but realized it made more sense here.)
@ocornut
TL;DR: Adding multi-viewport support to imgui_impl_sdlrenderer
requires disabling user texture support, dirty hacks, or changes to SDL.
Note that multi-viewport for SDL_Renderer backend is likely to be a relatively easy feature to add.
So I started to look into adding this, and it turns out it actually might not be trivial. I started writing some advice to @Vin789 on how they might do it, then I decided to just do it myself, and then I found what might be a showstopper.
Each SDL_Window
must have its own SDL_Renderer
(it's a strict 1:1 relationship internally.) It turns out that you cannot share textures between different SDL_Renderer
instances, which means you can't share textures between windows. This is enforced by the API, it doesn't matter whether the render drivers match.
If you want a quick copy+pastable smoke test:
// Add this to the initialization of example_sdl_sdlrenderer's main
SDL_Window* window2;
SDL_Renderer* renderer2;
SDL_CreateWindowAndRenderer(300, 300, window_flags, &window2, &renderer2);
// ...
// And this to the end of the main loop
SDL_SetRenderDrawColor(renderer2, 255, 0, 0, 255);
SDL_RenderClear(renderer2);
if (SDL_RenderCopy(renderer2, (SDL_Texture*)io.Fonts->TexID, nullptr, nullptr) != 0)
SDL_Log("Error rendering texture: %s", SDL_GetError());
SDL_RenderPresent(renderer2);
The second window will be blank red and the log will be spammed with errors about the texture and renderer not matching.
I see the following solutions to this problem:
master
and docking
.SDL_Renderer
does not appear to provide the APIs necessary to do this for all types of textures. (As far as I can see there's no way to copy a GPU-only texture to a readback texture, which would be required for SDL_TEXTUREACCESS_STATIC
and possibly SDL_TEXTUREACCESS_TARGET
textures.)SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl");
to enable secondary viewports and render all secondary viewports using OpenGL directly.
SDL_GL_BindTexture
for this, but looking at the implementation I don't think it'd actually work because it changes the GL context to the renderer's.)SDL_RENDERER_TEXTURESHARING
capability flag and allow sharing textures between SDL_Renderer
instances of the same driver when supported.
imgui_impl_opengl3
with SDL_Renderer
SDL_GL_BindTexture
)SDL_Texture
would not be usable as ImTextureID
, user textures would need to be OpenGL textures or we'd have to poke at SDL internals as mentioned above.SDL_Renderer
if they want multi-viewports.Hello @PathogenDavid, I have forwarded your message to the SDL Discord. Let's see what the SDL devs have to say on this matter :)
Hey, I kinda wish you hadn't. My message was pretty explicitly intended to share my findings with Omar (and to save someone else time investigating in the future.) I'm capable of reaching out to the SDL developers myself and IMO it is premature to do so at this time.
2023 edit: This was a little excessively grumpy. I was pretty stressed out by life stuff at the time and I think I perceived your sharing as indirectly creating an obligation for me. Sorry about that!
For clarification, the SDL discord is much more of just a community for learning and using SDL. There's one maintainer there, but they're not particularly active (I'm sure they're incredibly busy!). So don't worry too much about the SDL maintainers getting premature information. Thanks for the write up though! I'm sure this will be interesting to those of us who've delved into the SDL source :)
Thanks David for investigating and thoroughly reporting those.
I think the best path is to get multiple SDL_Renderer windows to support shared resources. It's already supported by SDL for OpenGL, and it's easy for all Dear ImGui backends to do it so I would assume SDL could do it as well for all their underlying backends. SDL teams have been very proactive and reactive lately so it might happen fast enough and anyhow the earlier we formally push for it the better?
But I also think "Put in the effort to figure out how to properly use imgui_impl_opengl3 with SDL_Renderer" should be possible to solve, maybe investigating SDL_Renderer sources we can find which states are clashing. imgui_impl_opengl3
aims to set all the states it needs and backup/restore but there are always missing things.
If SDL_Renderer if using OpenGL, wouldn't calling this after creating the first window be enough?
SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1)
How would that work though? AFAIK SDL_Renderer uses DX11 by default,
@ocornut nice catch. Calling this before SDL_CreateWindow/SDL_CreateRenderer/SDL_GL_CreateContext:
// Create window with graphics context
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1); // Here
SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL2+OpenGL3 example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, window_flags);
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
SDL_GLContext gl_context = SDL_GL_CreateContext(window);
SDL_GL_MakeCurrent(window, gl_context);
SDL_GL_SetSwapInterval(1); // Enable vsync
Seems to works (not working after SDL_GL_CreateContext however):
Oups sorry I must precise it's the OpenGL3 example not the SDL_Renderer example. But at least it's working fine with the additional line (mixing SDL image and ImGui + viewports). I guess for the SDL_Renderer it still need more code.
Ok I'm stupid sorry for all these comments. My example was indeed the example_sdl_opengl3 but WITH the usage of the SDL_Renderer. So it seems that with:
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl");
@sam20908 to force the usage of opengl
and :
SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
it's working.
@Vin789 Can you provide a complete minimal code of it working? I'm confused on the details like do I need to call ImGui_ImplSDL2_InitForRenderer
and ImGui_ImplOpenGL3_Init
, and so on...
Sure. Line 110 you need to set the path to your own image for testing. It's an updated version of main.cpp inside example_sdl_opengl3.
#include "imgui.h"
#include "imgui_impl_sdl.h"
#include "imgui_impl_opengl3.h"
#include <stdio.h>
#include <SDL.h>
#if defined(IMGUI_IMPL_OPENGL_ES2)
#include <SDL_opengles2.h>
#else
#include <SDL_opengl.h>
#endif
// Main code
int main(int, char**)
{
SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl");
// Setup SDL
// (Some versions of SDL before <2.0.10 appears to have performance/stalling issues on a minority of Windows systems,
// depending on whether SDL_INIT_GAMECONTROLLER is enabled or disabled.. updating to the latest version of SDL is recommended!)
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0)
{
printf("Error: %s\n", SDL_GetError());
return -1;
}
// Decide GL+GLSL versions
#if defined(IMGUI_IMPL_OPENGL_ES2)
// GL ES 2.0 + GLSL 100
const char* glsl_version = "#version 100";
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
#elif defined(__APPLE__)
// GL 3.2 Core + GLSL 150
const char* glsl_version = "#version 150";
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, SDL_GL_CONTEXT_FORWARD_COMPATIBLE_FLAG); // Always required on Mac
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
#else
// GL 3.0 + GLSL 130
const char* glsl_version = "#version 130";
SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
#endif
// Create window with graphics context
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
SDL_Window* window = SDL_CreateWindow("Dear ImGui SDL2+OpenGL3 example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720, window_flags);
SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);
SDL_GLContext gl_context = SDL_GL_CreateContext(window);
SDL_GL_MakeCurrent(window, gl_context);
SDL_GL_SetSwapInterval(1); // Enable vsync
// Setup Dear ImGui context
IMGUI_CHECKVERSION();
ImGui::CreateContext();
ImGuiIO& io = ImGui::GetIO(); (void)io;
io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
//io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
io.ConfigFlags |= ImGuiConfigFlags_DockingEnable; // Enable Docking
io.ConfigFlags |= ImGuiConfigFlags_ViewportsEnable; // Enable Multi-Viewport / Platform Windows
//io.ConfigViewportsNoAutoMerge = true;
//io.ConfigViewportsNoTaskBarIcon = true;
// Setup Dear ImGui style
ImGui::StyleColorsDark();
//ImGui::StyleColorsLight();
// When viewports are enabled we tweak WindowRounding/WindowBg so platform windows can look identical to regular ones.
ImGuiStyle& style = ImGui::GetStyle();
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
{
style.WindowRounding = 0.0f;
style.Colors[ImGuiCol_WindowBg].w = 1.0f;
}
// Setup Platform/Renderer backends
ImGui_ImplSDL2_InitForOpenGL(window, gl_context);
ImGui_ImplOpenGL3_Init(glsl_version);
// Load Fonts
// - If no fonts are loaded, dear imgui will use the default font. You can also load multiple fonts and use ImGui::PushFont()/PopFont() to select them.
// - AddFontFromFileTTF() will return the 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 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 ImFontAtlas::Build()/GetTexDataAsXXXX(), which ImGui_ImplXXXX_NewFrame below will call.
// - Use '#define IMGUI_ENABLE_FREETYPE' in your imconfig file to use Freetype for higher quality font rendering.
// - Read 'docs/FONTS.md' for more instructions and details.
// - Remember that in C/C++ if you want to include a backslash \ in a string literal you need to write a double backslash \\ !
//io.Fonts->AddFontDefault();
//io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\segoeui.ttf", 18.0f);
//io.Fonts->AddFontFromFileTTF("../../misc/fonts/DroidSans.ttf", 16.0f);
//io.Fonts->AddFontFromFileTTF("../../misc/fonts/Roboto-Medium.ttf", 16.0f);
//io.Fonts->AddFontFromFileTTF("../../misc/fonts/Cousine-Regular.ttf", 15.0f);
//ImFont* font = io.Fonts->AddFontFromFileTTF("c:\\Windows\\Fonts\\ArialUni.ttf", 18.0f, NULL, io.Fonts->GetGlyphRangesJapanese());
//IM_ASSERT(font != NULL);
// Our state
bool show_demo_window = true;
bool show_another_window = false;
ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
SDL_Surface* surface = SDL_LoadBMP("D:/test.bmp");
SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface);
// Main loop
bool done = false;
while (!done)
{
// Poll and handle events (inputs, window resize, etc.)
// You can read the io.WantCaptureMouse, io.WantCaptureKeyboard flags to tell if dear imgui wants to use your inputs.
// - When io.WantCaptureMouse is true, do not dispatch mouse input data to your main application, or clear/overwrite your copy of the mouse data.
// - When io.WantCaptureKeyboard is true, do not dispatch keyboard input data to your main application, or clear/overwrite your copy of the keyboard data.
// Generally you may always pass all inputs to dear imgui, and hide them from your application based on those two flags.
SDL_Event event;
while (SDL_PollEvent(&event))
{
ImGui_ImplSDL2_ProcessEvent(&event);
if (event.type == SDL_QUIT)
done = true;
if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE && event.window.windowID == SDL_GetWindowID(window))
done = true;
}
// Start the Dear ImGui frame
ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplSDL2_NewFrame();
ImGui::NewFrame();
// 1. Show the big demo window (Most of the sample code is in ImGui::ShowDemoWindow()! You can browse its code to learn more about Dear ImGui!).
if (show_demo_window)
ImGui::ShowDemoWindow(&show_demo_window);
// 2. Show a simple window that we create ourselves. We use a Begin/End pair to create a named window.
{
static float f = 0.0f;
static int counter = 0;
ImGui::Begin("Hello, world!"); // Create a window called "Hello, world!" and append into it.
ImGui::Text("This is some useful text."); // Display some text (you can use a format strings too)
ImGui::Checkbox("Demo Window", &show_demo_window); // Edit bools storing our window open/close state
ImGui::Checkbox("Another Window", &show_another_window);
ImGui::SliderFloat("float", &f, 0.0f, 1.0f); // Edit 1 float using a slider from 0.0f to 1.0f
ImGui::ColorEdit3("clear color", (float*)&clear_color); // Edit 3 floats representing a color
if (ImGui::Button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated)
counter++;
ImGui::SameLine();
ImGui::Text("counter = %d", counter);
ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", 1000.0f / ImGui::GetIO().Framerate, ImGui::GetIO().Framerate);
ImGui::End();
}
// 3. Show another simple window.
if (show_another_window)
{
ImGui::Begin("Another Window", &show_another_window); // Pass a pointer to our bool variable (the window will have a closing button that will clear the bool when clicked)
ImGui::Text("Hello from another window!");
if (ImGui::Button("Close Me"))
show_another_window = false;
ImGui::End();
}
// Rendering
ImGui::Render();
glViewport(0, 0, (int)io.DisplaySize.x, (int)io.DisplaySize.y);
glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w);
glClear(GL_COLOR_BUFFER_BIT);
// SDL_renderer
SDL_Rect rct2;
rct2.x = rct2.y = 200;
rct2.w = rct2.h = 400;
SDL_RenderCopy(renderer, texture, 0, &rct2);
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
// Update and Render additional Platform Windows
// (Platform functions may change the current OpenGL context, so we save/restore it to make it easier to paste this code elsewhere.
// For this specific demo app we could also call SDL_GL_MakeCurrent(window, gl_context) directly)
if (io.ConfigFlags & ImGuiConfigFlags_ViewportsEnable)
{
SDL_Window* backup_current_window = SDL_GL_GetCurrentWindow();
SDL_GLContext backup_current_context = SDL_GL_GetCurrentContext();
ImGui::UpdatePlatformWindows();
ImGui::RenderPlatformWindowsDefault();
SDL_GL_MakeCurrent(backup_current_window, backup_current_context);
}
SDL_GL_SwapWindow(window);
}
// Cleanup
SDL_DestroyTexture(texture);
SDL_FreeSurface(surface);
ImGui_ImplOpenGL3_Shutdown();
ImGui_ImplSDL2_Shutdown();
ImGui::DestroyContext();
SDL_GL_DeleteContext(gl_context);
SDL_DestroyRenderer(renderer);
SDL_DestroyWindow(window);
SDL_Quit();
return 0;
}
Assuming SDL_Renderer only has this sharing issue when using OpenGL and we can detect that SDL_Renderer is using OpenGL we can do perform the call ourselves; meaning with build that backend.
Is it an issue with D3D11? I'm kind of hoping I don't have to use OpenGL + Renderer it just seems bad
@Vin789 Thanks for that example. How is the performance for you when you use that? I hope it's around 1% CPU usage
@sam20908 well it's look ok on the task manager (~2%) but I'm not really sure looking at this is really useful. I don't know what's your usage of ImGui but if performance is important to you I guess you should probably use specific tool to monitor Ram/CPU/GPU depending on your case. In my case I'm working on a 2D Game editor so performance in the editor himself are not my top priority. The game running in the viewport window with multi threading on the other hand is my priority, I need to keep good performance for Render Thread / Update thread (but it's not ImGui related).
Yes, I understand, I use it to judge whether the program is running in a normal range of resource usage. If it's 10% then something is very wrong.
The ideal situation I think still is to have it work for SDL_Renderer without needing to rely on OpenGL 3.
Hi @PathogenDavid !
It's been nearly one year since the creation of this thread. I found some times to work again on my personal editor. I was wondering if you know if the situation changed with ImGui + SDL + Multi viewport. I'm still able to draw the main window with ImGui + SDL and others windows with ImGui. But I can't draw SDL in an other window than the main one. I guess it still the issue with the texture sharing between SDL_Renderer ?
Do you know if SDL3 (I'm still on 2) improve the issue you mentioned with SDL_Renderer ? Or there is still no proper way to mix severals SDL_Window/SDL_Renderer with ImGui ?
Thx for your time.
Vincent
Hey @Vin789
I was wondering if you know if the situation changed with ImGui + SDL + Multi viewport.
Sorry to say the situation has not changed.
Do you know if SDL3 (I'm still on 2) improve the issue you mentioned with SDL_Renderer ?
The same check still exists in SDL 3, so I assume the answer is no.
I believe the path forward is someone needs to do a deeper investigation into the various solutions I proposed and determine which are good candidates to propose to the SDL team.
Alternatively though, since SDL 3 is still in flux now might be a good idea to make a more general feature request to allow either allow SDL_Renderer instances to be associated with multiple windows or to allow textures to be shared between multiple renderers.
(At the time I think I was mainly concerned with finding a solution that didn't have too much of an impact on SDL, but that might be less of a concern with SDL 3.)
I see. Thx a lot for the update.
I was waiting because I had works and others stuffs to do on my project. But one of my 3D programmer friend is pushing me to use GLFW + OpenGL directly instead of the SDL. I saw that ImGui seems to have a nice backend for it (and this time it's works fine with multi viewports). So I may just jump on this.
Thx again for your time.
I don't imagine it is particularly difficult to add support for multi-window in SDL_Renderer, someone just has to do it...
My bad I was hasty and have brain-farted, I forgot the crux of discussion was about portably supporting texture sharing across graphical contexts regardless of what backend SDL_Renderer uses. I apologize for confusion. David summed it up well above.
It's mostly a problem of pushing this to SDL developer and I assume it would be a small change for them. Since SDL3 is in active development they may accept a PR quite easily.
Maybe we can setup a basic impl for them to test?
Today I thought I would try to push this a little further, by trying to implement (broken) support in our backend so it would be easier to make missing changes to SDL.
I pushed a branch: https://github.com/ocornut/imgui/tree/features/sdl_renderer3_multiviewports
It's currently not working, because of two issues which would need work on SDL side:
(1) As discussed, textures are not shared so things appears black. https://github.com/libsdl-org/SDL/issues/6742 I will post some details in said issues.
(2) Another unexpected issues is that SDL_Renderer doesn't seem to support a projection matrix.
Multi-viewports uses absolute system coordinates for vertices so all rendering is mis-positionned (but ClipRect coordinates are correct). If I dig a little bit internally, I noticed RenderScale is applied on vertices in CPU manually, so I presume it might make sense if it evolve into a full-on projection matrix which can later be supported by GPU/shaders instead of processed on CPU.
Since so much is done on CPU (including SDL_RenderGeometryRaw()
calling SDL_RenderGeometryRawFloat()
) perhaps we can bite the bullet and perform the offset on our side, that would be a workaround but we're putting even more weight on CPU.
Seeing SDL3 progressing toward more general GPU support, I assumed SDL_Renderer was relying on this base already, but it's not since the earlier is not ready yet. So SDL_Renderer is still an API with a handful of CPU side transforms, checks etc. I would assume the natural path for SDL would be that once better GPU support is in, SDL_Renderer will take natural advantage of it, and then it might become trivial for them to add projection matrix etc. in which case it may make sense for them to extend their renderscale concept and add e.g. renderoffset, or a full blown matrix. I'll poke them about this.
Version/Branch of Dear ImGui:
Version: 1.88 Branch: docking
Back-end/Renderer/Compiler/OS
Back-ends: imgui_impl_sdl.cpp + imgui_implsdlrenderer.cpp Compiler: XXX (if the question is related to building or platform specific features)_ Operating System: Windows 11
My Issue/Question:
It seems there's no officially documented way to setup mult-viewports with SDL_Renderer. I am aware that I can enable multi-viewports with SDL + OpenGL3, but I not really good with OpenGL and yet I want the hardware acceleration, so SDL_Renderer API is the best choice for me right now. I also thought about just creating the renderer, but then I wasn't sure how to render the viewports because there isn't any way shown in https://github.com/ocornut/imgui/blob/docking/examples/example_sdl_sdlrenderer/main.cpp.
If there's a way to setup multi-viewports with SDL_Renderer, LMK!
Screenshots/Video
Standalone, minimal, complete and verifiable example: