Backends: DX9: programmable rendering pipeline

This commit is contained in:
Kuanlan 2022-11-11 20:53:22 +08:00
parent d4ddc46e77
commit bdf517c1c1
1 changed files with 328 additions and 20 deletions

View File

@ -47,6 +47,14 @@ struct ImGui_ImplDX9_Data
int VertexBufferSize;
int IndexBufferSize;
// Direct3D 9 programmable rendering pipeline implementation.
// Nearly all graphic card released after 2007 support Shader Model 2.
// So theoretically, this feature is supported on almost all current devices.
bool IsShaderSupported;
IDirect3DVertexDeclaration9* pInputLayout;
IDirect3DVertexShader9* pVertexShader;
IDirect3DPixelShader9* pPixelShader;
ImGui_ImplDX9_Data() { memset((void*)this, 0, sizeof(*this)); VertexBufferSize = 5000; IndexBufferSize = 10000; }
};
@ -71,6 +79,311 @@ static ImGui_ImplDX9_Data* ImGui_ImplDX9_GetBackendData()
return ImGui::GetCurrentContext() ? (ImGui_ImplDX9_Data*)ImGui::GetIO().BackendRendererUserData : nullptr;
}
// Shared
static D3DMATRIX ImGui_ImplDX9_BuildProjectionMatrix(ImDrawData* draw_data)
{
// 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;
const 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,
} } };
return mat_projection;
}
// Programmable render pipeline
static bool ImGui_ImplDX9WithShader_CreateDeviceObjects()
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
D3DCAPS9 caps; ZeroMemory(&caps, sizeof(D3DCAPS9));
if (D3D_OK != bd->pd3dDevice->GetDeviceCaps(&caps))
return false;
if (caps.VertexShaderVersion < D3DVS_VERSION(2,0) || caps.PixelShaderVersion < D3DPS_VERSION(2, 0))
return false;
// Input layout of default ImDrawVert
static const D3DVERTEXELEMENT9 InputLayout[4] = {
{ 0, 0, D3DDECLTYPE_FLOAT2 , D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
{ 0, 8, D3DDECLTYPE_FLOAT2 , D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },
{ 0, 16, D3DDECLTYPE_D3DCOLOR, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR , 0 },
D3DDECL_END(),
};
if (D3D_OK != bd->pd3dDevice->CreateVertexDeclaration(InputLayout, &bd->pInputLayout))
return false;
// Shaders
/*
float4x4 mvp : register(c0);
struct VS_Input
{
float2 pos : POSITION0;
float2 uv : TEXCOORD0;
float4 col : COLOR0;
};
struct VS_Output
{
float4 pos : POSITION;
float2 uv : TEXCOORD0;
float4 col : COLOR0;
};
VS_Output main(VS_Input input)
{
VS_Output output;
output.pos = mul(mvp, float4(input.pos.x, input.pos.y, 0.0f, 1.0f));
output.uv = input.uv;
output.col = input.col;
return output;
};
*/
// Precompile with Visual Studio HLSL compiler, vs_2_0
static const BYTE VertexShaderByteCode[244] = { 0, 2, 254, 255, 254, 255, 30, 0, 67, 84, 65, 66, 28, 0, 0, 0, 75, 0, 0, 0, 0, 2, 254, 255, 1, 0, 0, 0, 28, 0, 0, 0, 0, 1, 0, 0, 68, 0, 0, 0, 48, 0, 0, 0, 2, 0, 0, 0, 4, 0, 2, 0, 52, 0, 0, 0, 0, 0, 0, 0, 109, 118, 112, 0, 3, 0, 3, 0, 4, 0, 4, 0, 1, 0, 0, 0, 0, 0, 0, 0, 118, 115, 95, 50, 95, 48, 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 49, 48, 46, 49, 0, 171, 31, 0, 0, 2, 0, 0, 0, 128, 0, 0, 15, 144, 31, 0, 0, 2, 5, 0, 0, 128, 1, 0, 15, 144, 31, 0, 0, 2, 10, 0, 0, 128, 2, 0, 15, 144, 5, 0, 0, 3, 0, 0, 15, 128, 0, 0, 85, 144, 1, 0, 228, 160, 4, 0, 0, 4, 0, 0, 15, 128, 0, 0, 228, 160, 0, 0, 0, 144, 0, 0, 228, 128, 2, 0, 0, 3, 0, 0, 15, 192, 0, 0, 228, 128, 3, 0, 228, 160, 1, 0, 0, 2, 0, 0, 3, 224, 1, 0, 228, 144, 1, 0, 0, 2, 0, 0, 15, 208, 2, 0, 228, 144, 255, 255, 0, 0 };
/*
sampler tex0 : register(s0);
struct PS_Input
{
float4 pos : VPOS;
float2 uv : TEXCOORD0;
float4 col : COLOR0;
};
struct PS_Output
{
float4 col : COLOR;
};
PS_Output main(PS_Input input)
{
PS_Output output;
output.col = input.col * tex2D(tex0, input.uv);
return output;
};
*/
// Precompile with Visual Studio HLSL compiler, ps_2_0
static const BYTE PixelShaderByteCode[216] = { 0, 2, 255, 255, 254, 255, 31, 0, 67, 84, 65, 66, 28, 0, 0, 0, 79, 0, 0, 0, 0, 2, 255, 255, 1, 0, 0, 0, 28, 0, 0, 0, 0, 1, 0, 0, 72, 0, 0, 0, 48, 0, 0, 0, 3, 0, 0, 0, 1, 0, 2, 0, 56, 0, 0, 0, 0, 0, 0, 0, 116, 101, 120, 48, 0, 171, 171, 171, 4, 0, 12, 0, 1, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 112, 115, 95, 50, 95, 48, 0, 77, 105, 99, 114, 111, 115, 111, 102, 116, 32, 40, 82, 41, 32, 72, 76, 83, 76, 32, 83, 104, 97, 100, 101, 114, 32, 67, 111, 109, 112, 105, 108, 101, 114, 32, 49, 48, 46, 49, 0, 171, 31, 0, 0, 2, 0, 0, 0, 128, 0, 0, 3, 176, 31, 0, 0, 2, 0, 0, 0, 128, 0, 0, 15, 144, 31, 0, 0, 2, 0, 0, 0, 144, 0, 8, 15, 160, 66, 0, 0, 3, 0, 0, 15, 128, 0, 0, 228, 176, 0, 8, 228, 160, 5, 0, 0, 3, 0, 0, 15, 128, 0, 0, 228, 128, 0, 0, 228, 144, 1, 0, 0, 2, 0, 8, 15, 128, 0, 0, 228, 128, 255, 255, 0, 0 };
if (D3D_OK != bd->pd3dDevice->CreateVertexShader((DWORD*)VertexShaderByteCode, &bd->pVertexShader))
return false;
if (D3D_OK != bd->pd3dDevice->CreatePixelShader((DWORD*)PixelShaderByteCode, &bd->pPixelShader))
return false;
bd->IsShaderSupported = true;
ImGui::GetIO().BackendRendererName = "imgui_impl_dx9 (shader)";
return true;
}
static void ImGui_ImplDX9WithShader_InvalidateDeviceObjects()
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
if (bd->pInputLayout) { bd->pInputLayout->Release(); bd->pInputLayout = nullptr; }
if (bd->pVertexShader) { bd->pVertexShader->Release(); bd->pVertexShader = nullptr; }
if (bd->pPixelShader) { bd->pPixelShader->Release(); bd->pPixelShader = nullptr; }
bd->IsShaderSupported = false;
}
static bool ImGui_ImplDX9WithShader_SetRenderState(ImDrawData* draw_data)
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
if (!bd->IsShaderSupported)
return false;
IDirect3DDevice9* ctx = bd->pd3dDevice;
D3DMATRIX mat_projection = ImGui_ImplDX9_BuildProjectionMatrix(draw_data);
D3DVIEWPORT9 viewport = { 0, 0, (DWORD)draw_data->DisplaySize.x, (DWORD)draw_data->DisplaySize.y, 0.0f, 1.0f };
// [IA Stage]
ctx->SetVertexDeclaration(bd->pInputLayout);
ctx->SetStreamSource(0, bd->pVB, 0, sizeof(ImDrawVert));
ctx->SetStreamSourceFreq(0, 1); // no instantiated drawing
ctx->SetIndices(bd->pIB);
// [VS Stage]
ctx->SetVertexShaderConstantF(0, (float*)&mat_projection, 4); // constant buffer: float4x4 matrix
ctx->SetVertexShader(bd->pVertexShader);
// [RS Stage]
ctx->SetRenderState(D3DRS_FILLMODE, D3DFILL_SOLID); // rasterizer state
ctx->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
ctx->SetRenderState(D3DRS_SCISSORTESTENABLE, TRUE);
ctx->SetRenderState(D3DRS_MULTISAMPLEANTIALIAS, FALSE);
ctx->SetRenderState(D3DRS_ANTIALIASEDLINEENABLE, FALSE);
ctx->SetViewport(&viewport);
// [PS Stage]
ctx->SetSamplerState(0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP); // sampler state
ctx->SetSamplerState(0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP);
ctx->SetSamplerState(0, D3DSAMP_ADDRESSW, D3DTADDRESS_CLAMP);
ctx->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
ctx->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
ctx->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
ctx->SetPixelShader(bd->pPixelShader);
// [OM Stage]
ctx->SetRenderState(D3DRS_ZENABLE, D3DZB_FALSE); // depth stencil state
ctx->SetRenderState(D3DRS_STENCILENABLE, FALSE);
ctx->SetRenderState(D3DRS_ALPHABLENDENABLE, TRUE); // blend state
ctx->SetRenderState(D3DRS_SEPARATEALPHABLENDENABLE, TRUE);
ctx->SetRenderState(D3DRS_BLENDOP, D3DBLENDOP_ADD);
ctx->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
ctx->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);
ctx->SetRenderState(D3DRS_BLENDOPALPHA, D3DBLENDOP_ADD);
ctx->SetRenderState(D3DRS_SRCBLENDALPHA, D3DBLEND_ONE);
ctx->SetRenderState(D3DRS_DESTBLENDALPHA, D3DBLEND_INVSRCALPHA);
ctx->SetRenderState(D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_RED | D3DCOLORWRITEENABLE_GREEN | D3DCOLORWRITEENABLE_BLUE | D3DCOLORWRITEENABLE_ALPHA);
// [Fixed Pipeline] disable these features, especially lighting
ctx->SetRenderState(D3DRS_CLIPPING, FALSE);
ctx->SetRenderState(D3DRS_CLIPPLANEENABLE, 0);
ctx->SetRenderState(D3DRS_LIGHTING, FALSE);
ctx->SetRenderState(D3DRS_SPECULARENABLE, FALSE);
ctx->SetRenderState(D3DRS_POINTSPRITEENABLE, FALSE);
ctx->SetRenderState(D3DRS_FOGENABLE, FALSE);
ctx->SetRenderState(D3DRS_RANGEFOGENABLE, FALSE);
ctx->SetRenderState(D3DRS_ALPHATESTENABLE, FALSE);
return true;
}
static bool ImGui_ImplDX9WithShader_UploadBuffers(ImDrawData* draw_data)
{
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
// Create or resize buffers if needed
const DWORD usage = D3DUSAGE_DYNAMIC | D3DUSAGE_WRITEONLY;
const D3DFORMAT format = sizeof(ImDrawIdx) == 2 ? D3DFMT_INDEX16 : D3DFMT_INDEX32;
if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount)
{
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
while (bd->VertexBufferSize < draw_data->TotalVtxCount) { bd->VertexBufferSize = draw_data->TotalVtxCount + 5000; }
if (D3D_OK != bd->pd3dDevice->CreateVertexBuffer(bd->VertexBufferSize * sizeof(ImDrawVert), usage, 0, D3DPOOL_DEFAULT, &bd->pVB, nullptr))
return false;
}
if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount)
{
if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
while (bd->IndexBufferSize < draw_data->TotalIdxCount) { bd->IndexBufferSize = draw_data->TotalIdxCount + 10000; }
if (D3D_OK != bd->pd3dDevice->CreateIndexBuffer(bd->IndexBufferSize * sizeof(ImDrawIdx), usage, format, D3DPOOL_DEFAULT, &bd->pIB, nullptr))
return false;
}
// Copy and convert all vertices into a single contiguous buffer, convert colors to DX9 default format.
// FIXME-OPT: This is a minor waste of resource, the ideal is to:
// 1) to avoid repacking colors: #define IMGUI_USE_BGRA_PACKED_COLOR
// Copy vertex buffer
ImDrawVert* vtx_dst = nullptr;
if (D3D_OK != bd->pVB->Lock(0, (UINT)(draw_data->TotalVtxCount * sizeof(ImDrawVert)), (void**)&vtx_dst, D3DLOCK_DISCARD))
return false;
for (int n = 0; n < draw_data->CmdListsCount; n++)
{
const ImDrawList* cmd_list = draw_data->CmdLists[n];
#ifndef IMGUI_USE_BGRA_PACKED_COLOR
ImDrawVert* vtx_src = cmd_list->VtxBuffer.Data;
for (int i = 0; i < cmd_list->VtxBuffer.Size; i++)
{
vtx_dst->pos = vtx_src->pos;
vtx_dst->uv = vtx_src->uv;
vtx_dst->col = IMGUI_COL_TO_DX9_ARGB(vtx_src->col);
vtx_src++;
vtx_dst++;
}
#else
memcpy(vtx_dst, cmd_list->VtxBuffer.Data, cmd_list->VtxBuffer.Size * sizeof(ImDrawVert));
vtx_dst += cmd_list->VtxBuffer.Size;
#endif
}
if (D3D_OK != bd->pVB->Unlock())
return false;
// Copy index buffer
ImDrawIdx* idx_dst = nullptr;
if (D3D_OK != bd->pIB->Lock(0, (UINT)(draw_data->TotalIdxCount * sizeof(ImDrawIdx)), (void**)&idx_dst, D3DLOCK_DISCARD))
return false;
for (int n = 0; n < draw_data->CmdListsCount; n++)
{
const ImDrawList* cmd_list = draw_data->CmdLists[n];
memcpy(idx_dst, cmd_list->IdxBuffer.Data, cmd_list->IdxBuffer.Size * sizeof(ImDrawIdx));
idx_dst += cmd_list->IdxBuffer.Size;
}
if (D3D_OK != bd->pIB->Unlock())
return false;
return true;
}
static bool ImGui_ImplDX9WithShader_RenderDrawData(ImDrawData* draw_data)
{
// Avoid rendering when minimized
if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
return true;
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
if (!ImGui_ImplDX9WithShader_UploadBuffers(draw_data))
return false;
// Backup the DX9 state
IDirect3DStateBlock9* d3d9_state_block = nullptr;
if (D3D_OK != bd->pd3dDevice->CreateStateBlock(D3DSBT_ALL, &d3d9_state_block))
return false;
if (D3D_OK != d3d9_state_block->Capture())
{
d3d9_state_block->Release();
return false;
}
D3DMATRIX last_float4x4;
bd->pd3dDevice->GetVertexShaderConstantF(0, (float*)&last_float4x4, 4);
// Setup desired DX state
ImGui_ImplDX9WithShader_SetRenderState(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 != 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.)
if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
ImGui_ImplDX9WithShader_SetRenderState(draw_data);
else
pcmd->UserCallback(cmd_list, pcmd);
}
else
{
// Project scissor/clipping rectangles into framebuffer space
ImVec2 clip_min(pcmd->ClipRect.x - clip_off.x, pcmd->ClipRect.y - clip_off.y);
ImVec2 clip_max(pcmd->ClipRect.z - clip_off.x, pcmd->ClipRect.w - clip_off.y);
if (clip_max.x < clip_min.x || clip_max.y < clip_min.y)
continue;
// Apply Scissor/clipping rectangle, Bind texture, Draw
const RECT rect = { (LONG)clip_min.x, (LONG)clip_min.y, (LONG)clip_max.x, (LONG)clip_max.y };
bd->pd3dDevice->SetScissorRect(&rect);
bd->pd3dDevice->SetTexture(0, (IDirect3DTexture9*)pcmd->GetTexID());
bd->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 state
bd->pd3dDevice->SetVertexShaderConstantF(0, (float*)&last_float4x4, 4);
d3d9_state_block->Apply();
d3d9_state_block->Release();
return true;
}
// Functions
static void ImGui_ImplDX9_SetupRenderState(ImDrawData* draw_data)
{
@ -120,25 +433,11 @@ static void ImGui_ImplDX9_SetupRenderState(ImDrawData* draw_data)
bd->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
} } };
bd->pd3dDevice->SetTransform(D3DTS_WORLD, &mat_identity);
bd->pd3dDevice->SetTransform(D3DTS_VIEW, &mat_identity);
bd->pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat_projection);
}
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 = ImGui_ImplDX9_BuildProjectionMatrix(draw_data);
bd->pd3dDevice->SetTransform(D3DTS_WORLD, &mat_identity);
bd->pd3dDevice->SetTransform(D3DTS_VIEW, &mat_identity);
bd->pd3dDevice->SetTransform(D3DTS_PROJECTION, &mat_projection);
}
// Render function.
@ -148,8 +447,15 @@ void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data)
if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f)
return;
// Create and grow buffers if needed
// If available, render via programmable render pipeline
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
if (bd->IsShaderSupported)
{
ImGui_ImplDX9WithShader_RenderDrawData(draw_data);
return;
}
// Create and grow buffers if needed
if (!bd->pVB || bd->VertexBufferSize < draw_data->TotalVtxCount)
{
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
@ -357,6 +663,7 @@ bool ImGui_ImplDX9_CreateDeviceObjects()
return false;
if (!ImGui_ImplDX9_CreateFontsTexture())
return false;
ImGui_ImplDX9WithShader_CreateDeviceObjects();
return true;
}
@ -365,6 +672,7 @@ void ImGui_ImplDX9_InvalidateDeviceObjects()
ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData();
if (!bd || !bd->pd3dDevice)
return;
ImGui_ImplDX9WithShader_InvalidateDeviceObjects();
if (bd->pVB) { bd->pVB->Release(); bd->pVB = nullptr; }
if (bd->pIB) { bd->pIB->Release(); bd->pIB = nullptr; }
if (bd->FontTexture) { bd->FontTexture->Release(); bd->FontTexture = nullptr; ImGui::GetIO().Fonts->SetTexID(0); } // We copied bd->pFontTextureView to io.Fonts->TexID so let's clear that as well.