Skip to content

Per-texture filter option for add_static_texture / add_dynamic_texture / add_raw_texture#2637

Open
ricky-groenewald wants to merge 2 commits intohoffstadt:masterfrom
ricky-groenewald:feat/773-texture-filter-options
Open

Per-texture filter option for add_static_texture / add_dynamic_texture / add_raw_texture#2637
ricky-groenewald wants to merge 2 commits intohoffstadt:masterfrom
ricky-groenewald:feat/773-texture-filter-options

Conversation

@ricky-groenewald
Copy link
Copy Markdown


name: Pull Request
about: Create a pull request to help us improve
title: Per-texture filter option for add_static_texture / add_dynamic_texture / add_raw_texture
assignees: @hoffstadt @v-ein


Description:

Closes #773

Adds a filter keyword argument to add_static_texture, add_dynamic_texture, and add_raw_texture, letting the caller choose between bilinear (default) and nearest-neighbor sampling. Addresses the long-standing request to avoid blurred interpolation on pixel-art, discrete heatmaps, and other textures where point sampling is the right call.

with dpg.texture_registry():
    dpg.add_static_texture(w, h, data, tag="pixels",
                           filter=dpg.mvTextureFilter_Nearest)

dpg.add_image("pixels")          # renders with nearest
dpg.draw_image("pixels", ...)    # same

Two new module constants are exposed: mvTextureFilter_Linear (0, default) and mvTextureFilter_Nearest (1). The filter lives on the texture item, so every widget that references that texture inherits its mode. Affects add_image, add_image_button, draw_image, draw_image_quad, and add_image_series.

The filter is mutable after creation via configure_item, and round-trips through get_item_configuration:

dpg.configure_item("pixels", filter=dpg.mvTextureFilter_Linear)
dpg.get_item_configuration("pixels")["filter"]  # 0

Internally, the constants are defined as a file-scoped enum in src/mvUtilities.h so C++ call sites reference them by name.

Cross-platform. Filter state lives in a different architectural layer on each backend, so the implementation differs:

  • OpenGL (Linux): glTexParameteri(GL_TEXTURE_MIN/MAG_FILTER, ...) is set on the GL texture object. Mutation is handled by a render-thread ApplyTextureFilter call on the next draw() after configure_item marks _filterDirty.

  • D3D11 (Windows): imgui_impl_dx11.cpp uses a single global pTexSamplerLinear. DPG creates a second sampler (pTexSamplerNearest) on mvGraphics_D3D11 at init, and image-drawing widgets emit an ImDrawList::AddCallback pair around the draw: the first swaps PSSetSamplers to nearest, the second is the built-in ImDrawCallback_ResetRenderState sentinel which restores the backend's defaults. Since the callback reads _filter live every frame, mutation requires no GPU-side rebuild.

  • Metal (macOS): imgui_impl_metal.mm bakes a constexpr sampler linearSampler into its MSL fragment shader, so a CPU-side setFragmentSamplerState call has no effect without a shader change. Rather than patch the ImGui backend, DPG ships a small parallel Metal pipeline in mvGraphics_apple.mm with a bindable sampler. The shader mirrors the ImGui one and consumes the same vertex buffer and uniforms layout; the callback binds the DPG pipeline + a nearest MTLSamplerState, and ImDrawCallback_ResetRenderState hands rendering back to the ImGui pipeline. mvViewport_apple.mm stashes the current MTLRenderCommandEncoder on mvGraphics_Metal each frame so the callback can reach it. A static_assert on sizeof(ImDrawVert) guards against future vertex-format drift.

Fully backwards compatible. Default path is linear and has zero overhead — the ImDrawList::AddCallback pair is only emitted when the bound texture has _filter == mvTextureFilter_Nearest.

Tested end-to-end on all three backends:

  • macOS (Apple Silicon, Metal) [Native].
  • Windows 11 (ARM64 host via Parallels Desktop; x64 Python through Prism emulation → D3D11).
  • Ubuntu 24.04.3 LTS (ARM64 via Parallels Desktop; native OpenGL).

Each platform ran side-by-side comparisons of linear vs. nearest across add_image, draw_image, and add_image_series, plus runtime toggling via configure_item. Surrounding ImGui widgets (text, buttons, plot axes) render with linear filtering every frame regardless of any texture's filter state. The callback swap is scoped per image and ImDrawCallback_ResetRenderState restores the backend's defaults. Additional verification on bare-metal x64 Windows and native Linux hardware from contributors with access to those machines would be welcome.

Demo

texture_filter_toggle

The new per-texture filter option switched live via configure_item on six 4×4 checkerboards rendered at 200×200. No data re-upload. Only the sampling mode changes, so toggling is free regardless of texture size.

Concerning Areas:

  • Metal shader duplication. The DPG-owned pipeline in mvGraphics_apple.mm duplicates the vertex layout and blend state of ImGui's Metal shader. A future change to ImGui's ImDrawVert layout would require updating the DPG shader as the static_assert on sizeof(ImDrawVert) == 20 turns this into a build-time failure rather than a runtime one. The cleaner long-term fix is an upstream PR to imgui_impl_metal.mm making its sampler bindable, after which this parallel pipeline can be removed. Happy to follow up separately if desired.

  • Draw-call batching on nearest-filtered images. Each nearest-filtered draw emits two ImDrawList::AddCallback entries, which break ImGui's draw-call merging. Cost is bounded (a few hundred extra GPU commands for a few hundred nearest images — sub-millisecond on any modern GPU) and is only paid on the nearest path. The default linear path is unchanged.

  • ImFontAtlasFlags_NoBakedLines not set. The ImGui DX11 backend comments that bilinear sampling is required for baked antialiased lines (imgui_impl_dx11.cpp:578), but our sampler swap is scoped per-image-draw and ImDrawCallback_ResetRenderState restores linear before the AA-line textures are sampled. Worth flagging in case DX11 testing on native machines surfaces an interaction not reproducible on macOS-native host.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

add texture filter/interpolation options for nearest neighbor

1 participant