texus / TGUI

Cross-platform modern c++ GUI

Home Page:https://tgui.eu

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Raylib backend?

ethindp opened this issue · comments

I've no idea how this could be done, but would it be possible to make a backend for Raylib? If the backend requires control over the window handle while TGUI is in use this might not be feasible (unless you want to use the native window handle). On the other hand, if control over the window handle isn't required and all the backend does is draw things, this would be relatively simple. In this scenario, a window backend wouldn't be necessary, only a renderer backend to make calls to Raylib.

No control over the window is needed by TGUI, but the window backend needs to pass events to TGUI (e.g. key presses and mouse clicks). So you would still need to create all three parts of the backend: window handling, font loading and rendering. Nothing can be reused from the other backends. If you can get events from the raylib window and you can draw triangles with it then it can probably be done.

The one thing that I'm not certain about is font loading: TGUI expects that the backend can load individual font glyphs and put them in a texture so that it can render all text at once using that texture. If it can't do that then it would make implementing the backend a lot more complicated. It would still be possible, but you would also need to re-implement text rendering in the backend (while all existing backends share the same code for that).

To get an idea of the amount of work, you would need to copy and the rewrite the SDL-Renderer.cpp, Font/SDL_ttf/*.cpp, Renderer/SDL_Renderer/*.cpp and Window/SDL/*.cpp files that are found inside the src/Backend folder.

To address each of your points:

  1. Events are easily done via GetKey{Pressed|Released|...}/GetMouseX|.../touch functions
  2. Font loading might be tricky. Raylib has a few functions related to fonts: GetFontDefault, LoadFont, LoadFontEx, LoadFontFromImage, LoadFontFromMemory, and LoadFontData, as well as the respective UnloadFontData and UnloadFont. I believe the way it works is that you call one of the aforementioned functions to load the font, then use the following for drawing text:
RLAPI void DrawTextEx(Font font, const char *text, Vector2 position, float fontSize, float spacing, Color tint); // Draw text using font and additional parameters
RLAPI void DrawTextPro(Font font, const char *text, Vector2 position, Vector2 origin, float rotation, float fontSize, float spacing, Color tint); // Draw text using Font and pro parameters (rotation)
RLAPI void DrawTextCodepoint(Font font, int codepoint, Vector2 position, float fontSize, Color tint); // Draw one character (codepoint)
RLAPI void DrawTextCodepoints(Font font, const int *codepoints, int codepointCount, Vector2 position, float fontSize, float spacing, Color tint); // Draw multiple character (codepoint)

// Text font info functions
RLAPI void SetTextLineSpacing(int spacing);                                                 // Set vertical line spacing when drawing with line-breaks
RLAPI int MeasureText(const char *text, int fontSize);                                      // Measure string width for default font
RLAPI Vector2 MeasureTextEx(Font font, const char *text, float fontSize, float spacing);    // Measure string size for Font
RLAPI int GetGlyphIndex(Font font, int codepoint);                                          // Get glyph index position in font for a codepoint (unicode character), fallback to '?' if not found
RLAPI GlyphInfo GetGlyphInfo(Font font, int codepoint);                                     // Get glyph font info data for a codepoint (unicode character), fallback to '?' if not found
RLAPI Rectangle GetGlyphAtlasRec(Font font, int codepoint);                                 // Get glyph rectangle in font atlas for a codepoint (unicode character), fallback to '?' if not found

Drawing triangles is easy:

RLAPI void DrawTriangle(Vector2 v1, Vector2 v2, Vector2 v3, Color color);                                // Draw a color-filled triangle (vertex in counter-clockwise order!)
RLAPI void DrawTriangleLines(Vector2 v1, Vector2 v2, Vector2 v3, Color color);                           // Draw triangle outline (vertex in counter-clockwise order!)
RLAPI void DrawTriangleFan(Vector2 *points, int pointCount, Color color);                                // Draw a triangle fan defined by points (first vertex is the center)
RLAPI void DrawTriangleStrip(Vector2 *points, int pointCount, Color color);                              // Draw a triangle strip defined by points

There are a bunch of other shapes too. Would it be better to just take the existing SDL renderer, keep all the functions but modify them to use Raylib as a starting point?

Would it be better to just take the existing SDL renderer, keep all the functions but modify them to use Raylib as a starting point?

Yes. If you were to create new files then you would also need to update the cmake scripts, so it might be easier to just replace the existing code and worry about the correct project structure later (which is something that I would be willing to do if you really manage to write a working raylib backend).

Font loading might be tricky

Yeah, I would start with trying to implement src/Backend/Font/SDL_ttf/BackendFontSDLttf.cpp with raylib, as that file will be the hardest to do. That way you would know very early on whether or not you can do it and don't waste time implementing everything else first.

The GetGlyphAtlasRec seems to give you the glyph information needed by getGlyph. Is there also a function that retrieves the texture atlas inside the font? Most of the code in the backend font is for creating a texture atlas inside TGUI, but if raylib gives you access to its own texture atlas then you might be able to remove almost all code. You would then just need to convert the texture to a TGUI texture (once when creating the font) and let getTexture return that cached texture.

Can raylib render to a texture instead of to the screen? I just looked at the SDL code and noticed that it also doesn't really provide access to the glyph pixels. Instead it renders a single glyph to a texture and then copies the pixels of that texture. So if rendering to an image is possible then it should be possible to implement the font backend without needing access to the glyph atlas in the raylib font.

Yeah so the Raylib backend is going to be interesting. Mainly because of the following reasons:

  • Everything is global state: there isn't a renderer or window you pass around. So the pattern that exists now where you pass in windows/renderers can pretty much be dispensed with, though figuring out how to modify the code to take this into account is something I'm unsure of. But maybe I'm overthinking it?
  • Raylib uses it's own font functions and such, so we wouldn't use freetype (though I suppose you could get Freetype to work... I have no idea how that'd be accomplished though).

I might be overthinking/over-complicating things. You can take a look at the full Raylib API if you want an idea of how the library works.

Can you have multiple windows with Raylib? If so, then how do you specify which one you are rendering to?

It's fine to assume that there will only be a single window and thus also a single TGUI renderer. So the window and rendering backends simply don't need to store a state and can just call the global functions.

FreeType is not a requirement. Neither the SDL_ttf nor SFML-Graphics font backends use freetype directly. You just need to be able to render a single character to an image and read the pixels from it. Would "ImageText" do this?

I noticed that the Font class does provide access to the texture atlas, which would simplify the font backend significantly (as we would be able to use their atlas instead of creating our own). However the text rendering code currently assumes a hardcoded pixel in the atlas to render the line underneath underlined text, which wouldn't exist in their atlas. So this is a path that I might look into and something that you can ignore.

If you can't figure it out then maybe this weekend I'll have a look at it myself.

@texus I don't think you can have multiple windows. The InitWindowfunction returnsvoid`, but since GLFW/SDL is being used under the hood, and both require storing window state somewhere, I'm guessing there's static storage or something similar going on.

I think I can make it work, but raylib is a lot more basic than I initially thought. It doesn't support kerning and doesn't even support text styles like bold, italic and underline?

Wow I didn't know that....

Text looks worse than with other backends, I faked bold text by rendering a second time with 1px shifted to the right and I needed several other hacks to make it work, but overall I'm pretty happy to see that text rendering with raylib is functional:
image

The window and actual rendering were still done with SFML, as I only wrote the BackendFont implementation so far.

I thought the next steps would be easier, but I'm already starting to seriously doubt it. When I asked whether raylib could render triangles, I didn't actually mean normal triangles, and those DrawTriangle functions aren't what I need (which I'm only realizing now). I need a draw call that can render many vertices at once, but with a texture attached to those triangles. I think that raylib supports this, because what I need seems to be part of the Mesh struct, but meshes provide a lot more functionality than what I need, so it will be more complex to actually get it working like I want it.

Edit: I've committed the code that I have so far in the 1.x-WIP-raylib branch

An alternative solution is to pull in rlgl.h, it contains bindings to OpenGL so if all else fails you could do rendering via a modified OpenGL renderer?

There is still work to do that will happen during the next week, like allowing the gui builder to be build when the raylib backend is selected and actually testing all the new code, but the raylib backend itself has been fully implemented in the 1.x-WIP-raylib branch.

I ended up needing rlgl.h, but only to temporarily disable backface culling while rendering.

Oh very nice! I'm sorry I couldn't be of more help. :-(

I'd be happy to try to test the renderer, maybe by following up on the accessibility issue I opened and seeing how far I can get with that maybe?

Feel free to already test the code in the 1.x-WIP-raylib branch. It should be almost finished now.

There are still 2 things remaining that I need to fix before merging the code in the 1.x branch:

  • Rendered text has a different size compared to other backends, which is causing some tests to fail (e.g. clicking and dragging in edit box on hardcoded positions now selects different characters). Given that raylib doesn't support kerning, this is probably unfixable and I'll just have to disable these tests.
  • I haven't been able to test on Windows yet. I expect it to work fine though.

When building TGUI with CMake, you can set TGUI_BACKEND=RAYLIB to use the raylib backend. Depending on how you are using it, you may also need to set raylib_ROOT to the install location of raylib, raylib_INCLUDE_DIR to the include directory and raylib_LIBRARY to the library.

Here is the example code that you can start with to test the raylib backend:

#include <TGUI/TGUI.hpp>
#include <TGUI/Backend/raylib.hpp>

void run_application() // We don't put this code in main() to make sure that all TGUI resources are destroyed before destroying raylib
{
    tgui::Gui gui;
    while (!WindowShouldClose())
    {
        gui.handleEvents(); // Handles all non-keyboard events

        int pressedChar = GetCharPressed();
        while (pressedChar)
        {
            gui.handleCharPressed(pressedChar);
            pressedChar = GetCharPressed();
        }

        int pressedKey = GetKeyPressed();
        while (pressedKey)
        {
            gui.handleKeyPressed(pressedKey);
            pressedKey = GetKeyPressed();
        }

        BeginDrawing();
        ClearBackground({240, 240, 240, 255});
        gui.draw();
        EndDrawing();
    }
}

int main()
{
    SetTraceLogLevel(LOG_WARNING);

    InitWindow(800, 600, "TGUI example (RAYLIB)");
    SetTargetFPS(30);

    run_application();

    CloseWindow();
}

The raylib backend has been merged into 1.x