imgui icon indicating copy to clipboard operation
imgui copied to clipboard

DirectX12 engine-ImGui backend compatibility issue

Open dmitrymosiychuck opened this issue 11 months ago • 13 comments

Version/Branch of Dear ImGui:

Latest Version(Any Version)

Back-ends:

imgui_impl_win32 + imgui_impl_dx12

Compiler, OS:

Windows 11, MSVS 2022

Full config/build information:

No response

Details:

Hi, I have a compatibility issue between my DX12 based renderer and ImGui DX12 backend.

My Dx12 renderer creates two(Num Frames In Flight) gpu visible descriptor heaps. Descriptors are allocated as a growing continues list. At the end of the frame heaps are being swapped and previous frame heap gets reset.

ImGui DX12 backend expects me to provide alloc/free callbacks. This is easy enough to do. I could allocate new descriptors at the top of current frame heap while free function can simply do nothing(Whole heap will be reset at the end of the frame anyway).

But ImGui DX12 backend creates one descriptor at the start(presumably for font texture) and this descriptor is supposed to be valid through application lifetime.

So this creates architectural problem...

What is the best strategy to circumvent this issue?

Should I use single descriptor heap and split it internally into two parts? Because that would be undesirable since it will make architecture more cumbersome.

Thank you in advance!

Screenshots/Video:

No response

Minimal, Complete and Verifiable Example code:

dmitrymosiychuck avatar Mar 22 '25 15:03 dmitrymosiychuck

Why not just creating a heap for the backend, aside from your existing ones?

ocornut avatar Mar 22 '25 15:03 ocornut

That was my first idea and I did just that=)

It was crashing in ID3D12GraphicsCommandList* cmd_list = (ID3D12GraphicsCommandList*)GAPI -> GetPlatformDevice3(); ImGui_ImplDX12_RenderDrawData( ImGui::GetDrawData(), cmd_list);

With error message: D3D12 ERROR: CGraphicsCommandList::SetGraphicsRootDescriptorTable: Specified GPU descriptor handle ptr=0x0 does not refer to a location in a descriptor heap.

I think it make sense. Since now some commands in ImGui draw list are using descriptors from dedicated heap(ImGui Text) and others are using descriptors from my ring-buffer like heaps.

And two SRV heaps couldn't be set at the same time...

Hope this make sense.

dmitrymosiychuck avatar Mar 22 '25 16:03 dmitrymosiychuck

Currently I'm trying to move font texture SRV creation from

ImGui_ImplDX12_Init To ImGui_ImplDX12_NewFrame

So that I could ask for a new valid descriptor for font texture at the frame start from now active gpu descriptor heap. But so far no luck=(

Does this approach even make sense or something more simple can be done?

dmitrymosiychuck avatar Mar 22 '25 16:03 dmitrymosiychuck

I don’t know, you may need to do a bit of research to help find a solution.

Note that upcoming change (see the dynamic_fonts topic pinned) will have the backend more dynamically add/remove textures.

ocornut avatar Mar 22 '25 16:03 ocornut

I’m still interested in #2697 as a possible way to solve that too.

ocornut avatar Mar 22 '25 16:03 ocornut

Hmm, is there any ETA on that?

dmitrymosiychuck avatar Mar 22 '25 16:03 dmitrymosiychuck

Anyway, thank you for looking into it.

dmitrymosiychuck avatar Mar 22 '25 16:03 dmitrymosiychuck

1 or 2 months for dynamic_fonts Meaning whatever solution you come up with now will need to work with that more dynamic logic. But it’d be great to find a solution and add support in the backend.

ocornut avatar Mar 22 '25 16:03 ocornut

Well, I don't want to change my internal heap allocation logic too much.

So it seems it leaves me with only option of making my idea from above post work(Allocating font SRV every frame)?

I would be infinitely glad if you could look at code and maybe give me some general guidelines about how to do that safely.

Anyway if I manage to solve it myself I'll keep you updated.

dmitrymosiychuck avatar Mar 22 '25 17:03 dmitrymosiychuck

Ok, I managed to make my temporary fix work.

This is what my ImGui_ImplDX12_NewFrame() functions looks now:

void ImGui_ImplDX12_NewFrame()
{
    ImGui_ImplDX12_Data* bd = ImGui_ImplDX12_GetBackendData();
    IM_ASSERT(bd != nullptr && "Context or backend not initialized! Did you call ImGui_ImplDX12_Init()?");

    if (!bd->pPipelineState)
        ImGui_ImplDX12_CreateDeviceObjects();



    // acquire SRV for font texture.
    // temporary fix.
    ImGui_ImplDX12_Texture* font_tex = &bd->FontTexture;

    // Allocate 1 SRV descriptor for the font texture
    ImGui_ImplDX12_InitInfo* init_info = &bd->InitInfo;

    IM_ASSERT(init_info->SrvDescriptorAllocFn != nullptr && init_info->SrvDescriptorFreeFn != nullptr);
    init_info->SrvDescriptorAllocFn(&bd->InitInfo, &bd->FontTexture.hFontSrvCpuDescHandle, &bd->FontTexture.hFontSrvGpuDescHandle);

    // Create texture view
    D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc;
    ZeroMemory(&srvDesc, sizeof(srvDesc));
    srvDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
    srvDesc.Texture2D.MipLevels = 1;
    srvDesc.Texture2D.MostDetailedMip = 0;
    srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
    bd->pd3dDevice->CreateShaderResourceView(font_tex->pTextureResource, &srvDesc, font_tex->hFontSrvCpuDescHandle);

    // Store our identifier
    ImGuiIO& io = ImGui::GetIO();
    io.Fonts->SetTexID((ImTextureID)font_tex->hFontSrvGpuDescHandle.ptr);
}

It is important to note that all function sections:

  • // Allocate 1 SRV descriptor for the font texture
  • // Create texture view
  • // Store our identifier must be moved here from their original locations.

Everything seems to work perfect now.

dmitrymosiychuck avatar Mar 23 '25 16:03 dmitrymosiychuck

My Dx12 renderer creates two(Num Frames In Flight) gpu visible descriptor heaps. Descriptors are allocated as a growing continues list. At the end of the frame heaps are being swapped and previous frame heap gets reset.

This is a very unusual way to handle descriptor heaps in D3D12. Most people just allocate a single giant GPU-visible descriptor heap and subdivide it as necessary.

EG: Allocate one section for resident descriptors and use a ring buffer for dynamic tables. If you want to avoid changing your architecture significantly you could just split the heap in N chunks, one for each frame.

(Although personally-speaking, these days I'd do away with dynamic tables entirely and just go full bindless.)

It won't really matter if you're only doing it once per frame, but changing the bound descriptor heap set is not recommended in general as it causes pipeline stalls.


As an aside, based on the workaround code you provided in your last comment: I assume you're populating your descriptor heaps with newly-created views every single frame?

Typically D3D12 engines with dynamic table support will create all of their views upon source creation in a CPU-only descriptor heap (IE: with D3D12_DESCRIPTOR_HEAP_FLAG_NONE) and copy them into their dynamic GPU-visible heap as needed using CopyDescriptorsSimple.

PathogenDavid avatar Mar 23 '25 21:03 PathogenDavid

Yes, instead of splitting one giant GPU-visible descriptor heap into N chunks I create N descriptor heaps and swap them every frame. Conceptually GPU-visible descriptors heaps are structured as stacks. There are no persistent GPU-visible descriptors in my design. Descriptors are copied via CopyDescriptorSimple from CPU-visible to GPU-visible heap before setting root tables.

This gets me few things:

  • Simplicity. Memory management in GPU-visible heap is simple and safe. Descriptors are only added to the top of the current frame heap.
  • More space to work with. 1m descriptors is a lot but when dividing into 2(3) parts it become not out of the realm of imagination to run out of them.

Yes, documentation states about pipeline stalls but in my testing performance effect of this is negligible. Anyway I plan to switch to bindless at some point and current design was used mostly to ease integration with my DX11-style abstract GAPI.

dmitrymosiychuck avatar Mar 24 '25 09:03 dmitrymosiychuck

I guess I can avoid creating SRV for font texture in ImGui_ImplDX12_NewFrame() by creating at the start and only copying it every frame. But then again, creating only a single SRV doesn't seemed that bad for performance. I will probably try it later.

dmitrymosiychuck avatar Mar 24 '25 09:03 dmitrymosiychuck