IDI-Systems / UnrealImGui

Unreal plug-in that integrates Dear ImGui framework into Unreal Engine 4/5.
MIT License
121 stars 27 forks source link

Dont forget about `VtxOffset` When it comes to Draw Commands #26

Open kalebs-anotheraxiom opened 2 months ago

kalebs-anotheraxiom commented 2 months ago

I've just spent the last 12 hours diving deep into my own personal fork of UnrealImGui trying to figure out why my embedded NetImGui application was failing, So im here to share what I found.

Under normal circumstances and in 99.9% of cases when ImGui outputs Command Buffers the Vtx Offset will always be zero, However theres a situation where you can store the information like this

VtxBuffer : [a, b, c, d, e, f, g, H, I, J, K, L, M, N]
IdxBuffer : [0, 1, 2, 3, 4, 5, 6, 0, 1, 2, 3, 4, 5, 6 ]
             -------------------  -------------------
                Command One          Command Two
CmdBuffer : [ 
    { 
        VtxOffset: 0 
        IdxOffset: 0
    }, 
    { 
        VtxOffset: 7 
        IdxOffset: 7
    } 
]

Which in imguis internal rendering examples it takes VtxOffset into account and artifically adds 7 to all of the Idx's in the buffer between 7-13. Which would result in a draw call like

[a, b, c, d, e, f, g, H, I, J, K, L, M, N]

However in UnrealImGui's Application of this there is no such check for VtxOffset and it would actually render

[a, b, c, d, e, f, g, a, b, c, d, e, f, g]

What I did was modify the CopyIndexData implementation to accept a 4th VertexOffset value which does the appropriate offset

void FImGuiDrawList::CopyIndexData(TArray<SlateIndex>& OutIndexBuffer, const int32 StartIndex, const int32 NumElements, const int32 VertexOffset) const
{
    // Reset buffer.
    OutIndexBuffer.SetNumUninitialized(NumElements, false);

    // Copy elements (slow copy because of different sizes of ImDrawIdx and SlateIndex and because SlateIndex can
    // have different size on different platforms).
    for (int i = 0; i < NumElements; i++)
    {
        OutIndexBuffer[i] = ImGuiIndexBuffer[StartIndex + i] + VertexOffset;
    }
}

Modifying the call


DrawList.CopyIndexData(IndexBuffer, IndexBufferOffset, DrawCommand.NumElements, DrawCommand.VertexOffset);

As well as modifying the FImGuiDrawCommand

struct FImGuiDrawCommand
{
    uint32 NumElements;
    FSlateRect ClippingRect;
    TextureIndex TextureId;
    uint32 VertexOffset;
};

And The call that makes it inside of FImGuiDrawList

FImGuiDrawCommand GetCommand(int CommandNb, const FTransform2D& Transform) const
{
    const ImDrawCmd& ImGuiCommand = ImGuiCommandBuffer[CommandNb];
    return { ImGuiCommand.ElemCount, TransformRect(Transform, ImGuiInterops::ToSlateRect(ImGuiCommand.ClipRect)),
             ImGuiInterops::ToTextureIndex(ImGuiCommand.TextureId), ImGuiCommand.VtxOffset };
}

You can look at imgui_impl_dx11.cpp to see that this is meant to be handled

ctx->DrawIndexed(pcmd->ElemCount, pcmd->IdxOffset + global_idx_offset, pcmd->VtxOffset + global_vtx_offset);

virtual void STDMETHODCALLTYPE DrawIndexed( 
            /* [annotation] */ 
            _In_  UINT IndexCount,
            /* [annotation] */ 
            _In_  UINT StartIndexLocation,
            /* [annotation] */ 
            _In_  INT BaseVertexLocation) = 0;

Feel free to do whatever implementation you want but as it currently operates things will not behave properly in nastily subtle ways that are a pain to track down

jonpas commented 2 days ago

Thank you for this! Your implementation looks good.

The only thing I am missing is IndexBufferOffset, where did you get that from?