Every renderer is based on the UImGui::GenericRenderer
abstract class. It looks like this:
class GenericRenderer
{
public:
GenericRenderer() noexcept = default;
virtual void parseCustomConfig(YAML::Node& config) noexcept = 0;
virtual void setupWindowIntegration() noexcept = 0;
virtual void setupPostWindowCreation() noexcept = 0;
virtual void init(RendererInternalMetadata& metadata) noexcept = 0;
virtual void renderStart(double deltaTime) noexcept = 0;
virtual void renderEnd(double deltaTime) noexcept = 0;
virtual void destroy() noexcept = 0;
virtual void ImGuiNewFrame() noexcept = 0;
virtual void ImGuiShutdown() noexcept = 0;
virtual void ImGuiInit() noexcept = 0;
virtual void ImGuiRenderData() noexcept = 0;
virtual void waitOnGPU() noexcept = 0;
virtual ~GenericRenderer() noexcept = default;
RendererAPITypeHint type;
};Construction
In the constructor, it's important to set your renderer API type hint
that is stored in the type variable which is of the enum
type of RendererAPITypeHint, which looks like this:
enum RendererAPITypeHint
{
UIMGUI_RENDERER_API_TYPE_HINT_OPENGL,
UIMGUI_RENDERER_API_TYPE_HINT_VULKAN,
UIMGUI_RENDERER_API_TYPE_HINT_WEBGPU,
UIMGUI_RENDERER_API_TYPE_HINT_METAL,
UIMGUI_RENDERER_API_TYPE_HINT_D3D,
UIMGUI_RENDERER_API_TYPE_HINT_OTHER
};Tip
These hints are not necessary for your renderer to work by default. If using a custom window backend, they could be used by it for some of its initialisation steps. We recommend that you still set them in case we also start using them.
The
parseCustomConfig() function
The parseCustomConfig() function is called during the
loading of Config/Core/Renderer.yaml and it gives you
access to a YAML::Node& to the
custom-renderer key in Renderer.yaml. In C++
you can use the bundled yaml-cpp library to parse
it.
Note
This function is not available in the C API. Config parsing and saving has to be done manually with your own library there.
The
setupWindowIntegration() function
This function is called before the window is created. Renderers
should set up their window hints there. For example, an OpenGL renderer
may call UImGui::RendererUtils::OpenGL::setHints() or
another renderer may call
UImGui::RendererUtils::setupManually().
You can also set up any additional raw hints here.
Tip
This is also a good place to set your texture renderer type using a
line like
UImGui::Renderer::data()->textureRendererType = UIMGUI_RENDERER_TYPE_CUSTOM;
The
setupPostWindowCreation function
This function is called after the window is created and is mainly used for OpenGL renderers, because in OpenGL the context needs to be created and set up as part of the window creation process. For example, an OpenGL renderer might have code similar to this there:
void UImGui::OpenGLRenderer::setupPostWindowCreation() noexcept
{
RendererUtils::OpenGL::setCurrentContext(RendererUtils::OpenGL::createContext());
RendererUtils::OpenGL::setSwapInterval(Renderer::data().bUsingVSync);
#if !__APPLE__
const int version = gladLoadGL(RendererUtils::OpenGL::getProcAddressFunction());
Logger::log
(
#ifdef __EMSCRIPTEN__
"Successfully loaded WebGL ",
#else
"Successfully loaded OpenGL ",
#endif
ULOG_LOG_TYPE_SUCCESS, GLAD_VERSION_MAJOR(version), ".", GLAD_VERSION_MINOR(version)
);
#endif
glEnable(GL_MULTISAMPLE);
glEnable(GL_DEPTH_TEST);
const auto size = Window::getWindowSize();
// Set viewport and global pointer to use in callbacks
glViewport(0, 0, CAST(int, size.x), CAST(int, size.y));
}The init() function
The init() function is after a renderer is set up and
initialised with a window. Here a renderer also has access to a
UImGui::RendererInternalMetadata& structure which
allows the developer to set the platform strings from the API. The
structure looks like this:
struct RendererInternalMetadata
{
FString vendorString;
FString apiVersion;
FString driverVersion;
FString gpuName;
};As you can see, the strings map 1:1 to the platform strings you get immutable access to from the Renderer interface.
In the C API, setting these strings is part of the Renderer interface. The functions are as follows:
void UImGui_RendererInternalMetadata_setVendorString(UImGui_String str);
void UImGui_RendererInternalMetadata_setApiVersion(UImGui_String str);
void UImGui_RendererInternalMetadata_setDriverVersion(UImGui_String str);
void UImGui_RendererInternalMetadata_setGPUName(UImGui_String str);The renderStart()
function
This function is called at the beginning of each render loop
iteration. It receives a float deltaTime argument.
In our example code, this function is mainly used in the OpenGL renderer to set the clear colour and in the WebGPU renderer to do surface resize checks
The renderEnd()
function
This function is called at the end of each render loop iteration. It
receives a float deltaTime argument.
It's recommended that you do your framebuffer swapping here
The destroy() function
This function is called when the renderer instance is destroyed after the framework has exited past the render loop.
The ImGuiNewFrame()
function
This function is called every frame and is used to tell dear imgui to
start constructing a new frame. You need to call your dear imgui's
platform backend's specific NewFrame function and then call
RendererUtils::beginImGuiFrame();.
For example:
void UImGui::VulkanRenderer::ImGuiNewFrame() noexcept
{
ImGui_ImplVulkan_NewFrame();
RendererUtils::beginImGuiFrame();
}Tip
For OpenGL renderers, it's important that you also call
glUseProgram(0) like this:
void UImGui::OpenGLRenderer::ImGuiNewFrame() noexcept
{
ImGui_ImplOpenGL3_NewFrame();
RendererUtils::beginImGuiFrame();
glUseProgram(0);
}The ImGuiShutdown()
function
Here you can call your platform backend-specific dear imgui
Shutdown function. For example:
void UImGui::OpenGLRenderer::ImGuiShutdown() noexcept
{
ImGui_ImplOpenGL3_Shutdown();
}Tip
For Vulkan renderers, always wait on your device before calling
ImGui_ImplVulkan_Shutdown(), for example:
void UImGui::VulkanRenderer::ImGuiShutdown() noexcept
{
device.waitIdle();
ImGui_ImplVulkan_Shutdown();
}The ImGuiInit()
function
In this function, you need to initialise your imgui platform backend-specific function.
OpenGL example:
void UImGui::OpenGLRenderer::ImGuiInit() noexcept
{
RendererUtils::OpenGL::ImGuiInit();
RendererUtils::ImGuiInstallCallbacks();
ImGui_ImplOpenGL3_Init(UIMGUI_LATEST_GLSL_VERSION);
}WebGPU example:
void UImGui::WebGPURenderer::ImGuiInit() noexcept
{
RendererUtils::ImGuiInitOther();
RendererUtils::ImGuiInstallCallbacks();
ImGui_ImplWGPU_InitInfo initInfo{};
initInfo.Device = device;
initInfo.NumFramesInFlight = 3;
initInfo.RenderTargetFormat = preferredFormat;
initInfo.DepthStencilFormat = WGPUTextureFormat_Undefined;
initInfo.PipelineMultisampleState.count = static_cast<uint32_t>(Renderer::data().msaaSamples > 1 ? 4 : 1);
ImGui_ImplWGPU_Init(&initInfo);
}Vulkan example:
void UImGui::WebGPURenderer::ImGuiInit() noexcept
{
RendererUtils::Vulkan::ImGuiInit();
ImGui_ImplVulkan_InitInfo initInfo =
{
.ApiVersion = VK_API_VERSION_1_4,
.Instance = instance->data(),
.PhysicalDevice = device->physicalDevice,
.Device = device->device,
.QueueFamily = CAST(uint32_t, device->indices.graphicsFamily),
.Queue = device->queue,
.DescriptorPool = device->descriptorPools.pool,
.MinImageCount = CAST(uint32_t, minimalImageCount),
.ImageCount = window.ImageCount,
.PipelineCache = VK_NULL_HANDLE,
.RenderPass = window.RenderPass,
.Subpass = 0,
.MSAASamples = CAST(VkSampleCountFlagBits, Renderer::data().msaaSamples),
.Allocator = nullptr,
.CheckVkResultFn = [](VkResult result) -> void
{
if (result != VK_SUCCESS)
{
Logger::log("Dear imgui Vulkan rendering failure. Error code: ", ULOG_LOG_TYPE_ERROR, result);
std::terminate();
}
}
};
ImGui_ImplVulkan_Init(&initInfo);
}The ImGuiRenderData()
function
In this function you must call your platform backend-specific
RenderData function. For example:
void UImGui::OpenGLRenderer::ImGuiRenderData() noexcept
{
ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
}The waitOnGPU()
function
This function is only used for low level APIs like Vulkan, where one needs to do waiting on the GPU manually. This function is used when clearing textures or when destroying the renderer. Most graphics APIs will not need to write anything here.
Registering your renderer
Now that you have your renderer done, you need to register it with the framework so that you can use it.
To register it, simply create an instance of its derived class and
assign your Instance's InitInfo struct's
customRenderer member to its address.
C API
The C API for custom renderers supports the same events. Registration
is done through the same customRenderer field of the
CInitInfo struct.
Custom renderers in the C API do not have access to parsing the
custom-renderer YAML field in
Config/Core/Renderer.yaml.
The CGenericRenderer is implemented like this:
typedef void(*UImGui_CGenericRenderer_VoidVoidFun)(UImGui_CGenericRenderer_InitInfo*);
typedef void(*UImGui_CGenericRenderer_TickEvent)(UImGui_CGenericRenderer_InitInfo*, float);
struct UImGui_CGenericRenderer_InitInfo
{
UImGui_CGenericRenderer_VoidVoidFun setupWindowIntegration;
UImGui_CGenericRenderer_VoidVoidFun setupPostWindowIntegration;
UImGui_CGenericRenderer_VoidVoidFun init;
UImGui_CGenericRenderer_TickEvent renderStart;
UImGui_CGenericRenderer_TickEvent renderEnd;
UImGui_CGenericRenderer_VoidVoidFun destroy;
UImGui_CGenericRenderer_VoidVoidFun ImGuiNewFrame;
UImGui_CGenericRenderer_VoidVoidFun ImGuiShutdown;
UImGui_CGenericRenderer_VoidVoidFun ImGuiInit;
UImGui_CGenericRenderer_VoidVoidFun ImGuiRenderData;
UImGui_CGenericRenderer_VoidVoidFun waitOnGPU;
UImGui_CGenericRenderer_VoidVoidFun destruct;
UImGui_RendererAPITypeHint rendererAPI;
void* context;
size_t contextSize;
// Do not touch
UImGui_CGenericRenderer* instance;
};
void UImGui_CGenericRenderer_init(UImGui_CGenericRenderer_InitInfo* initInfo);
void UImGui_CGenericRenderer_free(const UImGui_CGenericRenderer_InitInfo* instance);To create an instance of your custom renderer create an instance of
UImGui_CGenericRenderer_InitInfo and fill it out.
The events are the same, except that they take a pointer to your
instance of UImGui_CGenericRenderer_InitInfo as their first
argument. This allows you to reference any of your own additional
context data inside the callback functions.
The context and contextSize variables are
optional so they can be left null.
The instance variable is internal and represents the
internal GenericRenderer instance. This is only used when
setting the customRenderer field of the
CInitInfo struct.
- Home
- Beginner content
- Install guide
- Creating and using the UI components
- The Instance
- The Init Info struct
- Building better titlebar menus
- Textures
- Logging
- Unicode support
- Additional features
- Client-side bar
- Custom type definitions
- Memory management
- C API development
- Config files and Folders
- Interfaces
- Internal Event safety
- Customising the build system
- Modules system
- Collaborating with others
- Advanced content
- Loading dynamic libraries at runtime
- Understanding the library layout
- Compilation mode modifiers
- Supporting plugins
- Production export and deployment
- OS integration tips
- Targeting WASM
- Using a custom rendering engine:
- Using a custom windowing backend:
- Developer and contributor resources
- Misc