Replacement for tgui::Canvas in SDL backend

Started by provener, 09 March 2021, 01:48:48

provener

Hello,

I'm migrating an app from SFML to SDL, and I'd very much like to keep my TGUI interfaces. The primary layout for my app is a set of panels on one side of the screen, with a tgui::Canvas on the other side for drawing sf::Drawables:

Code (cpp) Select
  auto main_hud = tgui::HorizontalLayout::create();
  // add side panels...
  this.primary_canvas_ = tgui::Canvas::create();
  main_hud->add(primary_canvas_, 1.0f);
  main_hud->add(side_panels, 0.8f);


In TGUI 0.9.1, it appears as if tgui::Canvas is hidden behind ifdef guards for the SFML backend exclusively.

What is the proper way, with the SDL backend, to reserve an area of the GUI window for directly rendering, say, SDL_Textures to? And additionally, is it possible to have multiple such areas of the GUI window?

Cheers!

texus

What kind of things do you need to draw on the canvas?

QuoteWhat is the proper way, with the SDL backend, to reserve an area of the GUI window for directly rendering, say, SDL_Textures to?
This is currently unsupported in the SDL backend. The canvas code is behind and ifdef guard because it didn't get ported.

The main problem that I had was not knowing how people were going to use it.
The SDL/OpenGL backend doesn't use SDL_Texture, so that couldn't really be used. The canvas would have to use opengl code, but I don't have enough experience with it to assure that your own opengl code wouldn't conflict with the TGUI code.

The only workaround that I know if performance isn't an issue is to somehow get what you want to render into an image. You can create a tgui::Texture and call loadFromPixelData and then set that texture in a tgui::Picture widget.

The SDL renderer itself was too limited to port TGUI to, which is why I had to use opengl. But that probably also means that your code can't use SDL_CreateRenderer and simple SDL rendering functions.
So I'm not sure how to best solve this situation either.

provener

I was using canvas because it seemed like the correct way to use the layout engine to separate your screen into resize-/scale-friendly panels, and then draw all application-related sprites/textures to it. It may be the case that all I need to do is use the layout engine, have it set up a horizontal panel on the side, and draw things directly to the screen in whatever way the API expects (passing sf::Drawables to Canvas#draw for SFML, or SDL_RenderCopy for SDL), in the "left-over" space that is not taken by the GUI.

What makes it awkward without the notion of a canvas is if I have two areas on screen that I want to directly render to, one of which is contained within the layout of a GUI, I will have to keep track of the location of the individual GUI widgets in order to render textures at the right coordinate positions. Does that make sense?

texus

I think I understand what you are trying to do, and you would need a Canvas if you don't want to manually keep track of gui widgets coordinates.

The problem is that what you are describing technically can't work correctly as far as I know. Are you already rendering with SDL while also drawing with TGUI or is this something you plan on doing? From what I read, you can't use an SDL_Renderer and OpenGL context on the same window without messing up the rendering (and TGUI requires an OpenGL context on the window). So even without the Canvas widget, manually drawing stuff with SDL leads to undefined behavior (such as rectangles being drawn instead of text characters).

The only way you would be able to manually draw with SDL_Renderer without causing a rendering conflict is if TGUI's SDL backend would use an SDL_Renderer instead of OpenGL for drawing (but I can't do that because SDL_Renderer can't render triangles).

provener

I gotcha. Thank you for the context (hah).

I haven't even gotten as far as to tear out SFML in my GUI code yet, so no actual prototypes with TGUI/SDL, but it seems like I'd end up at the impasse you described above, especially since I'm attempting to use OS-exclusive renderers where I can (i.e. Metal for macOS and Direct3D11 for Windows).

Thank you again!

texus

#5
Quoteespecially since I'm attempting to use OS-exclusive renderers where I can (i.e. Metal for macOS and Direct3D11 for Windows)
TGUI has to use the same renderer as your code, so if you were to use these renderers in your code directly then you would have to write a TGUI backend for each platform to do so.

You can rely on SDL_Renderer to abstract the backends so that you only need to use SDL_Renderer and don't have to bother with whether it uses metal or directx internally, but SDL_Renderer has its limits (it can't even draw a simple filled triangle or circle). When I started porting TGUI from SFML to SDL I started with the SDL_Renderer, until I encountered things that I simply couldn't do with it and learned that many projects don't use SDL_Renderer and instead use their own rendering code (which makes it very difficult to have the same rendering code in TGUI as in user code, hence why I skipped adding the Canvas class).

Using OS-exclusive renderers isn't always the best approach either. According to https://stackoverflow.com/a/59725539, the Metal backend used by SDL_Renderer is even slower than the OpenGL backend on macOS (unless that changed within the last year).

If you wouldn't care about OS-exclusive renderers then perhaps I could consider creating a Canvas widget where you can render simple text and images to (via functions on the Canvas object, e.g. it would have a drawText function). Then you would use TGUI for rendering them instead of accessing SDL directly. It would be a very limited interface and it would require using OpenGL on all platforms though.

Technically TGUI could be made compatible with SDL_Renderer (by adding a new backend for it), but it would have to result to software rendering for drawing triangles, which would make it very slow.

So depending on what you need, TGUI might not work for you.

provener

Sorry, this is all pretty new to me. On the surface level it seemed like SDL was this API-agnostic layer on top of any renderer that was appropriate for the platform, but it seems like most SDL programs just assume OpenGL is the rendererĂ¢â‚¬"not just TGUI but others. So I kind of thought that with the move from SFML to SDL2 I was gaining access to a broader amount of libraries guaranteed to work cross-platform. But it really does seem like most of the ecosystem is just SDL2 with the expectation that OpenGL is available. It's pretty confusing for someone who hasn't really worked with these kinds of libraries before.

texus

For anyone that sees this in the future: the situation is now completely different.

SDL 2.0.17 has added a SDL_RenderGeometry function which makes it possible to use the SDL_Renderer for all rendering instead of requiring OpenGL directly.

TGUI 0.10-dev now also has an SDL_RENDERER backend which makes use of this functionality. A CanvasSDL class has just been added to this backend to which you could draw SDL_Texture objects:
Code (cpp) Select

SDL_Texture* imgTexture;  // The image to render to the canvas

auto canvas = tgui::CanvasSDL::create({400, 300});

SDL_SetRenderTarget(renderer, canvas->getTextureTarget());  // Let drawing happen on the canvas instead of the window
SDL_RenderClear(renderer);                                  // Clear the contents of the canvas
SDL_RenderCopy(renderer, imgTexture, NULL, NULL);           // Draw an image to the canvas
SDL_SetRenderTarget(renderer, nullptr);                     // Let further drawing happen on the window again