Backends: Metal: add sampler states and DrawCallback_SetSamplerLinear / DrawCallback_SetSamplerNearest callbacks. (#9381, #9371, #9378)
diff --git a/backends/imgui_impl_metal.h b/backends/imgui_impl_metal.h
index 879fadc..8af2c8b 100644
--- a/backends/imgui_impl_metal.h
+++ b/backends/imgui_impl_metal.h
@@ -5,8 +5,6 @@
 //  [X] Renderer: User texture binding. Use 'MTLTexture' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
 //  [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
 //  [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
-// Missing features or Issues:
-//  [ ] Renderer: Missing support for DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest callbacks.
 
 // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
 // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
diff --git a/backends/imgui_impl_metal.mm b/backends/imgui_impl_metal.mm
index db2fcd3..652668c 100644
--- a/backends/imgui_impl_metal.mm
+++ b/backends/imgui_impl_metal.mm
@@ -5,8 +5,6 @@
 //  [X] Renderer: User texture binding. Use 'MTLTexture' as texture identifier. Read the FAQ about ImTextureID/ImTextureRef!
 //  [X] Renderer: Large meshes support (64k+ vertices) even with 16-bit indices (ImGuiBackendFlags_RendererHasVtxOffset).
 //  [X] Renderer: Texture updates support for dynamic font atlas (ImGuiBackendFlags_RendererHasTextures).
-// Missing features or Issues:
-//  [ ] Renderer: Missing support for DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest callbacks.
 
 // You can use unmodified imgui_impl_* files in your project. See examples/ folder for examples of using this.
 // Prefer including the entire imgui/ repository into your project (either as a copy or as a submodule), and only build the backends you need.
@@ -18,6 +16,7 @@
 
 // CHANGELOG
 // (minor and older changes stripped away, please see git history for details)
+//  2026-04-28: Added support for standard draw callbacks (in platform_io): DrawCallback_SetSamplerLinear and DrawCallback_SetSamplerNearest. (#9378, #9381)
 //  2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState (others are not yet supported). (#9378)
 //  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)
 //  2026-04-03: Metal: avoid redundant vertex buffer bind in SetupRenderState. (#9343)
@@ -79,6 +78,8 @@
 @interface MetalContext : NSObject
 @property (nonatomic, strong) id<MTLDevice>                 device;
 @property (nonatomic, strong) id<MTLDepthStencilState>      depthStencilState;
+@property (nonatomic, strong) id<MTLSamplerState>           samplerStateLinear;
+@property (nonatomic, strong) id<MTLSamplerState>           samplerStateNearest;
 @property (nonatomic, strong) FramebufferDescriptor*        framebufferDescriptor; // framebuffer descriptor for current frame; transient
 @property (nonatomic, strong) NSMutableDictionary*          renderPipelineStateCache; // pipeline cache; keyed on framebuffer descriptors
 @property (nonatomic, strong) NSMutableArray<MetalBuffer*>* bufferCache;
@@ -91,6 +92,7 @@
 struct ImGui_ImplMetal_Data
 {
     MetalContext*               SharedMetalContext;
+    id<MTLRenderCommandEncoder> RenderCommandEncoder;
 
     ImGui_ImplMetal_Data()      { memset((void*)this, 0, sizeof(*this)); }
 };
@@ -185,12 +187,15 @@
     [commandEncoder setVertexBytes:&ortho_projection length:sizeof(ortho_projection) atIndex:1];
 
     [commandEncoder setRenderPipelineState:renderPipelineState];
+    [commandEncoder setFragmentSamplerState:bd->SharedMetalContext.samplerStateLinear atIndex:0];
 
     [commandEncoder setVertexBuffer:vertexBuffer.buffer offset:vertexBufferOffset atIndex:0];
 }
 
 // Draw callbacks
-static void ImGui_ImplMetal_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*) {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way.
+static void ImGui_ImplMetal_DrawCallback_ResetRenderState(const ImDrawList*, const ImDrawCmd*)  {} // Intentionally empty. Used as an identifier for rendering loop to call its code. Simpler to implement this way.
+static void ImGui_ImplMetal_DrawCallback_SetSamplerLinear(const ImDrawList*, const ImDrawCmd*)  { ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); [bd->RenderCommandEncoder setFragmentSamplerState:bd->SharedMetalContext.samplerStateLinear atIndex:0]; }
+static void ImGui_ImplMetal_DrawCallback_SetSamplerNearest(const ImDrawList*, const ImDrawCmd*) { ImGui_ImplMetal_Data* bd = ImGui_ImplMetal_GetBackendData(); [bd->RenderCommandEncoder setFragmentSamplerState:bd->SharedMetalContext.samplerStateNearest atIndex:0]; }
 
 // Metal Render function.
 void ImGui_ImplMetal_RenderDrawData(ImDrawData* draw_data, id<MTLCommandBuffer> commandBuffer, id<MTLRenderCommandEncoder> commandEncoder)
@@ -228,6 +233,7 @@
     MetalBuffer* vertexBuffer = [ctx dequeueReusableBufferOfLength:vertexBufferLength device:commandBuffer.device];
     MetalBuffer* indexBuffer = [ctx dequeueReusableBufferOfLength:indexBufferLength device:commandBuffer.device];
 
+    bd->RenderCommandEncoder = commandEncoder;
     ImGui_ImplMetal_SetupRenderState(draw_data, commandBuffer, commandEncoder, renderPipelineState, vertexBuffer, 0);
 
     // Will project scissor/clipping rectangles into framebuffer space
@@ -306,6 +312,7 @@
             [sharedMetalContext.bufferCache addObject:indexBuffer];
         }
     }];
+    bd->RenderCommandEncoder = nil;
 }
 
 static void ImGui_ImplMetal_DestroyTexture(ImTextureData* tex)
@@ -382,7 +389,17 @@
     depthStencilDescriptor.depthWriteEnabled = NO;
     depthStencilDescriptor.depthCompareFunction = MTLCompareFunctionAlways;
     bd->SharedMetalContext.depthStencilState = [device newDepthStencilStateWithDescriptor:depthStencilDescriptor];
+    MTLSamplerDescriptor* samplerDescriptor = [[MTLSamplerDescriptor alloc] init];
+    samplerDescriptor.minFilter = MTLSamplerMinMagFilterLinear;
+    samplerDescriptor.magFilter = MTLSamplerMinMagFilterLinear;
+    samplerDescriptor.mipFilter = MTLSamplerMipFilterLinear;
+    bd->SharedMetalContext.samplerStateLinear = [device newSamplerStateWithDescriptor:samplerDescriptor];
+    samplerDescriptor.minFilter = MTLSamplerMinMagFilterNearest;
+    samplerDescriptor.magFilter = MTLSamplerMinMagFilterNearest;
+    samplerDescriptor.mipFilter = MTLSamplerMipFilterNearest;
+    bd->SharedMetalContext.samplerStateNearest = [device newSamplerStateWithDescriptor:samplerDescriptor];
 #ifdef IMGUI_IMPL_METAL_CPP
+    [samplerDescriptor release];
     [depthStencilDescriptor release];
 #endif
 
@@ -399,6 +416,8 @@
             ImGui_ImplMetal_DestroyTexture(tex);
 
     [bd->SharedMetalContext.renderPipelineStateCache removeAllObjects];
+    bd->SharedMetalContext.samplerStateLinear = nil;
+    bd->SharedMetalContext.samplerStateNearest = nil;
 }
 
 bool ImGui_ImplMetal_Init(id<MTLDevice> device)
@@ -415,6 +434,8 @@
 
     ImGuiPlatformIO& platform_io = ImGui::GetPlatformIO();
     platform_io.DrawCallback_ResetRenderState = ImGui_ImplMetal_DrawCallback_ResetRenderState;
+    platform_io.DrawCallback_SetSamplerLinear = ImGui_ImplMetal_DrawCallback_SetSamplerLinear;
+    platform_io.DrawCallback_SetSamplerNearest = ImGui_ImplMetal_DrawCallback_SetSamplerNearest;
 
     bd->SharedMetalContext = [[MetalContext alloc] init];
     bd->SharedMetalContext.device = device;
@@ -599,9 +620,9 @@
     "}\n"
     "\n"
     "fragment half4 fragment_main(VertexOut in [[stage_in]],\n"
-    "                             texture2d<half, access::sample> texture [[texture(0)]]) {\n"
-    "    constexpr sampler linearSampler(coord::normalized, min_filter::linear, mag_filter::linear, mip_filter::linear);\n"
-    "    half4 texColor = texture.sample(linearSampler, in.texCoords);\n"
+    "                             texture2d<half, access::sample> texture [[texture(0)]],\n"
+    "                             sampler textureSampler [[sampler(0)]]) {\n"
+    "    half4 texColor = texture.sample(textureSampler, in.texCoords);\n"
     "    return half4(in.color) * texColor;\n"
     "}\n";
 
diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt
index d7bffe7..5b12ba4 100644
--- a/docs/CHANGELOG.txt
+++ b/docs/CHANGELOG.txt
@@ -149,7 +149,7 @@
     - DX10:         Reset     SetSamplerLinear  SetSamplerNearest
     - DX11:         Reset     SetSamplerLinear  SetSamplerNearest
     - DX12:         Reset     SetSamplerLinear  SetSamplerNearest
-    - Metal:        Reset     *missing*         *missing*
+    - Metal:        Reset     SetSamplerLinear  SetSamplerNearest
     - OpenGL2:      Reset     SetSamplerLinear  SetSamplerNearest
     - OpenGL3+:     Reset     SetSamplerLinear  SetSamplerNearest
     - SDLGPU3:      Reset     SetSamplerLinear  SetSamplerNearest
@@ -157,7 +157,7 @@
     - SDLRenderer3: Reset     SetSamplerLinear  SetSamplerNearest
     - Vulkan:       Reset     SetSamplerLinear  SetSamplerNearest
     - WebGPU:       Reset     SetSamplerLinear  SetSamplerNearest
-    (Vulkan backend by @yaz0r, others by @ocornut)
+    (Vulkan backend by @yaz0r, Metal by @ssh4net, others by @ocornut)
   - GLFW: added a Win32-specific implementation of `ImGui_ImplGlfw_GetContentScaleXXXX`
     functions for legacy GLFW 3.2. (#9003)
   - Metal: avoid redundant vertex buffer bind in `SetupRenderState()`, which leads
diff --git a/imgui.h b/imgui.h
index 9699ebf..51be8c5 100644
--- a/imgui.h
+++ b/imgui.h
@@ -4018,11 +4018,11 @@
     // Written by some backends during ImGui_ImplXXXX_RenderDrawData() call to point backend_specific ImGui_ImplXXXX_RenderState* structure.
     void*       Renderer_RenderState;
 
-    // Standard draw callbacks
+    // Standard draw callbacks provided by renderer backend.
     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
-    //ImDrawCallback  DrawCallback_SetSamplerCustom;    // Request to set current texture sampling using Backend Specific data.
+    ImDrawCallback  DrawCallback_SetSamplerLinear;      // Request backend to set texture sampling to Linear.
+    ImDrawCallback  DrawCallback_SetSamplerNearest;     // Request backend to set texture sampling to Nearest/Point.
+    //ImDrawCallback  DrawCallback_SetSamplerCustom;    // Request backend to set texture sampling using Backend Specific data.
 
     //------------------------------------------------------------------
     // Output