ImGui thin3d backend: Add texture binding support

This commit is contained in:
Henrik Rydgård 2024-11-26 09:05:29 +01:00
parent a74e4a105c
commit 57845b02c5
2 changed files with 74 additions and 28 deletions

View file

@ -12,28 +12,52 @@ static Lin::Matrix4x4 g_drawMatrix;
static ImFont *g_proportionalFont = nullptr;
static ImFont *g_fixedFont = nullptr;
struct ImGui_ImplThin3d_Data {
struct RegisteredTexture {
bool isFramebuffer;
union {
Draw::Texture *texture;
Draw::Framebuffer *framebuffer;
};
};
struct BackendData {
Draw::SamplerState *fontSampler = nullptr;
Draw::Texture *fontImage = nullptr;
Draw::Pipeline *pipeline = nullptr;
std::vector<RegisteredTexture> tempTextures;
};
#define TEX_ID_OFFSET 256
// 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.
// FIXME: multi-context support is not tested and probably dysfunctional in this backend.
static ImGui_ImplThin3d_Data* ImGui_ImplThin3d_GetBackendData() {
return ImGui::GetCurrentContext() ? (ImGui_ImplThin3d_Data *)ImGui::GetIO().BackendRendererUserData : nullptr;
static BackendData *ImGui_ImplThin3d_GetBackendData() {
return ImGui::GetCurrentContext() ? (BackendData *)ImGui::GetIO().BackendRendererUserData : nullptr;
}
static void ImGui_ImplThin3d_SetupRenderState(Draw::DrawContext *draw, ImDrawData* draw_data, Draw::Pipeline *pipeline, int fb_width, int fb_height) {
ImGui_ImplThin3d_Data* bd = ImGui_ImplThin3d_GetBackendData();
static void BindTexture(Draw::DrawContext *draw, BackendData *bd, ImTextureID textureId) {
if (!textureId) {
draw->BindTexture(0, bd->fontImage);
} else {
size_t index = (size_t)textureId - TEX_ID_OFFSET;
_dbg_assert_(index < bd->tempTextures.size());
if (bd->tempTextures[index].framebuffer) {
draw->BindFramebufferAsTexture(bd->tempTextures[index].framebuffer, 0, Draw::FBChannel::FB_COLOR_BIT, 0);
} else {
draw->BindTexture(0, bd->tempTextures[index].texture);
}
}
}
static void ImGui_ImplThin3d_SetupRenderState(Draw::DrawContext *draw, ImDrawData* drawData, Draw::Pipeline *pipeline, int fb_width, int fb_height) {
BackendData *bd = ImGui_ImplThin3d_GetBackendData();
// Bind pipeline and texture
draw->BindPipeline(pipeline);
draw->BindTexture(0, bd->fontImage);
draw->BindSamplerStates(0, 1, &bd->fontSampler);
// Setup viewport:
// Setup viewport
{
Draw::Viewport viewport;
viewport.TopLeftX = 0;
@ -46,10 +70,11 @@ static void ImGui_ImplThin3d_SetupRenderState(Draw::DrawContext *draw, ImDrawDat
}
// Setup scale and translation:
// Our visible imgui space lies from draw_data->DisplayPps (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps.
// We currently ignore DisplayPos.
// Our visible imgui space lies from drawData->DisplayPps (top left) to drawData->DisplayPos + drawData->DisplaySize (bottom right).
// DisplayPos is (0,0) for single viewport apps. We currently ignore DisplayPos.
// We probably only need to do this at the start of the frame.
{
Lin::Matrix4x4 mtx = ComputeOrthoMatrix(draw_data->DisplaySize.x, draw_data->DisplaySize.y, draw->GetDeviceCaps().coordConvention);
Lin::Matrix4x4 mtx = ComputeOrthoMatrix(drawData->DisplaySize.x, drawData->DisplaySize.y, draw->GetDeviceCaps().coordConvention);
Draw::VsTexColUB ub{};
memcpy(ub.WorldViewProj, mtx.getReadPtr(), sizeof(Lin::Matrix4x4));
@ -63,10 +88,11 @@ void ImGui_ImplThin3d_RenderDrawData(ImDrawData* draw_data, Draw::DrawContext *d
// Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates)
int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x);
int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y);
if (fb_width <= 0 || fb_height <= 0)
if (fb_width <= 0 || fb_height <= 0) {
return;
}
ImGui_ImplThin3d_Data* bd = ImGui_ImplThin3d_GetBackendData();
BackendData* bd = ImGui_ImplThin3d_GetBackendData();
// Setup desired Vulkan state
ImGui_ImplThin3d_SetupRenderState(draw, draw_data, bd->pipeline, fb_width, fb_height);
@ -77,6 +103,8 @@ void ImGui_ImplThin3d_RenderDrawData(ImDrawData* draw_data, Draw::DrawContext *d
_assert_(sizeof(ImDrawIdx) == 2);
ImTextureID prev = (ImTextureID)-1;
std::vector<Draw::ClippedDraw> draws;
// Render command lists
for (int n = 0; n < draw_data->CmdListsCount; n++) {
@ -84,6 +112,10 @@ void ImGui_ImplThin3d_RenderDrawData(ImDrawData* draw_data, Draw::DrawContext *d
draws.clear();
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) {
const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i];
if (pcmd->TextureId != prev) {
BindTexture(draw, bd, pcmd->TextureId);
prev = pcmd->TextureId;
}
if (pcmd->UserCallback != nullptr) {
// 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.)
@ -119,10 +151,13 @@ void ImGui_ImplThin3d_RenderDrawData(ImDrawData* draw_data, Draw::DrawContext *d
}
draw->SetScissorRect(0, 0, fb_width, fb_height);
// Discard temp textures.
bd->tempTextures.clear();
}
bool ImGui_ImplThin3d_CreateDeviceObjects(Draw::DrawContext *draw) {
ImGui_ImplThin3d_Data* bd = ImGui_ImplThin3d_GetBackendData();
BackendData* bd = ImGui_ImplThin3d_GetBackendData();
if (!bd->fontSampler) {
// Bilinear sampling is required by default. Set 'io.Fonts->Flags |= ImFontAtlasFlags_NoBakedLines' or 'style.AntiAliasedLinesUseTex = false' to allow point/nearest sampling.
@ -138,7 +173,7 @@ bool ImGui_ImplThin3d_CreateDeviceObjects(Draw::DrawContext *draw) {
}
if (!bd->pipeline) {
ImGui_ImplThin3d_Data* bd = ImGui_ImplThin3d_GetBackendData();
BackendData* bd = ImGui_ImplThin3d_GetBackendData();
using namespace Draw;
@ -179,7 +214,7 @@ bool ImGui_ImplThin3d_CreateDeviceObjects(Draw::DrawContext *draw) {
if (!bd->fontImage) {
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplThin3d_Data* bd = ImGui_ImplThin3d_GetBackendData();
BackendData* bd = ImGui_ImplThin3d_GetBackendData();
unsigned char* pixels;
int width, height;
@ -204,7 +239,7 @@ bool ImGui_ImplThin3d_CreateDeviceObjects(Draw::DrawContext *draw) {
void ImGui_ImplThin3d_DestroyDeviceObjects() {
ImGuiIO& io = ImGui::GetIO();
ImGui_ImplThin3d_Data* bd = ImGui_ImplThin3d_GetBackendData();
BackendData* bd = ImGui_ImplThin3d_GetBackendData();
if (bd->fontImage) {
bd->fontImage->Release();
bd->fontImage = nullptr;
@ -236,7 +271,7 @@ bool ImGui_ImplThin3d_Init(Draw::DrawContext *draw, const uint8_t *ttf_font, siz
IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
// Setup backend capabilities flags
ImGui_ImplThin3d_Data* bd = IM_NEW(ImGui_ImplThin3d_Data)();
BackendData* bd = IM_NEW(BackendData)();
io.BackendRendererUserData = (void*)bd;
io.BackendRendererName = "imgui_impl_thin3d";
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes.
@ -253,7 +288,7 @@ void ImGui_PopFont() {
}
void ImGui_ImplThin3d_Shutdown() {
ImGui_ImplThin3d_Data* bd = ImGui_ImplThin3d_GetBackendData();
BackendData* bd = ImGui_ImplThin3d_GetBackendData();
IM_ASSERT(bd != nullptr && "No renderer backend to shutdown, or already shutdown?");
ImGuiIO& io = ImGui::GetIO();
@ -265,7 +300,7 @@ void ImGui_ImplThin3d_Shutdown() {
}
void ImGui_ImplThin3d_NewFrame(Draw::DrawContext *draw, Lin::Matrix4x4 drawMatrix) {
ImGui_ImplThin3d_Data* bd = ImGui_ImplThin3d_GetBackendData();
BackendData* bd = ImGui_ImplThin3d_GetBackendData();
IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplThin3d_Init()?");
// This one checks if objects already have been created, so ok to call every time.
@ -273,13 +308,22 @@ void ImGui_ImplThin3d_NewFrame(Draw::DrawContext *draw, Lin::Matrix4x4 drawMatri
g_drawMatrix = drawMatrix;
}
// Register a texture. No-op.
ImTextureID ImGui_ImplThin3d_AddTexture(Draw::Texture *texture) {
ImGui_ImplThin3d_Data* bd = ImGui_ImplThin3d_GetBackendData();
return (void *)texture;
ImTextureID ImGui_ImplThin3d_AddTextureTemp(Draw::Texture *texture) {
BackendData* bd = ImGui_ImplThin3d_GetBackendData();
RegisteredTexture tex{ false };
tex.texture = texture;
bd->tempTextures.push_back(tex);
return (ImTextureID)(uint64_t)(TEX_ID_OFFSET + bd->tempTextures.size() - 1);
}
// Unregister a texture. No-op.
Draw::Texture *ImGui_ImplThin3d_RemoveTexture(ImTextureID tex) {
return (Draw::Texture *)tex;
ImTextureID ImGui_ImplThin3d_AddFBAsTextureTemp(Draw::Framebuffer *framebuffer) {
BackendData* bd = ImGui_ImplThin3d_GetBackendData();
RegisteredTexture tex{ true };
tex.framebuffer = framebuffer;
bd->tempTextures.push_back(tex);
return (ImTextureID)(uint64_t)(TEX_ID_OFFSET + bd->tempTextures.size() - 1);
}

View file

@ -41,8 +41,10 @@ IMGUI_IMPL_API void ImGui_ImplThin3d_RenderDrawData(ImDrawData* draw_dat
IMGUI_IMPL_API bool ImGui_ImplThin3d_CreateDeviceObjects(Draw::DrawContext *draw);
IMGUI_IMPL_API void ImGui_ImplThin3d_DestroyDeviceObjects();
IMGUI_IMPL_API ImTextureID ImGui_ImplThin3d_AddTexture(Draw::Texture *texture);
IMGUI_IMPL_API Draw::Texture *ImGui_ImplThin3d_RemoveTexture(ImTextureID texture);
// These register a texture for imgui drawing, but just for the current frame.
// Textures are unregistered again in RenderDrawData. This is just simpler.
IMGUI_IMPL_API ImTextureID ImGui_ImplThin3d_AddTextureTemp(Draw::Texture *texture);
IMGUI_IMPL_API ImTextureID ImGui_ImplThin3d_AddFBAsTextureTemp(Draw::Framebuffer *framebuffer);
void ImGui_PushFixedFont();
void ImGui_PopFont();