How do renderers create textures

When talking about the UImGui::Texture class, renderers must create their own classes that derive from the UImGui::GenericTexture abstract class.

However, GenericTexture and GenericRenderer do not depend on each other. This produces following side effects:

  1. Custom renderers can decide whether to implement a texture backend(some applications might prefer their own custom abstractions)
  2. Custom renderers based on OpenGL, Vulkan and/or WebGPU can decide to reuse the existing texture creation workflows
  3. Custom renderers can easily mix and match texture and renderer backends

This is why in the RendererData struct one can see that there are 2 members of the RendererType enum. One for the renderer, and one for the texture backend.

By default the rendererType and the textureRendererType fields are set to be equal to each other. In order for your renderer to reuse one of the existing texture renderer backends use UImGui::Renderer::data().textureRendererType to set it to another type before creating your texture.

The GenericTexture class

The UImGui::GenericTexture abstract class looks like this:

class GenericTexture
{
public:
    // Event Safety - Any time
    virtual void init(TextureData& dt, String location, bool bFiltered) noexcept = 0;

    // Event Safety - Post-begin
    virtual uintptr_t get(TextureData& dt) noexcept = 0;

    // Event Safety - Post-begin
    virtual void load(TextureData& dt, void* data, FVector2 size, uint32_t depth, bool bFreeImageData,
                            const TFunction<void(void*)>& freeFunc) noexcept = 0;

    template<TextureFormat format>
    static bool saveToFile(TextureData& dt, const String location, const TextureFormat fmt = format, const uint8_t jpegQuality = 100);

    // Cleans up the image data
    // Event Safety - All initiated
    virtual void clear(TextureData& dt) noexcept = 0;
    virtual ~GenericTexture() noexcept = default;
protected:
    friend class Texture;

    static void beginLoad(TextureData& dt, void** data, FVector2& size) noexcept;
    static void endLoad(TextureData& dt, void* data, bool bFreeImageData, const TFunction<void(void*)>& freeFunc) noexcept;
    static void defaultInit(TextureData& dt, String location, bool bFiltered) noexcept;
    static void defaultClear(TextureData& dt) noexcept;
};

If you compare it to the normal UImGui::Texture class, you'll discover that most of the functions map 1:1, except that most functions in the GenericTexture class get a TextureData& as their first argument. This is to ensure that derivations based on GenericTexture are always code-only, since the same backend instance is used between all textures.

The TextureData struct looks like this:

struct TextureData
{
    String filename;
    FVector2 size;
    bool bFiltered;
    int channels;
    uintptr_t id;

    void* data;

    // This stores the string location for the internal C storage system
    size_t storageIndex;

    CustomSaveFunction customSaveFunction;

    // Data for each custom texture renderer backend
    void* context;
    size_t contextSize;
};

The context and contextSize members are used for storing any additional data that the custom texture backend instance may need.

All other members are part of what all textures store as their data. For more info, refer to the Textures entry.

Examples

Here is a pseudo-example that showcases how custom texture backends are generally implemented:

class PseudoTexture final : public UImGui::GenericTexture
{
public:
    virtual void init(UImGui::TextureData& dt, const UImGui::String location, const bool bFiltered) noexcept override
    {
        defaultInit(dt, location, bFiltered);
    }

    virtual void load(UImGui::TextureData& dt, void* data, UImGui::FVector2 size, uint32_t depth, bool bFreeImageData,
                            const TFunction<void(void*)>& freeFunc) noexcept override
    {
        beginLoad(dt, &data, size);
        // Load using your renderer API here
        endLoad(dt, data, bFreeImageData, freeFunc);
    }

    virtual uintptr_t get(UImGui::TextureData& dt) noexcept override
    {
        return dt.id;
    }

    virtual void clear(UImGui::TextureData& dt) noexcept override
    {
        // Clear with your renderer API here

        dt.id = 0;
        defaultClear(dt);
    }

    virtual ~TestOpenGLTexture() noexcept override = default;
};

To then use the PseudoTexture backend, create an instance of it and assign its address to the customTexture field of the InitInfo struct.

More examples can be found in the following documentation entry.

C API

The C API for the GenericTexture class looks like this:

UImGui_CGenericTexture* UImGui_CGenericTexture_make(
    UImGui_CGenericTexture_InitFun init,
    UImGui_CGenericTexture_GetFun get,
    UImGui_CGenericTexture_LoadFun load,
    UImGui_CGenericTexture_Clear clear
);

void UImGui_CGenericTexture_free(UImGui_CGenericTexture* texture);

As you can see, the callback functions provided to the UImGui_CGenericTexture_make() function are equivalent to the ones in the C++ class.

These function pointer types are implemented like this:

typedef void UImGui_CGenericTexture;
typedef void(*UImGui_CGenericTexture_VoidFun)(UImGui_TextureData*);

typedef void(*UImGui_CGenericTexture_InitFun)(UImGui_TextureData*, UImGui_String, bool);
typedef uintptr_t(*UImGui_CGenericTexture_GetFun)(UImGui_TextureData*);
typedef void(*UImGui_CGenericTexture_LoadFun)(UImGui_TextureData*, void*, UImGui_FVector2, uint32_t, bool);

typedef UImGui_CGenericTexture_VoidFun UImGui_CGenericTexture_Clear;

Event safety

All functions in the UImGui::GenericTexture class or C API are rated with the same event safety as their counterparts in the UImGui::Texture class.