Every renderer is based on the UImGui::GenericRenderer
abstract class. It looks like this:
class GenericRenderer
{
public:
() noexcept = default;
GenericRenderer
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;
};
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. For example, an
OpenGL renderer may want to enable MSAA using a line similar to this
glfwWindowHint(GLFW_SAMPLES, static_cast<int>(UImGui::Renderer::data().msaaSamples));
.
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:
virtual void setupPostWindowCreation() noexcept override
{
#if !__APPLE__
const int version = gladLoadGL(glfwGetProcAddress);
::log
Logger(
#ifdef __EMSCRIPTEN__
"Successfully loaded WebGL ",
#else
"Successfully loaded OpenGL ",
#endif
, GLAD_VERSION_MAJOR(version), ".", GLAD_VERSION_MINOR(version)
ULOG_LOG_TYPE_SUCCESS);
#endif
(UImGui_Renderer_data()->bUsingVSync);
glfwSwapInterval(GL_MULTISAMPLE);
glEnable(GL_DEPTH_TEST);
glEnable
const auto size = UImGui_Window_windowSize();
// Set viewport and global pointer to use in callbacks
(0, 0, CAST(int, size->x), CAST(int, size->y));
glViewport}
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::beginImGuiFrame();
RendererUtils}
Tip
For OpenGL renderers, it's important that you also call
glUseProgram(0)
like this:
void UImGui::OpenGLRenderer::ImGuiNewFrame() noexcept
{
();
ImGui_ImplOpenGL3_NewFrame::beginImGuiFrame();
RendererUtils(0);
glUseProgram}
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
{
.waitIdle();
device();
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
{
(Window::getInternal(), true);
ImGui_ImplGlfw_InitForOpenGL
#ifdef __EMSCRIPTEN__
(Window::getInternal(), "canvas");
ImGui_ImplGlfw_InstallEmscriptenCallbacks#endif
(UIMGUI_LATEST_GLSL_VERSION);
ImGui_ImplOpenGL3_Init}
WebGPU example:
void UImGui::WebGPURenderer::ImGuiInit() noexcept
{
(Window::getInternal(), true);
ImGui_ImplGlfw_InitForOther(Window::getInternal(), "canvas");
ImGui_ImplGlfw_InstallEmscriptenCallbacks}
Vulkan example:
void UImGui::WebGPURenderer::ImGuiInit() noexcept
{
(Window::getInternal(), true);
ImGui_ImplGlfw_InitForVulkan=
ImGui_ImplVulkan_InitInfo initInfo {
.Instance = instance->data(),
.PhysicalDevice = device->physicalDevice,
.Device = device->device,
.QueueFamily = CAST(uint32_t, device->indices.graphicsFamily),
.Queue = device->queue,
.DescriptorPool = device->descriptorPools.pool,
.RenderPass = window.RenderPass,
.MinImageCount = CAST(uint32_t, minimalImageCount),
.ImageCount = window.ImageCount,
.MSAASamples = CAST(VkSampleCountFlagBits, Renderer::data().msaaSamples),
.PipelineCache = VK_NULL_HANDLE,
.Subpass = 0,
.Allocator = nullptr,
.CheckVkResultFn = [](VkResult result) -> void
{
if (result != VK_SUCCESS)
{
::log("Dear imgui Vulkan rendering failure. Error code: ", ULOG_LOG_TYPE_ERROR, result);
Loggerstd::terminate();
}
}
};
(&initInfo);
ImGui_ImplVulkan_Init}
The ImGuiRenderData()
function
In this function you must call your platform backend-specific
RenderData
function. For example:
void UImGui::OpenGLRenderer::ImGuiRenderData() noexcept
{
(ImGui::GetDrawData());
ImGui_ImplOpenGL3_RenderDrawData}
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)(void);
typedef void(*UImGui_CGenericRenderer_TickEvent)(float);
* UImGui_CGenericRenderer_init(
UImGui_CGenericRenderer,
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);
void UImGui_CGenericRenderer_free(UImGui_CGenericRenderer* data);
As you can see the events are the same, the only difference is that you need to manage your memory yourself and your data for the renderer has to come through one of the different context pointers.
- Home
- Beginner content
- Install guide
- Creating and using the UI components
- The Instance
- The Init Info struct
- 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
- Developer and contributor resources
- Misc