mirror of
https://github.com/ocornut/imgui.git
synced 2026-11-09 07:55:52 +01:00
316 lines
11 KiB
C++
316 lines
11 KiB
C++
// dear imgui: Renderer Backend for Nintendo GameCube/Wii GX by Dakotath
|
|
// See imgui_impl_gx.h for usage and limitations.
|
|
|
|
#include "imgui.h"
|
|
#ifndef IMGUI_DISABLE
|
|
#include "imgui_impl_gx.h"
|
|
|
|
#include <gccore.h>
|
|
#include <malloc.h>
|
|
#include <string.h>
|
|
|
|
struct ImGui_ImplGX_Texture
|
|
{
|
|
GXTexObj TexObj;
|
|
void* Pixels;
|
|
int Width;
|
|
int Height;
|
|
};
|
|
|
|
static ImGui_ImplGX_Texture* ImGui_ImplGX_GetTexture(ImTextureData* tex)
|
|
{
|
|
return (ImGui_ImplGX_Texture*)tex->BackendUserData;
|
|
}
|
|
|
|
static int ImGui_ImplGX_Align4(int value) { return (value + 3) & ~3; }
|
|
|
|
// GX_TF_RGBA8 stores each 4x4 tile as AR bytes followed by GB bytes.
|
|
// GX_TF_RGBA8 stores each 4x4 tile as:
|
|
// AR AR AR AR
|
|
// AR AR AR AR
|
|
// AR AR AR AR
|
|
// AR AR AR AR
|
|
//
|
|
// GB GB GB GB
|
|
// GB GB GB GB
|
|
// GB GB GB GB
|
|
// GB GB GB GB
|
|
|
|
static void ImGui_ImplGX_ConvertRGBA8(
|
|
void* dst_data,
|
|
ImTextureData* tex,
|
|
int padded_width,
|
|
int padded_height)
|
|
{
|
|
unsigned char* dst = (unsigned char*)dst_data;
|
|
const unsigned char* src =
|
|
(const unsigned char*)tex->GetPixels();
|
|
|
|
const int pitch = tex->GetPitch();
|
|
|
|
for (int y = 0; y < padded_height; y += 4)
|
|
{
|
|
for (int x = 0; x < padded_width; x += 4)
|
|
{
|
|
/*
|
|
* AR plane
|
|
*/
|
|
for (int row = 0; row < 4; row++)
|
|
{
|
|
for (int col = 0; col < 4; col++)
|
|
{
|
|
unsigned char r = 0;
|
|
unsigned char a = 0;
|
|
|
|
if (x + col < tex->Width &&
|
|
y + row < tex->Height)
|
|
{
|
|
const unsigned char* pixel =
|
|
src +
|
|
(y + row) * pitch +
|
|
(x + col) * 4;
|
|
|
|
#ifdef GEKKO
|
|
// Wii/GameCube PowerPC:
|
|
// ImGui RGBA32 memory requires ABGR interpretation.
|
|
a = pixel[0];
|
|
r = pixel[3];
|
|
#else
|
|
a = pixel[3];
|
|
r = pixel[0];
|
|
#endif
|
|
}
|
|
|
|
// GX RGBA8 AR plane
|
|
*dst++ = a;
|
|
*dst++ = r;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* GB plane
|
|
*/
|
|
for (int row = 0; row < 4; row++)
|
|
{
|
|
for (int col = 0; col < 4; col++)
|
|
{
|
|
unsigned char g = 0;
|
|
unsigned char b = 0;
|
|
|
|
if (x + col < tex->Width &&
|
|
y + row < tex->Height)
|
|
{
|
|
const unsigned char* pixel =
|
|
src +
|
|
(y + row) * pitch +
|
|
(x + col) * 4;
|
|
|
|
#ifdef GEKKO
|
|
// ABGR source
|
|
b = pixel[1];
|
|
g = pixel[2];
|
|
#else
|
|
// RGBA source
|
|
g = pixel[1];
|
|
b = pixel[2];
|
|
#endif
|
|
}
|
|
|
|
// GX RGBA8 GB plane
|
|
*dst++ = g;
|
|
*dst++ = b;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ImGui_ImplGX_UpdateTexture(ImTextureData* tex)
|
|
{
|
|
if (tex->Status == ImTextureStatus_WantCreate)
|
|
{
|
|
IM_ASSERT(tex->TexID == ImTextureID_Invalid && tex->BackendUserData == nullptr);
|
|
IM_ASSERT(tex->Format == ImTextureFormat_RGBA32);
|
|
ImGui_ImplGX_Texture* texture = IM_NEW(ImGui_ImplGX_Texture)();
|
|
texture->Width = ImGui_ImplGX_Align4(tex->Width);
|
|
texture->Height = ImGui_ImplGX_Align4(tex->Height);
|
|
texture->Pixels = memalign(32, texture->Width * texture->Height * 4);
|
|
IM_ASSERT(texture->Pixels != nullptr);
|
|
ImGui_ImplGX_ConvertRGBA8(texture->Pixels, tex, texture->Width, texture->Height);
|
|
DCFlushRange(texture->Pixels, texture->Width * texture->Height * 4);
|
|
|
|
// TODO: Investigate fucking font problem
|
|
GX_InitTexObj(&texture->TexObj, texture->Pixels, texture->Width, texture->Height, GX_TF_RGBA8, GX_CLAMP, GX_CLAMP, GX_FALSE);
|
|
GX_InitTexObjFilterMode(&texture->TexObj, GX_LINEAR, GX_LINEAR);
|
|
tex->BackendUserData = texture;
|
|
tex->SetTexID((ImTextureID)&texture->TexObj);
|
|
tex->SetStatus(ImTextureStatus_OK);
|
|
}
|
|
else if (tex->Status == ImTextureStatus_WantUpdates)
|
|
{
|
|
// A tiled GX texture cannot safely receive arbitrary rectangular updates;
|
|
// repacking the atlas is small and keeps all dynamic-atlas updates correct.
|
|
ImGui_ImplGX_Texture* texture = ImGui_ImplGX_GetTexture(tex);
|
|
IM_ASSERT(texture != nullptr && tex->Format == ImTextureFormat_RGBA32);
|
|
ImGui_ImplGX_ConvertRGBA8(texture->Pixels, tex, texture->Width, texture->Height);
|
|
DCFlushRange(texture->Pixels, texture->Width * texture->Height * 4);
|
|
tex->SetStatus(ImTextureStatus_OK);
|
|
}
|
|
else if (tex->Status == ImTextureStatus_WantDestroy)
|
|
{
|
|
ImGui_ImplGX_Texture* texture = ImGui_ImplGX_GetTexture(tex);
|
|
if (texture != nullptr)
|
|
{
|
|
free(texture->Pixels);
|
|
IM_DELETE(texture);
|
|
}
|
|
tex->BackendUserData = nullptr;
|
|
tex->SetTexID(ImTextureID_Invalid);
|
|
tex->SetStatus(ImTextureStatus_Destroyed);
|
|
}
|
|
}
|
|
|
|
static void ImGui_ImplGX_SetupRenderState(ImDrawData* draw_data)
|
|
{
|
|
Mtx44 projection;
|
|
Mtx model;
|
|
const float left = draw_data->DisplayPos.x;
|
|
const float right = draw_data->DisplayPos.x + draw_data->DisplaySize.x;
|
|
const float top = draw_data->DisplayPos.y;
|
|
const float bottom = draw_data->DisplayPos.y + draw_data->DisplaySize.y;
|
|
|
|
guOrtho(projection, top, bottom, left, right, -1.0f, 1.0f);
|
|
guMtxIdentity(model);
|
|
GX_LoadProjectionMtx(projection, GX_ORTHOGRAPHIC);
|
|
GX_LoadPosMtxImm(model, GX_PNMTX0);
|
|
GX_SetCurrentMtx(GX_PNMTX0);
|
|
|
|
GX_SetNumChans(1);
|
|
GX_SetChanCtrl(GX_COLOR0A0, GX_DISABLE, GX_SRC_VTX, GX_SRC_VTX, GX_LIGHTNULL, GX_DF_NONE, GX_AF_NONE);
|
|
GX_SetNumTexGens(1);
|
|
GX_SetTexCoordGen(GX_TEXCOORD0, GX_TG_MTX2x4, GX_TG_TEX0, GX_IDENTITY);
|
|
GX_SetNumTevStages(1);
|
|
GX_SetTevOrder(GX_TEVSTAGE0, GX_TEXCOORD0, GX_TEXMAP0, GX_COLOR0A0);
|
|
GX_SetTevOp(GX_TEVSTAGE0, GX_MODULATE);
|
|
|
|
GX_SetBlendMode(GX_BM_BLEND, GX_BL_SRCALPHA, GX_BL_INVSRCALPHA, GX_LO_CLEAR);
|
|
GX_SetZMode(GX_FALSE, GX_ALWAYS, GX_FALSE);
|
|
GX_SetCullMode(GX_CULL_NONE);
|
|
GX_SetColorUpdate(GX_TRUE);
|
|
GX_SetAlphaUpdate(GX_TRUE);
|
|
|
|
GX_ClearVtxDesc();
|
|
GX_SetVtxDesc(GX_VA_POS, GX_DIRECT);
|
|
GX_SetVtxDesc(GX_VA_CLR0, GX_DIRECT);
|
|
GX_SetVtxDesc(GX_VA_TEX0, GX_DIRECT);
|
|
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_POS_XYZ, GX_F32, 0);
|
|
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_CLR0, GX_CLR_RGBA, GX_RGBA8, 0);
|
|
GX_SetVtxAttrFmt(GX_VTXFMT0, GX_VA_TEX0, GX_TEX_ST, GX_F32, 0);
|
|
}
|
|
|
|
static void ImGui_ImplGX_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {}
|
|
|
|
void ImGui_ImplGX_RenderDrawData(ImDrawData* draw_data)
|
|
{
|
|
const int fb_width = (int)(draw_data->DisplaySize.x * draw_data->FramebufferScale.x);
|
|
const int fb_height = (int)(draw_data->DisplaySize.y * draw_data->FramebufferScale.y);
|
|
if (fb_width <= 0 || fb_height <= 0)
|
|
return;
|
|
|
|
if (draw_data->Textures != nullptr)
|
|
for (ImTextureData* tex : *draw_data->Textures)
|
|
if (tex->Status != ImTextureStatus_OK)
|
|
ImGui_ImplGX_UpdateTexture(tex);
|
|
|
|
ImGui_ImplGX_SetupRenderState(draw_data);
|
|
const ImVec2 clip_off = draw_data->DisplayPos;
|
|
const ImVec2 clip_scale = draw_data->FramebufferScale;
|
|
for (const ImDrawList* draw_list : draw_data->CmdLists)
|
|
{
|
|
const ImDrawVert* vtx_buffer = draw_list->VtxBuffer.Data;
|
|
const ImDrawIdx* idx_buffer = draw_list->IdxBuffer.Data;
|
|
for (const ImDrawCmd* pcmd = draw_list->CmdBuffer.begin(); pcmd != draw_list->CmdBuffer.end(); pcmd++)
|
|
{
|
|
if (pcmd->UserCallback != nullptr)
|
|
{
|
|
if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
|
|
ImGui_ImplGX_SetupRenderState(draw_data);
|
|
else
|
|
pcmd->UserCallback(draw_list, pcmd);
|
|
continue;
|
|
}
|
|
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_max.x <= clip_min.x || clip_max.y <= clip_min.y)
|
|
continue;
|
|
const int x = (int)clip_min.x < 0 ? 0 : (int)clip_min.x;
|
|
const int y = (int)clip_min.y < 0 ? 0 : (int)clip_min.y;
|
|
const int w = (int)(clip_max.x - clip_min.x);
|
|
const int h = (int)(clip_max.y - clip_min.y);
|
|
GX_SetScissor(x, y, w, h);
|
|
GXTexObj* texture = (GXTexObj*)pcmd->GetTexID();
|
|
IM_ASSERT(texture != nullptr && "GX backend expects GXTexObj* texture IDs");
|
|
GX_LoadTexObj(texture, GX_TEXMAP0);
|
|
// GX_Begin() accepts a 16-bit vertex count. Split large commands while
|
|
// retaining ImGui's VtxOffset support for 16-bit index buffers.
|
|
unsigned int remaining = pcmd->ElemCount;
|
|
unsigned int index_offset = 0;
|
|
while (remaining > 0)
|
|
{
|
|
const unsigned int count = remaining > 65535 ? 65535 : remaining;
|
|
GX_Begin(GX_TRIANGLES, GX_VTXFMT0, (u16)count);
|
|
for (unsigned int index = 0; index < count; index++)
|
|
{
|
|
const ImDrawVert& v = vtx_buffer[pcmd->VtxOffset + idx_buffer[pcmd->IdxOffset + index_offset + index]];
|
|
GX_Position3f32(v.pos.x, v.pos.y, 0.0f);
|
|
GX_Color4u8((u8)(v.col & 0xff), (u8)((v.col >> 8) & 0xff), (u8)((v.col >> 16) & 0xff), (u8)((v.col >> 24) & 0xff));
|
|
GX_TexCoord2f32(v.uv.x, v.uv.y);
|
|
}
|
|
index_offset += count;
|
|
remaining -= count;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ImGui_ImplGX_NewFrame()
|
|
{
|
|
IM_ASSERT(ImGui::GetCurrentContext() != nullptr && ImGui::GetIO().BackendRendererUserData != nullptr && "Did you call ImGui_ImplGX_Init()?");
|
|
}
|
|
|
|
void ImGui_ImplGX_DestroyDeviceObjects()
|
|
{
|
|
for (ImTextureData* tex : ImGui::GetPlatformIO().Textures)
|
|
if (tex->RefCount == 1)
|
|
{
|
|
tex->SetStatus(ImTextureStatus_WantDestroy);
|
|
ImGui_ImplGX_UpdateTexture(tex);
|
|
}
|
|
}
|
|
|
|
bool ImGui_ImplGX_Init()
|
|
{
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
IMGUI_CHECKVERSION();
|
|
IM_ASSERT(io.BackendRendererUserData == nullptr && "Already initialized a renderer backend!");
|
|
io.BackendRendererUserData = (void*)1;
|
|
io.BackendRendererName = "imgui_impl_gx";
|
|
io.BackendFlags |= ImGuiBackendFlags_RendererHasTextures;
|
|
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset;
|
|
ImGui::GetPlatformIO().DrawCallback_ResetRenderState = ImGui_ImplGX_DrawCallback_ResetRenderState;
|
|
return true;
|
|
}
|
|
|
|
void ImGui_ImplGX_Shutdown()
|
|
{
|
|
ImGui_ImplGX_DestroyDeviceObjects();
|
|
ImGuiIO& io = ImGui::GetIO();
|
|
ImGui::GetPlatformIO().ClearRendererHandlers();
|
|
io.BackendRendererName = nullptr;
|
|
io.BackendRendererUserData = nullptr;
|
|
io.BackendFlags &= ~(ImGuiBackendFlags_RendererHasTextures | ImGuiBackendFlags_RendererHasVtxOffset);
|
|
}
|
|
|
|
#endif // #ifndef IMGUI_DISABLE
|