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(....);
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)
Pushed a few commits today to standardize a few things.
TL;DR; until now, trying to render a user image (via
ImGui::Image()orImDrawList::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
ImGuiPlatformIOstructure:So you can do:
Caveat
Note that technically the slight variation below of using
ImGui::Image()instead ofImDrawList::AddImage()is incorrect and even the previous manual equivalent always has been incorrect, even subtly so: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()orAddImage()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_ResetRenderStatewas 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 (nowplatform_io.DrawCallback_ResetRenderStateset 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:
ImDrawCallbacksignature made it awkward to pass all necessary render state and for the backend to implement complex feature.platform_io.RenderStatewhich 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 toImGui_ImplXXX_RenderStatepublic struct if there is a need. Most commonly they contain graphics command-lists. (btw the sampler stuff in backendImGui_ImplXXX_RenderStatestructures 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.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:
ImDrawCallback_ResetRenderStatein favor of usingImGui::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.ImGuiPlatformIOfor standard backend-agnostic draw callbacks. Those callbacksare 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 Linearplatform_io.DrawCallback_SetSamplerNearest; // Request to set current texture sampling to Nearest/PointNote that some backends might not support all callbacks.
AddCallback()user data default to Null for convenience.SDL_SCALE_MODEwhile rendering.(Linking to related issues: #9371 #3590 #8926 #2973 #7485 #7468 #6969 #5118 #7616 #9173 #8322 #7230 #5999 #6452 #5156 #7342 #7592 #7511, #9381)