2023-05-30 20:36:07 +02:00
// dear imgui: Renderer Backend for SDL_Renderer for SDL3
2023-05-30 20:30:30 +02:00
// (Requires: SDL 3.0.0+)
2024-09-03 19:05:32 +02:00
// (**IMPORTANT: SDL 3.0.0 is NOT YET RELEASED AND CURRENTLY HAS A FAST CHANGING API. THIS CODE BREAKS OFTEN AS SDL3 CHANGES.**)
2023-05-30 20:36:07 +02:00
// Note how SDL_Renderer is an _optional_ component of SDL3.
2023-05-30 20:30:30 +02:00
// For a multi-platform app consider using e.g. SDL+DirectX on Windows and SDL+OpenGL on Linux/OSX.
// If your application will want to render any non trivial amount of graphics other than UI,
2023-05-30 20:36:07 +02:00
// please be aware that SDL_Renderer currently offers a limited graphic API to the end-user and
// it might be difficult to step out of those boundaries.
2023-05-30 20:30:30 +02:00
// Implemented features:
// [X] Renderer: User texture binding. Use 'SDL_Texture*' as ImTextureID. Read the FAQ about ImTextureID!
// [X] Renderer: Large meshes support (64k+ vertices) with 16-bit indices.
2024-10-09 16:39:28 +02:00
// [X] Renderer: Expose selected render state for draw callbacks to use. Access in '(ImGui_ImplXXXX_RenderState*)GetPlatformIO().Renderer_RenderState'.
2023-05-30 20:30:30 +02:00
// You can copy and use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
2023-09-11 13:47:08 +02:00
// Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
// Learn about Dear ImGui:
// - FAQ https://dearimgui.com/faq
// - Getting Started https://dearimgui.com/getting-started
// - Documentation https://dearimgui.com/docs (same as your local docs/ folder).
// - Introduction, links and more at the top of imgui.cpp
2023-05-30 20:30:30 +02:00
// CHANGELOG
2024-10-09 16:39:28 +02:00
// 2024-10-09: Expose selected render state in ImGui_ImplSDLRenderer3_RenderState, which you can access in 'void* platform_io.Renderer_RenderState' during draw callbacks.
2024-07-01 12:04:36 +02:00
// 2024-07-01: Update for SDL3 api changes: SDL_RenderGeometryRaw() uint32 version was removed (SDL#9009).
2024-05-14 19:10:31 +02:00
// 2024-05-14: *BREAKING CHANGE* ImGui_ImplSDLRenderer3_RenderDrawData() requires SDL_Renderer* passed as parameter.
2024-02-12 14:57:39 +01:00
// 2024-02-12: Amend to query SDL_RenderViewportSet() and restore viewport accordingly.
2023-05-30 20:36:07 +02:00
// 2023-05-30: Initial version.
2023-05-30 20:30:30 +02:00
# include "imgui.h"
2023-07-13 11:27:52 +02:00
# ifndef IMGUI_DISABLE
2023-05-30 20:30:30 +02:00
# include "imgui_impl_sdlrenderer3.h"
# include <stdint.h> // intptr_t
// Clang warnings with -Weverything
# if defined(__clang__)
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Wsign-conversion" // warning: implicit conversion changes signedness
# endif
// SDL
# include <SDL3/SDL.h>
# if !SDL_VERSION_ATLEAST(3,0,0)
# error This backend requires SDL 3.0.0+
# endif
// SDL_Renderer data
2023-05-30 20:36:07 +02:00
struct ImGui_ImplSDLRenderer3_Data
2023-05-30 20:30:30 +02:00
{
2024-07-01 12:04:36 +02:00
SDL_Renderer * Renderer ; // Main viewport's renderer
SDL_Texture * FontTexture ;
ImVector < SDL_FColor > ColorBuffer ;
2024-05-14 19:10:31 +02:00
ImGui_ImplSDLRenderer3_Data ( ) { memset ( ( void * ) this , 0 , sizeof ( * this ) ) ; }
2023-05-30 20:30:30 +02:00
} ;
// Backend data stored in io.BackendRendererUserData to allow support for multiple Dear ImGui contexts
// It is STRONGLY preferred that you use docking branch with multi-viewports (== single Dear ImGui context + multiple windows) instead of multiple Dear ImGui contexts.
2023-05-30 20:36:07 +02:00
static ImGui_ImplSDLRenderer3_Data * ImGui_ImplSDLRenderer3_GetBackendData ( )
2023-05-30 20:30:30 +02:00
{
2023-05-30 20:36:07 +02:00
return ImGui : : GetCurrentContext ( ) ? ( ImGui_ImplSDLRenderer3_Data * ) ImGui : : GetIO ( ) . BackendRendererUserData : nullptr ;
2023-05-30 20:30:30 +02:00
}
// Functions
2023-05-30 20:36:07 +02:00
bool ImGui_ImplSDLRenderer3_Init ( SDL_Renderer * renderer )
2023-05-30 20:30:30 +02:00
{
ImGuiIO & io = ImGui : : GetIO ( ) ;
2024-05-13 15:07:13 +02:00
IMGUI_CHECKVERSION ( ) ;
2023-05-30 20:30:30 +02:00
IM_ASSERT ( io . BackendRendererUserData = = nullptr & & " Already initialized a renderer backend! " ) ;
IM_ASSERT ( renderer ! = nullptr & & " SDL_Renderer not initialized! " ) ;
// Setup backend capabilities flags
2023-05-30 20:36:07 +02:00
ImGui_ImplSDLRenderer3_Data * bd = IM_NEW ( ImGui_ImplSDLRenderer3_Data ) ( ) ;
2023-05-30 20:30:30 +02:00
io . BackendRendererUserData = ( void * ) bd ;
2023-05-30 20:36:07 +02:00
io . BackendRendererName = " imgui_impl_sdlrenderer3 " ;
2023-05-30 20:30:30 +02:00
io . BackendFlags | = ImGuiBackendFlags_RendererHasVtxOffset ; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
2024-05-14 19:10:31 +02:00
bd - > Renderer = renderer ;
2023-05-30 20:30:30 +02:00
return true ;
}
2023-05-30 20:36:07 +02:00
void ImGui_ImplSDLRenderer3_Shutdown ( )
2023-05-30 20:30:30 +02:00
{
2023-05-30 20:36:07 +02:00
ImGui_ImplSDLRenderer3_Data * bd = ImGui_ImplSDLRenderer3_GetBackendData ( ) ;
2023-05-30 20:30:30 +02:00
IM_ASSERT ( bd ! = nullptr & & " No renderer backend to shutdown, or already shutdown? " ) ;
ImGuiIO & io = ImGui : : GetIO ( ) ;
2023-05-30 20:36:07 +02:00
ImGui_ImplSDLRenderer3_DestroyDeviceObjects ( ) ;
2023-05-30 20:30:30 +02:00
io . BackendRendererName = nullptr ;
io . BackendRendererUserData = nullptr ;
2023-05-30 20:36:07 +02:00
io . BackendFlags & = ~ ImGuiBackendFlags_RendererHasVtxOffset ;
2023-05-30 20:30:30 +02:00
IM_DELETE ( bd ) ;
}
2024-05-14 19:10:31 +02:00
static void ImGui_ImplSDLRenderer3_SetupRenderState ( SDL_Renderer * renderer )
2023-05-30 20:30:30 +02:00
{
// Clear out any viewports and cliprect set by the user
// FIXME: Technically speaking there are lots of other things we could backup/setup/restore during our render process.
2024-05-14 19:10:31 +02:00
SDL_SetRenderViewport ( renderer , nullptr ) ;
SDL_SetRenderClipRect ( renderer , nullptr ) ;
2023-05-30 20:30:30 +02:00
}
2023-05-30 20:36:07 +02:00
void ImGui_ImplSDLRenderer3_NewFrame ( )
2023-05-30 20:30:30 +02:00
{
2023-05-30 20:36:07 +02:00
ImGui_ImplSDLRenderer3_Data * bd = ImGui_ImplSDLRenderer3_GetBackendData ( ) ;
2024-05-07 16:53:03 +02:00
IM_ASSERT ( bd ! = nullptr & & " Context or backend not initialized! Did you call ImGui_ImplSDLRenderer3_Init()? " ) ;
2023-05-30 20:30:30 +02:00
if ( ! bd - > FontTexture )
2023-05-30 20:36:07 +02:00
ImGui_ImplSDLRenderer3_CreateDeviceObjects ( ) ;
2023-05-30 20:30:30 +02:00
}
2024-07-01 12:04:36 +02:00
// https://github.com/libsdl-org/SDL/issues/9009
static int SDL_RenderGeometryRaw8BitColor ( SDL_Renderer * renderer , ImVector < SDL_FColor > & colors_out , SDL_Texture * texture , const float * xy , int xy_stride , const SDL_Color * color , int color_stride , const float * uv , int uv_stride , int num_vertices , const void * indices , int num_indices , int size_indices )
{
const Uint8 * color2 = ( const Uint8 * ) color ;
colors_out . resize ( num_vertices ) ;
SDL_FColor * color3 = colors_out . Data ;
for ( int i = 0 ; i < num_vertices ; i + + )
{
color3 [ i ] . r = color - > r / 255.0f ;
color3 [ i ] . g = color - > g / 255.0f ;
color3 [ i ] . b = color - > b / 255.0f ;
color3 [ i ] . a = color - > a / 255.0f ;
color2 + = color_stride ;
color = ( const SDL_Color * ) color2 ;
}
return SDL_RenderGeometryRaw ( renderer , texture , xy , xy_stride , color3 , sizeof ( * color3 ) , uv , uv_stride , num_vertices , indices , num_indices , size_indices ) ;
}
2024-05-14 19:10:31 +02:00
void ImGui_ImplSDLRenderer3_RenderDrawData ( ImDrawData * draw_data , SDL_Renderer * renderer )
2023-05-30 20:30:30 +02:00
{
2024-07-01 12:04:36 +02:00
ImGui_ImplSDLRenderer3_Data * bd = ImGui_ImplSDLRenderer3_GetBackendData ( ) ;
2023-05-30 20:30:30 +02:00
// If there's a scale factor set by the user, use that instead
// If the user has specified a scale factor to SDL_Renderer already via SDL_RenderSetScale(), SDL will scale whatever we pass
// to SDL_RenderGeometryRaw() by that scale factor. In that case we don't want to be also scaling it ourselves here.
float rsx = 1.0f ;
float rsy = 1.0f ;
2024-05-14 19:10:31 +02:00
SDL_GetRenderScale ( renderer , & rsx , & rsy ) ;
2023-05-30 20:30:30 +02:00
ImVec2 render_scale ;
render_scale . x = ( rsx = = 1.0f ) ? draw_data - > FramebufferScale . x : 1.0f ;
render_scale . y = ( rsy = = 1.0f ) ? draw_data - > FramebufferScale . y : 1.0f ;
// Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
int fb_width = ( int ) ( draw_data - > DisplaySize . x * render_scale . x ) ;
int fb_height = ( int ) ( draw_data - > DisplaySize . y * render_scale . y ) ;
if ( fb_width = = 0 | | fb_height = = 0 )
return ;
// Backup SDL_Renderer state that will be modified to restore it afterwards
struct BackupSDLRendererState
{
SDL_Rect Viewport ;
2024-02-12 14:57:39 +01:00
bool ViewportEnabled ;
2023-05-30 20:30:30 +02:00
bool ClipEnabled ;
SDL_Rect ClipRect ;
} ;
BackupSDLRendererState old = { } ;
2024-09-19 16:02:23 +02:00
old . ViewportEnabled = SDL_RenderViewportSet ( renderer ) ;
old . ClipEnabled = SDL_RenderClipEnabled ( renderer ) ;
2024-05-14 19:10:31 +02:00
SDL_GetRenderViewport ( renderer , & old . Viewport ) ;
SDL_GetRenderClipRect ( renderer , & old . ClipRect ) ;
2023-05-30 20:30:30 +02:00
2024-10-09 16:39:28 +02:00
// Setup desired state
ImGui_ImplSDLRenderer3_SetupRenderState ( renderer ) ;
// Setup render state structure (for callbacks and custom texture bindings)
ImGuiPlatformIO & platform_io = ImGui : : GetPlatformIO ( ) ;
ImGui_ImplSDLRenderer3_RenderState render_state ;
render_state . Renderer = renderer ;
platform_io . Renderer_RenderState = & render_state ;
2023-05-30 20:30:30 +02:00
// Will project scissor/clipping rectangles into framebuffer space
ImVec2 clip_off = draw_data - > DisplayPos ; // (0,0) unless using multi-viewports
ImVec2 clip_scale = render_scale ;
// Render command lists
for ( int n = 0 ; n < draw_data - > CmdListsCount ; n + + )
{
2024-10-07 17:52:57 +02:00
const ImDrawList * draw_list = draw_data - > CmdLists [ n ] ;
const ImDrawVert * vtx_buffer = draw_list - > VtxBuffer . Data ;
const ImDrawIdx * idx_buffer = draw_list - > IdxBuffer . Data ;
2023-05-30 20:30:30 +02:00
2024-10-07 17:52:57 +02:00
for ( int cmd_i = 0 ; cmd_i < draw_list - > CmdBuffer . Size ; cmd_i + + )
2023-05-30 20:30:30 +02:00
{
2024-10-07 17:52:57 +02:00
const ImDrawCmd * pcmd = & draw_list - > CmdBuffer [ cmd_i ] ;
2023-05-30 20:30:30 +02:00
if ( pcmd - > UserCallback )
{
// User callback, registered via ImDrawList::AddCallback()
// (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
if ( pcmd - > UserCallback = = ImDrawCallback_ResetRenderState )
2024-05-14 19:10:31 +02:00
ImGui_ImplSDLRenderer3_SetupRenderState ( renderer ) ;
2023-05-30 20:30:30 +02:00
else
2024-10-07 17:52:57 +02:00
pcmd - > UserCallback ( draw_list , pcmd ) ;
2023-05-30 20:30:30 +02:00
}
else
{
// Project scissor/clipping rectangles into framebuffer space
ImVec2 clip_min ( ( pcmd - > ClipRect . x - clip_off . x ) * clip_scale . x , ( pcmd - > ClipRect . y - clip_off . y ) * clip_scale . y ) ;
ImVec2 clip_max ( ( pcmd - > ClipRect . z - clip_off . x ) * clip_scale . x , ( pcmd - > ClipRect . w - clip_off . y ) * clip_scale . y ) ;
if ( clip_min . x < 0.0f ) { clip_min . x = 0.0f ; }
if ( clip_min . y < 0.0f ) { clip_min . y = 0.0f ; }
if ( clip_max . x > ( float ) fb_width ) { clip_max . x = ( float ) fb_width ; }
if ( clip_max . y > ( float ) fb_height ) { clip_max . y = ( float ) fb_height ; }
if ( clip_max . x < = clip_min . x | | clip_max . y < = clip_min . y )
continue ;
SDL_Rect r = { ( int ) ( clip_min . x ) , ( int ) ( clip_min . y ) , ( int ) ( clip_max . x - clip_min . x ) , ( int ) ( clip_max . y - clip_min . y ) } ;
2024-05-14 19:10:31 +02:00
SDL_SetRenderClipRect ( renderer , & r ) ;
2023-05-30 20:30:30 +02:00
2023-11-09 16:44:39 +01:00
const float * xy = ( const float * ) ( const void * ) ( ( const char * ) ( vtx_buffer + pcmd - > VtxOffset ) + offsetof ( ImDrawVert , pos ) ) ;
const float * uv = ( const float * ) ( const void * ) ( ( const char * ) ( vtx_buffer + pcmd - > VtxOffset ) + offsetof ( ImDrawVert , uv ) ) ;
const SDL_Color * color = ( const SDL_Color * ) ( const void * ) ( ( const char * ) ( vtx_buffer + pcmd - > VtxOffset ) + offsetof ( ImDrawVert , col ) ) ; // SDL 2.0.19+
2023-05-30 20:30:30 +02:00
// Bind texture, Draw
SDL_Texture * tex = ( SDL_Texture * ) pcmd - > GetTexID ( ) ;
2024-07-01 12:04:36 +02:00
SDL_RenderGeometryRaw8BitColor ( renderer , bd - > ColorBuffer , tex ,
2023-05-30 20:30:30 +02:00
xy , ( int ) sizeof ( ImDrawVert ) ,
color , ( int ) sizeof ( ImDrawVert ) ,
uv , ( int ) sizeof ( ImDrawVert ) ,
2024-10-07 17:52:57 +02:00
draw_list - > VtxBuffer . Size - pcmd - > VtxOffset ,
2023-05-30 20:30:30 +02:00
idx_buffer + pcmd - > IdxOffset , pcmd - > ElemCount , sizeof ( ImDrawIdx ) ) ;
}
}
}
2024-10-09 16:39:28 +02:00
platform_io . Renderer_RenderState = NULL ;
2023-05-30 20:30:30 +02:00
// Restore modified SDL_Renderer state
2024-05-14 19:10:31 +02:00
SDL_SetRenderViewport ( renderer , old . ViewportEnabled ? & old . Viewport : nullptr ) ;
SDL_SetRenderClipRect ( renderer , old . ClipEnabled ? & old . ClipRect : nullptr ) ;
2023-05-30 20:30:30 +02:00
}
// Called by Init/NewFrame/Shutdown
2023-05-30 20:36:07 +02:00
bool ImGui_ImplSDLRenderer3_CreateFontsTexture ( )
2023-05-30 20:30:30 +02:00
{
ImGuiIO & io = ImGui : : GetIO ( ) ;
2023-05-30 20:36:07 +02:00
ImGui_ImplSDLRenderer3_Data * bd = ImGui_ImplSDLRenderer3_GetBackendData ( ) ;
2023-05-30 20:30:30 +02:00
// Build texture atlas
unsigned char * pixels ;
int width , height ;
io . Fonts - > GetTexDataAsRGBA32 ( & pixels , & width , & height ) ; // Load as RGBA 32-bit (75% of the memory is wasted, but default font is so small) because it is more likely to be compatible with user's existing shaders. If your ImTextureId represent a higher-level concept than just a GL texture id, consider calling GetTexDataAsAlpha8() instead to save on GPU memory.
// Upload texture to graphics system
// (Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling)
2024-05-14 19:10:31 +02:00
bd - > FontTexture = SDL_CreateTexture ( bd - > Renderer , SDL_PIXELFORMAT_ABGR8888 , SDL_TEXTUREACCESS_STATIC , width , height ) ;
2023-05-30 20:30:30 +02:00
if ( bd - > FontTexture = = nullptr )
{
SDL_Log ( " error creating texture " ) ;
return false ;
}
SDL_UpdateTexture ( bd - > FontTexture , nullptr , pixels , 4 * width ) ;
SDL_SetTextureBlendMode ( bd - > FontTexture , SDL_BLENDMODE_BLEND ) ;
SDL_SetTextureScaleMode ( bd - > FontTexture , SDL_SCALEMODE_LINEAR ) ;
// Store our identifier
io . Fonts - > SetTexID ( ( ImTextureID ) ( intptr_t ) bd - > FontTexture ) ;
return true ;
}
2023-05-30 20:36:07 +02:00
void ImGui_ImplSDLRenderer3_DestroyFontsTexture ( )
2023-05-30 20:30:30 +02:00
{
ImGuiIO & io = ImGui : : GetIO ( ) ;
2023-05-30 20:36:07 +02:00
ImGui_ImplSDLRenderer3_Data * bd = ImGui_ImplSDLRenderer3_GetBackendData ( ) ;
2023-05-30 20:30:30 +02:00
if ( bd - > FontTexture )
{
io . Fonts - > SetTexID ( 0 ) ;
SDL_DestroyTexture ( bd - > FontTexture ) ;
bd - > FontTexture = nullptr ;
}
}
2023-05-30 20:36:07 +02:00
bool ImGui_ImplSDLRenderer3_CreateDeviceObjects ( )
2023-05-30 20:30:30 +02:00
{
2023-05-30 20:36:07 +02:00
return ImGui_ImplSDLRenderer3_CreateFontsTexture ( ) ;
2023-05-30 20:30:30 +02:00
}
2023-05-30 20:36:07 +02:00
void ImGui_ImplSDLRenderer3_DestroyDeviceObjects ( )
2023-05-30 20:30:30 +02:00
{
2023-05-30 20:36:07 +02:00
ImGui_ImplSDLRenderer3_DestroyFontsTexture ( ) ;
2023-05-30 20:30:30 +02:00
}
2023-07-13 11:27:52 +02:00
//-----------------------------------------------------------------------------
2023-05-30 20:30:30 +02:00
# if defined(__clang__)
# pragma clang diagnostic pop
# endif
2023-07-13 11:27:52 +02:00
# endif // #ifndef IMGUI_DISABLE