Skip to content

Standardized, backend-agnostic DrawCallbacks to change texture sampler (Linear vs Point filtering) & more? #9378

@ocornut

Description

@ocornut

Pushed a few commits today to standardize a few things.

TL;DR; until now, trying to render a user image (via ImGui::Image() or ImDrawList::AddImage()) using Point/Nearest filtering required registering and creating a custom callback which would be tied to the Rendering Backend.
I estimate that a vast majority of people messing with ImDrawCallback did it solely to change the sampler to point/nearest filtering, and it was always a goal of me to standardize this so it would be easier to write code that worked on all backend.

There are now standardized, named callbacks inside ImGuiPlatformIO structure:

// Standard draw callbacks
ImDrawCallback  DrawCallback_ResetRenderState;      // Request to reset the graphics/render state.
ImDrawCallback  DrawCallback_SetSamplerLinear;      // Request to set current texture sampling to Linear
ImDrawCallback  DrawCallback_SetSamplerNearest;     // Request to set current texture sampling to Nearest/Point

So you can do:

draw_list->AddCallback(ImGui::GetPlatformIO().DrawCallback_SetSamplerNearest);
draw_list->AddImage(....);
Image

Caveat

Note that technically the slight variation below of using ImGui::Image() instead of ImDrawList::AddImage() is incorrect and even the previous manual equivalent always has been incorrect, even subtly so:

draw_list->AddCallback(ImGui::GetPlatformIO().DrawCallback_SetSamplerNearest);
ImGui::Image(....); // or ImageButton();
draw_list->AddCallback(ImGui::GetPlatformIO().DrawCallback_SetSamplerLinear);

Why?
Because ImGui::Image() is a widget which might be configured to do e.g. a thick rounded border, and technically our draw list shape rendering might require bilinear filtering. So changing the filtering for the whole widget is not correct.
In practice you might not notice the difference. Otherwise use AddImage() + e.g. AddRect() for borders.

Internal Musing

This WILL be solved presumably by adding flags to e.g. Image() or AddImage() to specify "i want this image to be linear" and then it'll handle changing the sampler at the right time, and will make user code even simpler as they don't even have to know about those draw callbacks.

But being able to do this requires the widget code to know if the backend can actually support this feature, and the previous way of passing a standardized callback (previously ImDrawCallback_ResetRenderState was defined to (void*)-8) would not allow us deal with that nicely, because any new callback not supported by the backend would just crash. That's why I've moved everything to function pointers (now platform_io.DrawCallback_ResetRenderState set by renderer backend) which we can NULL check for as a feature check.

It's been suggested in the past to use function pointers, and that suggestion was always right, but I faced issues:

  • The ImDrawCallback signature made it awkward to pass all necessary render state and for the backend to implement complex feature.
  • Now this has been improved a little bit: (1) we can pass arbitrary amount of data to callbacks (since 1.91.4) and (2) as we always assumed that current ImGui context was bound during the _RenderDrawData() call, we can access render loop state indirectly via e.g. platform_io.RenderState which we exposed and improved in recent year versions, partly as a way to change the sampler but also do other things. We can freely expose extra rendering state to ImGui_ImplXXX_RenderState public struct if there is a need. Most commonly they contain graphics command-lists. (btw the sampler stuff in backend ImGui_ImplXXX_RenderState structures will be phased out as there are no point to use those over the new system). Anyhow this is the "complex" path, and from today it's not necessary to use that just to change a sampler.
  • I was falsely stuck on the fact that the if (pcmd->UserCallback == ImDrawCallback_ResetRenderState) { } check which very conveniently allowed us to implement specific "complex" features directly in the render loop without the hassle of passing too much data around to other functions via user data.... I somehow got myself to believe the trick wouldn't work if we changed our hard-coded define to function pointers... well it was a complete blind spot of me! The backend can still run the exact same check using legit function pointers! if (pcmd->UserCallback == ImGui_ImplSDLGPU3_DrawCallback_ResetRenderState) {}: it is totally fine for the backend to setup empty/dummy functions and use them as unique identifiers without calling the function, which allows them maintain that trick, simplify code for all backend and user's custom backends.

Changelog

Breaking:

  • DrawList:
    • Obsoleted ImDrawCallback_ResetRenderState in favor of using ImGui::GetPlatformIO().DrawCallback_ResetRenderState, which is part of our new standard draw callbacks. Redirecting the earlier value into the later one when set, so both old and new code should work.
  • DrawList:
    • Added room in ImGuiPlatformIO for standard backend-agnostic draw callbacks. Those callbacks
      are setup/provided by the backend and available in most of our standard backends.
      They allow backend-agnostic code from e.g. switching to a Nearest/Point sampler without
      messing with custom Renderer-specific callbacks.
      platform_io.DrawCallback_ResetRenderState; // Request to reset the graphics/render state.
      platform_io.DrawCallback_SetSamplerLinear; // Request to set current texture sampling to Linear
      platform_io.DrawCallback_SetSamplerNearest; // Request to set current texture sampling to Nearest/Point
      Note that some backends might not support all callbacks.
    • Made AddCallback() user data default to Null for convenience.
  • Backends:
    • Added support for new standardized draw callbacks in most backends:
Allegro5:     Reset     n/a               n/a              
DX9:          Reset     SetSamplerLinear  SetSamplerNearest
DX10:         Reset     SetSamplerLinear  SetSamplerNearest
DX11:         Reset     SetSamplerLinear  SetSamplerNearest
DX12:         Reset     SetSamplerLinear  SetSamplerNearest
Metal:        Reset     SetSamplerLinear  SetSamplerNearest
OpenGL2:      Reset     SetSamplerLinear  SetSamplerNearest
OpenGL3+:     Reset     SetSamplerLinear  SetSamplerNearest
SDLGPU3:      Reset     SetSamplerLinear  SetSamplerNearest
SDLRenderer2: Reset     n/a               n/a              
SDLRenderer3: Reset     SetSamplerLinear  SetSamplerNearest
Vulkan:       Reset     SetSamplerLinear  SetSamplerNearest
WebGPU:       Reset     SetSamplerLinear  SetSamplerNearest
  • Allegro5 cannot change sampler after bitmap creation.
  • SDLRenderer2 batching doesn't seem to support switching SDL_SCALE_MODE while rendering.

(Linking to related issues: #9371 #3590 #8926 #2973 #7485 #7468 #6969 #5118 #7616 #9173 #8322 #7230 #5999 #6452 #5156 #7342 #7592 #7511, #9381)

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions