Created
August 10, 2020 16:50
-
-
Save gdianaty/7b81dacbb28b38afc3ba7be09edc7904 to your computer and use it in GitHub Desktop.
ImGui DX9 Impl with sRGB color space correction
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ================================================== | |
// ImGui - LunchHouse Software Modified 8/10/2020 | |
// Modified to be sRGB color space corrected for use | |
// in Valve's Source Engine. | |
// ================================================== | |
// dear imgui: Renderer for DirectX9 | |
// This needs to be used along with a Platform Binding (e.g. Win32) | |
// Implemented features: | |
// [X] Renderer: User texture binding. Use 'LPDIRECT3DTEXTURE9' as ImTextureID. Read the FAQ about ImTextureID! | |
// [X] Renderer: Support for large meshes (64k+ vertices) with 16-bit indices. | |
// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. | |
// If you are new to dear imgui, read examples/README.txt and read the documentation at the top of imgui.cpp. | |
// https://github.com/ocornut/imgui | |
// CHANGELOG | |
// (minor and older changes stripped away, please see git history for details) | |
// 2019-05-29: DirectX9: Added support for large mesh (64K+ vertices), enable ImGuiBackendFlags_RendererHasVtxOffset flag. | |
// 2019-04-30: DirectX9: Added support for special ImDrawCallback_ResetRenderState callback to reset render state. | |
// 2019-03-29: Misc: Fixed erroneous assert in ImGui_ImplDX9_InvalidateDeviceObjects(). | |
// 2019-01-16: Misc: Disabled fog before drawing UI's. Fixes issue #2288. | |
// 2018-11-30: Misc: Setting up io.BackendRendererName so it can be displayed in the About Window. | |
// 2018-06-08: Misc: Extracted imgui_impl_dx9.cpp/.h away from the old combined DX9+Win32 example. | |
// 2018-06-08: DirectX9: Use draw_data->DisplayPos and draw_data->DisplaySize to setup projection matrix and clipping rectangle. | |
// 2018-05-07: Render: Saving/restoring Transform because they don't seem to be included in the StateBlock. Setting shading mode to Gouraud. | |
// 2018-02-16: Misc: Obsoleted the io.RenderDrawListsFn callback and exposed ImGui_ImplDX9_RenderDrawData() in the .h file so you can call it yourself. | |
// 2018-02-06: Misc: Removed call to ImGui::Shutdown() which is not available from 1.60 WIP, user needs to call CreateContext/DestroyContext themselves. | |
#include "imgui.h" | |
#include "imgui_dx9_impl.h" | |
// DirectX | |
#include <d3d9.h> | |
#define DIRECTINPUT_VERSION 0x0800 | |
#include <dinput.h> | |
// tgmath for pow() | |
#include "tgmath.h" | |
// DirectX data | |
static LPDIRECT3DDEVICE9 g_pd3dDevice = NULL; | |
static LPDIRECT3DVERTEXBUFFER9 g_pVB = NULL; | |
static LPDIRECT3DINDEXBUFFER9 g_pIB = NULL; | |
static LPDIRECT3DTEXTURE9 g_FontTexture = NULL; | |
static int g_VertexBufferSize = 5000, g_IndexBufferSize = 10000; | |
struct CUSTOMVERTEX | |
{ | |
float pos[3]; | |
D3DCOLOR col; | |
float uv[2]; | |
}; | |
#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX1) | |
// SRGB corrects according to https://github.com/ocornut/imgui/issues/578 | |
D3DCOLOR D3DCOLOR_sRGB_CORRECTION(D3DCOLOR color) | |
{ | |
#define GAMMAVAL 2.2 | |
// unpack D3DCOLOR into something we can manipulate | |
D3DCOLORVALUE color2; | |
color2.a = ((color & 0xff000000) >> 24) / 255.0f; | |
color2.r = ((color & 0x00ff0000) >> 16) / 255.0f; | |
color2.g = ((color & 0x0000ff00) >> 8) / 255.0f; | |
color2.b = (color & 0x000000ff) / 255.0f; | |
// gamma correct colors... | |
color2.r = pow(color2.r, GAMMAVAL); | |
color2.g = pow(color2.g, GAMMAVAL); | |
color2.b = pow(color2.b, GAMMAVAL); | |
// repack the aforementioned data into the colorvalue for returning | |
return D3DCOLOR_COLORVALUE(color2.r, color2.g, color2.b, color2.a); | |
} | |
static void ImGui_ImplDX9_SetupRenderState(ImDrawData* draw_data) | |
{ | |
// Setup viewport | |
D3DVIEWPORT9 vp; | |
vp.X = vp.Y = 0; | |
vp.Width = (DWORD)draw_data->DisplaySize.x; | |
vp.Height = (DWORD)draw_data->DisplaySize.y; | |
vp.MinZ = 0.0f; | |
vp.MaxZ = 1.0f; | |
g_pd3dDevice->SetViewport(&vp); | |
// Setup render state: fixed-pipeline, alpha-blending, no face culling, no depth testing, shade mode (for gradient) | |
g_pd3dDevice->SetPixelShader(NULL); | |
g_pd3dDevice->SetVertexShader(NULL); | |
g_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); | |
g_pd3dDevice->SetRenderState(D3DRS_LIGHTING, FALSE); | |
g_pd3dDevice->SetRenderState(D3DRS_ZENABLE, FALSE); | |
g_pd3dDevice->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); | |
g_pd3dDevice->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE); | |
g_pd3dDevice->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD); | |
g_pd3dDevice->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA); | |
g_pd3dDevice->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA); | |
g_pd3dDevice->SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE); | |
g_pd3dDevice->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD); | |
g_pd3dDevice->SetRenderState(D3DRS_FOGENABLE, FALSE); | |
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE); | |
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE); | |
g_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE); | |
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_MODULATE); | |
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE); | |
g_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE); | |
g_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR); | |
g_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR); | |
// Setup orthographic projection matrix | |
// Our visible imgui space lies from draw_data->DisplayPos (top left) to draw_data->DisplayPos+data_data->DisplaySize (bottom right). DisplayPos is (0,0) for single viewport apps. | |
// Being agnostic of whether <d3dx9.h> or <DirectXMath.h> can be used, we aren't relying on D3DXMatrixIdentity()/D3DXMatrixOrthoOffCenterLH() or DirectX::XMMatrixIdentity()/DirectX::XMMatrixOrthographicOffCenterLH() | |
{ | |
float L = draw_data->DisplayPos.x + 0.5f; | |
float R = draw_data->DisplayPos.x + draw_data->DisplaySize.x + 0.5f; | |
float T = draw_data->DisplayPos.y + 0.5f; | |
float B = draw_data->DisplayPos.y + draw_data->DisplaySize.y + 0.5f; | |
D3DMATRIX mat_identity = { { { 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f } } }; | |
D3DMATRIX mat_projection = | |
{ { { | |
2.0f/(R-L), 0.0f, 0.0f, 0.0f, | |
0.0f, 2.0f/(T-B), 0.0f, 0.0f, | |
0.0f, 0.0f, 0.5f, 0.0f, | |
(L+R)/(L-R), (T+B)/(B-T), 0.5f, 1.0f | |
} } }; | |
g_pd3dDevice->SetTransform(D3DTS_WORLD, &mat_identity); | |
g_pd3dDevice->SetTransform(D3DTS_VIEW, &mat_identity); | |
g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat_projection); | |
} | |
} | |
// Render function. | |
// (this used to be set in io.RenderDrawListsFn and called by ImGui::Render(), but you can now call this directly from your main loop) | |
void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data) | |
{ | |
// Avoid rendering when minimized | |
if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f) | |
return; | |
// Create and grow buffers if needed | |
if (!g_pVB || g_VertexBufferSize < draw_data->TotalVtxCount) | |
{ | |
if (g_pVB) { g_pVB->Release(); g_pVB = NULL; } | |
g_VertexBufferSize = draw_data->TotalVtxCount + 5000; | |
if (g_pd3dDevice->CreateVertexBuffer(g_VertexBufferSize * sizeof(CUSTOMVERTEX), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &g_pVB, NULL) < 0) | |
return; | |
} | |
if (!g_pIB || g_IndexBufferSize < draw_data->TotalIdxCount) | |
{ | |
if (g_pIB) { g_pIB->Release(); g_pIB = NULL; } | |
g_IndexBufferSize = draw_data->TotalIdxCount + 10000; | |
if (g_pd3dDevice->CreateIndexBuffer(g_IndexBufferSize * sizeof(ImDrawIdx), D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY, sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32, D3DPOOL_DEFAULT, &g_pIB, NULL) < 0) | |
return; | |
} | |
// Backup the DX9 state | |
IDirect3DStateBlock9* d3d9_state_block = NULL; | |
if (g_pd3dDevice->CreateStateBlock(D3DSBT_ALL, &d3d9_state_block) < 0) | |
return; | |
// Backup the DX9 transform (DX9 documentation suggests that it is included in the StateBlock but it doesn't appear to) | |
D3DMATRIX last_world, last_view, last_projection; | |
g_pd3dDevice->GetTransform(D3DTS_WORLD, &last_world); | |
g_pd3dDevice->GetTransform(D3DTS_VIEW, &last_view); | |
g_pd3dDevice->GetTransform(D3DTS_PROJECTION, &last_projection); | |
// Copy and convert all vertices into a single contiguous buffer, convert colors to DX9 default format. | |
// FIXME-OPT: This is a waste of resource, the ideal is to use imconfig.h and | |
// 1) to avoid repacking colors: #define IMGUI_USE_BGRA_PACKED_COLOR | |
// 2) to avoid repacking vertices: #define IMGUI_OVERRIDE_DRAWVERT_STRUCT_LAYOUT struct ImDrawVert { ImVec2 pos; float z; ImU32 col; ImVec2 uv; } | |
CUSTOMVERTEX* vtx_dst; | |
ImDrawIdx* idx_dst; | |
if (g_pVB->Lock(0, (UINT)(draw_data->TotalVtxCount * sizeof(CUSTOMVERTEX)), (void**)&vtx_dst, D3DLOCK_DISCARD) < 0) | |
return; | |
if (g_pIB->Lock(0, (UINT)(draw_data->TotalIdxCount * sizeof(ImDrawIdx)), (void**)&idx_dst, D3DLOCK_DISCARD) < 0) | |
return; | |
for (int n = 0; n < draw_data->CmdListsCount; n++) | |
{ | |
const ImDrawList* cmd_list = draw_data->CmdLists[n]; | |
const ImDrawVert* vtx_src = cmd_list->VtxBuffer.Data; | |
for (int i = 0; i < cmd_list->VtxBuffer.Size; i++) | |
{ | |
vtx_dst->pos[0] = vtx_src->pos.x; | |
vtx_dst->pos[1] = vtx_src->pos.y; | |
vtx_dst->pos[2] = 0.0f; | |
vtx_dst->col = (vtx_src->col & 0xFF00FF00) | ((vtx_src->col & 0xFF0000) >> 16) | ((vtx_src->col & 0xFF) << 16); // RGBA --> ARGB for DirectX9 | |
// do a color space correction | |
vtx_dst->col = D3DCOLOR_sRGB_CORRECTION(vtx_dst->col); | |
vtx_dst->uv[0] = vtx_src->uv.x; | |
vtx_dst->uv[1] = vtx_src->uv.y; | |
vtx_dst++; | |
vtx_src++; | |
} | |
memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx)); | |
idx_dst += cmd_list->IdxBuffer.Size; | |
} | |
g_pVB->Unlock(); | |
g_pIB->Unlock(); | |
g_pd3dDevice->SetStreamSource(0, g_pVB, 0, sizeof(CUSTOMVERTEX)); | |
g_pd3dDevice->SetIndices(g_pIB); | |
g_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX); | |
// Setup desired DX state | |
ImGui_ImplDX9_SetupRenderState(draw_data); | |
// Render command lists | |
// (Because we merged all buffers into a single one, we maintain our own offset into them) | |
int global_vtx_offset = 0; | |
int global_idx_offset = 0; | |
ImVec2 clip_off = draw_data->DisplayPos; | |
for (int n = 0; n < draw_data->CmdListsCount; n++) | |
{ | |
const ImDrawList* cmd_list = draw_data->CmdLists[n]; | |
for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) | |
{ | |
const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; | |
if (pcmd->UserCallback != NULL) | |
{ | |
// 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) | |
ImGui_ImplDX9_SetupRenderState(draw_data); | |
else | |
pcmd->UserCallback(cmd_list, pcmd); | |
} | |
else | |
{ | |
const RECT r = { (LONG)(pcmd->ClipRect.x - clip_off.x), (LONG)(pcmd->ClipRect.y - clip_off.y), (LONG)(pcmd->ClipRect.z - clip_off.x), (LONG)(pcmd->ClipRect.w - clip_off.y) }; | |
const LPDIRECT3DTEXTURE9 texture = (LPDIRECT3DTEXTURE9)pcmd->TextureId; | |
g_pd3dDevice->SetTexture(0, texture); | |
g_pd3dDevice->SetScissorRect(&r); | |
g_pd3dDevice->DrawIndexedPrimitive(D3DPT_TRIANGLELIST, pcmd->VtxOffset + global_vtx_offset, 0, (UINT)cmd_list->VtxBuffer.Size, pcmd->IdxOffset + global_idx_offset, pcmd->ElemCount / 3); | |
} | |
} | |
global_idx_offset += cmd_list->IdxBuffer.Size; | |
global_vtx_offset += cmd_list->VtxBuffer.Size; | |
} | |
// Restore the DX9 transform | |
g_pd3dDevice->SetTransform(D3DTS_WORLD, &last_world); | |
g_pd3dDevice->SetTransform(D3DTS_VIEW, &last_view); | |
g_pd3dDevice->SetTransform(D3DTS_PROJECTION, &last_projection); | |
// Restore the DX9 state | |
d3d9_state_block->Apply(); | |
d3d9_state_block->Release(); | |
} | |
bool ImGui_ImplDX9_Init(IDirect3DDevice9* device) | |
{ | |
// Setup back-end capabilities flags | |
ImGuiIO& io = ImGui::GetIO(); | |
io.BackendRendererName = "imgui_impl_dx9"; | |
io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset; // We can honor the ImDrawCmd::VtxOffset field, allowing for large meshes. | |
g_pd3dDevice = device; | |
g_pd3dDevice->AddRef(); | |
return true; | |
} | |
void ImGui_ImplDX9_Shutdown() | |
{ | |
ImGui_ImplDX9_InvalidateDeviceObjects(); | |
if (g_pd3dDevice) { g_pd3dDevice->Release(); g_pd3dDevice = NULL; } | |
} | |
static bool ImGui_ImplDX9_CreateFontsTexture() | |
{ | |
// Build texture atlas | |
ImGuiIO& io = ImGui::GetIO(); | |
unsigned char* pixels; | |
int width, height, bytes_per_pixel; | |
io.Fonts->GetTexDataAsRGBA32(&pixels, &width, &height, &bytes_per_pixel); | |
// Upload texture to graphics system | |
g_FontTexture = NULL; | |
if (g_pd3dDevice->CreateTexture(width, height, 1, D3DUSAGE_DYNAMIC, D3DFMT_A8R8G8B8, D3DPOOL_DEFAULT, &g_FontTexture, NULL) < 0) | |
return false; | |
D3DLOCKED_RECT tex_locked_rect; | |
if (g_FontTexture->LockRect(0, &tex_locked_rect, NULL, 0) != D3D_OK) | |
return false; | |
for (int y = 0; y < height; y++) | |
memcpy((unsigned char*)tex_locked_rect.pBits + tex_locked_rect.Pitch * y, pixels + (width * bytes_per_pixel) * y, (width * bytes_per_pixel)); | |
g_FontTexture->UnlockRect(0); | |
// Store our identifier | |
io.Fonts->TexID = (ImTextureID)g_FontTexture; | |
return true; | |
} | |
bool ImGui_ImplDX9_CreateDeviceObjects() | |
{ | |
if (!g_pd3dDevice) | |
return false; | |
if (!ImGui_ImplDX9_CreateFontsTexture()) | |
return false; | |
return true; | |
} | |
void ImGui_ImplDX9_InvalidateDeviceObjects() | |
{ | |
if (!g_pd3dDevice) | |
return; | |
if (g_pVB) { g_pVB->Release(); g_pVB = NULL; } | |
if (g_pIB) { g_pIB->Release(); g_pIB = NULL; } | |
if (g_FontTexture) { g_FontTexture->Release(); g_FontTexture = NULL; ImGui::GetIO().Fonts->TexID = NULL; } // We copied g_pFontTextureView to io.Fonts->TexID so let's clear that as well. | |
} | |
void ImGui_ImplDX9_NewFrame() | |
{ | |
if (!g_FontTexture) | |
ImGui_ImplDX9_CreateDeviceObjects(); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment