From 31ad115fb9df578c2e86bb253c98483e2961e9f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=92=80=E5=A2=83=E7=9F=B3?= Date: Sun, 14 Nov 2021 16:25:17 +0800 Subject: [PATCH] Backends: DX9: programmable rendering pipeline --- backends/imgui_impl_dx9.cpp | 439 ++++++++++++++++++++++++++++++++++++ 1 file changed, 439 insertions(+) diff --git a/backends/imgui_impl_dx9.cpp b/backends/imgui_impl_dx9.cpp index 9234cb01a8d5..85011c9dd9a4 100644 --- a/backends/imgui_impl_dx9.cpp +++ b/backends/imgui_impl_dx9.cpp @@ -46,6 +46,11 @@ struct ImGui_ImplDX9_Data int VertexBufferSize; int IndexBufferSize; + IDirect3DVertexDeclaration9* pInputLayout; + IDirect3DVertexShader9* pVertexShader; + IDirect3DPixelShader9* pPixelShader; + bool IsShaderPipeline; + ImGui_ImplDX9_Data() { memset(this, 0, sizeof(*this)); VertexBufferSize = 5000; IndexBufferSize = 10000; } }; @@ -70,6 +75,435 @@ static ImGui_ImplDX9_Data* ImGui_ImplDX9_GetBackendData() return ImGui::GetCurrentContext() ? (ImGui_ImplDX9_Data*)ImGui::GetIO().BackendRendererUserData : NULL; } +// Shader pipeline +bool ImGui_ImplDX9_Shader_CreatePipeline() +{ + 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 + #define CODE(b1, b2, b3, b4) 0x##b1, 0x##b2, 0x##b3, 0x##b4 + static const BYTE VertexShaderByteCode[] = { + // HLSL source code + /* + 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 2019 HLSL compiler, vs_2_0 + CODE(00,02,FE,FF), + CODE(FE,FF,1E,00), + CODE(43,54,41,42), + CODE(1C,00,00,00), + CODE(4B,00,00,00), + CODE(00,02,FE,FF), + CODE(01,00,00,00), + CODE(1C,00,00,00), + CODE(00,01,00,00), + CODE(44,00,00,00), + CODE(30,00,00,00), + CODE(02,00,00,00), + CODE(04,00,02,00), + CODE(34,00,00,00), + CODE(00,00,00,00), + CODE(6D,76,70,00), + CODE(03,00,03,00), + CODE(04,00,04,00), + CODE(01,00,00,00), + CODE(00,00,00,00), + CODE(76,73,5F,32), + CODE(5F,30,00,4D), + CODE(69,63,72,6F), + CODE(73,6F,66,74), + CODE(20,28,52,29), + CODE(20,48,4C,53), + CODE(4C,20,53,68), + CODE(61,64,65,72), + CODE(20,43,6F,6D), + CODE(70,69,6C,65), + CODE(72,20,31,30), + CODE(2E,31,00,AB), + CODE(1F,00,00,02), + CODE(00,00,00,80), + CODE(00,00,0F,90), + CODE(1F,00,00,02), + CODE(05,00,00,80), + CODE(01,00,0F,90), + CODE(1F,00,00,02), + CODE(0A,00,00,80), + CODE(02,00,0F,90), + CODE(05,00,00,03), + CODE(00,00,0F,80), + CODE(00,00,55,90), + CODE(01,00,E4,A0), + CODE(04,00,00,04), + CODE(00,00,0F,80), + CODE(00,00,E4,A0), + CODE(00,00,00,90), + CODE(00,00,E4,80), + CODE(02,00,00,03), + CODE(00,00,0F,C0), + CODE(00,00,E4,80), + CODE(03,00,E4,A0), + CODE(01,00,00,02), + CODE(00,00,03,E0), + CODE(01,00,E4,90), + CODE(01,00,00,02), + CODE(00,00,0F,D0), + CODE(02,00,E4,90), + CODE(FF,FF,00,00), + }; + static const BYTE PixelShaderByteCode[] = { + // HLSL source code + /* + 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 2019 HLSL compiler, ps_2_0 + CODE(00,02,FF,FF), + CODE(FE,FF,1F,00), + CODE(43,54,41,42), + CODE(1C,00,00,00), + CODE(4F,00,00,00), + CODE(00,02,FF,FF), + CODE(01,00,00,00), + CODE(1C,00,00,00), + CODE(00,01,00,00), + CODE(48,00,00,00), + CODE(30,00,00,00), + CODE(03,00,00,00), + CODE(01,00,02,00), + CODE(38,00,00,00), + CODE(00,00,00,00), + CODE(74,65,78,30), + CODE(00,AB,AB,AB), + CODE(04,00,0C,00), + CODE(01,00,01,00), + CODE(01,00,00,00), + CODE(00,00,00,00), + CODE(70,73,5F,32), + CODE(5F,30,00,4D), + CODE(69,63,72,6F), + CODE(73,6F,66,74), + CODE(20,28,52,29), + CODE(20,48,4C,53), + CODE(4C,20,53,68), + CODE(61,64,65,72), + CODE(20,43,6F,6D), + CODE(70,69,6C,65), + CODE(72,20,31,30), + CODE(2E,31,00,AB), + CODE(1F,00,00,02), + CODE(00,00,00,80), + CODE(00,00,03,B0), + CODE(1F,00,00,02), + CODE(00,00,00,80), + CODE(00,00,0F,90), + CODE(1F,00,00,02), + CODE(00,00,00,90), + CODE(00,08,0F,A0), + CODE(42,00,00,03), + CODE(00,00,0F,80), + CODE(00,00,E4,B0), + CODE(00,08,E4,A0), + CODE(05,00,00,03), + CODE(00,00,0F,80), + CODE(00,00,E4,80), + CODE(00,00,E4,90), + CODE(01,00,00,02), + CODE(00,08,0F,80), + CODE(00,00,E4,80), + CODE(FF,FF,00,00), + }; + #undef CODE + 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->IsShaderPipeline = true; + ImGui::GetIO().BackendRendererName = "imgui_impl_dx9 (shader)"; + return true; +} +void ImGui_ImplDX9_Shader_DestroyPipeline() +{ + ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); + if (bd->pInputLayout) { bd->pInputLayout->Release(); bd->pInputLayout = NULL; } + if (bd->pVertexShader) { bd->pVertexShader->Release(); bd->pVertexShader = NULL; } + if (bd->pPixelShader) { bd->pPixelShader->Release(); bd->pPixelShader = NULL; } + bd->IsShaderPipeline = false; +} +bool ImGui_ImplDX9_Shader_SetRenderState(ImDrawData* draw_data) +{ + ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); + if (!bd->IsShaderPipeline) + return false; + + IDirect3DDevice9* ctx = bd->pd3dDevice; + + // 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 or 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, + } } }; + // Setup viewport + const 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; +} +bool ImGui_ImplDX9_Shader_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 = NULL; } + 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, NULL)) + return false; + } + if (!bd->pIB || bd->IndexBufferSize < draw_data->TotalIdxCount) + { + if (bd->pIB) { bd->pIB->Release(); bd->pIB = NULL; } + 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, NULL)) + 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 = NULL; + 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 = NULL; + 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; +} +bool ImGui_ImplDX9_Shader_RenderDrawData(ImDrawData* draw_data) +{ + // Avoid rendering when minimized + if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f) + return true; // skip + + ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); + if (!bd->IsShaderPipeline) + return false; + + if (!ImGui_ImplDX9_Shader_UploadBuffers(draw_data)) + return true; // skip + + // Backup the DX9 state + IDirect3DStateBlock9* d3d9_state_block = NULL; + if (D3D_OK != bd->pd3dDevice->CreateStateBlock(D3DSBT_ALL, &d3d9_state_block)) + return true; + if (D3D_OK != d3d9_state_block->Capture()) + { + d3d9_state_block->Release(); + return true; + } + D3DMATRIX last_float4x4; + bd->pd3dDevice->GetVertexShaderConstantF(0, (float*)&last_float4x4, 4); + + // Setup desired DX state + ImGui_ImplDX9_Shader_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 != 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_Shader_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) { @@ -143,6 +577,9 @@ static void ImGui_ImplDX9_SetupRenderState(ImDrawData* draw_data) // Render function. void ImGui_ImplDX9_RenderDrawData(ImDrawData* draw_data) { + if (ImGui_ImplDX9_Shader_RenderDrawData(draw_data)) + return; + // Avoid rendering when minimized if (draw_data->DisplaySize.x <= 0.0f || draw_data->DisplaySize.y <= 0.0f) return; @@ -355,6 +792,7 @@ bool ImGui_ImplDX9_CreateDeviceObjects() return false; if (!ImGui_ImplDX9_CreateFontsTexture()) return false; + ImGui_ImplDX9_Shader_CreatePipeline(); return true; } @@ -363,6 +801,7 @@ void ImGui_ImplDX9_InvalidateDeviceObjects() ImGui_ImplDX9_Data* bd = ImGui_ImplDX9_GetBackendData(); if (!bd || !bd->pd3dDevice) return; + ImGui_ImplDX9_Shader_DestroyPipeline(); if (bd->pVB) { bd->pVB->Release(); bd->pVB = NULL; } if (bd->pIB) { bd->pIB->Release(); bd->pIB = NULL; } if (bd->FontTexture) { bd->FontTexture->Release(); bd->FontTexture = NULL; ImGui::GetIO().Fonts->SetTexID(NULL); } // We copied bd->pFontTextureView to io.Fonts->TexID so let's clear that as well.