#9504

Request for feedback: improved ImDrawList functions (for incoming v1.93)

Openocornutopened 3mo ago

We are looking for feedback/early adopter to test a series of changes/refactor of the ImDrawList system!

  • Any issue? Any misunderstanding? Let us know.
  • If you try this please post some info/details here.

Pushed an experimental features/drawlist_193 branch. The branch is currently based over docking and will be rebased/force-pushed until settling. When this is done it will be moved to master branch.

We've been working on this since end of April. Most of the work courtesy of Mikko Mononen (@memononen). Reviewing/control freaking/fixing/tweaks by @ocornut. Occasionally inspired by older work bits from @thedmd, @potocpav.

This is expected to merge around Sept/October depending on feedback.

Code

New Wiki Guides!

ImDrawList Vector Rendering Reference https://github.com/ocornut/imgui/wiki/Draw-List (includes a precise list of differences between 1.92.9 and 1.93.0).

Pixel Perfect Anti-aliased Rendering https://github.com/ocornut/imgui/wiki/Pixel-Perfect-Rendering

How Anti-aliased Polyline Rendering is Implemented https://github.com/ocornut/imgui/wiki/Polyline-Rendering

Related Issues

TL;DR

  • Many consistency fixes.
  • Many optimizations.
  • New rendering options.
  • Polyline rendering improvements (sharp angles w/ thick strokes, bevel).
  • Most ImDrawList functions take additional flags + some of them can be altered for a given scope.
  • The new flags makes it easier to have pixel-perfect rendering, and will be facilitate creating nicer looking and more stylable widgets in the future.
  • You can start using AddLineH() / AddLineV() already if you are on 1.92.8. They will be faster, simpler call site, and reduce issues related to fixing the AddLine() offset. Every AddLine() that can be turned into AddLineH() / AddLineV() will make your life easier when we release this.

Draw flags include:

  • ImDrawFlags_StrokeInside: stroke is expanded right of the path.
  • ImDrawFlags_StrokeCenter: stroke is expanded equally to both sides of the path.
  • ImDrawFlags_StrokeCenterBiased: stroke is expanded to both sides, so that one side always has integer offset, odd sizes grow inside. This allows center like strokes which are pixel aligned.
  • ImDrawFlags_StrokeOutside: stroke is expanded left of the path
  • ImDrawFlags_StrokeLegacy: stroke position mimicks the old API behavior (may differ per function)
  • ImDrawFlags_Closed: This option closes the path by drawing a line from first to last point. Applies to AddPolyline()and PathStroke()
  • ImDrawFlags_JoinMiter: This option chooses a fast path in the rendering and skips logic that tried to handle very sharp corners. Can be used for well behaved shapes to speed up rendering. Applies to all stroked shapes.
  • ImDrawFlags_CapSquare: Expands the ends of an open path so that they extend hand line thickess beyond the end points. Applies to AddPolyline()and PathStroke().
  • ImDrawFlags_AAFill: Enable anti-aliasing for Filled shapes.
  • ImDrawFlags_AALines: Enable anti-aliasing for Strokes.
  • ImDrawFlags_AALineEnds: Enable anti-aliasing for Strokes Ends. Slightly slower but also smoother, and prevents aliasing and missed pixels. Applies to AddPolyline()and PathStroke().

Changelog

In 1.93.0, the ImDrawList rendering API has been improved to be more robust and more consistent over the whole range of shapes. The rendering now matches much closer to common vector drawing APIs, like html canvas. Improvements, like anti-aliased line endings and stroke position were added. The coordinates passed to the API are now consistently describing shape outlines.

For code using custom rendering: some details are now rendered slightly differently (even if generally more consistent). The difference in rendering is in the range of half a pixel, but that can lead to issues like some lines now look blurrier or more transparent. All ImGui widgets should appear identical or better.

The rendering of common shapes was optimized when the shapes fall on integer coordinates (which is most of the UI). This includes skipping anti-aliasing when not needed, or using textures instead of tessellated geometry to draw round corners.

New rendering code fixes some inconsistencies between graphics APIs (e.g. DirectX vs OpenGL) which are using different diamond exits rules. New code should work precisely the same regardless of graphics API.

New rendering code has better match between textured path and full polygon path.

New rendering code deals better with animating thickness and rounding.

While the current version doesn't make lots of visible use of the new features and facilities, they are among the pillars that will make it easier to create prettier contents, both for custom rendering and future versions of the library.

Breaking Changes:

  • ImDrawList:
    • AddLine(): removed the (+0.5f,+0.5f) offset that was sneakily added to input coordinates.
      • This fixes inconsistencies in the API and matches the PathXXX API.
      • By default, stroke thickness extends on both side of the given segment. e.g for a "pixel-perfect" looking line with thickness=1.0f, coords should be passed as center of each ends of the line.
      • Use ImDrawFlags_StrokeLegacy to use old offset if required. Or you can apply the offset manually!
      • Generally better to use the recently introduced AddLineH(), AddLineV() functions added in 1.92.8. READ IF YOU ARE MINDFUL OF PIXEL-PERFECTNESS IN YOUR CUSTOM RENDERING/WIDGETS:
      • TRANSITION GUIDE FOR AXIS-ALIGNED LINES:
        • When switching from legacy AddLine() values to AddLineV(), AddLineH() you can keep same inputs coordinates as before:
          • Old AddLine({x, y1}, {x, y2}, col) --> AddLineV(x, y1, y2, col); // Vertical line.
          • Old AddLine({x1, y}, {x2, y}, col) --> AddLineH(x1, x2, y, col); // Horizontal line.
          • This will be equivalent and faster for thickness=1.0f lines.
        • Since AddLine() use default stroke pos Center and AddLineV(), AddLineH() use Inside, thickness>1.0f strokes will differ. Using ImDrawFlags_StrokeCenter will match old result more closely (aka look centered), but is more likely to look blurry as it already did before.
        • Read table below about StrokePos and the "Pixel-Perfect Rendering" guide if you care about pixel-perfect lines.
      • TRANSITION GUIDE FOR DIAGONAL LINES:
        • Your lines will appear offset by -0.5f pixels on each axis. Being anti-aliased diagonal lines, they won't look particularly better or worse, just slightly offset. It'll likely only be noticeable if you carefully combined them with other primitives for a pixel-perfect result.
        • Old AddLine({x1, y1}, {x2, y2}, col) --> AddLine({x1 + 0.5f, y1 + 0.5f}, {x2+ 0.5f, y2 + 0.5f}, col); // Same or use ImDrawFlags_StrokeLegacy --> `AddLine({x1, y1}, {x2, y2}, col, thickness, ImDrawFlags_StrokeLegacy); // Offset by +0.5f + disable AA ends. This reapplies the old offset, and should get you exactly the same result you previously got for thickness=1.0f lines. But said result were previously sometimes ambiguous and renderer-dependent. (#3116, #3258, #2441)
        • Old code did not support anti-aliased ends, which could create gaps when rendering shapes out of multiple contiguous lines instead of using AddPolyline(). There is a possibility that you legacy code added fudge offsets here and there; which may not be necessary anymore if you use _AALineEnds, or if you combine rendering of contiguous lines using AddPolyline().
    • AddRect, AddCircle, AddNgon, AddEllipse, AddTriangle, AddQuad: defaulting to "inside" stroke.
      • All closed shapes with thickness=1.0f will appear identical.
      • The difference for thickness>1.0f shapes may be minimal since very large strokes were not well supported for widgets, but stroke will default inside widgets.
    • Merged ImDrawListFlags into ImDrawFlags. ImDrawListFlags was rarely used directly and generally setup by the ImGui context. Keep inline redirection enums (will obsolete).
      • ImDrawListFlags_AntiAliasedLines -> ImDrawFlags_AALines
      • ImDrawListFlags_AntiAliasedFill -> ImDrawFlags_AAFill
      • ImDrawListFlags_AllowVtxOffset -> ImDrawFlags_UseVtxOffset
      • ImDrawListFlags_TextNoPixelSnap -> ImDrawFlags_TextNoPixelSnap Unifying them allows easily using them for both per-primitives and scope alterations.
    • Marked ImDrawListFlags_AntiAliasedLinesUseTex and style.AntiAliasedLinesUseTex as obsolete, as the new line rendering code always needs textures.
    • Enabling ImFontAtlasFlags_NoBakedLines in the font atlas will now disable support for anti-aliased lines by clearing style.AntiAliasedLines/style.AntiAliasedLineEnds.

Other Changes:

  • ImDrawList:
    • Added flags to specify stroke position in all ImDrawList stroking functions: [@memononen]
      • ImDrawFlags_StrokeInside (default for Rect, Circle/Ellipse, Triangle, Quad, Ngon + AddLineH, AddLineV)
      • ImDrawFlags_StrokeCenter (default for Line, Polyline, Beziers, Paths function)
      • ImDrawFlags_StrokeCenterBiased (center w/ bias for pixel perfect alignment)
      • ImDrawFlags_StrokeOutside
      • ImDrawFlags_StrokeLegacy (match old behavior)
      • Legacy mode may be set as default for a given scope using PushDrawFlag(ImDrawFlags_StrokeLegacy, true).
      • Legacy code was generally applying +0.50f offsets which meant that,
        • with thickness=1.0f: the stroke would appear inside.
        • with thickness>1.0f: the stroke would start expanding on both sides but starting from that small initial offset.
        • with thickness>1.0f for even integer values, the stroke would look blurry.
        • TL;DR; the logic didn't make much sense for thickness>1.0f.
      • Defaulting to Inside for closed shapes ensure that rectangles and lines are never blurry, regardless of thickness, as long as input coordinates/sizes are integers. (#9359). Read our Wiki guides for more details: https://github.com/ocornut/imgui/wiki/Draw-List. Recap:
      ------------------------------------------------------------------------
       Thickness   Legacy     Inside    Outside    Center   CenterBiased
      ------------------------------------------------------------------------
       1.0f        sharp      sharp     sharp      blurry   sharp
       2.0f        blurry     sharp     sharp      sharp    sharp
       3.0f        sharp      sharp     sharp      blurry   sharp
      ------------------------------------------------------------------------
  • AddPolyline(), PathStroke(), AddTriangle(): the algorithm to render lines got overhauled. (#2183, #2964, #7972 + #9360, #6331, #4250, #4091) [@memononen]
    • Generally fixed rendering of thick strokes/paths.
    • The new stroke expansion now uses corner miter calculation, which keeps thickness along the line segments consistent. If the corner becomes too sharp, it will be beveled to avoid long spikes at corners.
    • There are also some robustness measures for the case where the line thickness is larger than the features being drawn.
    • Rendering artifacts are still possible when the path features are smaller than line thickness, but the artifacts should be more localized. The new implementation attempted to favor simple implementation and robustness over the corner case accuracy.
  • All functions: new rendering code fixes some inconsistencies between graphics APIs (e.g. DirectX vs OpenGL) which are using different diamond exits rules. New code should work precisely the same regardless of graphics API. (#3116, #3258, #2441) [@memononen]
  • Added ImDrawFlags_AALineEnds flag to enable anti-aliased line ends.
    • Most noticeable for thick lines.
    • Also helps fill minor gaps if combining multiple individual lines. For best looking continuous lines you should use AddPolyline(), it creates joins between line segments to avoid any gaps.
    • Added style.AntiAliasedLineEnds in ImGui to set default state.
    • Enabling AA Ends by default would be more "correct", but since it is very slightly slower and increases vertex/index data we decided to not enable it by default. This keeps situations such as "throw tens of thousands of lines at ImDrawList" optimal. You can opt-in for a given shape or in a scope using PushDrawFlag().
  • Added ImDrawFlags_JoinMiter flag to disable using beveled corner.
    • Closer to legacy rendering and slightly faster, but corners sharper than 90 degrees may expand far out.
    • Automatically used by AddRect(), AddCircle(), AddNgon(), AddEllipse() functions since we know that the input path does not contain sharper corners.
  • Added ImDrawFlags_CapSquare flag to extend lines ends using square caps. (#9360)
  • AddConvexPolyFilled()/PathFillConvex(): improved to handle input geometry more robustly. The rendering of sharp corners was improved. Earlier there were issues with sharp corners either creating long spikes, or messing up the adjacent edge's anti-aliasing. Now the anti-aliasing fringe calculation is more consistent, and sharp corners get bevelled to avoid spikes. There might still be issues when the smallest extent of a long thin polygon is less than a pixel. Also supports ImDrawFlags_JoinMiter, to facilitate matching a stroke using the same flag.
  • AddRect(), AddRectFilled(): various optimizations for rounded rectangles. (#1962)
    • Integer-aligned coordinates and thicknesses will automatically use baked textures, saving on both CPU and vertex/index data.
    • Enabled by default. Use ImFontAtlasFlags_NoBakedRoundCorners to disable baking round corners in font atlas.
    • Added ImDrawFlags_UseTexForRoundCorners flag to enable in scope or per-shape. Added for consistency and debugging, not really needed in practice.
  • AddRectFilled(): non-integer coordinates will now display anti-aliased edges. Previously, non-integer coordinates rendered with aliased edges snapped by the rasterizer. (#6971)
  • AddCircle, AddCircleFilled: fixed large circles being under-tessellated (when above ~140 with default CircleTessellationMaxError).
  • Improved debug-build performance in various locations.
  • Improved minor mismatches when overlapping strokes and filled shapes. For example, when using inside strokes,
    • AddRect() and AddRectFilled() with rounding now overlap better. (#3656)
    • AddCircle() and AddCircleFilled() now overlap better.
  • Added ImDrawList::PushDrawFlag()/PopDrawFlag() to alter certain flags for a scope and a given ImDrawList. (#9489)
    • e.g. PushDrawFlag(ImDrawFlags_StrokeLegacy, true); enforce legacy tweaks.
  • About ImDrawFlags_StrokeLegacy:
    • This is designed to emulate old behavior:
      • Most shapes use ImDrawFlags_StrokeCenter.
      • AddLine() add +0.5f,+0.5f offset to position.
      • AddCircle(), AddNgon() add a -0.5f offset to radius.
      • Closed shapes use miter corners.
      • Lines don't have anti-aliased ends.
    • But there are known difference between legacy code and ImDrawFlags_StrokeLegacy:
      • Thick shapes with acute angles will now preserve thickness better, which may make them appear as protruding a little more.
  • About disabling anti-aliasing:
    • Disabling anti-aliasing is now emulated by applying different UV coordinates.
      • It does not result in a performance increase or vertex/index data decrease any more (however the tendency is that the new code behaves better than old code).
      • But may be a useful stylistic choice in some circumstances.
    • Many thick shapes had broken corners with old no-AA code, and the old no-AA code was wildly different from the AA code. Not the case any more. (#288)
  • Widgets:
    • Checkbox, Menus: check marks use anti-aliased ends.
    • Tweaked rendering in various locations to avoid blurryness:
      • Windows: title-bar and menu-bar border (when thickness>1.0f).
      • SeparatorText: border (when thickness>1.0f).
      • Tree: hierarchy lines (when thickness>1.0f).
      • InputText: input caret (when thickness>1.0f).
      • TabBar: selected overline border (when thickness>1.0f).
  • Demo: Custom Rendering: exposed new flags; showcasing overlapping strokes and filled shapes; added an option to animate thickness; added new shapes to showcase new rendering features that previously hit limitations.

Bonus

Remaining Todo

  • Dynamically bake corners/thicknesses textures into texture atlas when possible. The currently pre-baked values make less sense on some setups (e.g. 200%/300% retinas). Dynamic baking would cater to so many more cases.

Activity

10
mswf3mo ago

Looks great, thank you so much for the efforts. Definitely fixes some inconsistencies that I've run in too.
Would it make sense to expose ImDrawFlags_StrokeCenterBiased with an Inner and Outer variant to round out uses of this flag? If I wanted my horizontal/vertical lines pixel perfect as the scale of them changes, I can imagine that I'd want control over how 2 perpendicular lines may both snap with a bias towards each other, without having to flip the input coordinates to make that happen? Or do you foresee the use to be so niche that you would actually want the user to deal with it by manually flipping input coordinates if they want to do that?

lailoken3mo ago

I'm just wondering about: Inside is right of the path direction. That is, if you draw clock wise closed shape, the inside will be visually inside the shape.

What about a closed shape that is not convex, something that is mostly clockwise, but with one or two nodes that are not? (Like when I render a bar chart with a slice extruded?

EDIT: Ok, I think I understand it now, but the clockwise threw me... I thought the side was determined by the angle between vertices, not simply the direction of the vector.

lailoken3mo ago

PS: The image with the three line ends did not render correctly.

lailoken3mo ago

Can miter limits be adjusted? Also do they have a "biased" rendering option as well? Not that I require this, I just recall what I've seen implemented in other libraries.

Having these consistent drawing routines will greatly clean up our graph code (and make it look much better)

ocornut3mo ago

Looks great, thank you so much for the efforts. Definitely fixes some inconsistencies that I've run in too. Would it make sense to expose ImDrawFlags_StrokeCenterBiased with an Inner and Outer variant to round out uses of this flag? If I wanted my horizontal/vertical lines pixel perfect as the scale of them changes, I can imagine that I'd want control over how 2 perpendicular lines may both snap with a bias towards each other, without having to flip the input coordinates to make that happen? Or do you foresee the use to be so niche that you would actually want the user to deal with it by manually flipping input coordinates if they want to do that?

Mikko might have a more informed opinion, but I feel it is good to encourage people to have their lines use consistent winding. If you do, you can switch to any of _StrokeCenterBiased/_Inside/_Outside consistently over those consecutive lines. Encouraging a swap of Inner vs Outer bias would be similar to encourage micro managing flags per segment. Let me know if I misunderstood your question.

PS: The image with the three line ends did not render correctly.

Please reach out by PM with details.

Can miter limits be adjusted?

Not currently but it is possible to add (it would likely be an ImDrawList state). I'd prefer to not expose many things unless there is a need.

Also do they have a "biased" rendering option as well?

Not sure what this means.

memononen3mo ago

Mikko might have a more informed opinion, but I feel it is good to encourage people to have their lines use consistent winding.

If you need to keep 2 parallel lines in sync for scaling, the changes are that you may need to deal with more constraints.

I ran into similar case when rendering double underlines, and in that case the most robust solution was the manually quantize the thickness + space + thickness separately to always make sure that the lines were same size and the space did not disappear. And the lines were rendering similarly to _StrokeInside. Your constraints are likely different.

If you have a specific case in mind, please let us know.

lailoken3mo ago

@ocornut the biased rendering there refers to the miter edges, in much the same way line edges/caps are AA vs not. (I assume biased means non-aa, and clipping to nearest pixel)

mswf3mo ago

I feel it is good to encourage people to have their lines use consistent winding. If you do, you can switch to any of _StrokeCenterBiased/_Inside/_Outside consistently over those consecutive lines. Encouraging a swap of Inner vs Outer bias would be similar to encourage micro managing flags per segment. Let me know if I misunderstood your question.

Thanks, that answered my question perfectly.

ocornut1mo ago

The code is now merged in main branches. Please read updated post above for details.

Links: 📘 ImDrawList Vector Rendering Reference ❤️ https://github.com/ocornut/imgui/wiki/Draw-List (includes a precise list of differences between 1.92.9 and 1.93.0).

🔍 Interactive 1.92.9 vs 1.93 Troubleshooting / Testbed ❤️ https://www.dearimgui.com/docs/drawlist_v193

📘 Pixel Perfect Anti-aliased Rendering ❤️ https://github.com/ocornut/imgui/wiki/Pixel-Perfect-Rendering

📘 How Anti-aliased Polyline Rendering is Implemented ❤️ https://github.com/ocornut/imgui/wiki/Polyline-Rendering


If you have questions/regressions in your app and want to discuss them, please open a new issue!

pthom1mo ago

Hello, Omar,

I just updated Dear ImGui Bundle to use this new version: I'm using the docking branch at 778992fac.

In short, the following libraries do work correctly: ImPlot, ImPlot3D, ImGuizmo, imgui-node-editor, imgui-knobs, imspinner, ImAnim, ImGuiColorTextEdit, ImmVision, the markdown renderer.

For a quick reference, here are some comparative screenshots with 1.92.9 and 1.93 WIP, with many widgets from the ecosystem: the differences are the expected ones: borders drawn inside, lines moved by a fraction of a pixel. I found nothing to worry about.


A quick note on the ecosystem status:

  • ImPlot: fixed upstream (epezent/implot#712, merged 2026-08-06).
  • ImGuizmo: fixed upstream (CedricGuillemet/ImGuizmo#434, merged 2026-10-10).
  • ImPlot3D: its author's PR brenocq/implot3d#195, still open. ImPlot3D reads TexUvLines, whose layout changed: without the PR, lines are wrong or invisible.
  • imspinner: one ImDrawListFlags_AntiAliasedFill use; no upstream fix yet.
  • ImAnim, ImGuiColorTextEdit: the renames only.
  • imgui-knobs: nothing.

For imgui-node-editor, there were some simple fixes and two more complex (more details here)

  • a blurry grid at 1x (AddLine() is now centered: fixed with AddLineV() / AddLineH()),
  • Outlines: the node editor draws its hover and selection outlines in a channel under the node background: With 1.93's default for closed shapes (the stroke inside), the background covers them, and the outlines nearly vanish. I had to use ImDrawFlags_StrokeCenter, and everything is now better:
  • _FringeScale handling: the node editor canvas used writes drawList->_FringeScale directly, to keep the AA sharp when zoomed. A fix is to call _SetPixelDensity() instead (this perhaps deserves a note in the changelog).