Skip to content

Commit 294ef4d

Browse files
committed
Add support for linear/nearest texture sampling
Introduce explicit support for selecting linear vs nearest samplers for preview and pixel-closeup textures. Adds three ImGui platform draw callbacks (ResetRenderState, SetSamplerLinear, SetSamplerNearest) and implements them in the Metal backend with dedicated sampler states; Metal rendering now tracks a current sampler state when processing draw callbacks. Adds queue_texture_sampler_callback(draw_list, linear) to enqueue sampler selection callbacks from UI code. Propagates new boolean flags (main_texture_linear, closeup_texture_linear) through renderer vtables and backend implementations (Metal, OpenGL, Vulkan) so backends can report which preview texture variant they provide. Vulkan texture types and creation code now record whether linear filtering is supported and simplified the ImGui texture registration to use view+layout, while ensuring linear_filter_supported is set. Header and API changes update function signatures accordingly. These changes enable correct nearest sampling for pixel closeups while keeping linear sampling for ordinary previews. Signed-off-by: Vlad <shaamaan@gmail.com>
1 parent f52174e commit 294ef4d

14 files changed

Lines changed: 162 additions & 109 deletions

src/imiv/external/imgui_impl_metal_imiv.mm

Lines changed: 60 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
// CHANGELOG
1919
// (minor and older changes stripped away, please see git history for details)
2020
// 2026-XX-XX: Metal: Added support for multiple windows via the ImGuiPlatformIO interface.
21+
// 2026-04-26: Metal: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest.
2122
// 2026-04-14: Metal: use a dedicated bufferCacheLock to avoid crashing when bufferCache is replaced by a new object while being used for @synchronize(). (#9367)
2223
// 2026-04-03: Metal: avoid redundant vertex buffer bind in SetupRenderState. (#9343)
2324
// 2026-03-19: Fixed issue in ImGui_ImplMetal_RenderDrawData() if ImTextureID_Invalid is defined to be != 0, which became the default since 2026-03-12. (#9295, #9310)
@@ -93,6 +94,8 @@ @interface MetalContext : NSObject
9394
@property (nonatomic, strong) id<MTLDevice> device;
9495
@property (nonatomic, strong) id<MTLDepthStencilState> depthStencilState;
9596
@property (nonatomic, strong) id<MTLSamplerState> defaultSamplerState;
97+
@property (nonatomic, strong) id<MTLSamplerState> linearSamplerState;
98+
@property (nonatomic, strong) id<MTLSamplerState> nearestSamplerState;
9699
@property (nonatomic, strong) FramebufferDescriptor*
97100
framebufferDescriptor; // framebuffer descriptor for current frame; transient
98101
@property (nonatomic, strong) NSMutableDictionary*
@@ -127,6 +130,26 @@ - (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length
127130
IM_DELETE(ImGui_ImplMetal_GetBackendData());
128131
}
129132

133+
// Draw callbacks. They are intentionally empty and used as stable identifiers
134+
// for the render loop, matching Dear ImGui's standard backend convention.
135+
static void
136+
ImGui_ImplMetal_DrawCallback_ResetRenderState(const ImDrawList*,
137+
const ImDrawCmd*)
138+
{
139+
}
140+
141+
static void
142+
ImGui_ImplMetal_DrawCallback_SetSamplerLinear(const ImDrawList*,
143+
const ImDrawCmd*)
144+
{
145+
}
146+
147+
static void
148+
ImGui_ImplMetal_DrawCallback_SetSamplerNearest(const ImDrawList*,
149+
const ImDrawCmd*)
150+
{
151+
}
152+
130153
static inline CFTimeInterval
131154
GetMachAbsoluteTimeInSeconds()
132155
{
@@ -175,7 +198,8 @@ - (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length
175198
bool
176199
ImGui_ImplMetal_Init(id<MTLDevice> device)
177200
{
178-
ImGuiIO& io = ImGui::GetIO();
201+
ImGuiIO& io = ImGui::GetIO();
202+
ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
179203
IMGUI_CHECKVERSION();
180204
IM_ASSERT(io.BackendRendererUserData == nullptr
181205
&& "Already initialized a renderer backend!");
@@ -193,6 +217,13 @@ - (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length
193217
bd->SharedMetalContext = [[MetalContext alloc] init];
194218
bd->SharedMetalContext.device = device;
195219

220+
platform_io.DrawCallback_ResetRenderState
221+
= ImGui_ImplMetal_DrawCallback_ResetRenderState;
222+
platform_io.DrawCallback_SetSamplerLinear
223+
= ImGui_ImplMetal_DrawCallback_SetSamplerLinear;
224+
platform_io.DrawCallback_SetSamplerNearest
225+
= ImGui_ImplMetal_DrawCallback_SetSamplerNearest;
226+
196227
ImGui_ImplMetal_InitMultiViewportSupport();
197228

198229
return true;
@@ -375,6 +406,8 @@ - (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length
375406
// Render command lists
376407
size_t vertexBufferOffset = 0;
377408
size_t indexBufferOffset = 0;
409+
id<MTLSamplerState> currentSamplerState
410+
= bd->SharedMetalContext.defaultSamplerState;
378411
for (const ImDrawList* draw_list : draw_data->CmdLists) {
379412
memcpy((char*)vertexBuffer.buffer.contents + vertexBufferOffset,
380413
draw_list->VtxBuffer.Data,
@@ -387,15 +420,26 @@ - (MetalBuffer*)dequeueReusableBufferOfLength:(NSUInteger)length
387420
const ImDrawCmd* pcmd = &draw_list->CmdBuffer[cmd_i];
388421
if (pcmd->UserCallback) {
389422
// User callback, registered via ImDrawList::AddCallback()
390-
// (ImDrawCallback_ResetRenderState is a special callback value used by the user to request the renderer to reset render state.)
391-
if (pcmd->UserCallback == ImDrawCallback_ResetRenderState)
423+
if (pcmd->UserCallback
424+
== ImGui_ImplMetal_DrawCallback_ResetRenderState) {
392425
ImGui_ImplMetal_SetupRenderState(draw_data, commandBuffer,
393426
commandEncoder,
394427
renderPipelineState,
395428
vertexBuffer,
396429
vertexBufferOffset);
397-
else
430+
currentSamplerState
431+
= bd->SharedMetalContext.defaultSamplerState;
432+
} else if (pcmd->UserCallback
433+
== ImGui_ImplMetal_DrawCallback_SetSamplerLinear) {
434+
currentSamplerState
435+
= bd->SharedMetalContext.linearSamplerState;
436+
} else if (pcmd->UserCallback
437+
== ImGui_ImplMetal_DrawCallback_SetSamplerNearest) {
438+
currentSamplerState
439+
= bd->SharedMetalContext.nearestSamplerState;
440+
} else {
398441
pcmd->UserCallback(draw_list, pcmd);
442+
}
399443
} else {
400444
// Project scissor/clipping rectangles into framebuffer space
401445
ImVec2 clip_min((pcmd->ClipRect.x - clip_off.x) * clip_scale.x,
@@ -431,9 +475,8 @@ ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x,
431475
[commandEncoder setScissorRect:scissorRect];
432476

433477
// Bind texture, Draw
434-
id<MTLSamplerState> sampler_state
435-
= bd->SharedMetalContext.defaultSamplerState;
436-
ImTextureID tex_id = pcmd->GetTexID();
478+
id<MTLSamplerState> sampler_state = currentSamplerState;
479+
ImTextureID tex_id = pcmd->GetTexID();
437480
if (tex_id != ImTextureID_Invalid) {
438481
id metal_object = (__bridge id)(void*)(intptr_t)(tex_id);
439482
if ([metal_object isKindOfClass:[MetalTexture class]]) {
@@ -578,7 +621,14 @@ ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x,
578621
samplerDescriptor.mipFilter = MTLSamplerMipFilterLinear;
579622
samplerDescriptor.sAddressMode = MTLSamplerAddressModeClampToEdge;
580623
samplerDescriptor.tAddressMode = MTLSamplerAddressModeClampToEdge;
581-
bd->SharedMetalContext.defaultSamplerState = [device
624+
bd->SharedMetalContext.linearSamplerState = [device
625+
newSamplerStateWithDescriptor:samplerDescriptor];
626+
bd->SharedMetalContext.defaultSamplerState
627+
= bd->SharedMetalContext.linearSamplerState;
628+
samplerDescriptor.minFilter = MTLSamplerMinMagFilterNearest;
629+
samplerDescriptor.magFilter = MTLSamplerMinMagFilterNearest;
630+
samplerDescriptor.mipFilter = MTLSamplerMipFilterNearest;
631+
bd->SharedMetalContext.nearestSamplerState = [device
582632
newSamplerStateWithDescriptor:samplerDescriptor];
583633
ImGui_ImplMetal_CreateDeviceObjectsForPlatformWindows();
584634
# ifdef IMGUI_IMPL_METAL_CPP
@@ -602,6 +652,8 @@ ImVec2 clip_max((pcmd->ClipRect.z - clip_off.x) * clip_scale.x,
602652
ImGui_ImplMetal_InvalidateDeviceObjectsForPlatformWindows();
603653
[bd->SharedMetalContext.renderPipelineStateCache removeAllObjects];
604654
bd->SharedMetalContext.defaultSamplerState = nil;
655+
bd->SharedMetalContext.linearSamplerState = nil;
656+
bd->SharedMetalContext.nearestSamplerState = nil;
605657
bd->SharedMetalContext.depthStencilState = nil;
606658
}
607659

src/imiv/imiv_image_view.cpp

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -238,12 +238,16 @@ draw_image_window_contents(ViewerState& viewer, PlaceholderUiState& ui_state,
238238
ImTextureRef closeup_texture_ref;
239239
bool has_main_texture = false;
240240
bool has_closeup_texture = false;
241+
bool main_texture_linear = false;
242+
bool closeup_texture_linear = false;
241243
bool image_canvas_hovered = false;
242244
bool image_canvas_active = false;
243245
PendingZoomRequest pending_zoom = shortcut_zoom_request;
244246
renderer_get_viewer_texture_refs(viewer, ui_state, main_texture_ref,
245-
has_main_texture, closeup_texture_ref,
246-
has_closeup_texture);
247+
has_main_texture, main_texture_linear,
248+
closeup_texture_ref,
249+
has_closeup_texture,
250+
closeup_texture_linear);
247251
ImageCoordinateMap coord_map;
248252
coord_map.source_width = viewer.image.width;
249253
coord_map.source_height = viewer.image.height;
@@ -305,8 +309,10 @@ draw_image_window_contents(ViewerState& viewer, PlaceholderUiState& ui_state,
305309
coord_map.image_rect_max);
306310
draw_list->PushClipRect(coord_map.viewport_rect_min,
307311
coord_map.viewport_rect_max, true);
312+
queue_texture_sampler_callback(draw_list, main_texture_linear);
308313
draw_list->AddImage(main_texture_ref, coord_map.image_rect_min,
309314
coord_map.image_rect_max);
315+
queue_texture_sampler_callback(draw_list, true);
310316
draw_list->PopClipRect();
311317
} else if (!has_main_texture) {
312318
const bool texture_loading = renderer_texture_is_loading(
@@ -541,10 +547,9 @@ draw_image_window_contents(ViewerState& viewer, PlaceholderUiState& ui_state,
541547
if (zoom_changed)
542548
queue_auto_subimage_from_zoom(viewer);
543549

544-
const OverlayPanelRect pixel_panel
545-
= draw_pixel_closeup_overlay(viewer, ui_state, coord_map,
546-
closeup_texture_ref,
547-
has_closeup_texture, fonts);
550+
const OverlayPanelRect pixel_panel = draw_pixel_closeup_overlay(
551+
viewer, ui_state, coord_map, closeup_texture_ref,
552+
has_closeup_texture, closeup_texture_linear, fonts);
548553
draw_area_probe_overlay(viewer, ui_state, coord_map, pixel_panel,
549554
fonts);
550555
} else if (viewer.last_error.empty()) {

src/imiv/imiv_probe_overlay.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -460,7 +460,8 @@ draw_pixel_closeup_overlay(const ViewerState& viewer,
460460
PlaceholderUiState& ui_state,
461461
const ImageCoordinateMap& map,
462462
ImTextureRef closeup_texture,
463-
bool has_closeup_texture, const AppFonts& fonts)
463+
bool has_closeup_texture,
464+
bool closeup_texture_linear, const AppFonts& fonts)
464465
{
465466
OverlayPanelRect panel;
466467
if (!ui_state.show_pixelview_window || !map.valid)
@@ -621,8 +622,10 @@ draw_pixel_closeup_overlay(const ViewerState& viewer,
621622
static_cast<float>(ybegin) / display_h);
622623
const ImVec2 uv_max(static_cast<float>(xend) / display_w,
623624
static_cast<float>(yend) / display_h);
625+
queue_texture_sampler_callback(draw_list, closeup_texture_linear);
624626
draw_list->AddImage(closeup_texture, closeup_min, closeup_max, uv_min,
625627
uv_max, IM_COL32_WHITE);
628+
queue_texture_sampler_callback(draw_list, true);
626629

627630
const float cell_w = closeup_window_size / patch_w;
628631
const float cell_h = closeup_window_size / patch_h;

src/imiv/imiv_probe_overlay.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ draw_pixel_closeup_overlay(const ViewerState& viewer,
3636
PlaceholderUiState& ui_state,
3737
const ImageCoordinateMap& map,
3838
ImTextureRef closeup_texture,
39-
bool has_closeup_texture, const AppFonts& fonts);
39+
bool has_closeup_texture,
40+
bool closeup_texture_linear, const AppFonts& fonts);
4041
void
4142
draw_area_probe_overlay(const ViewerState& viewer,
4243
const PlaceholderUiState& ui_state,

src/imiv/imiv_renderer.cpp

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -467,24 +467,26 @@ renderer_texture_is_loading(const RendererTexture& texture)
467467
}
468468

469469
void
470-
renderer_get_viewer_texture_refs(const ViewerState& viewer,
471-
const PlaceholderUiState& ui_state,
472-
ImTextureRef& main_texture_ref,
473-
bool& has_main_texture,
474-
ImTextureRef& closeup_texture_ref,
475-
bool& has_closeup_texture)
470+
renderer_get_viewer_texture_refs(
471+
const ViewerState& viewer, const PlaceholderUiState& ui_state,
472+
ImTextureRef& main_texture_ref, bool& has_main_texture,
473+
bool& main_texture_linear, ImTextureRef& closeup_texture_ref,
474+
bool& has_closeup_texture, bool& closeup_texture_linear)
476475
{
477476
main_texture_ref = ImTextureRef();
478477
closeup_texture_ref = ImTextureRef();
479478
has_main_texture = false;
480479
has_closeup_texture = false;
480+
main_texture_linear = false;
481+
closeup_texture_linear = false;
481482
const RendererBackendVTable* vtable = texture_dispatch_vtable(
482483
viewer.texture);
483484
if (vtable == nullptr || vtable->get_viewer_texture_refs == nullptr)
484485
return;
485486
vtable->get_viewer_texture_refs(viewer, ui_state, main_texture_ref,
486-
has_main_texture, closeup_texture_ref,
487-
has_closeup_texture);
487+
has_main_texture, main_texture_linear,
488+
closeup_texture_ref, has_closeup_texture,
489+
closeup_texture_linear);
488490
}
489491

490492
bool

src/imiv/imiv_renderer.h

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -176,12 +176,11 @@ bool
176176
renderer_texture_is_loading(const RendererTexture& texture);
177177

178178
void
179-
renderer_get_viewer_texture_refs(const ViewerState& viewer,
180-
const PlaceholderUiState& ui_state,
181-
ImTextureRef& main_texture_ref,
182-
bool& has_main_texture,
183-
ImTextureRef& closeup_texture_ref,
184-
bool& has_closeup_texture);
179+
renderer_get_viewer_texture_refs(
180+
const ViewerState& viewer, const PlaceholderUiState& ui_state,
181+
ImTextureRef& main_texture_ref, bool& has_main_texture,
182+
bool& main_texture_linear, ImTextureRef& closeup_texture_ref,
183+
bool& has_closeup_texture, bool& closeup_texture_linear);
185184

186185
bool
187186
renderer_create_texture(RendererState& renderer_state, const LoadedImage& image,

src/imiv/imiv_renderer_backend.h

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,11 @@ namespace Imiv {
1111
struct RendererBackendVTable {
1212
BackendKind kind = BackendKind::Auto;
1313
bool (*probe_runtime_support)(std::string& error_message) = nullptr;
14-
bool (*get_viewer_texture_refs)(const ViewerState& viewer,
15-
const PlaceholderUiState& ui_state,
16-
ImTextureRef& main_texture_ref,
17-
bool& has_main_texture,
18-
ImTextureRef& closeup_texture_ref,
19-
bool& has_closeup_texture)
14+
bool (*get_viewer_texture_refs)(
15+
const ViewerState& viewer, const PlaceholderUiState& ui_state,
16+
ImTextureRef& main_texture_ref, bool& has_main_texture,
17+
bool& main_texture_linear, ImTextureRef& closeup_texture_ref,
18+
bool& has_closeup_texture, bool& closeup_texture_linear)
2019
= nullptr;
2120
bool (*texture_is_loading)(const RendererTexture& texture) = nullptr;
2221
bool (*create_texture)(RendererState& renderer_state,

src/imiv/imiv_renderer_metal.mm

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1798,12 +1798,11 @@ bool render_preview_texture(MetalRendererBackendState& state,
17981798
error_message);
17991799
}
18001800

1801-
bool metal_get_viewer_texture_refs(const ViewerState& viewer,
1802-
const PlaceholderUiState& ui_state,
1803-
ImTextureRef& main_texture_ref,
1804-
bool& has_main_texture,
1805-
ImTextureRef& closeup_texture_ref,
1806-
bool& has_closeup_texture)
1801+
bool metal_get_viewer_texture_refs(
1802+
const ViewerState& viewer, const PlaceholderUiState& ui_state,
1803+
ImTextureRef& main_texture_ref, bool& has_main_texture,
1804+
bool& main_texture_linear, ImTextureRef& closeup_texture_ref,
1805+
bool& has_closeup_texture, bool& closeup_texture_linear)
18071806
{
18081807
const RendererTextureBackendState* state
18091808
= texture_backend_state<RendererTextureBackendState>(
@@ -1817,16 +1816,20 @@ bool metal_get_viewer_texture_refs(const ViewerState& viewer,
18171816
if (main_texture_id == ImTextureID_Invalid)
18181817
main_texture_id = state->preview_linear_tex_id;
18191818
if (main_texture_id != ImTextureID_Invalid) {
1820-
main_texture_ref = ImTextureRef(main_texture_id);
1821-
has_main_texture = true;
1819+
main_texture_ref = ImTextureRef(main_texture_id);
1820+
has_main_texture = true;
1821+
main_texture_linear = main_texture_id
1822+
== state->preview_linear_tex_id;
18221823
}
18231824

18241825
ImTextureID closeup_texture_id = state->preview_nearest_tex_id;
18251826
if (closeup_texture_id == ImTextureID_Invalid)
18261827
closeup_texture_id = state->preview_linear_tex_id;
18271828
if (closeup_texture_id != ImTextureID_Invalid) {
1828-
closeup_texture_ref = ImTextureRef(closeup_texture_id);
1829-
has_closeup_texture = true;
1829+
closeup_texture_ref = ImTextureRef(closeup_texture_id);
1830+
has_closeup_texture = true;
1831+
closeup_texture_linear = closeup_texture_id
1832+
== state->preview_linear_tex_id;
18301833
}
18311834
return has_main_texture || has_closeup_texture;
18321835
}

src/imiv/imiv_renderer_opengl.cpp

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1402,12 +1402,11 @@ void main()
14021402
return true;
14031403
}
14041404

1405-
bool opengl_get_viewer_texture_refs(const ViewerState& viewer,
1406-
const PlaceholderUiState& ui_state,
1407-
ImTextureRef& main_texture_ref,
1408-
bool& has_main_texture,
1409-
ImTextureRef& closeup_texture_ref,
1410-
bool& has_closeup_texture)
1405+
bool opengl_get_viewer_texture_refs(
1406+
const ViewerState& viewer, const PlaceholderUiState& ui_state,
1407+
ImTextureRef& main_texture_ref, bool& has_main_texture,
1408+
bool& main_texture_linear, ImTextureRef& closeup_texture_ref,
1409+
bool& has_closeup_texture, bool& closeup_texture_linear)
14111410
{
14121411
const RendererTextureBackendState* state
14131412
= texture_backend_state<RendererTextureBackendState>(
@@ -1421,16 +1420,19 @@ void main()
14211420
if (main_texture != 0) {
14221421
main_texture_ref = ImTextureRef(
14231422
static_cast<ImTextureID>(static_cast<intptr_t>(main_texture)));
1424-
has_main_texture = true;
1423+
has_main_texture = true;
1424+
main_texture_linear = main_texture == state->preview_linear_texture;
14251425
}
14261426

14271427
const GLuint closeup_texture = state->preview_nearest_texture != 0
14281428
? state->preview_nearest_texture
14291429
: state->preview_linear_texture;
14301430
if (closeup_texture != 0) {
1431-
closeup_texture_ref = ImTextureRef(static_cast<ImTextureID>(
1431+
closeup_texture_ref = ImTextureRef(static_cast<ImTextureID>(
14321432
static_cast<intptr_t>(closeup_texture)));
1433-
has_closeup_texture = true;
1433+
has_closeup_texture = true;
1434+
closeup_texture_linear = closeup_texture
1435+
== state->preview_linear_texture;
14341436
}
14351437
return has_main_texture || has_closeup_texture;
14361438
}

0 commit comments

Comments
 (0)