From 71fc553bc48ae75f52b9911834cc83b094fae740 Mon Sep 17 00:00:00 2001 From: tqjxlm Date: Tue, 6 Oct 2026 14:09:10 +0100 Subject: [PATCH 001/120] Backends: Vulkan: support rendering inside passes with multiple color attachments. (#9574) We render to the first color attachment and leave the other ones untouched (color write mask 0). With dynamic rendering, create one blend attachment state per PipelineRenderingCreateInfo::colorAttachmentCount. Requires the independentBlend device feature. --- backends/imgui_impl_vulkan.cpp | 14 +++++++++++--- backends/imgui_impl_vulkan.h | 1 + docs/CHANGELOG.txt | 4 ++++ 3 files changed, 16 insertions(+), 3 deletions(-) diff --git a/backends/imgui_impl_vulkan.cpp b/backends/imgui_impl_vulkan.cpp index 80b9d58ab..7017a6fd1 100644 --- a/backends/imgui_impl_vulkan.cpp +++ b/backends/imgui_impl_vulkan.cpp @@ -27,6 +27,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-10-06: Added support for dynamic rendering with multiple color attachments (PipelineRenderingCreateInfo.colorAttachmentCount > 1): we render to the first one and leave the others untouched. (#9574) // 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) // 2026-09-03: Added for support for multiple Vulkan contexts with custom loaders. (#6616) // 2026-04-23: Added support for standard draw callbacks (in platform_io): DrawCallback_ResetRenderState, DrawCallback_SetSamplerLinear, DrawCallback_SetSamplerNearest. (#9378) @@ -1062,7 +1063,14 @@ static VkPipeline ImGui_ImplVulkan_CreatePipeline(VkDevice device, const VkAlloc ms_info.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; ms_info.rasterizationSamples = (info->MSAASamples != 0) ? info->MSAASamples : VK_SAMPLE_COUNT_1_BIT; - VkPipelineColorBlendAttachmentState color_attachment[1] = {}; + // Blend into the first color attachment. With dynamic rendering, other color attachments of the rendering are left untouched (requires independentBlend feature). + int color_attachment_count = 1; +#ifdef IMGUI_IMPL_VULKAN_HAS_DYNAMIC_RENDERING + if (bd->VulkanInitInfo.UseDynamicRendering && info->PipelineRenderingCreateInfo.colorAttachmentCount > 1) + color_attachment_count = (int)info->PipelineRenderingCreateInfo.colorAttachmentCount; +#endif + ImVector color_attachment; + color_attachment.resize(color_attachment_count, VkPipelineColorBlendAttachmentState()); // colorWriteMask = 0 color_attachment[0].blendEnable = VK_TRUE; color_attachment[0].srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; color_attachment[0].dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; @@ -1077,8 +1085,8 @@ static VkPipeline ImGui_ImplVulkan_CreatePipeline(VkDevice device, const VkAlloc VkPipelineColorBlendStateCreateInfo blend_info = {}; blend_info.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; - blend_info.attachmentCount = 1; - blend_info.pAttachments = color_attachment; + blend_info.attachmentCount = (uint32_t)color_attachment.Size; + blend_info.pAttachments = color_attachment.Data; ImVector dynamic_states = info->ExtraDynamicStates; dynamic_states.push_back(VK_DYNAMIC_STATE_VIEWPORT); diff --git a/backends/imgui_impl_vulkan.h b/backends/imgui_impl_vulkan.h index 0f2da5e2d..44c76c9a0 100644 --- a/backends/imgui_impl_vulkan.h +++ b/backends/imgui_impl_vulkan.h @@ -101,6 +101,7 @@ struct ImGui_ImplVulkan_PipelineInfo // - As an convenience, by setting DescriptorPoolSize > 0 the backend will create one for you. // - About dynamic rendering: // - When using dynamic rendering, set UseDynamicRendering=true + fill PipelineInfoMain.PipelineRenderingCreateInfo structure. +// - PipelineRenderingCreateInfo may describe multiple color attachments: we render to the first one and leave the others untouched. This requires the 'independentBlend' device feature. struct ImGui_ImplVulkan_InitInfo { uint32_t ApiVersion; // Fill with API version of Instance, e.g. VK_API_VERSION_1_3 or your value of VkApplicationInfo::apiVersion. May be lower than header version (VK_HEADER_VERSION_COMPLETE) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 203922ab1..9f595ba55 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -171,6 +171,10 @@ Other Changes: Note: this callback is only supported by selected rendering backends: most other graphics API don't have the equivalent of `SDL_SetTextureScaleMode()` and the callback won't work with them. - Vulkan: added for support for multiple Vulkan contexts with custom loaders. (#6616) [@lstalmir] + - Vulkan: added support for rendering inside a render pass with multiple color attachments + (e.g. when merged with other passes): we render to the first color attachment and leave the + other ones untouched. (with dynamic rendering, `PipelineRenderingCreateInfo` may describe multiple + color attachments. This requires the `independentBlend` device feature). (#9574) [@tqjxlm] - WebGPU: fixed passing non-integer sizes to `wgpuRenderPassEncoderSetViewport()` when `FramebufferScale` is >1.0f. (#9515, #8628) [@WayU] - WebGPU: update to build with wgpu-native 29.0+. (#9377, #9532, #9530) [@lucascompython, @ivan-enzhaev] From f502f9c980ce8c71ac0a8e326637a7bc71b06c03 Mon Sep 17 00:00:00 2001 From: tqjxlm Date: Tue, 6 Oct 2026 14:09:10 +0100 Subject: [PATCH 002/120] Backends: Metal, Metal4: support rendering inside passes with multiple color attachments. (#9574) Key the pipeline cache on all color attachment pixel formats of the render pass descriptor passed to backend's NewFrame(). --- backends/imgui_impl_metal.mm | 32 ++++++++++++++++++++++++++------ backends/imgui_impl_metal4.mm | 32 ++++++++++++++++++++++++++------ docs/CHANGELOG.txt | 6 ++++-- 3 files changed, 56 insertions(+), 14 deletions(-) diff --git a/backends/imgui_impl_metal.mm b/backends/imgui_impl_metal.mm index 55b33de14..43019b54f 100644 --- a/backends/imgui_impl_metal.mm +++ b/backends/imgui_impl_metal.mm @@ -16,6 +16,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-10-06: Added support for render passes with multiple color attachments: we render to the first one and leave the others untouched. (#9574) // 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) // 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) @@ -58,14 +59,16 @@ - (instancetype)initWithBuffer:(id)buffer; @end +#define IMGUI_IMPL_METAL_MAX_COLOR_ATTACHMENTS 8 // Metal's limit (see Metal Feature Set Tables). Not configurable. + // An object that encapsulates the data necessary to uniquely identify a // render pipeline state. These are used as cache keys. @interface FramebufferDescriptor : NSObject @property (nonatomic, assign) unsigned long sampleCount; -@property (nonatomic, assign) MTLPixelFormat colorPixelFormat; @property (nonatomic, assign) MTLPixelFormat depthPixelFormat; @property (nonatomic, assign) MTLPixelFormat stencilPixelFormat; - (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor*)renderPassDescriptor; +- (MTLPixelFormat)colorPixelFormatAtIndex:(NSUInteger)index; @end @interface MetalTexture : NSObject @@ -477,23 +480,33 @@ void ImGui_ImplMetal_Shutdown() #pragma mark - FramebufferDescriptor implementation @implementation FramebufferDescriptor +{ + MTLPixelFormat _colorPixelFormats[IMGUI_IMPL_METAL_MAX_COLOR_ATTACHMENTS]; +} + - (instancetype)initWithRenderPassDescriptor:(MTLRenderPassDescriptor*)renderPassDescriptor { if ((self = [super init])) { _sampleCount = renderPassDescriptor.colorAttachments[0].texture.sampleCount; - _colorPixelFormat = renderPassDescriptor.colorAttachments[0].texture.pixelFormat; + for (NSUInteger n = 0; n < IMGUI_IMPL_METAL_MAX_COLOR_ATTACHMENTS; n++) + _colorPixelFormats[n] = renderPassDescriptor.colorAttachments[n].texture.pixelFormat; _depthPixelFormat = renderPassDescriptor.depthAttachment.texture.pixelFormat; _stencilPixelFormat = renderPassDescriptor.stencilAttachment.texture.pixelFormat; } return self; } +- (MTLPixelFormat)colorPixelFormatAtIndex:(NSUInteger)index +{ + return _colorPixelFormats[index]; +} + - (nonnull id)copyWithZone:(nullable NSZone*)zone { FramebufferDescriptor* copy = [[FramebufferDescriptor allocWithZone:zone] init]; copy.sampleCount = self.sampleCount; - copy.colorPixelFormat = self.colorPixelFormat; + memcpy(copy->_colorPixelFormats, _colorPixelFormats, sizeof(_colorPixelFormats)); copy.depthPixelFormat = self.depthPixelFormat; copy.stencilPixelFormat = self.stencilPixelFormat; return copy; @@ -502,10 +515,12 @@ void ImGui_ImplMetal_Shutdown() - (NSUInteger)hash { NSUInteger sc = _sampleCount & 0x3; - NSUInteger cf = _colorPixelFormat & 0x3FF; + NSUInteger cf = _colorPixelFormats[0] & 0x3FF; NSUInteger df = _depthPixelFormat & 0x3FF; NSUInteger sf = _stencilPixelFormat & 0x3FF; NSUInteger hash = (sf << 22) | (df << 12) | (cf << 2) | sc; + for (NSUInteger n = 1; n < IMGUI_IMPL_METAL_MAX_COLOR_ATTACHMENTS; n++) + hash = hash * 31 + _colorPixelFormats[n]; return hash; } @@ -515,7 +530,7 @@ void ImGui_ImplMetal_Shutdown() if (![other isKindOfClass:[FramebufferDescriptor class]]) return NO; return other.sampleCount == self.sampleCount && - other.colorPixelFormat == self.colorPixelFormat && + memcmp(other->_colorPixelFormats, _colorPixelFormats, sizeof(_colorPixelFormats)) == 0 && other.depthPixelFormat == self.depthPixelFormat && other.stencilPixelFormat == self.stencilPixelFormat; } @@ -661,7 +676,7 @@ void ImGui_ImplMetal_Shutdown() pipelineDescriptor.fragmentFunction = fragmentFunction; pipelineDescriptor.vertexDescriptor = vertexDescriptor; pipelineDescriptor.rasterSampleCount = self.framebufferDescriptor.sampleCount; - pipelineDescriptor.colorAttachments[0].pixelFormat = self.framebufferDescriptor.colorPixelFormat; + pipelineDescriptor.colorAttachments[0].pixelFormat = [self.framebufferDescriptor colorPixelFormatAtIndex:0]; pipelineDescriptor.colorAttachments[0].blendingEnabled = YES; pipelineDescriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd; pipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha; @@ -669,6 +684,11 @@ void ImGui_ImplMetal_Shutdown() pipelineDescriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd; pipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne; pipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; + for (NSUInteger n = 1; n < IMGUI_IMPL_METAL_MAX_COLOR_ATTACHMENTS; n++) // Don't write to other color attachments, if any. + { + pipelineDescriptor.colorAttachments[n].pixelFormat = [self.framebufferDescriptor colorPixelFormatAtIndex:n]; + pipelineDescriptor.colorAttachments[n].writeMask = MTLColorWriteMaskNone; + } pipelineDescriptor.depthAttachmentPixelFormat = self.framebufferDescriptor.depthPixelFormat; pipelineDescriptor.stencilAttachmentPixelFormat = self.framebufferDescriptor.stencilPixelFormat; diff --git a/backends/imgui_impl_metal4.mm b/backends/imgui_impl_metal4.mm index 73f8c5905..2f9a53bdb 100644 --- a/backends/imgui_impl_metal4.mm +++ b/backends/imgui_impl_metal4.mm @@ -19,6 +19,7 @@ // CHANGELOG // (minor and older changes stripped away, please see git history for details) +// 2026-10-06: Added support for render passes with multiple color attachments: we render to the first one and leave the others untouched. (#9574) // 2026-09-07: Round framebuffer dimensions to the nearest integer instead of truncating them. (#9538, 9515, #8628) // 2026-07-07: Metal 4: Added metal-cpp support. (#9461) // 2026-07-02: Metal 4: Added new Metal 4 backend implementation. (#9458) @@ -46,14 +47,16 @@ struct ImGui_Metal4_ConstantData - (instancetype)initWithBuffer:(id)buffer; @end +#define IMGUI_IMPL_METAL_MAX_COLOR_ATTACHMENTS 8 // Metal's limit (see Metal Feature Set Tables). Not configurable. + // An object that encapsulates the data necessary to uniquely identify a // render pipeline state. These are used as cache keys. @interface FramebufferDescriptor : NSObject @property (nonatomic, assign) unsigned long sampleCount; -@property (nonatomic, assign) MTLPixelFormat colorPixelFormat; @property (nonatomic, assign) MTLPixelFormat depthPixelFormat; @property (nonatomic, assign) MTLPixelFormat stencilPixelFormat; - (instancetype)initWithRenderPassDescriptor:(MTL4RenderPassDescriptor*)renderPassDescriptor; +- (MTLPixelFormat)colorPixelFormatAtIndex:(NSUInteger)index; @end @interface MetalTexture : NSObject @@ -514,23 +517,33 @@ void ImGui_ImplMetal4_Shutdown() #pragma mark - FramebufferDescriptor implementation @implementation FramebufferDescriptor +{ + MTLPixelFormat _colorPixelFormats[IMGUI_IMPL_METAL_MAX_COLOR_ATTACHMENTS]; +} + - (instancetype)initWithRenderPassDescriptor:(MTL4RenderPassDescriptor*)renderPassDescriptor { if ((self = [super init])) { _sampleCount = renderPassDescriptor.colorAttachments[0].texture.sampleCount; - _colorPixelFormat = renderPassDescriptor.colorAttachments[0].texture.pixelFormat; + for (NSUInteger n = 0; n < IMGUI_IMPL_METAL_MAX_COLOR_ATTACHMENTS; n++) + _colorPixelFormats[n] = renderPassDescriptor.colorAttachments[n].texture.pixelFormat; _depthPixelFormat = renderPassDescriptor.depthAttachment.texture.pixelFormat; _stencilPixelFormat = renderPassDescriptor.stencilAttachment.texture.pixelFormat; } return self; } +- (MTLPixelFormat)colorPixelFormatAtIndex:(NSUInteger)index +{ + return _colorPixelFormats[index]; +} + - (nonnull id)copyWithZone:(nullable NSZone*)zone { FramebufferDescriptor* copy = [[FramebufferDescriptor allocWithZone:zone] init]; copy.sampleCount = self.sampleCount; - copy.colorPixelFormat = self.colorPixelFormat; + memcpy(copy->_colorPixelFormats, _colorPixelFormats, sizeof(_colorPixelFormats)); copy.depthPixelFormat = self.depthPixelFormat; copy.stencilPixelFormat = self.stencilPixelFormat; return copy; @@ -539,10 +552,12 @@ void ImGui_ImplMetal4_Shutdown() - (NSUInteger)hash { NSUInteger sc = _sampleCount & 0x3; - NSUInteger cf = _colorPixelFormat & 0x3FF; + NSUInteger cf = _colorPixelFormats[0] & 0x3FF; NSUInteger df = _depthPixelFormat & 0x3FF; NSUInteger sf = _stencilPixelFormat & 0x3FF; NSUInteger hash = (sf << 22) | (df << 12) | (cf << 2) | sc; + for (NSUInteger n = 1; n < IMGUI_IMPL_METAL_MAX_COLOR_ATTACHMENTS; n++) + hash = hash * 31 + _colorPixelFormats[n]; return hash; } @@ -552,7 +567,7 @@ void ImGui_ImplMetal4_Shutdown() if (![other isKindOfClass:[FramebufferDescriptor class]]) return NO; return other.sampleCount == self.sampleCount && - other.colorPixelFormat == self.colorPixelFormat && + memcmp(other->_colorPixelFormats, _colorPixelFormats, sizeof(_colorPixelFormats)) == 0 && other.depthPixelFormat == self.depthPixelFormat && other.stencilPixelFormat == self.stencilPixelFormat; } @@ -719,7 +734,7 @@ fragment half4 fragment_main(VertexOut in [[stage_in]], pipelineDescriptor.fragmentFunction = fragmentFunction; pipelineDescriptor.vertexDescriptor = vertexDescriptor; pipelineDescriptor.rasterSampleCount = self.framebufferDescriptor.sampleCount; - pipelineDescriptor.colorAttachments[0].pixelFormat = self.framebufferDescriptor.colorPixelFormat; + pipelineDescriptor.colorAttachments[0].pixelFormat = [self.framebufferDescriptor colorPixelFormatAtIndex:0]; pipelineDescriptor.colorAttachments[0].blendingEnabled = YES; pipelineDescriptor.colorAttachments[0].rgbBlendOperation = MTLBlendOperationAdd; pipelineDescriptor.colorAttachments[0].sourceRGBBlendFactor = MTLBlendFactorSourceAlpha; @@ -727,6 +742,11 @@ fragment half4 fragment_main(VertexOut in [[stage_in]], pipelineDescriptor.colorAttachments[0].alphaBlendOperation = MTLBlendOperationAdd; pipelineDescriptor.colorAttachments[0].sourceAlphaBlendFactor = MTLBlendFactorOne; pipelineDescriptor.colorAttachments[0].destinationAlphaBlendFactor = MTLBlendFactorOneMinusSourceAlpha; + for (NSUInteger n = 1; n < IMGUI_IMPL_METAL_MAX_COLOR_ATTACHMENTS; n++) // Don't write to other color attachments, if any. + { + pipelineDescriptor.colorAttachments[n].pixelFormat = [self.framebufferDescriptor colorPixelFormatAtIndex:n]; + pipelineDescriptor.colorAttachments[n].writeMask = MTLColorWriteMaskNone; + } pipelineDescriptor.depthAttachmentPixelFormat = self.framebufferDescriptor.depthPixelFormat; pipelineDescriptor.stencilAttachmentPixelFormat = self.framebufferDescriptor.stencilPixelFormat; diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 9f595ba55..12c9de64c 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -152,6 +152,9 @@ Other Changes: - Style Editor: fixed style/colors combo box applying style when opening Combo. - Backends: - QNX: added QNX Screen backend. (#9492) [@mgorchak-blackberry] + - Metal, Metal4: added support for rendering inside a render pass with multiple color + attachments (e.g. when merged with other passes): we render to the first color attachment + and leave the other ones untouched. (#9574) [@tqjxlm] - OpenGL2, OpenGL3: added support for `platform_io.DrawCallback_SetSamplerFromTex` draw callback to request using the sampler associated already associated to a texture. (#9378) - OpenGL2, OpenGL3: fixed `DrawCallback_ResetRenderState` handler not reseting all states @@ -173,8 +176,7 @@ Other Changes: - Vulkan: added for support for multiple Vulkan contexts with custom loaders. (#6616) [@lstalmir] - Vulkan: added support for rendering inside a render pass with multiple color attachments (e.g. when merged with other passes): we render to the first color attachment and leave the - other ones untouched. (with dynamic rendering, `PipelineRenderingCreateInfo` may describe multiple - color attachments. This requires the `independentBlend` device feature). (#9574) [@tqjxlm] + other ones untouched. (#9574) [@tqjxlm] - WebGPU: fixed passing non-integer sizes to `wgpuRenderPassEncoderSetViewport()` when `FramebufferScale` is >1.0f. (#9515, #8628) [@WayU] - WebGPU: update to build with wgpu-native 29.0+. (#9377, #9532, #9530) [@lucascompython, @ivan-enzhaev] From 6335dfb19c4bda4bee269d33403db4e19ecc17fc Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Tue, 28 Apr 2026 00:04:59 +0200 Subject: [PATCH 003/120] (Breaking) DrawList: removed half pixel offset from ImDrawList::AddLine(). (#9504) --- imgui.cpp | 6 ++++ imgui_draw.cpp | 90 ++++++++++++++++++++++++++++++++++++++++-------- imgui_internal.h | 17 +++++++++ 3 files changed, 99 insertions(+), 14 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index e1b1b7dce..e2954904c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1374,6 +1374,9 @@ static const ImVec2 TOOLTIP_DEFAULT_PIVOT_TOUCH = ImVec2(0.5f, 1.0f); // Multi // [SECTION] FORWARD DECLARATIONS //------------------------------------------------------------------------- +extern bool g_LEGACY_STROKES; +bool g_LEGACY_STROKES = false; + static void SetCurrentWindow(ImGuiWindow* window); static ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags); static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window); @@ -5921,6 +5924,9 @@ void ImGui::NewFrame() g.DebugBeginReturnValueCullDepth = -1; #endif + g_LEGACY_STROKES = g.IO.KeyShift; + ImGui::Checkbox("g_LEGACY_STROKES", &g_LEGACY_STROKES); + CallContextHooks(&g, ImGuiContextHookType_NewFramePost); } diff --git a/imgui_draw.cpp b/imgui_draw.cpp index e2085f6e6..05772300d 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1484,11 +1484,20 @@ void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDr } } +extern bool g_LEGACY_STROKES; + void ImDrawList::AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness) { if ((col & IM_COL32_A_MASK) == 0) return; - const ImVec2 points[2] = { ImVec2(p1.x + 0.5f, p1.y + 0.5f), ImVec2(p2.x + 0.5f, p2.y + 0.5f) }; + if (g_LEGACY_STROKES) + { + const ImVec2 points[2] = { ImVec2(p1.x + 0.5f, p1.y + 0.5f), ImVec2(p2.x + 0.5f, p2.y + 0.5f) }; + AddPolyline(points, 2, col, thickness); + return; + } + + const ImVec2 points[2] = { p1, p2 }; AddPolyline(points, 2, col, thickness); } @@ -1496,16 +1505,46 @@ void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float th { if ((col & IM_COL32_A_MASK) == 0) return; - const ImVec2 points[2] = { ImVec2(min_x + 0.5f, y + 0.5f), ImVec2(max_x + 0.5f, y + 0.5f) }; // Same as AddLine() above. - AddPolyline(points, 2, col, thickness); + + if (g_LEGACY_STROKES) + { + const ImVec2 points[2] = { ImVec2(min_x + 0.5f, y + 0.5f), ImVec2(max_x + 0.5f, y + 0.5f) }; // Same as AddLine() above. + AddPolyline(points, 2, col, thickness); + return; + } + + if (ImIsTruncated4(min_x, max_x, y, thickness)) + { + PrimReserve(6, 4); + PrimRect(ImVec2(min_x, y), ImVec2(max_x, y + thickness), col); + } + else + { + AddRectFilled(ImVec2(min_x, y), ImVec2(max_x, y + thickness), col); + } } void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float thickness) { if ((col & IM_COL32_A_MASK) == 0) return; - const ImVec2 points[2] = { ImVec2(x + 0.5f, min_y + 0.5f), ImVec2(x + 0.5f, max_y + 0.5f) }; // Same as AddLine() above. - AddPolyline(points, 2, col, thickness); + + if (g_LEGACY_STROKES) + { + const ImVec2 points[2] = { ImVec2(x + 0.5f, min_y + 0.5f), ImVec2(x + 0.5f, max_y + 0.5f) }; // Same as AddLine() above. + AddPolyline(points, 2, col, thickness); + return; + } + + if (ImIsTruncated4(x, min_y, max_y, thickness)) + { + PrimReserve(6, 4); + PrimRect(ImTrunc(ImVec2(x, min_y)), ImTrunc(ImVec2(x + thickness, max_y)), col); + } + else + { + AddRectFilled(ImVec2(x, min_y), ImVec2(x + thickness, max_y), col); + } } // p_min = upper-left, p_max = lower-right @@ -1525,10 +1564,19 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl if ((col & IM_COL32_A_MASK) == 0) return; - if (Flags & ImDrawListFlags_AntiAliasedLines) - PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.50f, 0.50f), rounding, flags); - else - PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.49f, 0.49f), rounding, flags); // Better looking lower-right corner and rounded non-AA shapes. + + if (g_LEGACY_STROKES) + { + if (Flags & ImDrawListFlags_AntiAliasedLines) + PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.50f, 0.50f), rounding, flags); + else + PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.49f, 0.49f), rounding, flags); // Better looking lower-right corner and rounded non-AA shapes. + PathStroke(col, thickness, ImDrawFlags_Closed); + return; + } + + const ImVec2 offset(thickness * 0.5f, thickness * 0.5f); + PathRect(p_min + offset, p_max - offset, rounding - thickness * 0.5f, flags); PathStroke(col, thickness, ImDrawFlags_Closed); } @@ -1615,10 +1663,14 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu if ((col & IM_COL32_A_MASK) == 0 || radius < 0.5f) return; + if (g_LEGACY_STROKES) + radius -= 0.5f; + else + radius -= thickness * 0.5f; if (num_segments <= 0) { // Use arc with automatic segment count - _PathArcToFastEx(center, radius - 0.5f, 0, IM_DRAWLIST_ARCFAST_SAMPLE_MAX, 0); + _PathArcToFastEx(center, radius, 0, IM_DRAWLIST_ARCFAST_SAMPLE_MAX, 0); _Path.Size--; } else @@ -1628,7 +1680,7 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; - PathArcTo(center, radius - 0.5f, 0.0f, a_max, num_segments - 1); + PathArcTo(center, radius, 0.0f, a_max, num_segments - 1); } PathStroke(col, thickness, ImDrawFlags_Closed); @@ -1664,9 +1716,14 @@ void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_ if ((col & IM_COL32_A_MASK) == 0 || num_segments <= 2) return; + if (g_LEGACY_STROKES) + radius -= 0.5f; + else + radius -= thickness * 0.5f; + // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; - PathArcTo(center, radius - 0.5f, 0.0f, a_max, num_segments - 1); + PathArcTo(center, radius, 0.0f, a_max, num_segments - 1); PathStroke(col, thickness, ImDrawFlags_Closed); } @@ -1688,12 +1745,17 @@ void ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 co if ((col & IM_COL32_A_MASK) == 0) return; + ImVec2 r; + if (g_LEGACY_STROKES) + r = radius; + else + r = ImVec2(radius.x - thickness * 0.5f, radius.y - thickness * 0.5f); if (num_segments <= 0) - num_segments = _CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)); // A bit pessimistic, maybe there's a better computation to do here. + num_segments = _CalcCircleAutoSegmentCount(ImMax(r.x, r.y)); // A bit pessimistic, maybe there's a better computation to do here. // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = IM_PI * 2.0f * ((float)num_segments - 1.0f) / (float)num_segments; - PathEllipticalArcTo(center, radius, rot, 0.0f, a_max, num_segments - 1); + PathEllipticalArcTo(center, r, rot, 0.0f, a_max, num_segments - 1); PathStroke(col, thickness, ImDrawFlags_Closed); } diff --git a/imgui_internal.h b/imgui_internal.h index 538005fc9..31ee350ca 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -548,6 +548,23 @@ inline float ImLinearRemapClamp(float s0, float s1, float d0, float d1, float x inline ImVec2 ImMul(const ImVec2& lhs, const ImVec2& rhs) { return ImVec2(lhs.x * rhs.x, lhs.y * rhs.y); } inline bool ImIsFloatAboveGuaranteedIntegerPrecision(float f) { return f <= -16777216 || f >= 16777216; } inline float ImExponentialMovingAverage(float avg, float sample, int n){ avg -= avg / (float)n; avg += sample / (float)n; return avg; } + +inline bool ImIsTruncated(float x) { return (float)(int)x == x; } +#ifdef IMGUI_ENABLE_SSE +// Returns true if all parameters are whole numbers. +inline bool ImIsTruncated4(float x, float y, float z, float w) +{ + __m128 v = _mm_set_ps(x, y, z, w); + __m128i i = _mm_cvttps_epi32(v); + return _mm_movemask_ps(_mm_cmpeq_ps(v, _mm_cvtepi32_ps(i))) == 0xF; +} +#else +inline bool ImIsTruncated4(float x, float y, float z, float w) +{ + return (float)(int)x == x && (float)(int)y == y && (float)(int)z == z && (float)(int)w == w; +} +#endif + IM_MSVC_RUNTIME_CHECKS_RESTORE // Helpers: Geometry From f73d8f40299a124ecc5222e477b0236180a475d3 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Tue, 28 Apr 2026 18:14:34 +0200 Subject: [PATCH 004/120] DrawList: added StrokePos flags: ImDrawFlags_StrokeInside, ImDrawFlags_StrokeOutside, ImDrawFlags_StrokeCenter, ImDrawFlags_StrokeLegacy. (#9504) --- imgui.h | 19 ++++++++----- imgui_draw.cpp | 72 +++++++++++++++++++++++++++++++++++++++++++------- 2 files changed, 76 insertions(+), 15 deletions(-) diff --git a/imgui.h b/imgui.h index 8c5be4a44..9fab91ad1 100644 --- a/imgui.h +++ b/imgui.h @@ -30,7 +30,7 @@ // Library Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals, e.g. '#if IMGUI_VERSION_NUM >= 12345') #define IMGUI_VERSION "1.93.0 WIP" -#define IMGUI_VERSION_NUM 19297 +#define IMGUI_VERSION_NUM 19298 #define IMGUI_HAS_TABLE // Added BeginTable() - from IMGUI_VERSION_NUM >= 18000 #define IMGUI_HAS_TEXTURES // Added ImGuiBackendFlags_RendererHasTextures - from IMGUI_VERSION_NUM >= 19198 @@ -3321,6 +3321,13 @@ enum ImDrawFlags_ ImDrawFlags_Closed = 1 << 9, // PathStroke(), AddPolyline(): specify that shape should be closed. //ImDrawFlags_Closed = 1, // Prior to 1.92.8 (May 2026), ImDrawFlags_Closed was guaranteed to be == 1<<0 == 1 for legacy compatibility reason. Hardcoded use of 1 or true should be replaced. + // Stroke position relative to the shape outline + ImDrawFlags_StrokeInside = 0 << 16, // Draw stroke inside of the shape outline (Default) + ImDrawFlags_StrokeCenter = 1 << 16, // Draw stroke at the center of the shape outline + ImDrawFlags_StrokeOutside = 2 << 16, // Draw stroke outside of the shape outline + ImDrawFlags_StrokeLegacy = 3 << 16, // Use legacy position + ImDrawFlags_StrokeMask_ = 0x03 << 16, + ImDrawFlags_InvalidMask_ = ~0x7FFFFFF0, // == 0x8000000F, }; @@ -3389,8 +3396,8 @@ struct ImDrawList // In future versions we will use textures to provide cheaper and higher-quality circles. // Use AddNgon() and AddNgonFilled() functions if you need to guarantee a specific number of sides. IMGUI_API void AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness = 1.0f); - IMGUI_API void AddLineH(float min_x, float max_x, float y, ImU32 col, float thickness = 1.0f); - IMGUI_API void AddLineV(float x, float min_y, float max_y, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddLineH(float min_x, float max_x, float y, ImU32 col, float thickness = 1.0f, ImDrawFlags flags = 0); + IMGUI_API void AddLineV(float x, float min_y, float max_y, ImU32 col, float thickness = 1.0f, ImDrawFlags flags = 0); IMGUI_API void AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding = 0.0f, float thickness = 1.0f, ImDrawFlags flags = 0); // a: upper-left, b: lower-right (== upper-left + size) IMGUI_API void AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding = 0.0f, ImDrawFlags flags = 0); // a: upper-left, b: lower-right (== upper-left + size) IMGUI_API void AddRectFilledMultiColor(const ImVec2& p_min, const ImVec2& p_max, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left); @@ -3398,11 +3405,11 @@ struct ImDrawList IMGUI_API void AddQuadFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col); IMGUI_API void AddTriangle(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness = 1.0f); IMGUI_API void AddTriangleFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col); - IMGUI_API void AddCircle(const ImVec2& center, float radius, ImU32 col, int num_segments = 0, float thickness = 1.0f); + IMGUI_API void AddCircle(const ImVec2& center, float radius, ImU32 col, int num_segments = 0, float thickness = 1.0f, ImDrawFlags flags = 0); IMGUI_API void AddCircleFilled(const ImVec2& center, float radius, ImU32 col, int num_segments = 0); - IMGUI_API void AddNgon(const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness = 1.0f); + IMGUI_API void AddNgon(const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness = 1.0f, ImDrawFlags flags = 0); IMGUI_API void AddNgonFilled(const ImVec2& center, float radius, ImU32 col, int num_segments); - IMGUI_API void AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot = 0.0f, int num_segments = 0, float thickness = 1.0f); + IMGUI_API void AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot = 0.0f, int num_segments = 0, float thickness = 1.0f, ImDrawFlags flags = 0); IMGUI_API void AddEllipseFilled(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot = 0.0f, int num_segments = 0); IMGUI_API void AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL); IMGUI_API void AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f, const ImVec4* cpu_fine_clip_rect = NULL); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 05772300d..416b1cb45 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1501,7 +1501,7 @@ void ImDrawList::AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float th AddPolyline(points, 2, col, thickness); } -void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float thickness) +void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float thickness, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) return; @@ -1513,6 +1513,11 @@ void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float th return; } + ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); + if (stroke_pos == ImDrawFlags_StrokeCenter) + y -= thickness * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeOutside) + y -= thickness; if (ImIsTruncated4(min_x, max_x, y, thickness)) { PrimReserve(6, 4); @@ -1524,7 +1529,7 @@ void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float th } } -void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float thickness) +void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float thickness, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) return; @@ -1536,6 +1541,11 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th return; } + ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); + if (stroke_pos == ImDrawFlags_StrokeCenter) + x += thickness * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeOutside) + x -= thickness; if (ImIsTruncated4(x, min_y, max_y, thickness)) { PrimReserve(6, 4); @@ -1576,7 +1586,20 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl } const ImVec2 offset(thickness * 0.5f, thickness * 0.5f); - PathRect(p_min + offset, p_max - offset, rounding - thickness * 0.5f, flags); + const ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); + if (stroke_pos == ImDrawFlags_StrokeInside) + PathRect(p_min + offset, p_max - offset, rounding - thickness * 0.5f, flags); + else if (stroke_pos == ImDrawFlags_StrokeCenter) + PathRect(p_min, p_max, rounding, flags); + else if (stroke_pos == ImDrawFlags_StrokeOutside) + PathRect(p_min - offset, p_max + offset, rounding + thickness * 0.5f, flags); + else if (stroke_pos == ImDrawFlags_StrokeLegacy) + { + if (Flags & ImDrawListFlags_AntiAliasedLines) + PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.50f, 0.50f), rounding, flags); + else + PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.49f, 0.49f), rounding, flags); // Better looking lower-right corner and rounded non-AA shapes. + } PathStroke(col, thickness, ImDrawFlags_Closed); } @@ -1658,15 +1681,26 @@ void ImDrawList::AddTriangleFilled(const ImVec2& p1, const ImVec2& p2, const ImV PathFillConvex(col); } -void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness) +void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0 || radius < 0.5f) return; if (g_LEGACY_STROKES) + { radius -= 0.5f; + } else - radius -= thickness * 0.5f; + { + const ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); + if (stroke_pos == ImDrawFlags_StrokeInside) + radius -= thickness * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeOutside) + radius += thickness * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeLegacy) + radius -= 0.5f; + } + if (num_segments <= 0) { // Use arc with automatic segment count @@ -1711,15 +1745,25 @@ void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col, } // Guaranteed to honor 'num_segments' -void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness) +void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0 || num_segments <= 2) return; if (g_LEGACY_STROKES) + { radius -= 0.5f; + } else - radius -= thickness * 0.5f; + { + const ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); + if (stroke_pos == ImDrawFlags_StrokeInside) + radius -= thickness * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeOutside) + radius += thickness * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeLegacy) + radius -= 0.5f; + } // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; @@ -1740,16 +1784,26 @@ void ImDrawList::AddNgonFilled(const ImVec2& center, float radius, ImU32 col, in } // Ellipse -void ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot, int num_segments, float thickness) +void ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot, int num_segments, float thickness, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) return; ImVec2 r; if (g_LEGACY_STROKES) + { r = radius; + } else - r = ImVec2(radius.x - thickness * 0.5f, radius.y - thickness * 0.5f); + { + r = radius; + ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); + if (stroke_pos == ImDrawFlags_StrokeInside) + r -= ImVec2(thickness * 0.5f, thickness * 0.5f); + else if (stroke_pos == ImDrawFlags_StrokeOutside) + r += ImVec2(thickness * 0.5f, thickness * 0.5f); + } + if (num_segments <= 0) num_segments = _CalcCircleAutoSegmentCount(ImMax(r.x, r.y)); // A bit pessimistic, maybe there's a better computation to do here. From 43d829885e7bb918460fe0e3f4046092c3f335d5 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 4 May 2026 18:38:39 +0200 Subject: [PATCH 005/120] DrawList: added ImDrawFlags_StrokeLegacy support to AddLine(). --- imgui.h | 2 +- imgui_draw.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/imgui.h b/imgui.h index 9fab91ad1..d3e4ed665 100644 --- a/imgui.h +++ b/imgui.h @@ -3395,7 +3395,7 @@ struct ImDrawList // In older versions (until Dear ImGui 1.77) the AddCircle functions defaulted to num_segments == 12. // In future versions we will use textures to provide cheaper and higher-quality circles. // Use AddNgon() and AddNgonFilled() functions if you need to guarantee a specific number of sides. - IMGUI_API void AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness = 1.0f, ImDrawFlags flags = 0); IMGUI_API void AddLineH(float min_x, float max_x, float y, ImU32 col, float thickness = 1.0f, ImDrawFlags flags = 0); IMGUI_API void AddLineV(float x, float min_y, float max_y, ImU32 col, float thickness = 1.0f, ImDrawFlags flags = 0); IMGUI_API void AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding = 0.0f, float thickness = 1.0f, ImDrawFlags flags = 0); // a: upper-left, b: lower-right (== upper-left + size) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 416b1cb45..8f27e0506 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1486,11 +1486,11 @@ void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDr extern bool g_LEGACY_STROKES; -void ImDrawList::AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness) +void ImDrawList::AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) return; - if (g_LEGACY_STROKES) + if (g_LEGACY_STROKES || (flags & ImDrawFlags_StrokeMask_) == ImDrawFlags_StrokeLegacy) { const ImVec2 points[2] = { ImVec2(p1.x + 0.5f, p1.y + 0.5f), ImVec2(p2.x + 0.5f, p2.y + 0.5f) }; AddPolyline(points, 2, col, thickness); From 37f16412d59a0cb9d283e0ae1c93f2ef6d8799b5 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Tue, 28 Apr 2026 18:28:22 +0200 Subject: [PATCH 006/120] Fixed layout/pixel-perfect issues in various widgets (w/ g_LEGACY_STROKES toggle). (#9504) --- imgui.cpp | 16 ++++++++++++ imgui_tables.cpp | 22 +++++++++++++--- imgui_widgets.cpp | 65 +++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 89 insertions(+), 14 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index e2954904c..549973af2 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -7310,8 +7310,16 @@ static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window) } if (g.Style.FrameBorderSize > 0 && !(window->Flags & ImGuiWindowFlags_NoTitleBar)) { + if (g_LEGACY_STROKES) + { float y = window->Pos.y + window->TitleBarHeight - 1; window->DrawList->AddLineH(window->Pos.x + border_size * 0.5f, window->Pos.x + window->Size.x - border_size * 0.5f, y, border_col, g.Style.FrameBorderSize); + } + else + { + float y = window->Pos.y + window->TitleBarHeight - g.Style.FrameBorderSize; + window->DrawList->AddLineH(window->Pos.x + border_size, window->Pos.x + window->Size.x - border_size, y, border_col, g.Style.FrameBorderSize); + } } } @@ -7379,7 +7387,12 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar menu_bar_rect.ClipWith(window->Rect()); // Soft clipping, in particular child window don't have minimum size covering the menu bar so this is useful for them. window->DrawList->AddRectFilled(menu_bar_rect.Min, menu_bar_rect.Max, GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawFlags_RoundCornersTop); if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y) + { + if (g_LEGACY_STROKES) window->DrawList->AddLineH(menu_bar_rect.Min.x + window_border_size * 0.5f, menu_bar_rect.Max.x - window_border_size * 0.5f, menu_bar_rect.Max.y, GetColorU32(ImGuiCol_Border), style.FrameBorderSize); + else + window->DrawList->AddLineH(menu_bar_rect.Min.x + window_border_size, menu_bar_rect.Max.x - window_border_size, menu_bar_rect.Max.y - style.FrameBorderSize, GetColorU32(ImGuiCol_Border), style.FrameBorderSize); + } } // Scrollbars @@ -18326,7 +18339,10 @@ void ImGui::DebugDrawLineExtents(ImU32 col) float line_y1 = (window->DC.IsSameLine ? window->DC.CursorPosPrevLine.y : window->DC.CursorPos.y); float line_y2 = line_y1 + (window->DC.IsSameLine ? window->DC.PrevLineSize.y : window->DC.CurrLineSize.y); window->DrawList->AddLineH(curr_x - 5.0f, curr_x + 5.0f, line_y1, col, 1.0f); + if (g_LEGACY_STROKES) window->DrawList->AddLineV(curr_x - 0.5f, line_y1, line_y2, col, 1.0f); + else + window->DrawList->AddLineV(curr_x, line_y1, line_y2, col, 1.0f); window->DrawList->AddLineH(curr_x - 5.0f, curr_x + 5.0f, line_y2, col, 1.0f); } diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 5ba6cb28f..469311bcf 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -2947,6 +2947,8 @@ static ImU32 TableGetColumnBorderCol(ImGuiTable* table, int order_n, int column_ return table->BorderColorLight; } +extern bool g_LEGACY_STROKES; + // FIXME-TABLE: This is a mess, need to redesign how we render borders (as some are also done in TableEndRow) void ImGui::TableDrawBorders(ImGuiTable* table) { @@ -2961,7 +2963,11 @@ void ImGui::TableDrawBorders(ImGuiTable* table) // Draw inner border and resizing feedback ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent); const float border_size = TABLE_BORDER_SIZE; - const float draw_y1 = ImMax(table->InnerRect.Min.y, (table->FreezeRowsCount >= 1 ? table->InnerRect.Min.y : table->WorkRect.Min.y) + table->AngledHeadersHeight) + ((table->Flags & ImGuiTableFlags_BordersOuterH) ? 1.0f : 0.0f); + float draw_y1; + if (g_LEGACY_STROKES) + draw_y1 = ImMax(table->InnerRect.Min.y, (table->FreezeRowsCount >= 1 ? table->InnerRect.Min.y : table->WorkRect.Min.y) + table->AngledHeadersHeight) + ((table->Flags & ImGuiTableFlags_BordersOuterH) ? 1.0f : 0.0f); + else + draw_y1 = ImMax(table->InnerRect.Min.y, (table->FreezeRowsCount >= 1 ? table->InnerRect.Min.y : table->WorkRect.Min.y) + table->AngledHeadersHeight) + ((table->Flags & ImGuiTableFlags_BordersOuterH) ? border_size : 0.0f); const float draw_y2_body = table->InnerRect.Max.y; const float draw_y2_head = table->IsUsingHeaders ? ImMin(table->InnerRect.Max.y, (table->FreezeRowsCount >= 1 ? table->InnerRect.Min.y : table->WorkRect.Min.y) + table_instance->LastTopHeadersRowHeight) : draw_y1; if (table->Flags & ImGuiTableFlags_BordersInnerV) @@ -3018,7 +3024,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) else if (table->Flags & ImGuiTableFlags_BordersOuterV) { inner_drawlist->AddLineV(outer_border.Min.x, outer_border.Min.y, outer_border.Max.y, outer_col, border_size); - inner_drawlist->AddLineV(outer_border.Max.x, outer_border.Min.y, outer_border.Max.y, outer_col, border_size); + inner_drawlist->AddLineV(outer_border.Max.x - border_size, outer_border.Min.y, outer_border.Max.y, outer_col, border_size); } else if (table->Flags & ImGuiTableFlags_BordersOuterH) { @@ -3029,9 +3035,15 @@ void ImGui::TableDrawBorders(ImGuiTable* table) if ((table->Flags & ImGuiTableFlags_BordersInnerH) && table->RowPosY2 < table->OuterRect.Max.y) { // Draw bottom-most row border between it is above outer border. + const float h_inset = (table->Flags & ImGuiTableFlags_BordersOuterV) ? border_size : 0.0f; const float border_y = table->RowPosY2; if (border_y >= table->BgClipRect.Min.y && border_y < table->BgClipRect.Max.y) + { + if (g_LEGACY_STROKES) inner_drawlist->AddLineH(table->BorderX1, table->BorderX2, border_y, table->BorderColorLight, border_size); + else + inner_drawlist->AddLineH(table->BorderX1 + h_inset, table->BorderX2 - h_inset, border_y, table->BorderColorLight, border_size); + } } inner_drawlist->PopClipRect(); @@ -3624,7 +3636,11 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label if (pass == 1) { // Draw border - draw_list->AddLine(bg_shape[0] - ImVec2(TABLE_BORDER_SIZE * 0.5f, 0.0f), bg_shape[3] - ImVec2(TABLE_BORDER_SIZE * 0.5f, 0.0f), TableGetColumnBorderCol(table, order_n, column_n), TABLE_BORDER_SIZE); + const float border_size = TABLE_BORDER_SIZE; + if (g_LEGACY_STROKES) + draw_list->AddLine(bg_shape[0] - ImVec2(border_size * 0.5f, 0.0f), bg_shape[3] - ImVec2(border_size * 0.5f, 0.0f), TableGetColumnBorderCol(table, order_n, column_n), border_size); + else + draw_list->AddLine(bg_shape[0] + ImVec2(border_size * 0.5f, 0.0f), bg_shape[3] + ImVec2(border_size * 0.5f, 0.0f), TableGetColumnBorderCol(table, order_n, column_n), border_size); } } PopClipRect(); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index ceabc13ba..8413344a8 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -903,6 +903,8 @@ bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir) return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), ImGuiButtonFlags_None); } +extern bool g_LEGACY_STROKES; + // Button to close a window bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos) { @@ -932,7 +934,7 @@ bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos) window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col); RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact); const ImU32 cross_col = GetColorU32(ImGuiCol_Text); - const ImVec2 cross_center = bb.GetCenter() - ImVec2(0.5f, 0.5f); + const ImVec2 cross_center = g_LEGACY_STROKES ? (bb.GetCenter() - ImVec2(0.5f, 0.5f)) : bb.GetCenter(); const float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f; const float cross_thickness = 1.0f * (float)(int)g.Style._MainScale; // FIXME-DPI window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, +cross_extent), cross_center + ImVec2(-cross_extent, -cross_extent), cross_col, cross_thickness); @@ -1757,7 +1759,11 @@ void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end const float sep1_x1 = pos.x; const float sep2_x2 = bb.Max.x; - const float seps_y = ImTrunc((bb.Min.y + bb.Max.y) * 0.5f + 0.999f); + float seps_y; + if (g_LEGACY_STROKES) + seps_y = ImTrunc((bb.Min.y + bb.Max.y) * 0.5f + 0.999f); + else + seps_y = ImTrunc((bb.Min.y + bb.Max.y) * 0.5f + (-separator_thickness * 0.5f + 1.0f) + 0.999f); // Align hline vertically, and snap to pixels. const float label_avail_w = ImMax(0.0f, sep2_x2 - sep1_x1 - padding.x * 2.0f); const ImVec2 label_pos(pos.x + padding.x + ImMax(0.0f, (label_avail_w - label_size.x - extra_w) * style.SeparatorTextAlign.x), pos.y + text_baseline_y); // FIXME-ALIGN @@ -5759,7 +5765,11 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ state->CursorAnim += io.DeltaTime; bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f; ImVec2 cursor_screen_pos = ImTrunc(draw_pos + cursor_offset - draw_scroll); - ImRect cursor_screen_rect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 0.5f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.5f); + ImRect cursor_screen_rect; + if (g_LEGACY_STROKES) + cursor_screen_rect = ImRect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 0.5f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.5f); + else + cursor_screen_rect = ImRect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 1.0f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.0f); if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) draw_window->DrawList->AddLineV(cursor_screen_rect.Min.x, cursor_screen_rect.Min.y, cursor_screen_rect.Max.y, GetColorU32(ImGuiCol_InputTextCursor), style.InputTextCursorSize); @@ -7309,16 +7319,27 @@ void ImGui::TreeNodeDrawLineToChildNode(const ImVec2& target_pos) return; ImGuiTreeNodeStackData* parent_data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1]; - float x1 = ImTrunc(parent_data->DrawLinesX1); + float x1; + if (g_LEGACY_STROKES) + x1 = ImTrunc(parent_data->DrawLinesX1); + else + x1 = ImTrunc(parent_data->DrawLinesX1 - g.Style.TreeLinesSize * 0.5f) + g.Style.TreeLinesSize * 0.5f; // Draw line centered at X1, but snap to pixels boundary. float x2 = ImTrunc(target_pos.x - g.Style.ItemInnerSpacing.x); - float y = ImTrunc(target_pos.y); + float y; + if (g_LEGACY_STROKES) + y = ImTrunc(target_pos.y); + else + y = ImTrunc(target_pos.y) + g.Style.TreeLinesSize * 0.5f; float rounding = (g.Style.TreeLinesRounding > 0.0f) ? ImMin(x2 - x1, g.Style.TreeLinesRounding) : 0.0f; parent_data->DrawLinesToNodesY2 = ImMax(parent_data->DrawLinesToNodesY2, y - rounding); if (x1 >= x2) return; if (rounding > 0.0f) { - x1 += 0.5f + rounding; + if (g_LEGACY_STROKES) + x1 += 0.5f + rounding; + else + x1 += rounding; window->DrawList->PathArcToFast(ImVec2(x1, y - rounding), rounding, 6, 3); if (x1 < x2) window->DrawList->PathLineTo(ImVec2(x2, y)); @@ -7326,7 +7347,11 @@ void ImGui::TreeNodeDrawLineToChildNode(const ImVec2& target_pos) } else { - window->DrawList->AddLineH(x1, x2, y, GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); + // We use AddLine() instead of AddLineH() as we provide coordinates for line center, + // in order to make the code use same coordinates for both rounded and non-rounded version. + if (!g_LEGACY_STROKES) + x1 += g.Style.TreeLinesSize * 0.5f; // Avoid overdraw + window->DrawList->AddLine(ImVec2(x1, y), ImVec2(x2, y), GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); } } @@ -7349,10 +7374,15 @@ void ImGui::TreeNodeDrawLineToTreePop(const ImGuiTreeNodeStackData* data) y2 = ImMin(y2, window->ClipRect.Max.y); if (y2 <= y1) return; - float x = ImTrunc(data->DrawLinesX1); + float x; + if (g_LEGACY_STROKES) + x = ImTrunc(data->DrawLinesX1); + else + x = ImTrunc(data->DrawLinesX1 - g.Style.TreeLinesSize * 0.5f) + g.Style.TreeLinesSize * 0.5f; // Draw line centered at X1, but snap to pixels boundary. if (data->DrawLinesTableColumn != -1) TablePushColumnChannel(data->DrawLinesTableColumn); - window->DrawList->AddLineV(x, y1, y2, GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); + // We use AddLine() instead of AddLineV() as we provide coordinates for line center, + window->DrawList->AddLine(ImVec2(x, y1), ImVec2(x, y2), GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); if (data->DrawLinesTableColumn != -1) TablePopColumnChannel(); } @@ -10944,8 +10974,18 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, if (tab_contents_visible && (tab_bar->Flags & ImGuiTabBarFlags_DrawSelectedOverline) && style.TabBarOverlineSize > 0.0f) { // Might be moved to TabItemBackground() ? - ImVec2 tl = bb.GetTL() + ImVec2(0, 1.0f * g.CurrentDpiScale); - ImVec2 tr = bb.GetTR() + ImVec2(0, 1.0f * g.CurrentDpiScale); + ImVec2 tl; + ImVec2 tr; + if (g_LEGACY_STROKES) + { + tl = bb.GetTL() + ImVec2(0, 1.0f * g.CurrentDpiScale); + tr = bb.GetTR() + ImVec2(0, 1.0f * g.CurrentDpiScale); + } + else + { + tl = bb.GetTL() + ImVec2(0, style.TabBarOverlineSize * 0.5f); + tr = bb.GetTR() + ImVec2(0, style.TabBarOverlineSize * 0.5f); + } ImU32 overline_col = GetColorU32(tab_bar_focused ? ImGuiCol_TabSelectedOverline : ImGuiCol_TabDimmedSelectedOverline); if (style.TabRounding > 0.0f) { @@ -10956,7 +10996,10 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, } else { + if (g_LEGACY_STROKES) display_draw_list->AddLine(tl - ImVec2(0.5f, 0.5f), tr - ImVec2(0.5f, 0.5f), overline_col, style.TabBarOverlineSize); + else + display_draw_list->AddLine(tl, tr, overline_col, style.TabBarOverlineSize); } } RenderNavCursor(bb, id); From ae06573c9a2f84632347f034099e4c5d96845620 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Mon, 27 Apr 2026 23:39:17 +0200 Subject: [PATCH 007/120] DrawList: Textured Round Corners: first version. (#1962) --- imgui.h | 9 ++- imgui_draw.cpp | 155 ++++++++++++++++++++++++++++++++++++++++++++++- imgui_internal.h | 4 +- 3 files changed, 163 insertions(+), 5 deletions(-) diff --git a/imgui.h b/imgui.h index d3e4ed665..89371a239 100644 --- a/imgui.h +++ b/imgui.h @@ -3207,6 +3207,10 @@ struct ImGuiSelectionExternalStorage #define IM_DRAWLIST_TEX_LINES_WIDTH_MAX (32) #endif +#ifndef IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX +#define IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX (16) +#endif + // ImDrawIdx: vertex index. [Compile-time configurable type] // - To use 16-bit indices + allow large meshes: backend need to set 'io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset' and handle ImDrawCmd::VtxOffset (recommended). // - To use 32-bit indices: override with '#define ImDrawIdx unsigned int' in your imconfig.h file. @@ -3373,6 +3377,7 @@ struct ImDrawList ImVector _CallbacksDataBuf; // [Internal] float _FringeScale; // [Internal] anti-alias fringe is scaled by this value, this helps to keep things sharp while zooming at vertex buffer content. float _InvFringeScale; // [internal] 1.0 / _FringeScale // FIXME: Consider renaming to _PixelDensity. + bool _FringeScaleIsInteger; // [Internal] true if _FringeScale is a whole number, used to select fast path for rendering const char* _OwnerName; // Pointer to owner window's name for debugging // If you want to create ImDrawList instances, pass them ImGui::GetDrawListSharedData(). @@ -3517,6 +3522,7 @@ struct ImDrawList IMGUI_API int _CalcCircleAutoSegmentCount(float radius) const; IMGUI_API void _PathArcToFastEx(const ImVec2& center, float radius, int a_min_sample, int a_max_sample, int a_step); IMGUI_API void _PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments); + IMGUI_API void _AddMirrored9Slice(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, ImVec4 tex_uvs, ImDrawFlags flags); }; // All draw data to render a Dear ImGui frame @@ -3877,7 +3883,8 @@ struct ImFontAtlas ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel. May change as new texture gets created. ImVector Fonts; // Hold all the fonts returned by AddFont*. Fonts[0] is the default font upon calling ImGui::NewFrame(), use ImGui::PushFont()/PopFont() to change the current font. ImVector Sources; // Source/configuration data - ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines + ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines + ImVec4 TexUvCornerFills[IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX]; // UVs for baked anti-aliased corners int TexNextUniqueID; // Next value to be stored in TexData->UniqueID int FontNextUniqueID; // Next value to be stored in ImFont->FontID ImVector DrawListSharedDatas; // List of users for this atlas. Typically one per Dear ImGui context. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 8f27e0506..dee1a1ab3 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -725,6 +725,7 @@ void ImDrawList::_SetPixelDensity(float pixel_density) IM_ASSERT(pixel_density > 0.0f); _FringeScale = 1.0f / pixel_density; _InvFringeScale = pixel_density; + _FringeScaleIsInteger = ImIsTruncated(_FringeScale); } // Reserve space for a number of vertices and indices. @@ -1603,14 +1604,84 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl PathStroke(col, thickness, ImDrawFlags_Closed); } +void ImDrawList::_AddMirrored9Slice(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, ImVec4 tex_uvs, ImDrawFlags flags) +{ + // 9-slice with corner mirroring + const ImVec2 uv_tl(tex_uvs.x, tex_uvs.y); + const ImVec2 uv_tr(tex_uvs.z, tex_uvs.y); + const ImVec2 uv_br(tex_uvs.z, tex_uvs.w); + const ImVec2 uv_bl(tex_uvs.x, tex_uvs.w); + + PrimReserve(9 * 2 * 3, 4 * 4); + + const float r_tl = (flags & ImDrawFlags_RoundCornersTopLeft) ? r : 0.0f; + const float r_tr = (flags & ImDrawFlags_RoundCornersTopRight) ? r : 0.0f; + const float r_br = (flags & ImDrawFlags_RoundCornersBottomRight) ? r : 0.0f; + const float r_bl = (flags & ImDrawFlags_RoundCornersBottomLeft) ? r : 0.0f; + + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + + PrimWriteVtx(ImVec2(p_min.x, p_min.y), uv_tl, col); + PrimWriteVtx(ImVec2(p_min.x + r_tl, p_min.y), uv_tr, col); + PrimWriteVtx(ImVec2(p_max.x - r_tr, p_min.y), uv_tr, col); + PrimWriteVtx(ImVec2(p_max.x, p_min.y), uv_tl, col); + + PrimWriteVtx(ImVec2(p_min.x, p_min.y + r_tl), uv_bl, col); + PrimWriteVtx(ImVec2(p_min.x + r_tl, p_min.y + r_tl), uv_br, col); + PrimWriteVtx(ImVec2(p_max.x - r_tr, p_min.y + r_tr), uv_br, col); + PrimWriteVtx(ImVec2(p_max.x, p_min.y + r_tr), uv_bl, col); + + PrimWriteVtx(ImVec2(p_min.x, p_max.y - r_bl), uv_bl, col); + PrimWriteVtx(ImVec2(p_min.x + r_bl, p_max.y - r_bl), uv_br, col); + PrimWriteVtx(ImVec2(p_max.x - r_br, p_max.y - r_br), uv_br, col); + PrimWriteVtx(ImVec2(p_max.x, p_max.y - r_br), uv_bl, col); + + PrimWriteVtx(ImVec2(p_min.x, p_max.y), uv_tl, col); + PrimWriteVtx(ImVec2(p_min.x + r_bl, p_max.y), uv_tr, col); + PrimWriteVtx(ImVec2(p_max.x - r_br, p_max.y ), uv_tr, col); + PrimWriteVtx(ImVec2(p_max.x, p_max.y), uv_tl, col); + + for (int i = 0; i < 3; i++) + { + for (int j = 0; j < 3; j++) + { + PrimWriteIdx(idx+4); PrimWriteIdx(idx+0); PrimWriteIdx(idx+1); + PrimWriteIdx(idx+4); PrimWriteIdx(idx+1); PrimWriteIdx(idx+5); + idx++; + } + idx++; + } +} + void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) return; - if (rounding < 0.5f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) + + if (ImIsTruncated4(p_min.x, p_min.y, p_max.x, p_min.y) && ImIsTruncated(rounding)) { - PrimReserve(6, 4); - PrimRect(p_min, p_max, col); + if ((flags & ImDrawFlags_RoundCornersMask_) == 0) + flags |= ImDrawFlags_RoundCornersAll; + + rounding = ImMin(rounding, ImFabs(p_max.x - p_min.x) * (((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f) - 1.0f); + rounding = ImMin(rounding, ImFabs(p_max.y - p_min.y) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f); + + const int s_rounding = (int)rounding; + if (s_rounding <= 0 || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) + { + PrimReserve(6, 4); + PrimRect(p_min, p_max, col); + } + else if (s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) + { + ImVec4 tex_uvs = _Data->TexUvCornerFills[s_rounding - 1]; + _AddMirrored9Slice(p_min, p_max, col, ImMax(2.0f, (float)s_rounding), tex_uvs, flags); + } + else + { + PathRect(p_min, p_max, (float)s_rounding, flags); + PathFillConvex(col); + } } else { @@ -3754,6 +3825,82 @@ static void ImFontAtlasBuildUpdateTexDataBasic(ImFontAtlas* atlas) atlas->TexUvWhitePixel = ImVec2((r.x + 0.5f) * atlas->TexUvScale.x, (r.y + 0.5f) * atlas->TexUvScale.y); } +static ImU8 SampleCorner(float x, float y, float cx, float cy, float r) +{ + const float dx = ImMin(x, cx) - cx; + const float dy = ImMin(y, cy) - cy; + const float d = ImSqrt(dx*dx + dy*dy); + const float aa_width = 1.0f; + return (ImU8)(ImClamp(1.0f - (d - r + aa_width * 0.5f) / aa_width, 0.0f, 1.0f) * 255.0f); +} + +static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) +{ + // Pack and store identifier so we can refresh UV coordinates on texture resize. + ImTextureData* tex = atlas->TexData; + ImFontAtlasBuilder* builder = atlas->Builder; + + ImFontAtlasRect r; + bool add_and_draw = atlas->GetCustomRect(builder->PackIdCornersTexData, &r) == false; + if (add_and_draw) + { + ImVec2i pack_size(0, 0); + for (int i = 0; i < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX; i++) + { + pack_size.x += ImMax(2, i+1) + 2; + pack_size.y = ImMax(pack_size.y, (i+1) + 2); + } + builder->PackIdCornersTexData = atlas->AddCustomRect(pack_size.x, pack_size.y, &r); + IM_ASSERT(builder->PackIdCornersTexData != ImFontAtlasRectId_Invalid); + } + + int x = r.x; + int y = r.y; + for (int n = 0; n < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX; n++) // +1 because of the zero-width row + { + const int s = ImMax(2, n+1); // We need at least 2px so that stretching has solid color. + const int w = s + 2; + const int h = s + 2; + + // Corner circle + const float cx = 1.0f + (float)(n+1); + const float cy = 1.0f + (float)(n+1); + const float rad = (float)(n+1); + IM_ASSERT((x + w) <= (r.x + r.w) && (y + h) <= (r.y + r.h)); // Make sure we're inside the texture bounds before we start writing pixels + + // Write each slice + if (add_and_draw && tex->Format == ImTextureFormat_Alpha8) + { + ImU8* write_ptr = (ImU8*)tex->GetPixelsAt(x, y); + const int pitch = tex->Width; + for (int ly = 0; ly < h; ly++) + { + for (int lx = 0; lx < h; lx++) + write_ptr[lx] = SampleCorner((float)lx + 0.5f, (float)ly + 0.5f, cx, cy, rad); + write_ptr += pitch; + } + } + else if (add_and_draw && tex->Format == ImTextureFormat_RGBA32) + { + ImU32* write_ptr = (ImU32*)(void*)tex->GetPixelsAt(x, y); + const int pitch = tex->Width; + for (int ly = 0; ly < h; ly++) + { + for (int lx = 0; lx < h; lx++) + write_ptr[lx] = IM_COL32(255, 255, 255, SampleCorner((float)lx + 0.5f, (float)ly + 0.5f, cx, cy, rad)); + write_ptr += pitch; + } + } + + // Refresh UV coordinates + ImVec2 uv0 = ImVec2((float)(x + 1), (float)(y + 1)) * atlas->TexUvScale; + ImVec2 uv1 = ImVec2((float)(x + 1 + s), (float)(y + 1 + s)) * atlas->TexUvScale; + atlas->TexUvCornerFills[n] = ImVec4(uv0.x, uv0.y, uv1.x, uv1.y); + + x += w; + } +} + static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) { if (atlas->Flags & ImFontAtlasFlags_NoBakedLines) @@ -4189,6 +4336,7 @@ void ImFontAtlasUpdateDrawListsSharedData(ImFontAtlas* atlas) { shared_data->TexUvWhitePixel = atlas->TexUvWhitePixel; shared_data->TexUvLines = atlas->TexUvLines; + shared_data->TexUvCornerFills = atlas->TexUvCornerFills; } } @@ -4207,6 +4355,7 @@ static void ImFontAtlasBuildUpdateTexData(ImFontAtlas* atlas) { ImFontAtlasBuildUpdateTexDataBasic(atlas); ImFontAtlasBuildUpdateTexDataLines(atlas); + ImFontAtlasBuildUpdateTexDataCorners(atlas); } // Create a new texture, discard previous one diff --git a/imgui_internal.h b/imgui_internal.h index 31ee350ca..e6a9d0321 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -921,6 +921,7 @@ struct IMGUI_API ImDrawListSharedData { ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas (== FontAtlas->TexUvWhitePixel) const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas (== FontAtlas->TexUvLines) + const ImVec4* TexUvCornerFills; // UV of rounded corner (== FontAtlas->TexUvCornerFills) ImFontAtlas* FontAtlas; // Current font atlas ImFont* Font; // Current font (used for simplified AddText overload) float FontSize; // Current font size (used for simplified AddText overload) @@ -4055,8 +4056,9 @@ struct ImFontAtlasBuilder // Custom rectangle identifiers ImFontAtlasRectId PackIdMouseCursors; // White pixel + mouse cursors. Also happen to be fallback in case of packing failure. ImFontAtlasRectId PackIdLinesTexData; + ImFontAtlasRectId PackIdCornersTexData; - ImFontAtlasBuilder() { memset((void*)this, 0, sizeof(*this)); FrameCount = -1; RectsIndexFreeListStart = -1; PackIdMouseCursors = PackIdLinesTexData = -1; } + ImFontAtlasBuilder() { memset((void*)this, 0, sizeof(*this)); FrameCount = -1; RectsIndexFreeListStart = -1; PackIdMouseCursors = PackIdLinesTexData = PackIdCornersTexData = -1; } }; IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas); From 26652204093514cd1ff11e30914fb8d1a56b5a94 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Thu, 23 Apr 2026 12:10:26 +0300 Subject: [PATCH 008/120] DrawList: Textured Round Corners: implemented faster render path for stroked rectangles. (#1962) - added check to render simpler geometry when (stroked) rectangle has integer coords - render non.rounded with 4 non-AA rectangles - added textures for corners of 1-3 thickness and 1-16 rounding - use textured corners when possible, or fallback to generic one --- imgui.h | 7 +- imgui_draw.cpp | 270 +++++++++++++++++++++++++++++++++++++++++++++-- imgui_internal.h | 1 + 3 files changed, 269 insertions(+), 9 deletions(-) diff --git a/imgui.h b/imgui.h index 89371a239..87ddae804 100644 --- a/imgui.h +++ b/imgui.h @@ -3210,6 +3210,9 @@ struct ImGuiSelectionExternalStorage #ifndef IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX #define IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX (16) #endif +#ifndef IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX +#define IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX (3) +#endif // ImDrawIdx: vertex index. [Compile-time configurable type] // - To use 16-bit indices + allow large meshes: backend need to set 'io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset' and handle ImDrawCmd::VtxOffset (recommended). @@ -3522,7 +3525,8 @@ struct ImDrawList IMGUI_API int _CalcCircleAutoSegmentCount(float radius) const; IMGUI_API void _PathArcToFastEx(const ImVec2& center, float radius, int a_min_sample, int a_max_sample, int a_step); IMGUI_API void _PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments); - IMGUI_API void _AddMirrored9Slice(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, ImVec4 tex_uvs, ImDrawFlags flags); + IMGUI_API void _AddRectFilledBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, ImVec4 tex_uvs, ImDrawFlags flags); + IMGUI_API void _AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, float t, ImVec4 tex_uvs, ImDrawFlags flags); }; // All draw data to render a Dear ImGui frame @@ -3885,6 +3889,7 @@ struct ImFontAtlas ImVector Sources; // Source/configuration data ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines ImVec4 TexUvCornerFills[IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX]; // UVs for baked anti-aliased corners + ImVec4 TexUvCornerStrokes[IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX * IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX]; // UVs for baked anti-aliased corners int TexNextUniqueID; // Next value to be stored in TexData->UniqueID int FontNextUniqueID; // Next value to be stored in ImFont->FontID ImVector DrawListSharedDatas; // List of users for this atlas. Typically one per Dear ImGui context. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index dee1a1ab3..52f408843 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1558,6 +1558,125 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th } } + +void ImDrawList::_AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, float t, ImVec4 tex_uvs, ImDrawFlags flags) +{ + const ImVec2 uv_tl(tex_uvs.x, tex_uvs.y); + const ImVec2 uv_tr(tex_uvs.z, tex_uvs.y); + const ImVec2 uv_br(tex_uvs.z, tex_uvs.w); + const ImVec2 uv_bl(tex_uvs.x, tex_uvs.w); + + const int idx_allocated = 8 * 2 * 3; + const int vtx_allocated = 8 * 4; + PrimReserve(idx_allocated, vtx_allocated); + ImDrawVert* start_vtx_ptr = _VtxWritePtr; + ImDrawIdx* start_idx_ptr = _IdxWritePtr; + + if (flags & ImDrawFlags_RoundCornersTopLeft) + { + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + PrimWriteVtx(ImVec2(p_min.x, p_min.y), uv_tl, col); + PrimWriteVtx(ImVec2(p_min.x + r, p_min.y), uv_tr, col); + PrimWriteVtx(ImVec2(p_min.x + r, p_min.y + r), uv_br, col); + PrimWriteVtx(ImVec2(p_min.x, p_min.y + r), uv_bl, col); + PrimWriteIdx(idx+0); PrimWriteIdx(idx+1); PrimWriteIdx(idx+2); + PrimWriteIdx(idx+0); PrimWriteIdx(idx+2); PrimWriteIdx(idx+3); + } + + if (flags & ImDrawFlags_RoundCornersTopRight) + { + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + PrimWriteVtx(ImVec2(p_max.x - r, p_min.y), uv_tr, col); + PrimWriteVtx(ImVec2(p_max.x, p_min.y), uv_tl, col); + PrimWriteVtx(ImVec2(p_max.x, p_min.y + r), uv_bl, col); + PrimWriteVtx(ImVec2(p_max.x - r, p_min.y + r), uv_br, col); + PrimWriteIdx(idx+0); PrimWriteIdx(idx+1); PrimWriteIdx(idx+2); + PrimWriteIdx(idx+0); PrimWriteIdx(idx+2); PrimWriteIdx(idx+3); + } + + if (flags & ImDrawFlags_RoundCornersBottomRight) + { + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + PrimWriteVtx(ImVec2(p_max.x - r, p_max.y - r), uv_br, col); + PrimWriteVtx(ImVec2(p_max.x, p_max.y - r), uv_bl, col); + PrimWriteVtx(ImVec2(p_max.x, p_max.y), uv_tl, col); + PrimWriteVtx(ImVec2(p_max.x - r, p_max.y), uv_tr, col); + PrimWriteIdx(idx+0); PrimWriteIdx(idx+1); PrimWriteIdx(idx+2); + PrimWriteIdx(idx+0); PrimWriteIdx(idx+2); PrimWriteIdx(idx+3); + } + + if (flags & ImDrawFlags_RoundCornersBottomLeft) + { + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + PrimWriteVtx(ImVec2(p_min.x, p_max.y - r), uv_bl, col); + PrimWriteVtx(ImVec2(p_min.x + r, p_max.y - r), uv_br, col); + PrimWriteVtx(ImVec2(p_min.x + r, p_max.y), uv_tr, col); + PrimWriteVtx(ImVec2(p_min.x, p_max.y), uv_tl, col); + PrimWriteIdx(idx+0); PrimWriteIdx(idx+1); PrimWriteIdx(idx+2); + PrimWriteIdx(idx+0); PrimWriteIdx(idx+2); PrimWriteIdx(idx+3); + } + + const float r_tl = (flags & ImDrawFlags_RoundCornersTopLeft) ? r : 0; + const float r_tr = (flags & ImDrawFlags_RoundCornersTopRight) ? r : 0; + const float r_br = (flags & ImDrawFlags_RoundCornersBottomRight) ? r : 0; + const float r_bl = (flags & ImDrawFlags_RoundCornersBottomLeft) ? r : 0; + const ImVec2 opaque_uv = _Data->TexUvWhitePixel; + + { + // Top + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + const float left = p_min.x + r_tl; + const float right = p_max.x - r_tr; + PrimWriteVtx(ImVec2(left, p_min.y), opaque_uv, col); + PrimWriteVtx(ImVec2(right, p_min.y), opaque_uv, col); + PrimWriteVtx(ImVec2(right, p_min.y + t), opaque_uv, col); + PrimWriteVtx(ImVec2(left, p_min.y + t), opaque_uv, col); + PrimWriteIdx(idx+0); PrimWriteIdx(idx+1); PrimWriteIdx(idx+2); + PrimWriteIdx(idx+0); PrimWriteIdx(idx+2); PrimWriteIdx(idx+3); + } + { + // Left + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + const float top = p_min.y + ImMax(r_tl, t); + const float bottom = p_max.y - ImMax(r_bl, t); + PrimWriteVtx(ImVec2(p_min.x, top), opaque_uv, col); + PrimWriteVtx(ImVec2(p_min.x + t, top), opaque_uv, col); + PrimWriteVtx(ImVec2(p_min.x + t, bottom), opaque_uv, col); + PrimWriteVtx(ImVec2(p_min.x, bottom), opaque_uv, col); + PrimWriteIdx(idx+0); PrimWriteIdx(idx+1); PrimWriteIdx(idx+2); + PrimWriteIdx(idx+0); PrimWriteIdx(idx+2); PrimWriteIdx(idx+3); + } + { + // Right + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + const float top = p_min.y + ImMax(r_tr, t); + const float bottom = p_max.y - ImMax(r_br, t); + PrimWriteVtx(ImVec2(p_max.x - t, top), opaque_uv, col); + PrimWriteVtx(ImVec2(p_max.x, top), opaque_uv, col); + PrimWriteVtx(ImVec2(p_max.x, bottom), opaque_uv, col); + PrimWriteVtx(ImVec2(p_max.x - t, bottom), opaque_uv, col); + PrimWriteIdx(idx+0); PrimWriteIdx(idx+1); PrimWriteIdx(idx+2); + PrimWriteIdx(idx+0); PrimWriteIdx(idx+2); PrimWriteIdx(idx+3); + } + { + // Bottom + ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + const float left = p_min.x + r_bl; + const float right = p_max.x - r_br; + PrimWriteVtx(ImVec2(left, p_max.y - t), opaque_uv, col); + PrimWriteVtx(ImVec2(right, p_max.y - t), opaque_uv, col); + PrimWriteVtx(ImVec2(right, p_max.y), opaque_uv, col); + PrimWriteVtx(ImVec2(left, p_max.y), opaque_uv, col); + PrimWriteIdx(idx+0); PrimWriteIdx(idx+1); PrimWriteIdx(idx+2); + PrimWriteIdx(idx+0); PrimWriteIdx(idx+2); PrimWriteIdx(idx+3); + } + + const int idx_used = (int)(_IdxWritePtr - start_idx_ptr); + const int vtx_used = (int)(_VtxWritePtr - start_vtx_ptr); + if (idx_used < idx_allocated || vtx_used < vtx_allocated) + PrimUnreserve(idx_allocated - idx_used, vtx_allocated - vtx_used); +} + // p_min = upper-left, p_max = lower-right // Note we don't render 1 pixels sized rectangles properly. void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags) @@ -1586,8 +1705,49 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl return; } + ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); + if ((stroke_pos == ImDrawFlags_StrokeInside || stroke_pos == ImDrawFlags_StrokeOutside) && ImIsTruncated4(p_min.x, p_min.y, p_max.x, p_min.y) && ImIsTruncated(rounding) && ImIsTruncated(thickness)) + { + if ((flags & ImDrawFlags_RoundCornersMask_) == 0) + flags |= ImDrawFlags_RoundCornersAll; + + rounding = ImMin(rounding, ImFabs(p_max.x - p_min.x) * (((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f) - 1.0f); + rounding = ImMin(rounding, ImFabs(p_max.y - p_min.y) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f); + + int s_rounding = (int)rounding; + int s_thickness = (int)thickness; + + ImVec2 s_min = p_min; + ImVec2 s_max = p_max; + if (stroke_pos == ImDrawFlags_StrokeOutside) + { + s_min -= ImVec2(thickness, thickness); + s_max += ImVec2(thickness, thickness); + s_rounding += s_thickness; + } + + if (s_rounding <= 0 || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) + { + const float t = (float)s_thickness; + PrimReserve(6*4, 4*4); + PrimRect(ImVec2(p_min.x, p_min.y), ImVec2(p_max.x, p_min.y + t), col); + PrimRect(ImVec2(p_min.x, p_min.y + t), ImVec2(p_min.x + t, p_max.y - t), col); + PrimRect(ImVec2(p_max.x - t, p_min.y + t), ImVec2(p_max.x, p_max.y - t), col); + PrimRect(ImVec2(p_min.x, p_max.y - t), ImVec2(p_max.x, p_max.y), col); + return; + } + if (s_thickness <= IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX && s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) + { + const int size = ImMax(s_rounding, s_thickness); + const int idx = (s_thickness - 1) * IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX + s_rounding - 1; + IM_ASSERT(idx >= 0 && idx < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX * IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX); + const ImVec4 tex_uvs = _Data->TexUvCornerStrokes[idx]; + _AddRectBaked(s_min, s_max, col, (float)size, (float)thickness, tex_uvs, flags); + return; + } + } + const ImVec2 offset(thickness * 0.5f, thickness * 0.5f); - const ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); if (stroke_pos == ImDrawFlags_StrokeInside) PathRect(p_min + offset, p_max - offset, rounding - thickness * 0.5f, flags); else if (stroke_pos == ImDrawFlags_StrokeCenter) @@ -1604,7 +1764,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl PathStroke(col, thickness, ImDrawFlags_Closed); } -void ImDrawList::_AddMirrored9Slice(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, ImVec4 tex_uvs, ImDrawFlags flags) +void ImDrawList::_AddRectFilledBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, ImVec4 tex_uvs, ImDrawFlags flags) { // 9-slice with corner mirroring const ImVec2 uv_tl(tex_uvs.x, tex_uvs.y); @@ -1675,7 +1835,7 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c else if (s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) { ImVec4 tex_uvs = _Data->TexUvCornerFills[s_rounding - 1]; - _AddMirrored9Slice(p_min, p_max, col, ImMax(2.0f, (float)s_rounding), tex_uvs, flags); + _AddRectFilledBaked(p_min, p_max, col, ImMax(2.0f, (float)s_rounding), tex_uvs, flags); } else { @@ -3834,6 +3994,17 @@ static ImU8 SampleCorner(float x, float y, float cx, float cy, float r) return (ImU8)(ImClamp(1.0f - (d - r + aa_width * 0.5f) / aa_width, 0.0f, 1.0f) * 255.0f); } +static ImU8 SampleCornerStroke(float x, float y, float cx, float cy, float outer_r, float inner_r) +{ + const float dx = ImMin(x, cx) - cx; + const float dy = ImMin(y, cy) - cy; + const float d = ImSqrt(dx*dx + dy*dy); + const float aa_width = 1.0f; + const float outer = 1.0f - (d - outer_r + aa_width * 0.5f) / aa_width; + const float inner = (d - inner_r + aa_width * 0.5f) / aa_width; + return (ImU8)(ImClamp(ImMin(inner, outer), 0.0f, 1.0f) * 255.f); +} + static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) { // Pack and store identifier so we can refresh UV coordinates on texture resize. @@ -3845,10 +4016,30 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) if (add_and_draw) { ImVec2i pack_size(0, 0); + // Filled corners + int row_width = 0; + int row_height = 0; for (int i = 0; i < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX; i++) { - pack_size.x += ImMax(2, i+1) + 2; - pack_size.y = ImMax(pack_size.y, (i+1) + 2); + const int size = ImMax(2, i+1) + 2; + row_width += size; + row_height = ImMax(row_height, size); + } + pack_size.x = ImMax(pack_size.x, row_width); + pack_size.y += row_height; + // Rounded corners + for (int t = 0; t < IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX; t++) + { + row_width = 0; + row_height = 0; + for (int i = 0; i < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX; i++) + { + const int size = ImMax(2, ImMax(i+1, t+1)) + 2; + row_width += size; + row_height = ImMax(row_height, size); + } + pack_size.x = ImMax(pack_size.x, row_width); + pack_size.y += row_height; } builder->PackIdCornersTexData = atlas->AddCustomRect(pack_size.x, pack_size.y, &r); IM_ASSERT(builder->PackIdCornersTexData != ImFontAtlasRectId_Invalid); @@ -3856,16 +4047,20 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) int x = r.x; int y = r.y; - for (int n = 0; n < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX; n++) // +1 because of the zero-width row + int row_height = 0; + + // Filled + for (int n = 0; n < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX; n++) { - const int s = ImMax(2, n+1); // We need at least 2px so that stretching has solid color. + const int rounding = n+1; + const int s = ImMax(2, rounding); // We need at least 2px so that stretching has solid color. const int w = s + 2; const int h = s + 2; // Corner circle const float cx = 1.0f + (float)(n+1); const float cy = 1.0f + (float)(n+1); - const float rad = (float)(n+1); + const float rad = (float)rounding; IM_ASSERT((x + w) <= (r.x + r.w) && (y + h) <= (r.y + r.h)); // Make sure we're inside the texture bounds before we start writing pixels // Write each slice @@ -3898,6 +4093,64 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) atlas->TexUvCornerFills[n] = ImVec4(uv0.x, uv0.y, uv1.x, uv1.y); x += w; + row_height = ImMax(row_height, h); + } + y += row_height; + + // Stroked + for (int t = 0; t < IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX; t++) + { + x = r.x; + row_height = 0; + for (int n = 0; n < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX; n++) + { + const int rounding = n+1; + const int thickness = t+1; + const int s = ImMax(rounding, thickness); + const int w = s + 2; + const int h = s + 2; + + // Corner circle + const float cx = 1.0f + (float)(n + 1); + const float cy = 1.0f + (float)(n + 1); + const float outer_rad = (float)rounding; + const float inner_rad = (float)(rounding - thickness); + + IM_ASSERT((x + w) <= (r.x + r.w) && (y + h) <= (r.y + r.h)); // Make sure we're inside the texture bounds before we start writing pixels + + // Write each slice + if (add_and_draw && tex->Format == ImTextureFormat_Alpha8) + { + ImU8* write_ptr = (ImU8*)tex->GetPixelsAt(x, y); + const int pitch = tex->Width; + for (int ly = 0; ly < h; ly++) + { + for (int lx = 0; lx < h; lx++) + write_ptr[lx] = SampleCornerStroke((float)lx + 0.5f, (float)ly + 0.5f, cx, cy, outer_rad, inner_rad); + write_ptr += pitch; + } + } + else if (add_and_draw && tex->Format == ImTextureFormat_RGBA32) + { + ImU32* write_ptr = (ImU32*)(void*)tex->GetPixelsAt(x, y); + const int pitch = tex->Width; + for (int ly = 0; ly < h; ly++) + { + for (int lx = 0; lx < h; lx++) + write_ptr[lx] = IM_COL32(255, 255, 255, SampleCornerStroke((float)lx + 0.5f, (float)ly + 0.5f, cx, cy, outer_rad, inner_rad)); + write_ptr += pitch; + } + } + + // Refresh UV coordinates + ImVec2 uv0 = ImVec2((float)(x + 1), (float)(y + 1)) * atlas->TexUvScale; + ImVec2 uv1 = ImVec2((float)(x + 1 + s), (float)(y + 1 + s)) * atlas->TexUvScale; + atlas->TexUvCornerStrokes[t*IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX + n] = ImVec4(uv0.x, uv0.y, uv1.x, uv1.y); + + x += w; + row_height = ImMax(row_height, h); + } + y += row_height; } } @@ -4337,6 +4590,7 @@ void ImFontAtlasUpdateDrawListsSharedData(ImFontAtlas* atlas) shared_data->TexUvWhitePixel = atlas->TexUvWhitePixel; shared_data->TexUvLines = atlas->TexUvLines; shared_data->TexUvCornerFills = atlas->TexUvCornerFills; + shared_data->TexUvCornerStrokes = atlas->TexUvCornerStrokes; } } diff --git a/imgui_internal.h b/imgui_internal.h index e6a9d0321..d351411cc 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -922,6 +922,7 @@ struct IMGUI_API ImDrawListSharedData ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas (== FontAtlas->TexUvWhitePixel) const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas (== FontAtlas->TexUvLines) const ImVec4* TexUvCornerFills; // UV of rounded corner (== FontAtlas->TexUvCornerFills) + const ImVec4* TexUvCornerStrokes; ImFontAtlas* FontAtlas; // Current font atlas ImFont* Font; // Current font (used for simplified AddText overload) float FontSize; // Current font size (used for simplified AddText overload) From 237a498c2d6972b1be5e1a36b2f3a7a1cd6fdeac Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Tue, 28 Apr 2026 09:32:09 +0300 Subject: [PATCH 009/120] DrawList: Textured Round Corners: Added flags to disable textured rounded corners (#1962) - added ImFontAtlasFlags_NoBakedRoundCorners which controls if rounded corners should be baked to texture - added ImDrawListFlags_RoundCornersUseTex which controls if we should try to use textured roudned corners - fixed how _FringeScaleIsInteger is calculated (we want to detect integer pixel scale) - fixed how _FringeScale is used with textured rounded corners --- imgui.cpp | 2 ++ imgui.h | 4 +++- imgui_draw.cpp | 46 +++++++++++++++++++++++++++++----------------- 3 files changed, 34 insertions(+), 18 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 549973af2..b6cef8d0c 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -5624,6 +5624,8 @@ static void SetupDrawListSharedData() g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLines; if (g.Style.AntiAliasedLinesUseTex && !(g.IO.Fonts->Flags & ImFontAtlasFlags_NoBakedLines)) g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLinesUseTex; + if (!(g.IO.Fonts->Flags & ImFontAtlasFlags_NoBakedRoundCorners)) + g.DrawListSharedData.InitialFlags |= ImDrawListFlags_RoundCornersUseTex; if (g.Style.AntiAliasedFill) g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedFill; if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) diff --git a/imgui.h b/imgui.h index 87ddae804..88bbbf9b9 100644 --- a/imgui.h +++ b/imgui.h @@ -3348,6 +3348,7 @@ enum ImDrawListFlags_ ImDrawListFlags_AntiAliasedFill = 1 << 2, // Enable anti-aliased edge around filled shapes (rounded rectangles, circles). ImDrawListFlags_AllowVtxOffset = 1 << 3, // Can emit 'VtxOffset > 0' to allow large meshes. Set when 'ImGuiBackendFlags_RendererHasVtxOffset' is enabled. ImDrawListFlags_TextNoPixelSnap = 1 << 4, // Disable automatically snapping AddText() calls to pixel boundaries. + ImDrawListFlags_RoundCornersUseTex = 1 << 5, // Enable using textures instead of strokes to draw rounded corners/circles where possible. }; // Draw command list @@ -3380,7 +3381,7 @@ struct ImDrawList ImVector _CallbacksDataBuf; // [Internal] float _FringeScale; // [Internal] anti-alias fringe is scaled by this value, this helps to keep things sharp while zooming at vertex buffer content. float _InvFringeScale; // [internal] 1.0 / _FringeScale // FIXME: Consider renaming to _PixelDensity. - bool _FringeScaleIsInteger; // [Internal] true if _FringeScale is a whole number, used to select fast path for rendering + bool _FringeScaleIsInteger; // [Internal] true if 1/_FringeScale is a whole number, used to select fast path for rendering const char* _OwnerName; // Pointer to owner window's name for debugging // If you want to create ImDrawList instances, pass them ImGui::GetDrawListSharedData(). @@ -3744,6 +3745,7 @@ enum ImFontAtlasFlags_ ImFontAtlasFlags_NoPowerOfTwoHeight = 1 << 0, // Don't round the height to next power of two ImFontAtlasFlags_NoMouseCursors = 1 << 1, // Don't build software mouse cursors into the atlas (save a little texture memory) ImFontAtlasFlags_NoBakedLines = 1 << 2, // Don't build thick line textures into the atlas (save a little texture memory, allow support for point/nearest filtering). The AntiAliasedLinesUseTex features uses them, otherwise they will be rendered using polygons (more expensive for CPU/GPU). + ImFontAtlasFlags_NoBakedRoundCorners= 1 << 3, // Don't build round corners into the atlas. }; // Load and rasterize multiple TTF/OTF fonts into a same texture. The font atlas will build a single texture holding: diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 52f408843..e194075ef 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -725,7 +725,7 @@ void ImDrawList::_SetPixelDensity(float pixel_density) IM_ASSERT(pixel_density > 0.0f); _FringeScale = 1.0f / pixel_density; _InvFringeScale = pixel_density; - _FringeScaleIsInteger = ImIsTruncated(_FringeScale); + _FringeScaleIsInteger = ImIsTruncated(_InvFringeScale); } // Reserve space for a number of vertices and indices. @@ -1519,7 +1519,8 @@ void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float th y -= thickness * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeOutside) y -= thickness; - if (ImIsTruncated4(min_x, max_x, y, thickness)) + + if (_FringeScaleIsInteger && ImIsTruncated4(min_x, max_x, y, thickness)) { PrimReserve(6, 4); PrimRect(ImVec2(min_x, y), ImVec2(max_x, y + thickness), col); @@ -1547,10 +1548,11 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th x += thickness * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeOutside) x -= thickness; - if (ImIsTruncated4(x, min_y, max_y, thickness)) + + if (_FringeScaleIsInteger && ImIsTruncated4(x, min_y, max_y, thickness)) { PrimReserve(6, 4); - PrimRect(ImTrunc(ImVec2(x, min_y)), ImTrunc(ImVec2(x + thickness, max_y)), col); + PrimRect(ImVec2(x, min_y), ImVec2(x + thickness, max_y), col); } else { @@ -1705,8 +1707,9 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl return; } + const bool is_truncated = _FringeScaleIsInteger && ImIsTruncated4(p_min.x, p_min.y, p_max.x, p_min.y) && ImIsTruncated4(rounding, thickness, 0.0f, 0.0f); ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); - if ((stroke_pos == ImDrawFlags_StrokeInside || stroke_pos == ImDrawFlags_StrokeOutside) && ImIsTruncated4(p_min.x, p_min.y, p_max.x, p_min.y) && ImIsTruncated(rounding) && ImIsTruncated(thickness)) + if ((stroke_pos == ImDrawFlags_StrokeInside || stroke_pos == ImDrawFlags_StrokeOutside) && is_truncated) { if ((flags & ImDrawFlags_RoundCornersMask_) == 0) flags |= ImDrawFlags_RoundCornersAll; @@ -1714,8 +1717,8 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl rounding = ImMin(rounding, ImFabs(p_max.x - p_min.x) * (((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f) - 1.0f); rounding = ImMin(rounding, ImFabs(p_max.y - p_min.y) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f); - int s_rounding = (int)rounding; - int s_thickness = (int)thickness; + int s_rounding = (int)(rounding / _FringeScale); + int s_thickness = (int)(thickness / _FringeScale); ImVec2 s_min = p_min; ImVec2 s_max = p_max; @@ -1728,7 +1731,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl if (s_rounding <= 0 || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) { - const float t = (float)s_thickness; + const float t = (float)s_thickness * _FringeScale; PrimReserve(6*4, 4*4); PrimRect(ImVec2(p_min.x, p_min.y), ImVec2(p_max.x, p_min.y + t), col); PrimRect(ImVec2(p_min.x, p_min.y + t), ImVec2(p_min.x + t, p_max.y - t), col); @@ -1736,13 +1739,14 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl PrimRect(ImVec2(p_min.x, p_max.y - t), ImVec2(p_max.x, p_max.y), col); return; } - if (s_thickness <= IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX && s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) + if ((Flags & ImDrawListFlags_RoundCornersUseTex) && s_thickness <= IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX && s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) { - const int size = ImMax(s_rounding, s_thickness); + IM_ASSERT_PARANOID(!(_Data->Font->OwnerAtlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners)); + const int size = ImMax(s_rounding, s_thickness); // This is matching the baking calculations. const int idx = (s_thickness - 1) * IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX + s_rounding - 1; - IM_ASSERT(idx >= 0 && idx < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX * IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX); + IM_ASSERT_PARANOID(idx >= 0 && idx < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX * IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX); const ImVec4 tex_uvs = _Data->TexUvCornerStrokes[idx]; - _AddRectBaked(s_min, s_max, col, (float)size, (float)thickness, tex_uvs, flags); + _AddRectBaked(s_min, s_max, col, (float)size * _FringeScale, (float)thickness, tex_uvs, flags); return; } } @@ -1826,16 +1830,21 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c rounding = ImMin(rounding, ImFabs(p_max.x - p_min.x) * (((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f) - 1.0f); rounding = ImMin(rounding, ImFabs(p_max.y - p_min.y) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f); - const int s_rounding = (int)rounding; + + const int s_rounding = (int)(rounding / _FringeScale); if (s_rounding <= 0 || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) { PrimReserve(6, 4); PrimRect(p_min, p_max, col); } - else if (s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) + else if ((Flags & ImDrawListFlags_RoundCornersUseTex) && s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) { - ImVec4 tex_uvs = _Data->TexUvCornerFills[s_rounding - 1]; - _AddRectFilledBaked(p_min, p_max, col, ImMax(2.0f, (float)s_rounding), tex_uvs, flags); + IM_ASSERT_PARANOID(!(_Data->Font->OwnerAtlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners)); + const int size = ImMax(2, s_rounding); // This is matching the baking calculations. + const int idx = s_rounding - 1; + IM_ASSERT_PARANOID(idx >= 0 && idx < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX); + ImVec4 tex_uvs = _Data->TexUvCornerFills[idx]; + _AddRectFilledBaked(p_min, p_max, col, (float)size * _FringeScale, tex_uvs, flags); } else { @@ -4002,11 +4011,14 @@ static ImU8 SampleCornerStroke(float x, float y, float cx, float cy, float outer const float aa_width = 1.0f; const float outer = 1.0f - (d - outer_r + aa_width * 0.5f) / aa_width; const float inner = (d - inner_r + aa_width * 0.5f) / aa_width; - return (ImU8)(ImClamp(ImMin(inner, outer), 0.0f, 1.0f) * 255.f); + return (ImU8)(ImClamp(ImMin(inner, outer), 0.0f, 1.0f) * 255.0f); } static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) { + if (atlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners) + return; + // Pack and store identifier so we can refresh UV coordinates on texture resize. ImTextureData* tex = atlas->TexData; ImFontAtlasBuilder* builder = atlas->Builder; From b9707d72abb0ba9c263dd46da61a4a5df726aea5 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 5 May 2026 17:40:15 +0200 Subject: [PATCH 010/120] DrawList: Textured Round Corners: use macros + rename locals. (#1962) Squashed: Rename macros to IM_APPEND_VTX(), IM_APPEND_TRI() --- imgui_draw.cpp | 168 +++++++++++++++++++++++++++---------------------- 1 file changed, 93 insertions(+), 75 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index e194075ef..b4e1c5e65 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -819,6 +819,25 @@ void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, c #define IM_FIXNORMAL2F_MAX_INVLEN2 100.0f // 500.0f (see #4053, #3366) #define IM_FIXNORMAL2F(VX,VY) { float d2 = VX*VX + VY*VY; if (d2 > 0.000001f) { float inv_len2 = 1.0f / d2; if (inv_len2 > IM_FIXNORMAL2F_MAX_INVLEN2) inv_len2 = IM_FIXNORMAL2F_MAX_INVLEN2; VX *= inv_len2; VY *= inv_len2; } } (void)0 +// We avoid using the 'do { } while (false)` idiom in those macros as they typically have overhead in debug builds. +#define IM_APPEND_VTX(PX, PY, UV, COL) \ + { \ + _VtxWritePtr->pos.x = PX; \ + _VtxWritePtr->pos.y = PY; \ + _VtxWritePtr->uv = UV; \ + _VtxWritePtr->col = COL; \ + _VtxWritePtr++; \ + _VtxCurrentIdx++; \ + } (void)0 + +#define IM_APPEND_TRI(a, b, c) \ + { \ + _IdxWritePtr[0] = (ImDrawIdx)(a); \ + _IdxWritePtr[1] = (ImDrawIdx)(b); \ + _IdxWritePtr[2] = (ImDrawIdx)(c); \ + _IdxWritePtr += 3; \ + } (void)0 + // TODO: Thickness anti-aliased lines cap are missing their AA fringe. // We avoid using the ImVec2 math operators here to reduce cost to a minimum for debug/non-inlined builds. void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags) @@ -1560,7 +1579,6 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th } } - void ImDrawList::_AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, float t, ImVec4 tex_uvs, ImDrawFlags flags) { const ImVec2 uv_tl(tex_uvs.x, tex_uvs.y); @@ -1577,45 +1595,45 @@ void ImDrawList::_AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 c if (flags & ImDrawFlags_RoundCornersTopLeft) { ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; - PrimWriteVtx(ImVec2(p_min.x, p_min.y), uv_tl, col); - PrimWriteVtx(ImVec2(p_min.x + r, p_min.y), uv_tr, col); - PrimWriteVtx(ImVec2(p_min.x + r, p_min.y + r), uv_br, col); - PrimWriteVtx(ImVec2(p_min.x, p_min.y + r), uv_bl, col); - PrimWriteIdx(idx+0); PrimWriteIdx(idx+1); PrimWriteIdx(idx+2); - PrimWriteIdx(idx+0); PrimWriteIdx(idx+2); PrimWriteIdx(idx+3); + IM_APPEND_VTX(p_min.x, p_min.y, uv_tl, col); + IM_APPEND_VTX(p_min.x + r, p_min.y, uv_tr, col); + IM_APPEND_VTX(p_min.x + r, p_min.y + r, uv_br, col); + IM_APPEND_VTX(p_min.x, p_min.y + r, uv_bl, col); + IM_APPEND_TRI(idx + 0, idx + 1, idx + 2); + IM_APPEND_TRI(idx + 0, idx + 2, idx + 3); } if (flags & ImDrawFlags_RoundCornersTopRight) { ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; - PrimWriteVtx(ImVec2(p_max.x - r, p_min.y), uv_tr, col); - PrimWriteVtx(ImVec2(p_max.x, p_min.y), uv_tl, col); - PrimWriteVtx(ImVec2(p_max.x, p_min.y + r), uv_bl, col); - PrimWriteVtx(ImVec2(p_max.x - r, p_min.y + r), uv_br, col); - PrimWriteIdx(idx+0); PrimWriteIdx(idx+1); PrimWriteIdx(idx+2); - PrimWriteIdx(idx+0); PrimWriteIdx(idx+2); PrimWriteIdx(idx+3); + IM_APPEND_VTX(p_max.x - r, p_min.y, uv_tr, col); + IM_APPEND_VTX(p_max.x, p_min.y, uv_tl, col); + IM_APPEND_VTX(p_max.x, p_min.y + r, uv_bl, col); + IM_APPEND_VTX(p_max.x - r, p_min.y + r, uv_br, col); + IM_APPEND_TRI(idx + 0, idx + 1, idx + 2); + IM_APPEND_TRI(idx + 0, idx + 2, idx + 3); } if (flags & ImDrawFlags_RoundCornersBottomRight) { ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; - PrimWriteVtx(ImVec2(p_max.x - r, p_max.y - r), uv_br, col); - PrimWriteVtx(ImVec2(p_max.x, p_max.y - r), uv_bl, col); - PrimWriteVtx(ImVec2(p_max.x, p_max.y), uv_tl, col); - PrimWriteVtx(ImVec2(p_max.x - r, p_max.y), uv_tr, col); - PrimWriteIdx(idx+0); PrimWriteIdx(idx+1); PrimWriteIdx(idx+2); - PrimWriteIdx(idx+0); PrimWriteIdx(idx+2); PrimWriteIdx(idx+3); + IM_APPEND_VTX(p_max.x - r, p_max.y - r, uv_br, col); + IM_APPEND_VTX(p_max.x, p_max.y - r, uv_bl, col); + IM_APPEND_VTX(p_max.x, p_max.y, uv_tl, col); + IM_APPEND_VTX(p_max.x - r, p_max.y, uv_tr, col); + IM_APPEND_TRI(idx + 0, idx + 1, idx + 2); + IM_APPEND_TRI(idx + 0, idx + 2, idx + 3); } if (flags & ImDrawFlags_RoundCornersBottomLeft) { ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; - PrimWriteVtx(ImVec2(p_min.x, p_max.y - r), uv_bl, col); - PrimWriteVtx(ImVec2(p_min.x + r, p_max.y - r), uv_br, col); - PrimWriteVtx(ImVec2(p_min.x + r, p_max.y), uv_tr, col); - PrimWriteVtx(ImVec2(p_min.x, p_max.y), uv_tl, col); - PrimWriteIdx(idx+0); PrimWriteIdx(idx+1); PrimWriteIdx(idx+2); - PrimWriteIdx(idx+0); PrimWriteIdx(idx+2); PrimWriteIdx(idx+3); + IM_APPEND_VTX(p_min.x, p_max.y - r, uv_bl, col); + IM_APPEND_VTX(p_min.x + r, p_max.y - r, uv_br, col); + IM_APPEND_VTX(p_min.x + r, p_max.y, uv_tr, col); + IM_APPEND_VTX(p_min.x, p_max.y, uv_tl, col); + IM_APPEND_TRI(idx + 0, idx + 1, idx + 2); + IM_APPEND_TRI(idx + 0, idx + 2, idx + 3); } const float r_tl = (flags & ImDrawFlags_RoundCornersTopLeft) ? r : 0; @@ -1627,50 +1645,50 @@ void ImDrawList::_AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 c { // Top ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; - const float left = p_min.x + r_tl; - const float right = p_max.x - r_tr; - PrimWriteVtx(ImVec2(left, p_min.y), opaque_uv, col); - PrimWriteVtx(ImVec2(right, p_min.y), opaque_uv, col); - PrimWriteVtx(ImVec2(right, p_min.y + t), opaque_uv, col); - PrimWriteVtx(ImVec2(left, p_min.y + t), opaque_uv, col); - PrimWriteIdx(idx+0); PrimWriteIdx(idx+1); PrimWriteIdx(idx+2); - PrimWriteIdx(idx+0); PrimWriteIdx(idx+2); PrimWriteIdx(idx+3); + const float x0 = p_min.x + r_tl; + const float x1 = p_max.x - r_tr; + IM_APPEND_VTX(x0, p_min.y, opaque_uv, col); + IM_APPEND_VTX(x1, p_min.y, opaque_uv, col); + IM_APPEND_VTX(x1, p_min.y + t, opaque_uv, col); + IM_APPEND_VTX(x0, p_min.y + t, opaque_uv, col); + IM_APPEND_TRI(idx + 0, idx + 1, idx + 2); + IM_APPEND_TRI(idx + 0, idx + 2, idx + 3); } { // Left ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; - const float top = p_min.y + ImMax(r_tl, t); - const float bottom = p_max.y - ImMax(r_bl, t); - PrimWriteVtx(ImVec2(p_min.x, top), opaque_uv, col); - PrimWriteVtx(ImVec2(p_min.x + t, top), opaque_uv, col); - PrimWriteVtx(ImVec2(p_min.x + t, bottom), opaque_uv, col); - PrimWriteVtx(ImVec2(p_min.x, bottom), opaque_uv, col); - PrimWriteIdx(idx+0); PrimWriteIdx(idx+1); PrimWriteIdx(idx+2); - PrimWriteIdx(idx+0); PrimWriteIdx(idx+2); PrimWriteIdx(idx+3); + const float y0 = p_min.y + ImMax(r_tl, t); + const float y1 = p_max.y - ImMax(r_bl, t); + IM_APPEND_VTX(p_min.x, y0, opaque_uv, col); + IM_APPEND_VTX(p_min.x + t, y0, opaque_uv, col); + IM_APPEND_VTX(p_min.x + t, y1, opaque_uv, col); + IM_APPEND_VTX(p_min.x, y1, opaque_uv, col); + IM_APPEND_TRI(idx + 0, idx + 1, idx + 2); + IM_APPEND_TRI(idx + 0, idx + 2, idx + 3); } { // Right ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; - const float top = p_min.y + ImMax(r_tr, t); - const float bottom = p_max.y - ImMax(r_br, t); - PrimWriteVtx(ImVec2(p_max.x - t, top), opaque_uv, col); - PrimWriteVtx(ImVec2(p_max.x, top), opaque_uv, col); - PrimWriteVtx(ImVec2(p_max.x, bottom), opaque_uv, col); - PrimWriteVtx(ImVec2(p_max.x - t, bottom), opaque_uv, col); - PrimWriteIdx(idx+0); PrimWriteIdx(idx+1); PrimWriteIdx(idx+2); - PrimWriteIdx(idx+0); PrimWriteIdx(idx+2); PrimWriteIdx(idx+3); + const float y0 = p_min.y + ImMax(r_tr, t); + const float y1 = p_max.y - ImMax(r_br, t); + IM_APPEND_VTX(p_max.x - t, y0, opaque_uv, col); + IM_APPEND_VTX(p_max.x, y0, opaque_uv, col); + IM_APPEND_VTX(p_max.x, y1, opaque_uv, col); + IM_APPEND_VTX(p_max.x - t, y1, opaque_uv, col); + IM_APPEND_TRI(idx + 0, idx + 1, idx + 2); + IM_APPEND_TRI(idx + 0, idx + 2, idx + 3); } { // Bottom ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; - const float left = p_min.x + r_bl; - const float right = p_max.x - r_br; - PrimWriteVtx(ImVec2(left, p_max.y - t), opaque_uv, col); - PrimWriteVtx(ImVec2(right, p_max.y - t), opaque_uv, col); - PrimWriteVtx(ImVec2(right, p_max.y), opaque_uv, col); - PrimWriteVtx(ImVec2(left, p_max.y), opaque_uv, col); - PrimWriteIdx(idx+0); PrimWriteIdx(idx+1); PrimWriteIdx(idx+2); - PrimWriteIdx(idx+0); PrimWriteIdx(idx+2); PrimWriteIdx(idx+3); + const float x0 = p_min.x + r_bl; + const float x1 = p_max.x - r_br; + IM_APPEND_VTX(x0, p_max.y - t, opaque_uv, col); + IM_APPEND_VTX(x1, p_max.y - t, opaque_uv, col); + IM_APPEND_VTX(x1, p_max.y, opaque_uv, col); + IM_APPEND_VTX(x0, p_max.y, opaque_uv, col); + IM_APPEND_TRI(idx + 0, idx + 1, idx + 2); + IM_APPEND_TRI(idx + 0, idx + 2, idx + 3); } const int idx_used = (int)(_IdxWritePtr - start_idx_ptr); @@ -1785,32 +1803,32 @@ void ImDrawList::_AddRectFilledBaked(const ImVec2& p_min, const ImVec2& p_max, I ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; - PrimWriteVtx(ImVec2(p_min.x, p_min.y), uv_tl, col); - PrimWriteVtx(ImVec2(p_min.x + r_tl, p_min.y), uv_tr, col); - PrimWriteVtx(ImVec2(p_max.x - r_tr, p_min.y), uv_tr, col); - PrimWriteVtx(ImVec2(p_max.x, p_min.y), uv_tl, col); + IM_APPEND_VTX(p_min.x, p_min.y, uv_tl, col); + IM_APPEND_VTX(p_min.x + r_tl, p_min.y, uv_tr, col); + IM_APPEND_VTX(p_max.x - r_tr, p_min.y, uv_tr, col); + IM_APPEND_VTX(p_max.x, p_min.y, uv_tl, col); - PrimWriteVtx(ImVec2(p_min.x, p_min.y + r_tl), uv_bl, col); - PrimWriteVtx(ImVec2(p_min.x + r_tl, p_min.y + r_tl), uv_br, col); - PrimWriteVtx(ImVec2(p_max.x - r_tr, p_min.y + r_tr), uv_br, col); - PrimWriteVtx(ImVec2(p_max.x, p_min.y + r_tr), uv_bl, col); + IM_APPEND_VTX(p_min.x, p_min.y + r_tl, uv_bl, col); + IM_APPEND_VTX(p_min.x + r_tl, p_min.y + r_tl, uv_br, col); + IM_APPEND_VTX(p_max.x - r_tr, p_min.y + r_tr, uv_br, col); + IM_APPEND_VTX(p_max.x, p_min.y + r_tr, uv_bl, col); - PrimWriteVtx(ImVec2(p_min.x, p_max.y - r_bl), uv_bl, col); - PrimWriteVtx(ImVec2(p_min.x + r_bl, p_max.y - r_bl), uv_br, col); - PrimWriteVtx(ImVec2(p_max.x - r_br, p_max.y - r_br), uv_br, col); - PrimWriteVtx(ImVec2(p_max.x, p_max.y - r_br), uv_bl, col); + IM_APPEND_VTX(p_min.x, p_max.y - r_bl, uv_bl, col); + IM_APPEND_VTX(p_min.x + r_bl, p_max.y - r_bl, uv_br, col); + IM_APPEND_VTX(p_max.x - r_br, p_max.y - r_br, uv_br, col); + IM_APPEND_VTX(p_max.x, p_max.y - r_br, uv_bl, col); - PrimWriteVtx(ImVec2(p_min.x, p_max.y), uv_tl, col); - PrimWriteVtx(ImVec2(p_min.x + r_bl, p_max.y), uv_tr, col); - PrimWriteVtx(ImVec2(p_max.x - r_br, p_max.y ), uv_tr, col); - PrimWriteVtx(ImVec2(p_max.x, p_max.y), uv_tl, col); + IM_APPEND_VTX(p_min.x, p_max.y, uv_tl, col); + IM_APPEND_VTX(p_min.x + r_bl, p_max.y, uv_tr, col); + IM_APPEND_VTX(p_max.x - r_br, p_max.y, uv_tr, col); + IM_APPEND_VTX(p_max.x, p_max.y, uv_tl, col); for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { - PrimWriteIdx(idx+4); PrimWriteIdx(idx+0); PrimWriteIdx(idx+1); - PrimWriteIdx(idx+4); PrimWriteIdx(idx+1); PrimWriteIdx(idx+5); + IM_APPEND_TRI(idx + 4, idx + 0, idx + 1); + IM_APPEND_TRI(idx + 4, idx + 1, idx + 5); idx++; } idx++; From 80096045d9cf0e0f23298d9e4441e3322b93911c Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 5 May 2026 20:18:03 +0200 Subject: [PATCH 011/120] DrawList: AddRect: avoid altering rounding on StrokeOutside when initial value if 0 + comments --- imgui_draw.cpp | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index b4e1c5e65..0ec80274c 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1738,17 +1738,22 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl int s_rounding = (int)(rounding / _FringeScale); int s_thickness = (int)(thickness / _FringeScale); + // AddRectBaked() renders the stroke inside the rectangle, so we need to adjust the outside case + // This branch does not handle the center, since odd thickness strokes are not pixel aligned, even if the rectangle is. ImVec2 s_min = p_min; ImVec2 s_max = p_max; if (stroke_pos == ImDrawFlags_StrokeOutside) { s_min -= ImVec2(thickness, thickness); s_max += ImVec2(thickness, thickness); - s_rounding += s_thickness; + if (s_rounding != 0.0f) + s_rounding += s_thickness; } + // FIXME-WIP: Why isn't rounding offset in the _StrokeInside case? if (s_rounding <= 0 || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) { + // FIXME-WIP: This could use half the number of vertices. const float t = (float)s_thickness * _FringeScale; PrimReserve(6*4, 4*4); PrimRect(ImVec2(p_min.x, p_min.y), ImVec2(p_max.x, p_min.y + t), col); From 9199934a7f6a3253f70e93ced52cc0b7803a2f6d Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Fri, 8 May 2026 13:07:25 +0300 Subject: [PATCH 012/120] DrawList: added ImDrawFlags_StrokeCenterPixelAligned - added ImDrawFlags_StrokeCenterPixelAligned which center aligns stroke so that outside of the stroke is pixel aligned - fixed AddRect() non-rounded pixel aligned case to use correnct min/max - fixed AddRect() default case to not adjust rounding for non-rounded rects - fixed lineV() centered offset - renamed ImDrawFlags_StrokeCenterPixelAligned to ImDrawFlags_StrokeCenterAligned. - reduced debug build indirections. --- imgui.h | 7 +++--- imgui_draw.cpp | 66 +++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 58 insertions(+), 15 deletions(-) diff --git a/imgui.h b/imgui.h index 88bbbf9b9..7e48ce43b 100644 --- a/imgui.h +++ b/imgui.h @@ -3331,9 +3331,10 @@ enum ImDrawFlags_ // Stroke position relative to the shape outline ImDrawFlags_StrokeInside = 0 << 16, // Draw stroke inside of the shape outline (Default) ImDrawFlags_StrokeCenter = 1 << 16, // Draw stroke at the center of the shape outline - ImDrawFlags_StrokeOutside = 2 << 16, // Draw stroke outside of the shape outline - ImDrawFlags_StrokeLegacy = 3 << 16, // Use legacy position - ImDrawFlags_StrokeMask_ = 0x03 << 16, + ImDrawFlags_StrokeCenterPixelAligned = 2 << 16, // Draw stroke at the center of the shape outline, so that half thickness (rounded down) will be outside, and rest inside the shape outline. + ImDrawFlags_StrokeOutside = 3 << 16, // Draw stroke outside of the shape outline + ImDrawFlags_StrokeLegacy = 4 << 16, // Use legacy position + ImDrawFlags_StrokeMask_ = 0x07 << 16, ImDrawFlags_InvalidMask_ = ~0x7FFFFFF0, // == 0x8000000F, }; diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 0ec80274c..613010929 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1536,6 +1536,8 @@ void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float th ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); if (stroke_pos == ImDrawFlags_StrokeCenter) y -= thickness * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) + y -= IM_TRUNC(thickness * 0.5f / _FringeScale) * _FringeScale; else if (stroke_pos == ImDrawFlags_StrokeOutside) y -= thickness; @@ -1564,7 +1566,9 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); if (stroke_pos == ImDrawFlags_StrokeCenter) - x += thickness * 0.5f; + x -= thickness * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) + x -= IM_TRUNC(thickness * 0.5f / _FringeScale) * _FringeScale; else if (stroke_pos == ImDrawFlags_StrokeOutside) x -= thickness; @@ -1725,9 +1729,9 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl return; } - const bool is_truncated = _FringeScaleIsInteger && ImIsTruncated4(p_min.x, p_min.y, p_max.x, p_min.y) && ImIsTruncated4(rounding, thickness, 0.0f, 0.0f); + const bool is_truncated = _FringeScaleIsInteger&& ImIsTruncated4(p_min.x, p_min.y, p_max.x, p_min.y) && ImIsTruncated4(rounding, thickness, 0.0f, 0.0f); ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); - if ((stroke_pos == ImDrawFlags_StrokeInside || stroke_pos == ImDrawFlags_StrokeOutside) && is_truncated) + if ((stroke_pos == ImDrawFlags_StrokeInside || stroke_pos == ImDrawFlags_StrokeOutside || stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) && is_truncated) { if ((flags & ImDrawFlags_RoundCornersMask_) == 0) flags |= ImDrawFlags_RoundCornersAll; @@ -1740,15 +1744,29 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl // AddRectBaked() renders the stroke inside the rectangle, so we need to adjust the outside case // This branch does not handle the center, since odd thickness strokes are not pixel aligned, even if the rectangle is. + // Pixel aligned center works, since the stroke is aligned to pixels. ImVec2 s_min = p_min; ImVec2 s_max = p_max; if (stroke_pos == ImDrawFlags_StrokeOutside) { - s_min -= ImVec2(thickness, thickness); - s_max += ImVec2(thickness, thickness); - if (s_rounding != 0.0f) + s_min.x -= thickness; + s_min.y -= thickness; + s_max.x += thickness; + s_max.y += thickness; + if (s_rounding != 0) s_rounding += s_thickness; } + else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) + { + const float offset = (s_thickness / 2) * _FringeScale; // Using integer div, since we want IM_TRUNC(thickness/2). + s_min.x -= offset; + s_min.y -= offset; + s_max.x += offset; + s_max.y += offset; + if (s_rounding != 0) + s_rounding += s_thickness / 2; + } + // FIXME-WIP: Why isn't rounding offset in the _StrokeInside case? if (s_rounding <= 0 || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) @@ -1756,10 +1774,10 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl // FIXME-WIP: This could use half the number of vertices. const float t = (float)s_thickness * _FringeScale; PrimReserve(6*4, 4*4); - PrimRect(ImVec2(p_min.x, p_min.y), ImVec2(p_max.x, p_min.y + t), col); - PrimRect(ImVec2(p_min.x, p_min.y + t), ImVec2(p_min.x + t, p_max.y - t), col); - PrimRect(ImVec2(p_max.x - t, p_min.y + t), ImVec2(p_max.x, p_max.y - t), col); - PrimRect(ImVec2(p_min.x, p_max.y - t), ImVec2(p_max.x, p_max.y), col); + PrimRect(ImVec2(s_min.x, s_min.y), ImVec2(s_max.x, s_min.y + t), col); + PrimRect(ImVec2(s_min.x, s_min.y + t), ImVec2(s_min.x + t, s_max.y - t), col); + PrimRect(ImVec2(s_max.x - t, s_min.y + t), ImVec2(s_max.x, s_max.y - t), col); + PrimRect(ImVec2(s_min.x, s_max.y - t), ImVec2(s_max.x, s_max.y), col); return; } if ((Flags & ImDrawListFlags_RoundCornersUseTex) && s_thickness <= IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX && s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) @@ -1776,11 +1794,26 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl const ImVec2 offset(thickness * 0.5f, thickness * 0.5f); if (stroke_pos == ImDrawFlags_StrokeInside) - PathRect(p_min + offset, p_max - offset, rounding - thickness * 0.5f, flags); + { + rounding -= (rounding != 0.f) ? thickness * 0.5f : 0.f; + PathRect(p_min + offset, p_max - offset, rounding, flags); + } else if (stroke_pos == ImDrawFlags_StrokeCenter) + { PathRect(p_min, p_max, rounding, flags); + } + else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) + { + // PathRect() centers the stroke, just calculate the fractional offset. + const float fract = (thickness * 0.5f) - IM_TRUNC(thickness * 0.5f / _FringeScale) * _FringeScale; + rounding += (rounding != 0.f) ? fract : 0.f; + PathRect(ImVec2(p_min.x + fract, p_min.y + fract), ImVec2(p_max.x - fract, p_max.y - fract), rounding, flags); + } else if (stroke_pos == ImDrawFlags_StrokeOutside) - PathRect(p_min - offset, p_max + offset, rounding + thickness * 0.5f, flags); + { + rounding += (rounding != 0.f) ? thickness * 0.5f : 0.f; + PathRect(p_min - offset, p_max + offset, rounding, flags); + } else if (stroke_pos == ImDrawFlags_StrokeLegacy) { if (Flags & ImDrawListFlags_AntiAliasedLines) @@ -1958,6 +1991,8 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu const ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); if (stroke_pos == ImDrawFlags_StrokeInside) radius -= thickness * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) + radius += IM_TRUNC(thickness * 0.5f / _FringeScale) * _FringeScale - thickness * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeOutside) radius += thickness * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeLegacy) @@ -2022,6 +2057,8 @@ void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_ const ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); if (stroke_pos == ImDrawFlags_StrokeInside) radius -= thickness * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) + radius -= thickness * 0.5f - IM_TRUNC(thickness * 0.5f / _FringeScale) * _FringeScale; else if (stroke_pos == ImDrawFlags_StrokeOutside) radius += thickness * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeLegacy) @@ -2063,6 +2100,11 @@ void ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 co ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); if (stroke_pos == ImDrawFlags_StrokeInside) r -= ImVec2(thickness * 0.5f, thickness * 0.5f); + else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) + { + const float fract = thickness * 0.5f - IM_TRUNC(thickness * 0.5f / _FringeScale) * _FringeScale; + r -= ImVec2(fract, fract); + } else if (stroke_pos == ImDrawFlags_StrokeOutside) r += ImVec2(thickness * 0.5f, thickness * 0.5f); } From 789f0d7140dd1b5eb3546582e96fbe107b34f683 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Fri, 8 May 2026 13:44:14 +0300 Subject: [PATCH 013/120] DrawList: optimize non-AA non-rounded case of AddRect() --- imgui_draw.cpp | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 613010929..687b5d5f9 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1767,17 +1767,34 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl s_rounding += s_thickness / 2; } - // FIXME-WIP: Why isn't rounding offset in the _StrokeInside case? - if (s_rounding <= 0 || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) { - // FIXME-WIP: This could use half the number of vertices. + const ImVec2 opaque_uv = _Data->TexUvWhitePixel; const float t = (float)s_thickness * _FringeScale; - PrimReserve(6*4, 4*4); - PrimRect(ImVec2(s_min.x, s_min.y), ImVec2(s_max.x, s_min.y + t), col); - PrimRect(ImVec2(s_min.x, s_min.y + t), ImVec2(s_min.x + t, s_max.y - t), col); - PrimRect(ImVec2(s_max.x - t, s_min.y + t), ImVec2(s_max.x, s_max.y - t), col); - PrimRect(ImVec2(s_min.x, s_max.y - t), ImVec2(s_max.x, s_max.y), col); + + PrimReserve(8*3, 8); + const ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(s_min.x, s_min.y, opaque_uv, col); + IM_APPEND_VTX(s_min.x + t, s_min.y + t, opaque_uv, col); + IM_APPEND_VTX(s_max.x, s_min.y, opaque_uv, col); + IM_APPEND_VTX(s_max.x - t, s_min.y + t, opaque_uv, col); + IM_APPEND_VTX(s_max.x, s_max.y, opaque_uv, col); + IM_APPEND_VTX(s_max.x - t, s_max.y - t, opaque_uv, col); + IM_APPEND_VTX(s_min.x, s_max.y, opaque_uv, col); + IM_APPEND_VTX(s_min.x + t, s_max.y - t, opaque_uv, col); + + IM_APPEND_TRI(idx + 0, idx + 3, idx + 1); + IM_APPEND_TRI(idx + 0, idx + 2, idx + 3); + + IM_APPEND_TRI(idx + 2, idx + 5, idx + 3); + IM_APPEND_TRI(idx + 2, idx + 4, idx + 5); + + IM_APPEND_TRI(idx + 4, idx + 7, idx + 5); + IM_APPEND_TRI(idx + 4, idx + 6, idx + 7); + + IM_APPEND_TRI(idx + 6, idx + 1, idx + 7); + IM_APPEND_TRI(idx + 6, idx + 0, idx + 1); + return; } if ((Flags & ImDrawListFlags_RoundCornersUseTex) && s_thickness <= IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX && s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) From d6fee823b4166dc61bad32393c992438425df462 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 4 May 2026 15:55:46 +0200 Subject: [PATCH 014/120] Demo: Custom Rendering: expose Stroke Position. Demo: Custom Rendering: amend tests (to include legacy AddLine() for vertical/horizontal lines to compare. --- imgui_demo.cpp | 46 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 0b16dab3c..422da3c0c 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10406,6 +10406,15 @@ static void ShowExampleAppCustomRendering(bool* p_open) curve_segments_override |= ImGui::SliderInt("Curves segments override", &curve_segments_override_v, 3, 40); ImGui::ColorEdit4("Color", &colf.x); + static ImDrawFlags flags = 0; + ImGui::AlignTextToFramePadding(); + ImGui::Text("Stroke:"); + ImGui::SameLine(); ImGui::RadioButton("Inside", &flags, ImDrawFlags_StrokeInside); + ImGui::SameLine(); ImGui::RadioButton("Center", &flags, ImDrawFlags_StrokeCenter); + ImGui::SameLine(); ImGui::RadioButton("CenterPixelAligned", &flags, ImDrawFlags_StrokeCenterPixelAligned); + ImGui::SameLine(); ImGui::RadioButton("Outside", &flags, ImDrawFlags_StrokeOutside); + ImGui::SameLine(); ImGui::RadioButton("Legacy", &flags, ImDrawFlags_StrokeLegacy); + const ImVec2 p = ImGui::GetCursorScreenPos(); const ImU32 col = ImColor(colf); const float spacing = 10.0f; @@ -10422,23 +10431,27 @@ static void ShowExampleAppCustomRendering(bool* p_open) { // First line uses a thickness of 1.0f, second line uses the configurable thickness float th = (n == 0) ? 1.0f : thickness; - draw_list->AddNgon(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, ngon_sides, th); x += sz + spacing; // N-gon - draw_list->AddCircle(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, circle_segments, th); x += sz + spacing; // Circle - draw_list->AddEllipse(ImVec2(x + sz*0.5f, y + sz*0.5f), ImVec2(sz*0.5f, sz*0.3f), col, -0.3f, circle_segments, th); x += sz + spacing; // Ellipse - draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 0.0f, th); x += sz + spacing; // Square - draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, th); x += sz + spacing; // Square with all rounded corners - draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, th, corners_tl_br); x += sz + spacing; // Square with two rounded corners - draw_list->AddTriangle(ImVec2(x+sz*0.5f,y), ImVec2(x+sz, y+sz-0.5f), ImVec2(x, y+sz-0.5f), col, th);x += sz + spacing; // Triangle + draw_list->AddNgon(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, ngon_sides, th, flags); x += sz + spacing; // N-gon + draw_list->AddCircle(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, circle_segments, th, flags); x += sz + spacing; // Circle + draw_list->AddEllipse(ImVec2(x + sz*0.5f, y + sz*0.5f), ImVec2(sz*0.5f, sz*0.3f), col, -0.3f, circle_segments, th, flags); x += sz + spacing; // Ellipse + draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 0.0f, th, flags); x += sz + spacing; // Square + draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, th, flags); x += sz + spacing; // Square with all rounded corners + draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, th, flags | corners_tl_br); x += sz + spacing; // Square with two rounded corners + draw_list->AddTriangle(ImVec2(x+sz*0.5f,y), ImVec2(x+sz, y+sz-0.5f), ImVec2(x, y+sz-0.5f), col, th/*, flags*/);x += sz + spacing; // Triangle //draw_list->AddTriangle(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col, th);x+= sz*0.4f + spacing; // Thin triangle - PathConcaveShape(draw_list, x, y, sz); draw_list->PathStroke(col, th, ImDrawFlags_Closed); x += sz + spacing; // Concave Shape + PathConcaveShape(draw_list, x, y, sz); draw_list->PathStroke(col, th, flags | ImDrawFlags_Closed); x += sz + spacing; // Concave Shape //draw_list->AddPolyline(concave_shape, IM_COUNTOF(concave_shape), col, ImDrawFlags_Closed, th); - draw_list->AddLineH(x, x + sz, y, col, th); x += sz + spacing; // Horizontal line (note: drawing a filled rectangle will be faster!) - draw_list->AddLineV(x, y, y + sz, col, th); x += spacing; // Vertical line (note: drawing a filled rectangle will be faster!) - draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y + sz), col, th); x += sz + spacing; // Diagonal line + draw_list->AddLineH(x, x + sz, y, col, th, flags); x += sz + spacing; // Horizontal line (note: drawing a filled rectangle will be faster!) + draw_list->AddLineV(x, y, y + sz, col, th, flags); x += spacing; // Vertical line (note: drawing a filled rectangle will be faster!) + + draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y), col, th, flags); x += sz + spacing; + draw_list->AddLine(ImVec2(x, y), ImVec2(x, y + sz), col, th, flags); x += spacing; + + draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y + sz), col, th, flags); x += sz + spacing; // Diagonal line // Path draw_list->PathArcTo(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, 3.141592f, 3.141592f * -0.5f); - draw_list->PathStroke(col, th); + draw_list->PathStroke(col, th, flags); x += sz + spacing; // Quadratic Bezier Curve (3 control points) @@ -10463,8 +10476,13 @@ static void ShowExampleAppCustomRendering(bool* p_open) //draw_list->AddTriangleFilled(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col); x += sz*0.4f + spacing; // Thin triangle PathConcaveShape(draw_list, x, y, sz); draw_list->PathFillConcave(col); x += sz + spacing; // Concave shape draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + thickness), col); x += sz + spacing; // Horizontal line (faster than AddLine, but only handle integer thickness) - draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + thickness, y + sz), col); x += spacing * 2.0f;// Vertical line (faster than AddLine, but only handle integer thickness) - draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + 1, y + 1), col); x += sz; // Pixel (faster than AddLine) + draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + thickness, y + sz), col); x += spacing; // Vertical line (faster than AddLine, but only handle integer thickness) + x += sz + spacing; + x += spacing; + draw_list->AddRectFilled(ImVec2(x + 0, y), ImVec2(x + 0 + 1, y + 1), col); // Pixels (faster than AddLine) + draw_list->AddRectFilled(ImVec2(x + 2, y), ImVec2(x + 2 + 2, y + 2), col); + draw_list->AddRectFilled(ImVec2(x + 5, y), ImVec2(x + 5 + 3, y + 3), col); + x += spacing + sz; // Path draw_list->PathArcTo(ImVec2(x + sz * 0.5f, y + sz * 0.5f), sz * 0.5f, 3.141592f * -0.5f, 3.141592f); From 2361f61526ac22e6bb5e372d08d2a72eaf78d9cc Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 18 May 2026 18:42:23 +0200 Subject: [PATCH 015/120] DrawList: fixed is_truncated checks in AddRect() and AddRectFilled(). --- imgui_draw.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 687b5d5f9..5f9d2f017 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1729,7 +1729,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl return; } - const bool is_truncated = _FringeScaleIsInteger&& ImIsTruncated4(p_min.x, p_min.y, p_max.x, p_min.y) && ImIsTruncated4(rounding, thickness, 0.0f, 0.0f); + const bool is_truncated = _FringeScaleIsInteger && ImIsTruncated4(p_min.x, p_min.y, p_max.x, p_max.y) && ImIsTruncated4(rounding, thickness, 0.0f, 0.0f); ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); if ((stroke_pos == ImDrawFlags_StrokeInside || stroke_pos == ImDrawFlags_StrokeOutside || stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) && is_truncated) { @@ -1812,7 +1812,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl const ImVec2 offset(thickness * 0.5f, thickness * 0.5f); if (stroke_pos == ImDrawFlags_StrokeInside) { - rounding -= (rounding != 0.f) ? thickness * 0.5f : 0.f; + rounding -= (rounding != 0.0f) ? thickness * 0.5f : 0.0f; PathRect(p_min + offset, p_max - offset, rounding, flags); } else if (stroke_pos == ImDrawFlags_StrokeCenter) @@ -1823,12 +1823,12 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl { // PathRect() centers the stroke, just calculate the fractional offset. const float fract = (thickness * 0.5f) - IM_TRUNC(thickness * 0.5f / _FringeScale) * _FringeScale; - rounding += (rounding != 0.f) ? fract : 0.f; + rounding += (rounding != 0.0f) ? fract : 0.0f; PathRect(ImVec2(p_min.x + fract, p_min.y + fract), ImVec2(p_max.x - fract, p_max.y - fract), rounding, flags); } else if (stroke_pos == ImDrawFlags_StrokeOutside) { - rounding += (rounding != 0.f) ? thickness * 0.5f : 0.f; + rounding += (rounding != 0.0f) ? thickness * 0.5f : 0.0f; PathRect(p_min - offset, p_max + offset, rounding, flags); } else if (stroke_pos == ImDrawFlags_StrokeLegacy) @@ -1895,7 +1895,7 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c if ((col & IM_COL32_A_MASK) == 0) return; - if (ImIsTruncated4(p_min.x, p_min.y, p_max.x, p_min.y) && ImIsTruncated(rounding)) + if (ImIsTruncated4(p_min.x, p_min.y, p_max.x, p_max.y) && ImIsTruncated(rounding)) { if ((flags & ImDrawFlags_RoundCornersMask_) == 0) flags |= ImDrawFlags_RoundCornersAll; From 5a5a16da4d00d97df7063b3dfab882d61cc4ea52 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 19 May 2026 17:40:16 +0200 Subject: [PATCH 016/120] DrawList: added _GetStrokePos() helper indirection, made flags=0 select default at runtime. --- imgui.h | 11 ++++++----- imgui_demo.cpp | 3 ++- imgui_draw.cpp | 25 ++++++++++++++++++------- imgui_internal.h | 1 + 4 files changed, 27 insertions(+), 13 deletions(-) diff --git a/imgui.h b/imgui.h index 7e48ce43b..f947ee886 100644 --- a/imgui.h +++ b/imgui.h @@ -3329,11 +3329,11 @@ enum ImDrawFlags_ //ImDrawFlags_Closed = 1, // Prior to 1.92.8 (May 2026), ImDrawFlags_Closed was guaranteed to be == 1<<0 == 1 for legacy compatibility reason. Hardcoded use of 1 or true should be replaced. // Stroke position relative to the shape outline - ImDrawFlags_StrokeInside = 0 << 16, // Draw stroke inside of the shape outline (Default) - ImDrawFlags_StrokeCenter = 1 << 16, // Draw stroke at the center of the shape outline - ImDrawFlags_StrokeCenterPixelAligned = 2 << 16, // Draw stroke at the center of the shape outline, so that half thickness (rounded down) will be outside, and rest inside the shape outline. - ImDrawFlags_StrokeOutside = 3 << 16, // Draw stroke outside of the shape outline - ImDrawFlags_StrokeLegacy = 4 << 16, // Use legacy position + ImDrawFlags_StrokeInside = 1 << 16, // Draw stroke inside of the shape outline (Default) + ImDrawFlags_StrokeCenter = 2 << 16, // Draw stroke at the center of the shape outline + ImDrawFlags_StrokeCenterPixelAligned = 3 << 16, // Draw stroke at the center of the shape outline, so that half thickness (rounded down) will be outside, and rest inside the shape outline. + ImDrawFlags_StrokeOutside = 4 << 16, // Draw stroke outside of the shape outline + ImDrawFlags_StrokeLegacy = 5 << 16, // Use legacy position ImDrawFlags_StrokeMask_ = 0x07 << 16, ImDrawFlags_InvalidMask_ = ~0x7FFFFFF0, // == 0x8000000F, @@ -3524,6 +3524,7 @@ struct ImDrawList IMGUI_API void _OnChangedTexture(); IMGUI_API void _OnChangedVtxOffset(); IMGUI_API void _SetTexture(ImTextureRef tex_ref); + IMGUI_API ImDrawFlags _GetStrokePos(ImDrawFlags flags, ImDrawFlags default_stroke_pos); IMGUI_API int _CalcCircleAutoSegmentCount(float radius) const; IMGUI_API void _PathArcToFastEx(const ImVec2& center, float radius, int a_min_sample, int a_max_sample, int a_step); IMGUI_API void _PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments); diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 422da3c0c..615201f01 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10406,9 +10406,10 @@ static void ShowExampleAppCustomRendering(bool* p_open) curve_segments_override |= ImGui::SliderInt("Curves segments override", &curve_segments_override_v, 3, 40); ImGui::ColorEdit4("Color", &colf.x); - static ImDrawFlags flags = 0; + static ImDrawFlags flags = ImDrawFlags_None; ImGui::AlignTextToFramePadding(); ImGui::Text("Stroke:"); + ImGui::SameLine(); ImGui::RadioButton("Default", &flags, ImDrawFlags_None); ImGui::SameLine(); ImGui::RadioButton("Inside", &flags, ImDrawFlags_StrokeInside); ImGui::SameLine(); ImGui::RadioButton("Center", &flags, ImDrawFlags_StrokeCenter); ImGui::SameLine(); ImGui::RadioButton("CenterPixelAligned", &flags, ImDrawFlags_StrokeCenterPixelAligned); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 5f9d2f017..2bbdb8785 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1506,11 +1506,22 @@ void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDr extern bool g_LEGACY_STROKES; +// We intently don't turn g_LEGACY_STROKES into ImDrawFlags_StrokeLegacy here. +// The earlier should use verbatim legacy code but will be removed before release once we confirm that both matches. +ImDrawFlags ImDrawList::_GetStrokePos(ImDrawFlags flags, ImDrawFlags default_stroke_pos) +{ + if (flags & ImDrawFlags_StrokeMask_) + return (flags & ImDrawFlags_StrokeMask_); + return _Data->OverrideStrokePos ? _Data->OverrideStrokePos : default_stroke_pos; +} + void ImDrawList::AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) return; - if (g_LEGACY_STROKES || (flags & ImDrawFlags_StrokeMask_) == ImDrawFlags_StrokeLegacy) + + ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeCenter); // WIP: only Legacy is used. + if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) { const ImVec2 points[2] = { ImVec2(p1.x + 0.5f, p1.y + 0.5f), ImVec2(p2.x + 0.5f, p2.y + 0.5f) }; AddPolyline(points, 2, col, thickness); @@ -1533,7 +1544,7 @@ void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float th return; } - ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); + ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); if (stroke_pos == ImDrawFlags_StrokeCenter) y -= thickness * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) @@ -1564,7 +1575,7 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th return; } - ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); + ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); if (stroke_pos == ImDrawFlags_StrokeCenter) x -= thickness * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) @@ -1730,7 +1741,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl } const bool is_truncated = _FringeScaleIsInteger && ImIsTruncated4(p_min.x, p_min.y, p_max.x, p_max.y) && ImIsTruncated4(rounding, thickness, 0.0f, 0.0f); - ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); + ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); if ((stroke_pos == ImDrawFlags_StrokeInside || stroke_pos == ImDrawFlags_StrokeOutside || stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) && is_truncated) { if ((flags & ImDrawFlags_RoundCornersMask_) == 0) @@ -2005,7 +2016,7 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu } else { - const ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); + ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); if (stroke_pos == ImDrawFlags_StrokeInside) radius -= thickness * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) @@ -2071,7 +2082,7 @@ void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_ } else { - const ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); + ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); if (stroke_pos == ImDrawFlags_StrokeInside) radius -= thickness * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) @@ -2114,7 +2125,7 @@ void ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 co else { r = radius; - ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); + ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); if (stroke_pos == ImDrawFlags_StrokeInside) r -= ImVec2(thickness * 0.5f, thickness * 0.5f); else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) diff --git a/imgui_internal.h b/imgui_internal.h index d351411cc..9db27ab74 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -927,6 +927,7 @@ struct IMGUI_API ImDrawListSharedData ImFont* Font; // Current font (used for simplified AddText overload) float FontSize; // Current font size (used for simplified AddText overload) float FontScale; // Current font scale (== FontSize / Font->FontSize) + ImDrawFlags OverrideStrokePos; // Override default StrokePos when not specified by an individual primitive. == 0 or ImDrawFlags_StrokeLegacy only. float CurveTessellationMaxError; // Tessellation tolerance when using PathBezierCurveTo() float CircleTessellationMaxError; // Number of circle segments to use per pixel of radius for AddCircle() etc ImDrawListFlags InitialFlags; // Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards) From 442834a428a9df0ff3ce9f7d93e8684327e7e2cd Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Tue, 19 May 2026 23:13:33 +0300 Subject: [PATCH 017/120] DrawList: changed CenterPixelAligned growth to be smoother, added stroke pos to AddLine() - changed vertical AddLine() to be consistent with AddLineV() - changed CenterPixelAligned growth to be smoother - added (temporary) specialized stroke pos support for AddLine() --- imgui_demo.cpp | 2 +- imgui_draw.cpp | 49 ++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 41 insertions(+), 10 deletions(-) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 615201f01..a2caa877a 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10446,7 +10446,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) draw_list->AddLineV(x, y, y + sz, col, th, flags); x += spacing; // Vertical line (note: drawing a filled rectangle will be faster!) draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y), col, th, flags); x += sz + spacing; - draw_list->AddLine(ImVec2(x, y), ImVec2(x, y + sz), col, th, flags); x += spacing; + draw_list->AddLine(ImVec2(x, y + sz), ImVec2(x, y), col, th, flags); x += spacing; draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y + sz), col, th, flags); x += sz + spacing; // Diagonal line diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 2bbdb8785..415f584bb 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1504,6 +1504,19 @@ void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDr } } +static float CalculateCenterPixelAlignedOffset(float thickness, float _FringeScale) +{ + // Old jumpy one. + // return IM_TRUNC(thickness * 0.5f / _FringeScale) * _FringeScale; + + // Calculate outside offset for stroke position CenterPixelAligned + // so that one side the of the line is always at pixel boundary. + // On integer thickness both sides of the line are on pixel boundary. + const float screen_thickness = thickness / _FringeScale; + const int s_thickness = IM_TRUNC(screen_thickness); + return ((s_thickness / 2) + (s_thickness & 1) * (screen_thickness - s_thickness)) * _FringeScale; +} + extern bool g_LEGACY_STROKES; // We intently don't turn g_LEGACY_STROKES into ImDrawFlags_StrokeLegacy here. @@ -1528,8 +1541,26 @@ void ImDrawList::AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float th return; } - const ImVec2 points[2] = { p1, p2 }; - AddPolyline(points, 2, col, thickness); + ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); + ImVec2 off(0, 0); + if (stroke_pos != ImDrawFlags_StrokeCenter) + { + // FIXME: this should be removed when the generic PathStroke() stroke pos is merged. + float nx = -(p1.y - p2.y); + float ny = p1.x - p2.x; + IM_NORMALIZE2F_OVER_ZERO(nx, ny); + float offset = 0.0f; + if (stroke_pos == ImDrawFlags_StrokeInside) + offset = -thickness * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) + offset = -(thickness * 0.5f - CalculateCenterPixelAlignedOffset(thickness, _FringeScale)); + else if (stroke_pos == ImDrawFlags_StrokeOutside) + offset = thickness * 0.5f; + off.x = nx * offset; + off.y = ny * offset; + } + const ImVec2 points[2] = { p1 + off, p2 + off }; + AddPolyline(points, 2, col, thickness, flags); } void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float thickness, ImDrawFlags flags) @@ -1548,7 +1579,7 @@ void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float th if (stroke_pos == ImDrawFlags_StrokeCenter) y -= thickness * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) - y -= IM_TRUNC(thickness * 0.5f / _FringeScale) * _FringeScale; + y -= CalculateCenterPixelAlignedOffset(thickness, _FringeScale); else if (stroke_pos == ImDrawFlags_StrokeOutside) y -= thickness; @@ -1570,7 +1601,7 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th if (g_LEGACY_STROKES) { - const ImVec2 points[2] = { ImVec2(x + 0.5f, min_y + 0.5f), ImVec2(x + 0.5f, max_y + 0.5f) }; // Same as AddLine() above. + const ImVec2 points[2] = { ImVec2(x + 0.5f, max_y + 0.5f), ImVec2(x + 0.5f, min_y + 0.5f) }; // Same as AddLine() above. AddPolyline(points, 2, col, thickness); return; } @@ -1579,7 +1610,7 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th if (stroke_pos == ImDrawFlags_StrokeCenter) x -= thickness * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) - x -= IM_TRUNC(thickness * 0.5f / _FringeScale) * _FringeScale; + x -= CalculateCenterPixelAlignedOffset(thickness, _FringeScale); else if (stroke_pos == ImDrawFlags_StrokeOutside) x -= thickness; @@ -1833,7 +1864,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) { // PathRect() centers the stroke, just calculate the fractional offset. - const float fract = (thickness * 0.5f) - IM_TRUNC(thickness * 0.5f / _FringeScale) * _FringeScale; + const float fract = (thickness * 0.5f) - CalculateCenterPixelAlignedOffset(thickness, _FringeScale); rounding += (rounding != 0.0f) ? fract : 0.0f; PathRect(ImVec2(p_min.x + fract, p_min.y + fract), ImVec2(p_max.x - fract, p_max.y - fract), rounding, flags); } @@ -2020,7 +2051,7 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu if (stroke_pos == ImDrawFlags_StrokeInside) radius -= thickness * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) - radius += IM_TRUNC(thickness * 0.5f / _FringeScale) * _FringeScale - thickness * 0.5f; + radius -= thickness * 0.5f - CalculateCenterPixelAlignedOffset(thickness, _FringeScale); else if (stroke_pos == ImDrawFlags_StrokeOutside) radius += thickness * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeLegacy) @@ -2086,7 +2117,7 @@ void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_ if (stroke_pos == ImDrawFlags_StrokeInside) radius -= thickness * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) - radius -= thickness * 0.5f - IM_TRUNC(thickness * 0.5f / _FringeScale) * _FringeScale; + radius -= thickness * 0.5f - CalculateCenterPixelAlignedOffset(thickness, _FringeScale); else if (stroke_pos == ImDrawFlags_StrokeOutside) radius += thickness * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeLegacy) @@ -2130,7 +2161,7 @@ void ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 co r -= ImVec2(thickness * 0.5f, thickness * 0.5f); else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) { - const float fract = thickness * 0.5f - IM_TRUNC(thickness * 0.5f / _FringeScale) * _FringeScale; + const float fract = thickness * 0.5f - CalculateCenterPixelAlignedOffset(thickness, _FringeScale); r -= ImVec2(fract, fract); } else if (stroke_pos == ImDrawFlags_StrokeOutside) From 60a934afd903493de8669e241bf56347c7e8ecd9 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 20 May 2026 14:22:36 +0200 Subject: [PATCH 018/120] DrawList: fixed build/warnings + added pretty theorical support for _StrokeLegacy in AddLineV/AddLineH (Squash) Various zealous warning fixes. --- imgui_draw.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 415f584bb..b0abc0585 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1513,8 +1513,8 @@ static float CalculateCenterPixelAlignedOffset(float thickness, float _FringeSca // so that one side the of the line is always at pixel boundary. // On integer thickness both sides of the line are on pixel boundary. const float screen_thickness = thickness / _FringeScale; - const int s_thickness = IM_TRUNC(screen_thickness); - return ((s_thickness / 2) + (s_thickness & 1) * (screen_thickness - s_thickness)) * _FringeScale; + const int s_thickness = (int)screen_thickness; + return ((float)(s_thickness / 2) + (s_thickness & 1) * (screen_thickness - s_thickness)) * _FringeScale; } extern bool g_LEGACY_STROKES; @@ -1541,7 +1541,6 @@ void ImDrawList::AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float th return; } - ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); ImVec2 off(0, 0); if (stroke_pos != ImDrawFlags_StrokeCenter) { @@ -1568,14 +1567,14 @@ void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float th if ((col & IM_COL32_A_MASK) == 0) return; - if (g_LEGACY_STROKES) + ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); + if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) { const ImVec2 points[2] = { ImVec2(min_x + 0.5f, y + 0.5f), ImVec2(max_x + 0.5f, y + 0.5f) }; // Same as AddLine() above. AddPolyline(points, 2, col, thickness); return; } - ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); if (stroke_pos == ImDrawFlags_StrokeCenter) y -= thickness * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) @@ -1599,14 +1598,14 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th if ((col & IM_COL32_A_MASK) == 0) return; - if (g_LEGACY_STROKES) + ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); + if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) { const ImVec2 points[2] = { ImVec2(x + 0.5f, max_y + 0.5f), ImVec2(x + 0.5f, min_y + 0.5f) }; // Same as AddLine() above. AddPolyline(points, 2, col, thickness); return; } - ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); if (stroke_pos == ImDrawFlags_StrokeCenter) x -= thickness * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) @@ -1800,7 +1799,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl } else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) { - const float offset = (s_thickness / 2) * _FringeScale; // Using integer div, since we want IM_TRUNC(thickness/2). + const float offset = (float)(s_thickness / 2) * _FringeScale; // Using integer div, since we want IM_TRUNC(thickness/2). s_min.x -= offset; s_min.y -= offset; s_max.x += offset; @@ -1975,6 +1974,7 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c } // p_min = upper-left, p_max = lower-right +// Note: this does not support anti-aliasing when provided non-integer vertices. void ImDrawList::AddRectFilledMultiColor(const ImVec2& p_min, const ImVec2& p_max, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left) { if (((col_upr_left | col_upr_right | col_bot_right | col_bot_left) & IM_COL32_A_MASK) == 0) From c98d220e6ff70c818dcf45da63e21073349c5784 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Tue, 21 Apr 2026 10:46:18 +0300 Subject: [PATCH 019/120] DrawList: added improved polyline rendering. (#2183, #7972) - added 2 variants of polyline rendering - one handles integer-sized thickness using one strip of triangles along the line - second handles any thickness using 2 strips of triangles long the line --- imgui.h | 3 + imgui_demo.cpp | 2 +- imgui_draw.cpp | 724 ++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 726 insertions(+), 3 deletions(-) diff --git a/imgui.h b/imgui.h index f947ee886..c52455b3e 100644 --- a/imgui.h +++ b/imgui.h @@ -3430,6 +3430,7 @@ struct ImDrawList // - Only simple polygons are supported by filling functions (no self-intersections, no holes). // - Concave polygon fill is more expensive than convex one: it has O(N^2) complexity. Provided as a convenience for the user but not used by the main library. IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, float thickness, ImDrawFlags flags = 0); + IMGUI_API void AddPolylineLegacy(const ImVec2* points, int num_points, ImU32 col, float thickness, ImDrawFlags flags = 0); IMGUI_API void AddConvexPolyFilled(const ImVec2* points, int num_points, ImU32 col); IMGUI_API void AddConcavePolyFilled(const ImVec2* points, int num_points, ImU32 col); @@ -3530,6 +3531,8 @@ struct ImDrawList IMGUI_API void _PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments); IMGUI_API void _AddRectFilledBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, ImVec4 tex_uvs, ImDrawFlags flags); IMGUI_API void _AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, float t, ImVec4 tex_uvs, ImDrawFlags flags); + IMGUI_API void _AddPolylineThin(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, ImVec4 tex_uvs); + IMGUI_API void _AddPolylineThick(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags); }; // All draw data to render a Dear ImGui frame diff --git a/imgui_demo.cpp b/imgui_demo.cpp index a2caa877a..23d340dc4 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10396,7 +10396,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) static int curve_segments_override_v = 8; static ImVec4 colf = ImVec4(1.0f, 1.0f, 0.4f, 1.0f); ImGui::DragFloat("Size", &sz, 0.2f, 2.0f, 100.0f, "%.0f"); - ImGui::DragFloat("Thickness", &thickness, 0.05f, 1.0f, 8.0f, "%.02f"); + ImGui::DragFloat("Thickness", &thickness, 0.05f, 0.0f, 32.0f, "%.02f"); ImGui::SliderInt("N-gon sides", &ngon_sides, 3, 12); ImGui::Checkbox("##circlesegmentoverride", &circle_segments_override); ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index b0abc0585..37c5ae4b9 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -838,9 +838,729 @@ void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, c _IdxWritePtr += 3; \ } (void)0 -// TODO: Thickness anti-aliased lines cap are missing their AA fringe. -// We avoid using the ImVec2 math operators here to reduce cost to a minimum for debug/non-inlined builds. +#define IM_POLYLINE_MITER_ANGLE_LIMIT (-0.9999619f) // cos(179.5) +#define IM_POLYLINE_MITER_LIMIT (4.0f) + +static void CalcSegmentNormals(const ImVec2* points, const int points_count, ImVec2* normals, float* sqr_lengths, bool closed) +{ + // Calculate normals for each line segment + for (int i = 0; i < points_count - 1; i++) + { + float dx = points[i+1].x - points[i].x; + float dy = points[i+1].y - points[i].y; + const float d2 = dx*dx + dy*dy; + const float inv_len = (d2 > 0.0f) ? 1.f / sqrtf(d2) : 0.f; //ImRsqrt(d2) : 0.f; + normals[i].x = -dy * inv_len; + normals[i].y = dx * inv_len; + sqr_lengths[i] = d2; + } + if (closed) + { + const float dx = points[0].x - points[points_count - 1].x; + const float dy = points[0].y - points[points_count - 1].y; + const float d2 = dx*dx + dy*dy; + const float inv_len = (d2 > 0.0f) ? 1.f / sqrtf(d2) : 0.f; //ImRsqrt(d2) : 0.f; + normals[points_count - 1].x = -dy * inv_len; + normals[points_count - 1].y = dx * inv_len; + sqr_lengths[points_count - 1] = d2; + } + else + { + normals[points_count - 1] = normals[points_count - 2]; + sqr_lengths[points_count - 1] = 0.f; + } +} + +void ImDrawList::_AddPolylineThin(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, ImVec4 tex_uvs) +{ + const bool closed = (flags & ImDrawFlags_Closed) != 0; + + _Data->TempBuffer.reserve_discard(points_count * 2); + ImVec2* normals = _Data->TempBuffer.Data; + float* sqr_lengths = (float*)(normals + points_count); + CalcSegmentNormals(points, points_count, normals, sqr_lengths, closed); + + const ImU32 col_trans = col & ~IM_COL32_A_MASK; + + thickness += _FringeScale; + const float half_thickness = thickness * 0.5f; + const float miter_distance_limit = half_thickness * IM_POLYLINE_MITER_LIMIT; + const float miter_distance_limit_sqr = miter_distance_limit * miter_distance_limit; + const float half_aa = _FringeScale * 0.5f; + + // TODO: Each point will generate 2-7 vertices. The worst case happens really rarely. This can be issue with large polylines and 16it indices. + // One option could be to calculate the miter values and overl & bevel flags before, then alloc, and finally commit. + int idx_count = 0; + int vtx_count = 0; + if (closed) + { + vtx_count = /*body*/points_count * 7 + /*closing*/3; + idx_count = (/*body*/points_count * 5 + /*closing*/4) * 3; + } + else + { + // Body + caps + vtx_count = /*body*/(points_count - 2) * 7 + /*caps*/(6 * 2); + idx_count = (/*body*/(points_count - 2) * 5 + /*last seg*/4 + /*caps*/(4 * 2)) * 3; + } + + PrimReserve(idx_count, vtx_count); + + ImDrawVert* start_vtx_ptr = _VtxWritePtr; + ImDrawIdx* start_idx_ptr = _IdxWritePtr; + + int base_idx = (int)_VtxCurrentIdx; + + ImVec2 p1; + ImVec2 n1; + float len_sqr1; + + const float half_texel = (0.5f / _FringeScale) * _Data->FontAtlas->TexUvScale.x; + const ImVec2 uv0(tex_uvs.x + half_texel, tex_uvs.y); + const ImVec2 uv1(tex_uvs.z - half_texel, tex_uvs.y); + const ImVec2 uv2((uv0.x+uv1.x)*0.5f, tex_uvs.y); + + int point_idx = 0; + int point_end = points_count; + if (!closed) + { + // Start cap + point_idx++; + point_end--; + p1 = points[0]; + n1 = normals[0]; + len_sqr1 = sqr_lengths[0]; + + const ImVec2 dir(n1.y, -n1.x); + const ImVec2 pa = p1 - dir * half_aa; + const ImVec2 pb = p1 + dir * half_aa; + + base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(pa.x - n1.x * half_thickness , pa.y - n1.y * half_thickness , uv0, col_trans); + IM_APPEND_VTX(pa.x + n1.x * half_thickness , pa.y + n1.y * half_thickness , uv1, col_trans); + + int next_base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(pb.x - n1.x * half_thickness , pb.y - n1.y * half_thickness , uv0, col); + IM_APPEND_VTX(pb.x + n1.x * half_thickness , pb.y + n1.y * half_thickness , uv1, col); + + // AA cap + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); + IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); + base_idx = next_base_idx; + } + else + { + // Wrap around segment + p1 = points[points_count-1]; + n1 = normals[points_count-1]; + len_sqr1 = sqr_lengths[points_count-1]; + + // This will be filled later, allocate space. + base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(0, 0, uv0, col); + IM_APPEND_VTX(0, 0, uv1, col); + } + + while (point_idx < point_end) + { + ImVec2 n0 = n1; + float len_sqr0 = len_sqr1; + p1 = points[point_idx]; + n1 = normals[point_idx]; + len_sqr1 = sqr_lengths[point_idx]; + + // theta is the angle between two segments + const float cos_theta = n0.x * n1.x + n0.y * n1.y; + + // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm + const float miter_scale_factor = (cos_theta > IM_POLYLINE_MITER_ANGLE_LIMIT) ? half_thickness / (1.0f + cos_theta) : FLT_MAX; // avoid division by zero + const float miter_offset_x = (n0.x + n1.x) * miter_scale_factor; + const float miter_offset_y = (n0.y + n1.y) * miter_scale_factor; + const float miter_distance_sqr = miter_offset_x * miter_offset_x + miter_offset_y * miter_offset_y; + + const bool overlap = (len_sqr0 < miter_distance_sqr) || (len_sqr1 < miter_distance_sqr) || (cos_theta <= IM_POLYLINE_MITER_ANGLE_LIMIT); + const bool bevel = miter_distance_sqr > miter_distance_limit_sqr; + + if (bevel) + { + // Clipped bevel + const float sin_theta = n0.y * n1.x - n0.x * n1.y; + float bevel_normal_x = n0.x + n1.x; + float bevel_normal_y = n0.y + n1.y; + IM_NORMALIZE2F_OVER_ZERO(bevel_normal_x, bevel_normal_y); + + const float signed_miter_offset = sin_theta < 0.0f ? half_thickness : -half_thickness; + const float side_offset = half_thickness * ((n0.x * bevel_normal_x + n0.y * bevel_normal_y) - 1.f) / (n0.y * bevel_normal_x - n0.x * bevel_normal_y); + const float pt_x = p1.x - bevel_normal_x * signed_miter_offset; + const float pt_y = p1.y - bevel_normal_y * signed_miter_offset; + const float sd_x = bevel_normal_y * side_offset; + const float sd_y = -bevel_normal_x * side_offset; + + if (overlap) + { + // Dislocated bevel. + if (sin_theta < 0.f) + { + IM_APPEND_VTX(p1.x - n0.x * half_thickness, p1.y - n0.y * half_thickness, uv0, col); // 2 + IM_APPEND_VTX(p1.x + n0.x * half_thickness, p1.y + n0.y * half_thickness, uv1, col); // 3 + IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv0, col); // 4 + IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv0, col); // 5 + IM_APPEND_VTX(p1.x, p1.y, uv2, col); // 6 + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - n1.x * half_thickness, p1.y - n1.y * half_thickness, uv0, col); // 7 + IM_APPEND_VTX(p1.x + n1.x * half_thickness, p1.y + n1.y * half_thickness, uv1, col); // 8 + + // Connect prev to next + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); + IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); + // Bevel tris + IM_APPEND_TRI(base_idx + 6, base_idx + 2, base_idx + 4); + IM_APPEND_TRI(base_idx + 6, base_idx + 4, base_idx + 5); + IM_APPEND_TRI(base_idx + 6, base_idx + 5, base_idx + 7); + base_idx = next_base_idx; + } + else + { + IM_APPEND_VTX(p1.x - n0.x * half_thickness, p1.y - n0.y * half_thickness, uv0, col); // 2 + IM_APPEND_VTX(p1.x + n0.x * half_thickness, p1.y + n0.y * half_thickness, uv1, col); // 3 + IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv1, col); // 4 + IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv1, col); // 5 + IM_APPEND_VTX(p1.x, p1.y, uv2, col); // 6 + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - n1.x * half_thickness, p1.y - n1.y * half_thickness, uv0, col); // 7 + IM_APPEND_VTX(p1.x + n1.x * half_thickness, p1.y + n1.y * half_thickness, uv1, col); // 8 + + // Connect prev to next + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); + IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); + // Bevel + IM_APPEND_TRI(base_idx + 6, base_idx + 7, base_idx + 5); + IM_APPEND_TRI(base_idx + 6, base_idx + 5, base_idx + 4); + IM_APPEND_TRI(base_idx + 6, base_idx + 4, base_idx + 3); + base_idx = next_base_idx; + } + } + else + { + // Bevel + if (sin_theta < 0.f) + { +/* IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv0, col); + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv0, col); + IM_APPEND_VTX(p1.x + miter_offset_x, p1.y + miter_offset_y, uv1, col); + + // Bevel tri + IM_APPEND_TRI(base_idx+2, base_idx+3, base_idx+4); + // Connect prev to next + IM_APPEND_TRI(base_idx+0, base_idx+2, base_idx+4); + IM_APPEND_TRI(base_idx+0, base_idx+4, base_idx+1); + base_idx = next_base_idx;*/ + + IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv0, col); // 2 + IM_APPEND_VTX(p1.x, p1.y, uv2, col); // 3 + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv0, col); // 4 + IM_APPEND_VTX(p1.x + miter_offset_x, p1.y + miter_offset_y, uv1, col); // 5 + + // Connect prev to next + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 5); + IM_APPEND_TRI(base_idx + 0, base_idx + 5, base_idx + 1); + // Bevel + IM_APPEND_TRI(base_idx + 3, base_idx + 5, base_idx + 2); + IM_APPEND_TRI(base_idx + 3, base_idx + 2, base_idx + 4); + IM_APPEND_TRI(base_idx + 3, base_idx + 4, base_idx + 5); + base_idx = next_base_idx; + } + else + { + IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv1, col); // 2 + IM_APPEND_VTX(p1.x, p1.y, uv2, col); // 3 + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - miter_offset_x, p1.y - miter_offset_y, uv0, col); // 4 + IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv1, col); // 5 + + // Connect prev to next + IM_APPEND_TRI(base_idx + 0, base_idx + 4, base_idx + 2); + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 1); + // Bevel + IM_APPEND_TRI(base_idx + 3, base_idx + 2, base_idx + 4); + IM_APPEND_TRI(base_idx + 3, base_idx + 4, base_idx + 5); + IM_APPEND_TRI(base_idx + 3, base_idx + 5, base_idx + 2); + base_idx = next_base_idx; + +/* IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv1, col); + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - miter_offset_x, p1.y - miter_offset_y, uv0, col); + IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv1, col); + + // Bevel tri + IM_APPEND_TRI(base_idx + 3, base_idx + 4, base_idx + 2); + // Connect prev to next + IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 2); + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 1); + base_idx = next_base_idx;*/ + } + } + } + else + { + if (overlap) + { + // Dislocated Miter + const float sin_theta = n0.y * n1.x - n0.x * n1.y; + if (sin_theta < 0.f) + { + + IM_APPEND_VTX(p1.x - n0.x * half_thickness, p1.y - n0.y * half_thickness, uv0, col); // 2 + IM_APPEND_VTX(p1.x + n0.x * half_thickness, p1.y + n0.y * half_thickness, uv1, col); // 3 + IM_APPEND_VTX(p1.x - miter_offset_x, p1.y - miter_offset_y, uv0, col); // 4 + IM_APPEND_VTX(p1.x, p1.y, uv2, col); // 5 + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - n1.x * half_thickness, p1.y - n1.y * half_thickness, uv0, col); // 6 + IM_APPEND_VTX(p1.x + n1.x * half_thickness, p1.y + n1.y * half_thickness, uv1, col); // 7 + + // Connect prev to next + IM_APPEND_TRI(base_idx+0, base_idx+2, base_idx+3); + IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+1); + // Miter + IM_APPEND_TRI(base_idx+5, base_idx+2, base_idx+4); + IM_APPEND_TRI(base_idx+5, base_idx+4, base_idx+6); + + base_idx = next_base_idx; + } + else + { + IM_APPEND_VTX(p1.x - n0.x * half_thickness, p1.y - n0.y * half_thickness, uv0, col); // 2 + IM_APPEND_VTX(p1.x + n0.x * half_thickness, p1.y + n0.y * half_thickness, uv1, col); // 3 + IM_APPEND_VTX(p1.x + miter_offset_x, p1.y + miter_offset_y, uv1, col); // 4 + IM_APPEND_VTX(p1.x, p1.y, uv2, col); // 5 + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - n1.x * half_thickness, p1.y - n1.y * half_thickness, uv0, col); // 6 + IM_APPEND_VTX(p1.x + n1.x * half_thickness, p1.y + n1.y * half_thickness, uv1, col); // 7 + + // Connect prev to next + IM_APPEND_TRI(base_idx+0, base_idx+2, base_idx+3); + IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+1); + // Miter + IM_APPEND_TRI(base_idx+5, base_idx+4, base_idx+3); + IM_APPEND_TRI(base_idx+5, base_idx+7, base_idx+4); + + base_idx = next_base_idx; + } + } + else + { + // Miter + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - miter_offset_x, p1.y - miter_offset_y, uv0, col); // 2 + IM_APPEND_VTX(p1.x + miter_offset_x, p1.y + miter_offset_y, uv1, col); // 3 + + // Connect prev to next + IM_APPEND_TRI(base_idx+0, base_idx+2, base_idx+3); + IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+1); + + base_idx = next_base_idx; + } + } + + point_idx++; + } + + // End cap + if (!closed) + { + p1 = points[points_count-1]; + n1 = normals[points_count-1]; + + // End cap + const ImVec2 dir(n1.y, -n1.x); + const ImVec2 pa = p1 - dir * half_aa; + const ImVec2 pb = p1 + dir * half_aa; + + int next_base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(pa.x - n1.x * half_thickness, pa.y - n1.y * half_thickness, uv0, col); + IM_APPEND_VTX(pa.x + n1.x * half_thickness, pa.y + n1.y * half_thickness, uv1, col); + + IM_APPEND_VTX(pb.x - n1.x * half_thickness, pb.y - n1.y * half_thickness, uv0, col_trans); + IM_APPEND_VTX(pb.x + n1.x * half_thickness, pb.y + n1.y * half_thickness, uv1, col_trans); + + // Connect + IM_APPEND_TRI(base_idx+0, base_idx+2, base_idx+3); + IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+1); + base_idx = next_base_idx; + + // AA cap + IM_APPEND_TRI(base_idx+0, base_idx+2, base_idx+3); + IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+1); + } + else + { + // Connect the path. + start_vtx_ptr[0] = VtxBuffer.Data[base_idx+0]; + start_vtx_ptr[1] = VtxBuffer.Data[base_idx+1]; + } + + // Restore unused memory + const int vtx_used = (int)(_VtxWritePtr - start_vtx_ptr); + const int idx_used = (int)(_IdxWritePtr - start_idx_ptr); + IM_ASSERT(vtx_used <= vtx_count && idx_used <= idx_count); + PrimUnreserve(idx_count - idx_used, vtx_count - vtx_used); +} + +void ImDrawList::_AddPolylineThick(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags) +{ + const bool closed = (flags & ImDrawFlags_Closed) != 0; + + _Data->TempBuffer.reserve_discard(points_count * 2); + ImVec2* normals = _Data->TempBuffer.Data; + float* sqr_lengths = (float*)(normals + points_count); + CalcSegmentNormals(points, points_count, normals, sqr_lengths, closed); + + const ImU32 col_trans = col & ~IM_COL32_A_MASK; + + // TODO: we can expand this by having one very long texture strip for the lines, + // or if place black 2x2 pixel next to the while pixel, and use texture clamping (that thickness would expand out of the texture). + const float max_width = (IM_DRAWLIST_TEX_LINES_WIDTH_MAX-1) * _FringeScale * 2.f; + thickness = ImMin(thickness, max_width); + + thickness += _FringeScale; // Place half of AA fringe each side of the line. + const float half_thickness = thickness * 0.5f; + const float miter_distance_limit = half_thickness * IM_POLYLINE_MITER_LIMIT; + const float miter_distance_limit_sqr = miter_distance_limit * miter_distance_limit; + const float half_aa = _FringeScale * 0.5f; + + int idx_count = 0; + int vtx_count = 0; + if (closed) + { + vtx_count = /*body*/points_count * 5 + /*closing*/3; + idx_count = (/*body*/points_count * 5 + /*closing*/4) * 3; + } + else + { + // Body + caps + vtx_count = /*body*/(points_count - 2) * 5 + /*caps*/(6 * 2); + idx_count = (/*body*/(points_count - 2) * 5 + /*last seg*/4 + /*caps*/(4 * 2)) * 3; + } + + PrimReserve(idx_count, vtx_count); + + ImDrawVert* start_vtx_ptr = _VtxWritePtr; + ImDrawIdx* start_idx_ptr = _IdxWritePtr; + + int base_idx = (int)_VtxCurrentIdx; + + ImVec2 p1; + ImVec2 n1; + float len_sqr1; + + const ImVec4 tex_uvs = _Data->TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX]; + const ImVec2 uv_out(tex_uvs.x + (0.5f / _FringeScale) * _Data->FontAtlas->TexUvScale.x, tex_uvs.y); + const ImVec2 uv_in(tex_uvs.x + ((half_thickness + 0.5f) / _FringeScale) * _Data->FontAtlas->TexUvScale.x, tex_uvs.y); + + int point_idx = 0; + int point_end = points_count; + if (!closed) + { + point_idx++; + point_end--; + p1 = points[0]; + n1 = normals[0]; + len_sqr1 = sqr_lengths[0]; + + // Start cap + const ImVec2 dir(n1.y, -n1.x); + const ImVec2 pa = p1 - dir * half_aa; + const ImVec2 pb = p1 + dir * half_aa; + + base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(pa.x - n1.x * half_thickness , pa.y - n1.y * half_thickness , uv_out, col_trans); + IM_APPEND_VTX(pa.x, pa.y, uv_in, col_trans); + IM_APPEND_VTX(pa.x + n1.x * half_thickness , pa.y + n1.y * half_thickness , uv_out, col_trans); + + int next_base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(pb.x - n1.x * half_thickness , pb.y - n1.y * half_thickness , uv_out, col); + IM_APPEND_VTX(pb.x, pb.y, uv_in, col); + IM_APPEND_VTX(pb.x + n1.x * half_thickness , pb.y + n1.y * half_thickness , uv_out, col); + + // AA cap + IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+4); + IM_APPEND_TRI(base_idx+0, base_idx+4, base_idx+1); + IM_APPEND_TRI(base_idx+1, base_idx+4, base_idx+5); + IM_APPEND_TRI(base_idx+1, base_idx+5, base_idx+2); + base_idx = next_base_idx; + } + else + { + p1 = points[points_count-1]; + n1 = normals[points_count-1]; + len_sqr1 = sqr_lengths[points_count-1]; + + // This will be filled later, allocate space. + base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(0, 0, uv_out, col); + IM_APPEND_VTX(0, 0, uv_in, col); + IM_APPEND_VTX(0, 0, uv_out, col); + } + + while (point_idx < point_end) + { + ImVec2 n0 = n1; + float len_sqr0 = len_sqr1; + p1 = points[point_idx]; + n1 = normals[point_idx]; + len_sqr1 = sqr_lengths[point_idx]; + + // theta is the angle between two segments + const float cos_theta = n0.x * n1.x + n0.y * n1.y; + + // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm + const float miter_scale_factor = (cos_theta > IM_POLYLINE_MITER_ANGLE_LIMIT) ? half_thickness / (1.0f + cos_theta) : FLT_MAX; // avoid division by zero + const float miter_offset_x = (n0.x + n1.x) * miter_scale_factor; + const float miter_offset_y = (n0.y + n1.y) * miter_scale_factor; + const float miter_distance_sqr = miter_offset_x * miter_offset_x + miter_offset_y * miter_offset_y; + + const bool overlap = (len_sqr0 < miter_distance_sqr) || (len_sqr1 < miter_distance_sqr) || (cos_theta <= IM_POLYLINE_MITER_ANGLE_LIMIT); + const bool bevel = miter_distance_sqr > miter_distance_limit_sqr; + + if (bevel) + { + // Clipped bevel + const float sin_theta = n0.y * n1.x - n0.x * n1.y; + float bevel_normal_x = n0.x + n1.x; + float bevel_normal_y = n0.y + n1.y; + IM_NORMALIZE2F_OVER_ZERO(bevel_normal_x, bevel_normal_y); + + const float signed_miter_offset = sin_theta < 0.0f ? half_thickness : -half_thickness; + const float side_offset = half_thickness * ((n0.x * bevel_normal_x + n0.y * bevel_normal_y) - 1.f) / (n0.y * bevel_normal_x - n0.x * bevel_normal_y); + const float pt_x = p1.x - bevel_normal_x * signed_miter_offset; + const float pt_y = p1.y - bevel_normal_y * signed_miter_offset; + const float sd_x = bevel_normal_y * side_offset; + const float sd_y = -bevel_normal_x * side_offset; + + if (overlap) + { + // Dislocated bevel. + if (sin_theta < 0.f) + { + IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv_out, col); + IM_APPEND_VTX(p1.x + n0.x * half_thickness, p1.y + n0.y * half_thickness, uv_out, col); + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv_out, col); + IM_APPEND_VTX(p1.x, p1.y, uv_in, col); + IM_APPEND_VTX(p1.x + n1.x * half_thickness, p1.y + n1.y * half_thickness, uv_out, col); + + // Bevel tri + IM_APPEND_TRI(base_idx+3, base_idx+5, base_idx+6); + // Connect prev to next + IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+6); + IM_APPEND_TRI(base_idx+0, base_idx+6, base_idx+1); + IM_APPEND_TRI(base_idx+1, base_idx+6, base_idx+4); + IM_APPEND_TRI(base_idx+1, base_idx+4, base_idx+2); + base_idx = next_base_idx; + } + else + { + IM_APPEND_VTX(p1.x - n0.x * half_thickness, p1.y - n0.y * half_thickness, uv_out, col); + IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv_out, col); + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - n1.x * half_thickness, p1.y - n1.y * half_thickness, uv_out, col); + IM_APPEND_VTX(p1.x, p1.y, uv_in, col); + IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv_out, col); + + // Bevel tri + IM_APPEND_TRI(base_idx+6, base_idx+7, base_idx+4); + // Connect prev to next + IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+6); + IM_APPEND_TRI(base_idx+0, base_idx+6, base_idx+1); + IM_APPEND_TRI(base_idx+1, base_idx+6, base_idx+4); + IM_APPEND_TRI(base_idx+1, base_idx+4, base_idx+2); + base_idx = next_base_idx; + } + } + else + { + // Bevel + if (sin_theta < 0.f) + { + IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv_out, col); + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv_out, col); + IM_APPEND_VTX(p1.x, p1.y, uv_in, col); + IM_APPEND_VTX(p1.x + miter_offset_x, p1.y + miter_offset_y, uv_out, col); + + // Bevel tri + IM_APPEND_TRI(base_idx+3, base_idx+4, base_idx+5); + // Connect prev to next + IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+5); + IM_APPEND_TRI(base_idx+0, base_idx+5, base_idx+1); + IM_APPEND_TRI(base_idx+1, base_idx+5, base_idx+6); + IM_APPEND_TRI(base_idx+1, base_idx+6, base_idx+2); + base_idx = next_base_idx; + } + else + { + IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv_out, col); + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - miter_offset_x, p1.y - miter_offset_y, uv_out, col); + IM_APPEND_VTX(p1.x, p1.y, uv_in, col); + IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv_out, col); + + // Bevel tri + IM_APPEND_TRI(base_idx+5, base_idx+6, base_idx+3); + // Connect prev to next + IM_APPEND_TRI(base_idx+0, base_idx+4, base_idx+5); + IM_APPEND_TRI(base_idx+0, base_idx+5, base_idx+1); + IM_APPEND_TRI(base_idx+1, base_idx+5, base_idx+3); + IM_APPEND_TRI(base_idx+1, base_idx+3, base_idx+2); + base_idx = next_base_idx; + } + } + } + else + { + if (overlap) + { + // Dislocated miter + const float sin_theta = n0.y * n1.x - n0.x * n1.y; + if (sin_theta < 0.f) + { + IM_APPEND_VTX(p1.x + n0.x * half_thickness, p1.y + n0.y * half_thickness, uv_out, col); + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - miter_offset_x, p1.y - miter_offset_y, uv_out, col); + IM_APPEND_VTX(p1.x, p1.y, uv_in, col); + IM_APPEND_VTX(p1.x + n1.x * half_thickness, p1.y + n1.y * half_thickness, uv_out, col); + + // Connect prev to next + IM_APPEND_TRI(base_idx+0, base_idx+4, base_idx+5); + IM_APPEND_TRI(base_idx+0, base_idx+5, base_idx+1); + IM_APPEND_TRI(base_idx+1, base_idx+5, base_idx+3); + IM_APPEND_TRI(base_idx+1, base_idx+3, base_idx+2); + + base_idx = next_base_idx; + } + else + { + IM_APPEND_VTX(p1.x - n0.x * half_thickness, p1.y - n0.y * half_thickness, uv_out, col); + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - n1.x * half_thickness, p1.y - n1.y * half_thickness, uv_out, col); + IM_APPEND_VTX(p1.x, p1.y, uv_in, col); + IM_APPEND_VTX(p1.x + miter_offset_x, p1.y + miter_offset_y, uv_out, col); + + // Connect prev to next + IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+5); + IM_APPEND_TRI(base_idx+0, base_idx+5, base_idx+1); + IM_APPEND_TRI(base_idx+1, base_idx+5, base_idx+6); + IM_APPEND_TRI(base_idx+1, base_idx+6, base_idx+2); + + base_idx = next_base_idx; + } + } + else + { + // Miter + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - miter_offset_x, p1.y - miter_offset_y, uv_out, col); + IM_APPEND_VTX(p1.x, p1.y, uv_in, col); + IM_APPEND_VTX(p1.x + miter_offset_x, p1.y + miter_offset_y, uv_out, col); + + // Connect prev to next + IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+4); + IM_APPEND_TRI(base_idx+0, base_idx+4, base_idx+1); + IM_APPEND_TRI(base_idx+1, base_idx+4, base_idx+5); + IM_APPEND_TRI(base_idx+1, base_idx+5, base_idx+2); + + base_idx = next_base_idx; + } + } + + point_idx++; + } + + // End cap + if (!closed) + { + p1 = points[points_count-1]; + n1 = normals[points_count-1]; + + // End cap + const ImVec2 dir(n1.y, -n1.x); + const ImVec2 pa = p1 - dir * half_aa; + const ImVec2 pb = p1 + dir * half_aa; + + int next_base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(pa.x - n1.x * half_thickness, pa.y - n1.y * half_thickness, uv_out, col); + IM_APPEND_VTX(pa.x, pa.y, uv_in, col); + IM_APPEND_VTX(pa.x + n1.x * half_thickness, pa.y + n1.y * half_thickness, uv_out, col); + + IM_APPEND_VTX(pb.x - n1.x * half_thickness, pb.y - n1.y * half_thickness, uv_out, col_trans); + IM_APPEND_VTX(pb.x, pb.y, uv_in, col_trans); + IM_APPEND_VTX(pb.x + n1.x * half_thickness, pb.y + n1.y * half_thickness, uv_out, col_trans); + + // Connect + IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+4); + IM_APPEND_TRI(base_idx+0, base_idx+4, base_idx+1); + IM_APPEND_TRI(base_idx+1, base_idx+4, base_idx+5); + IM_APPEND_TRI(base_idx+1, base_idx+5, base_idx+2); + base_idx = next_base_idx; + + // AA cap + IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+4); + IM_APPEND_TRI(base_idx+0, base_idx+4, base_idx+1); + IM_APPEND_TRI(base_idx+1, base_idx+4, base_idx+5); + IM_APPEND_TRI(base_idx+1, base_idx+5, base_idx+2); + } + else + { + // Connect the path. + start_vtx_ptr[0] = VtxBuffer.Data[base_idx+0]; + start_vtx_ptr[1] = VtxBuffer.Data[base_idx+1]; + start_vtx_ptr[2] = VtxBuffer.Data[base_idx+2]; + } + + // Restore unused memory + const int vtx_used = (int)(_VtxWritePtr - start_vtx_ptr); + const int idx_used = (int)(_IdxWritePtr - start_idx_ptr); + IM_ASSERT(vtx_used <= vtx_count && idx_used <= idx_count); + PrimUnreserve(idx_count - idx_used, vtx_count - vtx_used); +} + void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags) +{ + if (points_count < 2 || (col & IM_COL32_A_MASK) == 0) + return; + + float screen_thickness = thickness / _FringeScale; + if (screen_thickness < 1.f/255.f) + return; + if (screen_thickness < 1.f) + { + const float alpha = thickness; + col = ImGui::GetColorU32(col, alpha); + screen_thickness = 1.f; + thickness = _FringeScale; + } + + // TODO: square vs miter cap + // TODO: support splitting very long lines to multiple draw calls. + + // We can use cheaper rendering if the thickness is integer size. + const int int_thickness = (int)screen_thickness; + const bool can_use_thin = ImAbs(screen_thickness - (float)int_thickness) < 0.01f && (int_thickness >= 1 && int_thickness < IM_DRAWLIST_TEX_LINES_WIDTH_MAX); + + if (can_use_thin) + { + const ImVec4 tex_uvs = _Data->TexUvLines[int_thickness]; + _AddPolylineThin(points, points_count, col, (float)int_thickness * _FringeScale, flags, tex_uvs); + } + else + { + _AddPolylineThick(points, points_count, col, thickness, flags); + } +} + +void ImDrawList::AddPolylineLegacy(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags) { if (points_count < 2 || (col & IM_COL32_A_MASK) == 0) return; From a5b8a920c323b5e1e2df40a0224d2e2aeca2103f Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Tue, 21 Apr 2026 15:28:20 +0300 Subject: [PATCH 020/120] DrawList: added better handling for rendering degenerate geometry. (#2183, #7972) - Add ImDrawFlags_MiterOnly which is used to indicate that a path contains simple geometry and can be rendered using mitered corners - Added handling of degenerated shapes for stroked rectangle, circle, ngon, and ellipse - Render shapes filled when outline covers the whole shape - Clamp very small circles and ngons, and use alpha fade below one pixel - Added correct offset for ellipse --- imgui.h | 5 +- imgui_demo.cpp | 2 +- imgui_draw.cpp | 263 ++++++++++++++++++++++++++++++++++++------------- 3 files changed, 200 insertions(+), 70 deletions(-) diff --git a/imgui.h b/imgui.h index c52455b3e..5d5599058 100644 --- a/imgui.h +++ b/imgui.h @@ -3308,6 +3308,7 @@ struct ImDrawListSplitter enum ImDrawFlags_ { ImDrawFlags_None = 0, + //ImDrawFlags_Closed = 1, // -> FLAG MOVED BELOW. Prior to 1.92.8 (May 2026): ImDrawFlags_Closed was guaranteed to be == (1<<0) == 1, for legacy compatibility reason. Hardcoded use of 1 or true should be replaced with 'ImDrawFlags_Closed'. // Rounding for AddRect(), AddRectFilled(), PathRect() // - When not specified, we defaults to ImDrawFlags_RoundCornersAll! So you only need to use those flags if you want another configuration. @@ -3325,8 +3326,8 @@ enum ImDrawFlags_ ImDrawFlags_RoundCornersMask_ = ImDrawFlags_RoundCornersAll | ImDrawFlags_RoundCornersNone, // Stroke options - ImDrawFlags_Closed = 1 << 9, // PathStroke(), AddPolyline(): specify that shape should be closed. - //ImDrawFlags_Closed = 1, // Prior to 1.92.8 (May 2026), ImDrawFlags_Closed was guaranteed to be == 1<<0 == 1 for legacy compatibility reason. Hardcoded use of 1 or true should be replaced. + ImDrawFlags_Closed = 1 << 9, // PathStroke(), AddPolyline(): specify that shape should be closed. + ImDrawFlags_MiterOnly = 1 << 10, // PathStroke(), AddPolyline(): use miter corners only. This assumes that the input polyline does not have corners sharper than 90 degrees. Slightly faster. // Stroke position relative to the shape outline ImDrawFlags_StrokeInside = 1 << 16, // Draw stroke inside of the shape outline (Default) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 23d340dc4..14de91638 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10395,7 +10395,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) static bool curve_segments_override = false; static int curve_segments_override_v = 8; static ImVec4 colf = ImVec4(1.0f, 1.0f, 0.4f, 1.0f); - ImGui::DragFloat("Size", &sz, 0.2f, 2.0f, 100.0f, "%.0f"); + ImGui::DragFloat("Size", &sz, 0.2f, 0.2f, 100.0f, "%.0f"); ImGui::DragFloat("Thickness", &thickness, 0.05f, 0.0f, 32.0f, "%.02f"); ImGui::SliderInt("N-gon sides", &ngon_sides, 3, 12); ImGui::Checkbox("##circlesegmentoverride", &circle_segments_override); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 37c5ae4b9..29aa4bf29 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -849,7 +849,7 @@ static void CalcSegmentNormals(const ImVec2* points, const int points_count, ImV float dx = points[i+1].x - points[i].x; float dy = points[i+1].y - points[i].y; const float d2 = dx*dx + dy*dy; - const float inv_len = (d2 > 0.0f) ? 1.f / sqrtf(d2) : 0.f; //ImRsqrt(d2) : 0.f; + const float inv_len = (d2 > 0.0f) ? 1.0f / sqrtf(d2) : 0.0f; //ImRsqrt(d2) : 0.0f; normals[i].x = -dy * inv_len; normals[i].y = dx * inv_len; sqr_lengths[i] = d2; @@ -859,7 +859,7 @@ static void CalcSegmentNormals(const ImVec2* points, const int points_count, ImV const float dx = points[0].x - points[points_count - 1].x; const float dy = points[0].y - points[points_count - 1].y; const float d2 = dx*dx + dy*dy; - const float inv_len = (d2 > 0.0f) ? 1.f / sqrtf(d2) : 0.f; //ImRsqrt(d2) : 0.f; + const float inv_len = (d2 > 0.0f) ? 1.0f / sqrtf(d2) : 0.0f; //ImRsqrt(d2) : 0.0f; normals[points_count - 1].x = -dy * inv_len; normals[points_count - 1].y = dx * inv_len; sqr_lengths[points_count - 1] = d2; @@ -867,13 +867,14 @@ static void CalcSegmentNormals(const ImVec2* points, const int points_count, ImV else { normals[points_count - 1] = normals[points_count - 2]; - sqr_lengths[points_count - 1] = 0.f; + sqr_lengths[points_count - 1] = 0.0f; } } void ImDrawList::_AddPolylineThin(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, ImVec4 tex_uvs) { const bool closed = (flags & ImDrawFlags_Closed) != 0; + const bool miters_only = (flags & ImDrawFlags_MiterOnly) != 0; _Data->TempBuffer.reserve_discard(points_count * 2); ImVec2* normals = _Data->TempBuffer.Data; @@ -892,16 +893,17 @@ void ImDrawList::_AddPolylineThin(const ImVec2* points, const int points_count, // One option could be to calculate the miter values and overl & bevel flags before, then alloc, and finally commit. int idx_count = 0; int vtx_count = 0; + const int max_verts_per_point = miters_only ? 2 : 7; + const int max_tris_per_point = miters_only ? 2 : 5; if (closed) { - vtx_count = /*body*/points_count * 7 + /*closing*/3; - idx_count = (/*body*/points_count * 5 + /*closing*/4) * 3; + vtx_count = /*body*/points_count * max_verts_per_point + /*closing*/2; + idx_count = (/*body*/points_count * max_tris_per_point + /*closing*/2) * 3; } else { - // Body + caps - vtx_count = /*body*/(points_count - 2) * 7 + /*caps*/(6 * 2); - idx_count = (/*body*/(points_count - 2) * 5 + /*last seg*/4 + /*caps*/(4 * 2)) * 3; + vtx_count = /*body*/(points_count - 2) * max_verts_per_point + /*caps*/(6 * 2); + idx_count = (/*body*/(points_count - 2) * max_tris_per_point + /*last seg*/4 + /*caps*/(4 * 2)) * 3; } PrimReserve(idx_count, vtx_count); @@ -978,8 +980,8 @@ void ImDrawList::_AddPolylineThin(const ImVec2* points, const int points_count, const float miter_offset_y = (n0.y + n1.y) * miter_scale_factor; const float miter_distance_sqr = miter_offset_x * miter_offset_x + miter_offset_y * miter_offset_y; - const bool overlap = (len_sqr0 < miter_distance_sqr) || (len_sqr1 < miter_distance_sqr) || (cos_theta <= IM_POLYLINE_MITER_ANGLE_LIMIT); - const bool bevel = miter_distance_sqr > miter_distance_limit_sqr; + const bool overlap = !miters_only && ((len_sqr0 < miter_distance_sqr) || (len_sqr1 < miter_distance_sqr) || (cos_theta <= IM_POLYLINE_MITER_ANGLE_LIMIT)); + const bool bevel = !miters_only && (miter_distance_sqr > miter_distance_limit_sqr); if (bevel) { @@ -990,7 +992,7 @@ void ImDrawList::_AddPolylineThin(const ImVec2* points, const int points_count, IM_NORMALIZE2F_OVER_ZERO(bevel_normal_x, bevel_normal_y); const float signed_miter_offset = sin_theta < 0.0f ? half_thickness : -half_thickness; - const float side_offset = half_thickness * ((n0.x * bevel_normal_x + n0.y * bevel_normal_y) - 1.f) / (n0.y * bevel_normal_x - n0.x * bevel_normal_y); + const float side_offset = half_thickness * ((n0.x * bevel_normal_x + n0.y * bevel_normal_y) - 1.0f) / (n0.y * bevel_normal_x - n0.x * bevel_normal_y); const float pt_x = p1.x - bevel_normal_x * signed_miter_offset; const float pt_y = p1.y - bevel_normal_y * signed_miter_offset; const float sd_x = bevel_normal_y * side_offset; @@ -999,7 +1001,7 @@ void ImDrawList::_AddPolylineThin(const ImVec2* points, const int points_count, if (overlap) { // Dislocated bevel. - if (sin_theta < 0.f) + if (sin_theta < 0.0f) { IM_APPEND_VTX(p1.x - n0.x * half_thickness, p1.y - n0.y * half_thickness, uv0, col); // 2 IM_APPEND_VTX(p1.x + n0.x * half_thickness, p1.y + n0.y * half_thickness, uv1, col); // 3 @@ -1043,7 +1045,7 @@ void ImDrawList::_AddPolylineThin(const ImVec2* points, const int points_count, else { // Bevel - if (sin_theta < 0.f) + if (sin_theta < 0.0f) { /* IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv0, col); const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; @@ -1109,7 +1111,7 @@ void ImDrawList::_AddPolylineThin(const ImVec2* points, const int points_count, { // Dislocated Miter const float sin_theta = n0.y * n1.x - n0.x * n1.y; - if (sin_theta < 0.f) + if (sin_theta < 0.0f) { IM_APPEND_VTX(p1.x - n0.x * half_thickness, p1.y - n0.y * half_thickness, uv0, col); // 2 @@ -1211,6 +1213,7 @@ void ImDrawList::_AddPolylineThin(const ImVec2* points, const int points_count, void ImDrawList::_AddPolylineThick(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags) { const bool closed = (flags & ImDrawFlags_Closed) != 0; + const bool miters_only = (flags & ImDrawFlags_MiterOnly) != 0; _Data->TempBuffer.reserve_discard(points_count * 2); ImVec2* normals = _Data->TempBuffer.Data; @@ -1221,7 +1224,7 @@ void ImDrawList::_AddPolylineThick(const ImVec2* points, const int points_count, // TODO: we can expand this by having one very long texture strip for the lines, // or if place black 2x2 pixel next to the while pixel, and use texture clamping (that thickness would expand out of the texture). - const float max_width = (IM_DRAWLIST_TEX_LINES_WIDTH_MAX-1) * _FringeScale * 2.f; + const float max_width = (IM_DRAWLIST_TEX_LINES_WIDTH_MAX-1) * _FringeScale * 2.0f; thickness = ImMin(thickness, max_width); thickness += _FringeScale; // Place half of AA fringe each side of the line. @@ -1232,16 +1235,18 @@ void ImDrawList::_AddPolylineThick(const ImVec2* points, const int points_count, int idx_count = 0; int vtx_count = 0; + const int max_verts_per_point = miters_only ? 3 : 5; + const int max_tris_per_point = miters_only ? 4 : 5; if (closed) { - vtx_count = /*body*/points_count * 5 + /*closing*/3; - idx_count = (/*body*/points_count * 5 + /*closing*/4) * 3; + vtx_count = /*body*/points_count * max_verts_per_point + /*closing*/3; + idx_count = (/*body*/points_count * max_tris_per_point + /*closing*/4) * 3; } else { // Body + caps - vtx_count = /*body*/(points_count - 2) * 5 + /*caps*/(6 * 2); - idx_count = (/*body*/(points_count - 2) * 5 + /*last seg*/4 + /*caps*/(4 * 2)) * 3; + vtx_count = /*body*/(points_count - 2) * max_verts_per_point + /*caps*/(6 * 2); + idx_count = (/*body*/(points_count - 2) * max_tris_per_point + /*last seg*/4 + /*caps*/(4 * 2)) * 3; } PrimReserve(idx_count, vtx_count); @@ -1321,11 +1326,12 @@ void ImDrawList::_AddPolylineThick(const ImVec2* points, const int points_count, const float miter_offset_y = (n0.y + n1.y) * miter_scale_factor; const float miter_distance_sqr = miter_offset_x * miter_offset_x + miter_offset_y * miter_offset_y; - const bool overlap = (len_sqr0 < miter_distance_sqr) || (len_sqr1 < miter_distance_sqr) || (cos_theta <= IM_POLYLINE_MITER_ANGLE_LIMIT); - const bool bevel = miter_distance_sqr > miter_distance_limit_sqr; + const bool overlap = !miters_only && ((len_sqr0 < miter_distance_sqr) || (len_sqr1 < miter_distance_sqr) || (cos_theta <= IM_POLYLINE_MITER_ANGLE_LIMIT)); + const bool bevel = !miters_only && (miter_distance_sqr > miter_distance_limit_sqr); if (bevel) { + IM_ASSERT(!miters_only); // Clipped bevel const float sin_theta = n0.y * n1.x - n0.x * n1.y; float bevel_normal_x = n0.x + n1.x; @@ -1333,7 +1339,7 @@ void ImDrawList::_AddPolylineThick(const ImVec2* points, const int points_count, IM_NORMALIZE2F_OVER_ZERO(bevel_normal_x, bevel_normal_y); const float signed_miter_offset = sin_theta < 0.0f ? half_thickness : -half_thickness; - const float side_offset = half_thickness * ((n0.x * bevel_normal_x + n0.y * bevel_normal_y) - 1.f) / (n0.y * bevel_normal_x - n0.x * bevel_normal_y); + const float side_offset = half_thickness * ((n0.x * bevel_normal_x + n0.y * bevel_normal_y) - 1.0f) / (n0.y * bevel_normal_x - n0.x * bevel_normal_y); const float pt_x = p1.x - bevel_normal_x * signed_miter_offset; const float pt_y = p1.y - bevel_normal_y * signed_miter_offset; const float sd_x = bevel_normal_y * side_offset; @@ -1342,7 +1348,7 @@ void ImDrawList::_AddPolylineThick(const ImVec2* points, const int points_count, if (overlap) { // Dislocated bevel. - if (sin_theta < 0.f) + if (sin_theta < 0.0f) { IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv_out, col); IM_APPEND_VTX(p1.x + n0.x * half_thickness, p1.y + n0.y * half_thickness, uv_out, col); @@ -1382,7 +1388,7 @@ void ImDrawList::_AddPolylineThick(const ImVec2* points, const int points_count, else { // Bevel - if (sin_theta < 0.f) + if (sin_theta < 0.0f) { IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv_out, col); const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; @@ -1422,9 +1428,10 @@ void ImDrawList::_AddPolylineThick(const ImVec2* points, const int points_count, { if (overlap) { + IM_ASSERT(!miters_only); // Dislocated miter const float sin_theta = n0.y * n1.x - n0.x * n1.y; - if (sin_theta < 0.f) + if (sin_theta < 0.0f) { IM_APPEND_VTX(p1.x + n0.x * half_thickness, p1.y + n0.y * half_thickness, uv_out, col); const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; @@ -1532,13 +1539,13 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 return; float screen_thickness = thickness / _FringeScale; - if (screen_thickness < 1.f/255.f) + if (screen_thickness < 1.0f / 255.0f) return; - if (screen_thickness < 1.f) + if (screen_thickness < 1.0f) { - const float alpha = thickness; + const float alpha = screen_thickness; col = ImGui::GetColorU32(col, alpha); - screen_thickness = 1.f; + screen_thickness = 1.0f; thickness = _FringeScale; } @@ -2463,7 +2470,6 @@ void ImDrawList::_AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 c } // p_min = upper-left, p_max = lower-right -// Note we don't render 1 pixels sized rectangles properly. void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags) { // If this assert triggers on legacy code: @@ -2490,8 +2496,51 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl return; } - const bool is_truncated = _FringeScaleIsInteger && ImIsTruncated4(p_min.x, p_min.y, p_max.x, p_max.y) && ImIsTruncated4(rounding, thickness, 0.0f, 0.0f); ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); +#if 0 // shiny_polyline + // FIXME-POLYLINE + + const ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); + ImVec2 adjusted_min = p_min; + ImVec2 adjusted_max = p_max; + float adjusted_rounding = rounding; + + const ImVec2 offset(thickness * 0.5f, thickness * 0.5f); + if (stroke_pos == ImDrawFlags_StrokeInside) + { + adjusted_min += offset; + adjusted_max -= offset; + adjusted_rounding -= thickness * 0.5f; + } + else if (stroke_pos == ImDrawFlags_StrokeOutside) + { + adjusted_min -= offset; + adjusted_max += offset; + adjusted_rounding += thickness * 0.5f; + } + else if (stroke_pos == ImDrawFlags_StrokeLegacy) + { + adjusted_min += ImVec2(0.50f, 0.50f); + if (Flags & ImDrawListFlags_AntiAliasedLines) + adjusted_max -= ImVec2(0.50f, 0.50f); + else + adjusted_max -= ImVec2(0.49f, 0.49f); // Better looking lower-right corner and rounded non-AA shapes. + } + + const float width = adjusted_max.x - adjusted_min.x; + const float height = adjusted_max.y - adjusted_min.y; + if (ImMin(width, height) < (thickness + _FringeScale)) + { + // Rectangle has collapsed into filled rectangle. + AddRectFilled(p_min, p_max, col, rounding, flags); + return; + } + + // TODO: does not handle well the case that radius < thickness/2. + PathRect(adjusted_min, adjusted_max, adjusted_rounding, flags); + PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | flags); +#else + const bool is_truncated = _FringeScaleIsInteger && ImIsTruncated4(p_min.x, p_min.y, p_max.x, p_max.y) && ImIsTruncated4(rounding, thickness, 0.0f, 0.0f); if ((stroke_pos == ImDrawFlags_StrokeInside || stroke_pos == ImDrawFlags_StrokeOutside || stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) && is_truncated) { if ((flags & ImDrawFlags_RoundCornersMask_) == 0) @@ -2592,14 +2641,8 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl rounding += (rounding != 0.0f) ? thickness * 0.5f : 0.0f; PathRect(p_min - offset, p_max + offset, rounding, flags); } - else if (stroke_pos == ImDrawFlags_StrokeLegacy) - { - if (Flags & ImDrawListFlags_AntiAliasedLines) - PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.50f, 0.50f), rounding, flags); - else - PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.49f, 0.49f), rounding, flags); // Better looking lower-right corner and rounded non-AA shapes. - } PathStroke(col, thickness, ImDrawFlags_Closed); +#endif } void ImDrawList::_AddRectFilledBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, ImVec4 tex_uvs, ImDrawFlags flags) @@ -2758,30 +2801,46 @@ void ImDrawList::AddTriangleFilled(const ImVec2& p1, const ImVec2& p2, const ImV void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness, ImDrawFlags flags) { - if ((col & IM_COL32_A_MASK) == 0 || radius < 0.5f) + if ((col & IM_COL32_A_MASK) == 0 || radius < 0.01f) return; + ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); + if (g_LEGACY_STROKES) { radius -= 0.5f; } else { - ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); if (stroke_pos == ImDrawFlags_StrokeInside) radius -= thickness * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) - radius -= thickness * 0.5f - CalculateCenterPixelAlignedOffset(thickness, _FringeScale); + radius -= thickness * 0.5f - CalculateCenterPixelAlignedOffset(thickness, _FringeScale); else if (stroke_pos == ImDrawFlags_StrokeOutside) radius += thickness * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeLegacy) radius -= 0.5f; } + float adjusted_radius = radius; + if (stroke_pos == ImDrawFlags_StrokeInside) + adjusted_radius -= thickness * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeOutside) + adjusted_radius += thickness * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeLegacy) + adjusted_radius -= 0.5f; + if (adjusted_radius < (thickness * 0.5f + _FringeScale * 0.5f)) + { + // The circle has collapsed into a filled circle. + AddCircleFilled(center, radius, col, num_segments); + return; + } + if (num_segments <= 0) { // Use arc with automatic segment count - _PathArcToFastEx(center, radius, 0, IM_DRAWLIST_ARCFAST_SAMPLE_MAX, 0); + const int a_step = IM_DRAWLIST_ARCFAST_SAMPLE_MAX / _CalcCircleAutoSegmentCount(radius); // Radius used here inteitionally so that the circle matches filled circle of same radius. + _PathArcToFastEx(center, adjusted_radius, 0, IM_DRAWLIST_ARCFAST_SAMPLE_MAX, a_step); _Path.Size--; } else @@ -2791,17 +2850,27 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; - PathArcTo(center, radius, 0.0f, a_max, num_segments - 1); + PathArcTo(center, adjusted_radius, 0.0f, a_max, num_segments - 1); } - PathStroke(col, thickness, ImDrawFlags_Closed); + PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly); } void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col, int num_segments) { - if ((col & IM_COL32_A_MASK) == 0 || radius < 0.5f) + if ((col & IM_COL32_A_MASK) == 0) return; + float screen_diameter = radius * 2.0f / _FringeScale; + if (screen_diameter < 1.0f / 255.0f) + return; + if (screen_diameter < 1.0f) + { + const float alpha = screen_diameter; + col = ImGui::GetColorU32(col, alpha); + radius = _FringeScale * 0.5f; + } + if (num_segments <= 0) { // Use arc with automatic segment count @@ -2828,26 +2897,30 @@ void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_ return; if (g_LEGACY_STROKES) - { radius -= 0.5f; - } - else + + const ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); + const float unit_apothem = ImCos(IM_PI / (float)num_segments); + const float miter_thickness = thickness / unit_apothem; + + float adjusted_radius = radius; + if (stroke_pos == ImDrawFlags_StrokeInside) + adjusted_radius -= miter_thickness * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeOutside) + adjusted_radius += miter_thickness * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeLegacy) + adjusted_radius -= 0.5f; + if (adjusted_radius < (miter_thickness * 0.5f + _FringeScale * 0.5f)) { - ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); - if (stroke_pos == ImDrawFlags_StrokeInside) - radius -= thickness * 0.5f; - else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) - radius -= thickness * 0.5f - CalculateCenterPixelAlignedOffset(thickness, _FringeScale); - else if (stroke_pos == ImDrawFlags_StrokeOutside) - radius += thickness * 0.5f; - else if (stroke_pos == ImDrawFlags_StrokeLegacy) - radius -= 0.5f; + // The polygon has collapsed into a filled polygon. + AddNgonFilled(center, radius, col, num_segments); + return; } - // Because we are filling a closed shape we remove 1 from the count of segments/points + // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; - PathArcTo(center, radius, 0.0f, a_max, num_segments - 1); - PathStroke(col, thickness, ImDrawFlags_Closed); + PathArcTo(center, adjusted_radius, 0.0f, a_max, num_segments - 1); + PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly); } // Guaranteed to honor 'num_segments' @@ -2856,6 +2929,16 @@ void ImDrawList::AddNgonFilled(const ImVec2& center, float radius, ImU32 col, in if ((col & IM_COL32_A_MASK) == 0 || num_segments <= 2) return; + float screen_diameter = radius * 2.0f / _FringeScale; + if (screen_diameter < 1.0f / 255.0f) + return; + if (screen_diameter < 1.0f) + { + const float alpha = screen_diameter; + col = ImGui::GetColorU32(col, alpha); + radius = _FringeScale * 0.5f; + } + // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; PathArcTo(center, radius, 0.0f, a_max, num_segments - 1); @@ -2868,6 +2951,8 @@ void ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 co if ((col & IM_COL32_A_MASK) == 0) return; + ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); + ImVec2 r; if (g_LEGACY_STROKES) { @@ -2876,7 +2961,6 @@ void ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 co else { r = radius; - ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); if (stroke_pos == ImDrawFlags_StrokeInside) r -= ImVec2(thickness * 0.5f, thickness * 0.5f); else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) @@ -2888,13 +2972,58 @@ void ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 co r += ImVec2(thickness * 0.5f, thickness * 0.5f); } - if (num_segments <= 0) - num_segments = _CalcCircleAutoSegmentCount(ImMax(r.x, r.y)); // A bit pessimistic, maybe there's a better computation to do here. + float stroke_offset = 0.0f; + if (stroke_pos == ImDrawFlags_StrokeInside) + stroke_offset = -thickness * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeOutside) + stroke_offset = thickness * 0.5f; + if ((ImMin(radius.x, radius.y) + stroke_offset) < (thickness * 0.5f + _FringeScale * 0.5f)) + { + AddEllipseFilled(center, radius, col, rot, num_segments); + return; + } - // Because we are filling a closed shape we remove 1 from the count of segments/points - const float a_max = IM_PI * 2.0f * ((float)num_segments - 1.0f) / (float)num_segments; - PathEllipticalArcTo(center, r, rot, 0.0f, a_max, num_segments - 1); - PathStroke(col, thickness, ImDrawFlags_Closed); + if (num_segments <= 0) + num_segments = _CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)); // A bit pessimistic, maybe there's a better computation to do here. + + _Path.reserve(_Path.Size + (num_segments + 1)); + + // Custom tessellation, since offset ellipse is not an ellipse anymore. + const float cos_rot = ImCos(rot); + const float sin_rot = ImSin(rot); + if (stroke_offset != 0.0f) + { + const float inv_rx = 1.0f / radius.x; + const float inv_ry = 1.0f / radius.y; + for (int i = 0; i < num_segments; i++) + { + const float a = ((float)i / (float)num_segments) * IM_PI * 2.0f; + const float dir_x = ImCos(a); + const float dir_y = ImSin(a); + float nx = dir_x * inv_rx; + float ny = dir_y * inv_ry; + IM_NORMALIZE2F_OVER_ZERO(nx, ny); + ImVec2 point(dir_x * radius.x + nx * stroke_offset, dir_y * radius.y + ny * stroke_offset); + const ImVec2 rel((point.x * cos_rot) - (point.y * sin_rot), (point.x * sin_rot) + (point.y * cos_rot)); + point.x = rel.x + center.x; + point.y = rel.y + center.y; + _Path.push_back(point); + } + } + else + { + for (int i = 0; i < num_segments; i++) + { + const float a = ((float)i / (float)num_segments) * IM_PI * 2.0f; + ImVec2 point(ImCos(a) * radius.x, ImSin(a) * radius.y); + const ImVec2 rel((point.x * cos_rot) - (point.y * sin_rot), (point.x * sin_rot) + (point.y * cos_rot)); + point.x = rel.x + center.x; + point.y = rel.y + center.y; + _Path.push_back(point); + } + } + + PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly); } void ImDrawList::AddEllipseFilled(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot, int num_segments) From 6c7b083e0e5167be8c5f4fa27e3516526ef091e7 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Wed, 22 Apr 2026 22:24:55 +0300 Subject: [PATCH 021/120] DrawList: improved handling of rounded stroked rectangles, where rounding is less than stroke thickness. --- imgui.h | 4 +-- imgui_draw.cpp | 83 ++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 66 insertions(+), 21 deletions(-) diff --git a/imgui.h b/imgui.h index 5d5599058..016a89a0d 100644 --- a/imgui.h +++ b/imgui.h @@ -3532,8 +3532,8 @@ struct ImDrawList IMGUI_API void _PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments); IMGUI_API void _AddRectFilledBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, ImVec4 tex_uvs, ImDrawFlags flags); IMGUI_API void _AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, float t, ImVec4 tex_uvs, ImDrawFlags flags); - IMGUI_API void _AddPolylineThin(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, ImVec4 tex_uvs); - IMGUI_API void _AddPolylineThick(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags); + IMGUI_API void _AddPolylineThin(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, ImVec4 tex_uvs); + IMGUI_API void _AddPolylineThick(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags); }; // All draw data to render a Dear ImGui frame diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 29aa4bf29..6fb8d056f 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -871,16 +871,10 @@ static void CalcSegmentNormals(const ImVec2* points, const int points_count, ImV } } -void ImDrawList::_AddPolylineThin(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, ImVec4 tex_uvs) +void ImDrawList::_AddPolylineThin(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, ImVec4 tex_uvs) { const bool closed = (flags & ImDrawFlags_Closed) != 0; const bool miters_only = (flags & ImDrawFlags_MiterOnly) != 0; - - _Data->TempBuffer.reserve_discard(points_count * 2); - ImVec2* normals = _Data->TempBuffer.Data; - float* sqr_lengths = (float*)(normals + points_count); - CalcSegmentNormals(points, points_count, normals, sqr_lengths, closed); - const ImU32 col_trans = col & ~IM_COL32_A_MASK; thickness += _FringeScale; @@ -1210,16 +1204,11 @@ void ImDrawList::_AddPolylineThin(const ImVec2* points, const int points_count, PrimUnreserve(idx_count - idx_used, vtx_count - vtx_used); } -void ImDrawList::_AddPolylineThick(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags) +void ImDrawList::_AddPolylineThick(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags) { const bool closed = (flags & ImDrawFlags_Closed) != 0; const bool miters_only = (flags & ImDrawFlags_MiterOnly) != 0; - _Data->TempBuffer.reserve_discard(points_count * 2); - ImVec2* normals = _Data->TempBuffer.Data; - float* sqr_lengths = (float*)(normals + points_count); - CalcSegmentNormals(points, points_count, normals, sqr_lengths, closed); - const ImU32 col_trans = col & ~IM_COL32_A_MASK; // TODO: we can expand this by having one very long texture strip for the lines, @@ -1556,14 +1545,19 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 const int int_thickness = (int)screen_thickness; const bool can_use_thin = ImAbs(screen_thickness - (float)int_thickness) < 0.01f && (int_thickness >= 1 && int_thickness < IM_DRAWLIST_TEX_LINES_WIDTH_MAX); + _Data->TempBuffer.reserve_discard(points_count * 2); + ImVec2* normals = _Data->TempBuffer.Data; + float* sqr_lengths = (float*)(normals + points_count); + CalcSegmentNormals(points, points_count, normals, sqr_lengths, (flags & ImDrawFlags_Closed) != 0); + if (can_use_thin) { const ImVec4 tex_uvs = _Data->TexUvLines[int_thickness]; - _AddPolylineThin(points, points_count, col, (float)int_thickness * _FringeScale, flags, tex_uvs); + _AddPolylineThin(points, normals, sqr_lengths, points_count, col, (float)int_thickness * _FringeScale, flags, tex_uvs); } else { - _AddPolylineThick(points, points_count, col, thickness, flags); + _AddPolylineThick(points, normals, sqr_lengths, points_count, col, thickness, flags); } } @@ -2469,6 +2463,25 @@ void ImDrawList::_AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 c PrimUnreserve(idx_allocated - idx_used, vtx_allocated - vtx_used); } +static void RectCornerArc(ImDrawList* draw_list, float cx, float cy, float radius, const int arc_step, const int arc_point_count, const int corner) +{ + if (radius == 0.f) + { + draw_list->_Path.push_back(ImVec2(cx, cy)); + return; + } + + int arc_idx = (IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4) * corner; + for (int i = 0; i < arc_point_count; i++) + { + ImVec2 pt; + pt.x = cx - draw_list->_Data->ArcFastVtx[arc_idx].x * radius; + pt.y = cy - draw_list->_Data->ArcFastVtx[arc_idx].y * radius; + draw_list->_Path.push_back(pt); + arc_idx = (arc_idx + arc_step) % IM_DRAWLIST_ARCFAST_TABLE_SIZE; + } +} + // p_min = upper-left, p_max = lower-right void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags) { @@ -2499,8 +2512,8 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); #if 0 // shiny_polyline // FIXME-POLYLINE + const bool has_rounding = (rounding >= _FringeScale); - const ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); ImVec2 adjusted_min = p_min; ImVec2 adjusted_max = p_max; float adjusted_rounding = rounding; @@ -2536,9 +2549,41 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl return; } - // TODO: does not handle well the case that radius < thickness/2. - PathRect(adjusted_min, adjusted_max, adjusted_rounding, flags); - PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | flags); + if ((flags & ImDrawFlags_RoundCornersMask_) == 0) + flags |= ImDrawFlags_RoundCornersAll; + + if (!has_rounding || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) + { + PathLineTo(adjusted_min); + PathLineTo(ImVec2(adjusted_max.x, adjusted_min.y)); + PathLineTo(adjusted_max); + PathLineTo(ImVec2(adjusted_min.x, adjusted_max.y)); + } + else + { + // Constrain rounding to rect dimensions + adjusted_rounding = ImMin(adjusted_rounding, ImFabs(width) * (((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f) - 1.0f); + adjusted_rounding = ImMin(adjusted_rounding, ImFabs(height) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f); + // We have to have some kind of curve to render the corner with PathStroke(). + // The small value limits the rounding to about half the stroke radius. + adjusted_rounding = ImMax(_FringeScale * 0.25f, adjusted_rounding); + + const float rounding_tl = (flags & ImDrawFlags_RoundCornersTopLeft) ? adjusted_rounding : 0.0f; + const float rounding_tr = (flags & ImDrawFlags_RoundCornersTopRight) ? adjusted_rounding : 0.0f; + const float rounding_br = (flags & ImDrawFlags_RoundCornersBottomRight) ? adjusted_rounding : 0.0f; + const float rounding_bl = (flags & ImDrawFlags_RoundCornersBottomLeft) ? adjusted_rounding : 0.0f; + + // Use custom arcs so that we can specify the point count. + const int arc_step = ImClamp(IM_DRAWLIST_ARCFAST_SAMPLE_MAX / this->_CalcCircleAutoSegmentCount(ImMax(adjusted_rounding, thickness * 0.5f)), 1, IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4); + const int arc_point_count = (IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4) / arc_step + 1; + + RectCornerArc(this, adjusted_min.x + rounding_tl, adjusted_min.y + rounding_tl, rounding_tl, arc_step, arc_point_count, 0); + RectCornerArc(this, adjusted_max.x - rounding_tr, adjusted_min.y + rounding_tr, rounding_tr, arc_step, arc_point_count, 1); + RectCornerArc(this, adjusted_max.x - rounding_br, adjusted_max.y - rounding_br, rounding_br, arc_step, arc_point_count, 2); + RectCornerArc(this, adjusted_min.x + rounding_bl, adjusted_max.y - rounding_bl, rounding_bl, arc_step, arc_point_count, 3); + } + + PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly); #else const bool is_truncated = _FringeScaleIsInteger && ImIsTruncated4(p_min.x, p_min.y, p_max.x, p_max.y) && ImIsTruncated4(rounding, thickness, 0.0f, 0.0f); if ((stroke_pos == ImDrawFlags_StrokeInside || stroke_pos == ImDrawFlags_StrokeOutside || stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) && is_truncated) From 049f1a799a376dbc094c4a697a49af51d81915d0 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Fri, 24 Apr 2026 10:14:11 +0300 Subject: [PATCH 022/120] DrawList: added special case rendering for the stroked rects when the raounding radius gets really small compared to the stroke thickness. --- imgui.h | 1 + imgui_draw.cpp | 135 ++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 111 insertions(+), 25 deletions(-) diff --git a/imgui.h b/imgui.h index 016a89a0d..f476f0c36 100644 --- a/imgui.h +++ b/imgui.h @@ -3534,6 +3534,7 @@ struct ImDrawList IMGUI_API void _AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, float t, ImVec4 tex_uvs, ImDrawFlags flags); IMGUI_API void _AddPolylineThin(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, ImVec4 tex_uvs); IMGUI_API void _AddPolylineThick(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags); + IMGUI_API void _AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags); }; // All draw data to render a Dear ImGui frame diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 6fb8d056f..63f4f70c6 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2463,23 +2463,108 @@ void ImDrawList::_AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 c PrimUnreserve(idx_allocated - idx_used, vtx_allocated - vtx_used); } -static void RectCornerArc(ImDrawList* draw_list, float cx, float cy, float radius, const int arc_step, const int arc_point_count, const int corner) +// Draws rounded rectangle where thickness/2 > rounding. If rendered using the regular polyline, the stroke will fold and leave artefact on the corner if rendered with transparency. +// The stroke is positioned inside the rectangle. +void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags) { - if (radius == 0.f) + thickness += _FringeScale; + + // Adjust the rounding to be the outside radius. + rounding += thickness * 0.5f; + + const int arc_step = ImClamp(IM_DRAWLIST_ARCFAST_SAMPLE_MAX / this->_CalcCircleAutoSegmentCount(rounding), 1, IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4); + const int arc_point_count = (IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4) / arc_step + 1; + + int vtx_count = (4 + arc_point_count) * 4 + 4; + int idx_count = (4 + arc_point_count - 1) * 4 * 3; + + PrimReserve(idx_count, vtx_count); + ImDrawVert* start_vtx_ptr = _VtxWritePtr; + ImDrawIdx* start_idx_ptr = _IdxWritePtr; + + const float ht = thickness * 0.5f; + const ImVec2 corner_pos[4] = { + ImVec2(p_min.x - ht, p_min.y - ht), + ImVec2(p_max.x + ht, p_min.y - ht), + ImVec2(p_max.x + ht, p_max.y + ht), + ImVec2(p_min.x - ht, p_max.y + ht) + }; + static const ImVec2 corner_offset[4] = { {1,1}, {-1,1}, {-1,-1}, {1,-1} }; + static const ImDrawFlags corner_flags[4] = { ImDrawFlags_RoundCornersTopLeft, ImDrawFlags_RoundCornersTopRight, ImDrawFlags_RoundCornersBottomRight, ImDrawFlags_RoundCornersBottomLeft }; + + const float stem_offset = thickness - ImMax(thickness - rounding, _FringeScale); + + const ImVec4 tex_uvs = _Data->TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX]; // TODO: max uv + const ImVec2 outer_uv0(tex_uvs.x + (0.5f / _FringeScale) * _Data->FontAtlas->TexUvScale.x, tex_uvs.y); + const ImVec2 outer_uv1(tex_uvs.x + ((0.5f + stem_offset) / _FringeScale) * _Data->FontAtlas->TexUvScale.x, tex_uvs.y); + const ImVec2 inner_uv0(tex_uvs.x + (0.5f / _FringeScale) * _Data->FontAtlas->TexUvScale.x, tex_uvs.y); + const ImVec2 inner_uv1(tex_uvs.x + ((0.5f + thickness - stem_offset) / _FringeScale) * _Data->FontAtlas->TexUvScale.x, tex_uvs.y); + + int base_idx = (int)_VtxCurrentIdx; + IM_POLYLINE_APPEND_VERTEX(0, 0, outer_uv0, col); + IM_POLYLINE_APPEND_VERTEX(0, 0, outer_uv1, col); + IM_POLYLINE_APPEND_VERTEX(0, 0, inner_uv1, col); + IM_POLYLINE_APPEND_VERTEX(0, 0, inner_uv0, col); + + for (int corner = 0; corner < 4; corner++) { - draw_list->_Path.push_back(ImVec2(cx, cy)); - return; + const ImVec2 stem = corner_pos[corner] + corner_offset[corner] * stem_offset; + const ImVec2 inner = corner_pos[corner] + corner_offset[corner] * thickness; + const bool is_rounded = flags & corner_flags[corner]; + + const int arc_idx = (int)_VtxCurrentIdx; + if (is_rounded) + { + const ImVec2 center = corner_pos[corner] + corner_offset[corner] * rounding; + int a = (IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4) * corner; + for (int i = 0; i < arc_point_count; i++) + { + const ImVec2 dir = _Data->ArcFastVtx[a]; + IM_POLYLINE_APPEND_VERTEX(center.x - dir.x * rounding, center.y - dir.y * rounding, outer_uv0, col); + a = (a + arc_step) % IM_DRAWLIST_ARCFAST_TABLE_SIZE; + } + } + else + { + IM_POLYLINE_APPEND_VERTEX(corner_pos[corner].x, corner_pos[corner].y, outer_uv0, col); + } + + const int stem0_idx = (int)_VtxCurrentIdx; + IM_POLYLINE_APPEND_VERTEX(stem.x, stem.y, outer_uv1, col); + const int stem1_idx = (int)_VtxCurrentIdx; + IM_POLYLINE_APPEND_VERTEX(stem.x, stem.y, inner_uv1, col); + const int inner_idx = (int)_VtxCurrentIdx; + IM_POLYLINE_APPEND_VERTEX(inner.x, inner.y, inner_uv0, col); + + // Arc + if (is_rounded) + { + for (int i = 0; i < arc_point_count-1; i++) + IM_POLYLINE_APPEND_TRI(stem0_idx, arc_idx + i, arc_idx + i + 1); + } + + // Connect with previous + IM_POLYLINE_APPEND_TRI(base_idx+0, arc_idx, stem0_idx); + IM_POLYLINE_APPEND_TRI(base_idx+0, stem0_idx, base_idx+1); + IM_POLYLINE_APPEND_TRI(base_idx+2, stem1_idx, inner_idx); + IM_POLYLINE_APPEND_TRI(base_idx+2, inner_idx, base_idx+3); + + + base_idx = stem0_idx - 1; } - int arc_idx = (IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4) * corner; - for (int i = 0; i < arc_point_count; i++) - { - ImVec2 pt; - pt.x = cx - draw_list->_Data->ArcFastVtx[arc_idx].x * radius; - pt.y = cy - draw_list->_Data->ArcFastVtx[arc_idx].y * radius; - draw_list->_Path.push_back(pt); - arc_idx = (arc_idx + arc_step) % IM_DRAWLIST_ARCFAST_TABLE_SIZE; - } + // Close + start_vtx_ptr[0] = VtxBuffer.Data[base_idx+0]; + start_vtx_ptr[1] = VtxBuffer.Data[base_idx+1]; + start_vtx_ptr[2] = VtxBuffer.Data[base_idx+2]; + start_vtx_ptr[3] = VtxBuffer.Data[base_idx+3]; + + const int idx_used = (int)(_IdxWritePtr - start_idx_ptr); + const int vtx_used = (int)(_VtxWritePtr - start_vtx_ptr); + IM_ASSERT(idx_used <= idx_count && vtx_used <= vtx_count); + + if (idx_used < idx_count || vtx_used < vtx_count) + PrimUnreserve(idx_count - idx_used, vtx_count - vtx_used); } // p_min = upper-left, p_max = lower-right @@ -2564,23 +2649,23 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl // Constrain rounding to rect dimensions adjusted_rounding = ImMin(adjusted_rounding, ImFabs(width) * (((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f) - 1.0f); adjusted_rounding = ImMin(adjusted_rounding, ImFabs(height) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f); - // We have to have some kind of curve to render the corner with PathStroke(). - // The small value limits the rounding to about half the stroke radius. - adjusted_rounding = ImMax(_FringeScale * 0.25f, adjusted_rounding); + + if (thickness * 0.5f > adjusted_rounding) + { + // Special case rendering to avoid rendering artifacts at the corners. + // If rendered using the regular polyline, the stroke will fold and leave artifact on the corner if rendered with transparency. + _AddRectTinyRounding(adjusted_min, adjusted_max, col, adjusted_rounding, thickness, flags); + return; + } const float rounding_tl = (flags & ImDrawFlags_RoundCornersTopLeft) ? adjusted_rounding : 0.0f; const float rounding_tr = (flags & ImDrawFlags_RoundCornersTopRight) ? adjusted_rounding : 0.0f; const float rounding_br = (flags & ImDrawFlags_RoundCornersBottomRight) ? adjusted_rounding : 0.0f; const float rounding_bl = (flags & ImDrawFlags_RoundCornersBottomLeft) ? adjusted_rounding : 0.0f; - - // Use custom arcs so that we can specify the point count. - const int arc_step = ImClamp(IM_DRAWLIST_ARCFAST_SAMPLE_MAX / this->_CalcCircleAutoSegmentCount(ImMax(adjusted_rounding, thickness * 0.5f)), 1, IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4); - const int arc_point_count = (IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4) / arc_step + 1; - - RectCornerArc(this, adjusted_min.x + rounding_tl, adjusted_min.y + rounding_tl, rounding_tl, arc_step, arc_point_count, 0); - RectCornerArc(this, adjusted_max.x - rounding_tr, adjusted_min.y + rounding_tr, rounding_tr, arc_step, arc_point_count, 1); - RectCornerArc(this, adjusted_max.x - rounding_br, adjusted_max.y - rounding_br, rounding_br, arc_step, arc_point_count, 2); - RectCornerArc(this, adjusted_min.x + rounding_bl, adjusted_max.y - rounding_bl, rounding_bl, arc_step, arc_point_count, 3); + PathArcToFast(ImVec2(adjusted_min.x + rounding_tl, adjusted_min.y + rounding_tl), rounding_tl, 6, 9); + PathArcToFast(ImVec2(adjusted_max.x - rounding_tr, adjusted_min.y + rounding_tr), rounding_tr, 9, 12); + PathArcToFast(ImVec2(adjusted_max.x - rounding_br, adjusted_max.y - rounding_br), rounding_br, 0, 3); + PathArcToFast(ImVec2(adjusted_min.x + rounding_bl, adjusted_max.y - rounding_bl), rounding_bl, 3, 6); } PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly); From 858b2b2952b95b1dc88156824c537b4ccce4f173 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Fri, 24 Apr 2026 13:44:23 +0300 Subject: [PATCH 023/120] DrawList: added more robust AddConvexPolyFilled(). (#2183, #7972) - add bevel handling for sharp corners - add limit to the miter distance to avoid inner shape spikes - add support for ImDrawFlags_MiterOnly to keep well behaved rendering fast --- imgui.h | 5 +- imgui_draw.cpp | 219 +++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 178 insertions(+), 46 deletions(-) diff --git a/imgui.h b/imgui.h index f476f0c36..0f5edb89a 100644 --- a/imgui.h +++ b/imgui.h @@ -3432,7 +3432,8 @@ struct ImDrawList // - Concave polygon fill is more expensive than convex one: it has O(N^2) complexity. Provided as a convenience for the user but not used by the main library. IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, float thickness, ImDrawFlags flags = 0); IMGUI_API void AddPolylineLegacy(const ImVec2* points, int num_points, ImU32 col, float thickness, ImDrawFlags flags = 0); - IMGUI_API void AddConvexPolyFilled(const ImVec2* points, int num_points, ImU32 col); + IMGUI_API void AddConvexPolyFilled(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags = 0); + IMGUI_API void AddConvexPolyFilledLegacy(const ImVec2* points, int num_points, ImU32 col); IMGUI_API void AddConcavePolyFilled(const ImVec2* points, int num_points, ImU32 col); // Image primitives @@ -3449,7 +3450,7 @@ struct ImDrawList inline void PathClear() { _Path.Size = 0; } inline void PathLineTo(const ImVec2& pos) { _Path.push_back(pos); } inline void PathLineToMergeDuplicate(const ImVec2& pos) { if (_Path.Size == 0 || memcmp(&_Path.Data[_Path.Size - 1], &pos, 8) != 0) _Path.push_back(pos); } - inline void PathFillConvex(ImU32 col) { AddConvexPolyFilled(_Path.Data, _Path.Size, col); _Path.Size = 0; } + inline void PathFillConvex(ImU32 col, ImDrawFlags flags = 0) { AddConvexPolyFilled(_Path.Data, _Path.Size, col, flags); _Path.Size = 0; } inline void PathFillConcave(ImU32 col) { AddConcavePolyFilled(_Path.Data, _Path.Size, col); _Path.Size = 0; } inline void PathStroke(ImU32 col, float thickness = 1.0f, ImDrawFlags flags = 0) { AddPolyline(_Path.Data, _Path.Size, col, thickness, flags); _Path.Size = 0; } IMGUI_API void PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments = 0); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 63f4f70c6..e3ee377db 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1041,18 +1041,6 @@ void ImDrawList::_AddPolylineThin(const ImVec2* points, ImVec2* normals, float* // Bevel if (sin_theta < 0.0f) { -/* IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv0, col); - const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; - IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv0, col); - IM_APPEND_VTX(p1.x + miter_offset_x, p1.y + miter_offset_y, uv1, col); - - // Bevel tri - IM_APPEND_TRI(base_idx+2, base_idx+3, base_idx+4); - // Connect prev to next - IM_APPEND_TRI(base_idx+0, base_idx+2, base_idx+4); - IM_APPEND_TRI(base_idx+0, base_idx+4, base_idx+1); - base_idx = next_base_idx;*/ - IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv0, col); // 2 IM_APPEND_VTX(p1.x, p1.y, uv2, col); // 3 const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; @@ -1084,18 +1072,6 @@ void ImDrawList::_AddPolylineThin(const ImVec2* points, ImVec2* normals, float* IM_APPEND_TRI(base_idx + 3, base_idx + 4, base_idx + 5); IM_APPEND_TRI(base_idx + 3, base_idx + 5, base_idx + 2); base_idx = next_base_idx; - -/* IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv1, col); - const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; - IM_APPEND_VTX(p1.x - miter_offset_x, p1.y - miter_offset_y, uv0, col); - IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv1, col); - - // Bevel tri - IM_APPEND_TRI(base_idx + 3, base_idx + 4, base_idx + 2); - // Connect prev to next - IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 2); - IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 1); - base_idx = next_base_idx;*/ } } } @@ -1825,7 +1801,161 @@ void ImDrawList::AddPolylineLegacy(const ImVec2* points, const int points_count, // - We intentionally avoid using ImVec2 and its math operators here to reduce cost to a minimum for debug/non-inlined builds. // - Filled shapes must always use clockwise winding order. The anti-aliasing fringe depends on it. Counter-clockwise shapes will have "inward" anti-aliasing. -void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_count, ImU32 col) +void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_count, ImU32 col, ImDrawFlags flags) +{ + if (points_count < 3 || (col & IM_COL32_A_MASK) == 0) + return; + +/* if (ImGui::GetIO().KeyShift) + { + AddConvexPolyFilledLegacy(points, points_count, col); + return; + }*/ + + const ImVec2 uv = _Data->TexUvWhitePixel; + const float half_aa = _FringeScale * 0.5f; + ImU32 col_trans = col & ~IM_COL32_A_MASK; + const bool miters_only = (flags & ImDrawFlags_MiterOnly) != 0; + + const int idx_count = ((points_count - 2) + points_count * 3) * 3; + const int vtx_count = (points_count * 3); + PrimReserve(idx_count, vtx_count); + ImDrawVert* start_vtx_ptr = _VtxWritePtr; + ImDrawIdx* start_idx_ptr = _IdxWritePtr; + + // Compute normals and segment lengths + _Data->TempBuffer.reserve_discard(points_count * 2); + ImVec2* temp_normals = _Data->TempBuffer.Data; + float* temp_sqr_lengths = (float*)(_Data->TempBuffer.Data + points_count); + for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) + { + const ImVec2& p0 = points[i0]; + const ImVec2& p1 = points[i1]; + float dx = p1.x - p0.x; + float dy = p1.y - p0.y; + float d2 = dx*dx + dy*dy; + if (d2 > 0.f) + { + float inv_len = ImRsqrt(d2); + dx *= inv_len; + dy *= inv_len; + } + temp_normals[i0].x = dy; + temp_normals[i0].y = -dx; + temp_sqr_lengths[i0] = d2; + } + + + unsigned int vtx_inner_idx = _VtxCurrentIdx; + ImDrawVert* inner_vtx_ptr = _VtxWritePtr; + _VtxWritePtr += points_count; + _VtxCurrentIdx += points_count; + + unsigned int prev_outer_idx = (unsigned int )-1; // We dont know oueter vert could yet, will need to patch once we're done. + + const float miter_distance_limit = half_aa * IM_POLYLINE_MITER_LIMIT; + const float miter_distance_limit_sqr = miter_distance_limit * miter_distance_limit; + + for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) + { + // Average normals + const ImVec2& n0 = temp_normals[i0]; + const ImVec2& n1 = temp_normals[i1]; + + // theta is the angle between two segments + const float cos_theta = n0.x * n1.x + n0.y * n1.y; + // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm + float miter_scale_factor = ImMin(1000.f, (cos_theta > IM_POLYLINE_MITER_ANGLE_LIMIT) ? (half_aa / (1.0f + cos_theta)) : FLT_MAX); // avoid division by zero + + float dm_x = (n0.x + n1.x) * miter_scale_factor; + float dm_y = (n0.y + n1.y) * miter_scale_factor; + bool bevel = false; + + if (!miters_only) + { + const float miter_distance_sqr = dm_x * dm_x + dm_y * dm_y; + bevel = miter_distance_sqr > miter_distance_limit_sqr; + + const float segment_limit_sqr = ImMax(temp_sqr_lengths[i0], temp_sqr_lengths[i1]); + const bool overshoot = miter_distance_sqr > segment_limit_sqr; + if (overshoot) + { + // Make sure the miter stays within the polygon bounds. + const float scale = ImSqrt(segment_limit_sqr / miter_distance_sqr); + dm_x *= scale; + dm_y *= scale; + } + } + + // Inner + inner_vtx_ptr->pos.x = (points[i1].x - dm_x); inner_vtx_ptr->pos.y = (points[i1].y - dm_y); inner_vtx_ptr->uv = uv; inner_vtx_ptr->col = col; + inner_vtx_ptr++; + + unsigned int prev_inner_idx = vtx_inner_idx + i0; + unsigned int inner_idx = vtx_inner_idx + i1; + unsigned int outer_idx = _VtxCurrentIdx; + + // Outer + if (bevel) + { + _VtxWritePtr->pos.x = (points[i1].x + n0.x * half_aa); _VtxWritePtr->pos.y = (points[i1].y + n0.y * half_aa); _VtxWritePtr->uv = uv; _VtxWritePtr->col = col_trans; + _VtxWritePtr++; + _VtxCurrentIdx++; + _VtxWritePtr->pos.x = (points[i1].x + n1.x * half_aa); _VtxWritePtr->pos.y = (points[i1].y + n1.y * half_aa); _VtxWritePtr->uv = uv; _VtxWritePtr->col = col_trans; + _VtxWritePtr++; + _VtxCurrentIdx++; + } + else + { + _VtxWritePtr->pos.x = (points[i1].x + dm_x); _VtxWritePtr->pos.y = (points[i1].y + dm_y); _VtxWritePtr->uv = uv; _VtxWritePtr->col = col_trans; + _VtxWritePtr++; + _VtxCurrentIdx++; + } + + // Connect with previous + _IdxWritePtr[0] = (ImDrawIdx)prev_outer_idx; + _IdxWritePtr[1] = (ImDrawIdx)outer_idx; + _IdxWritePtr[2] = (ImDrawIdx)inner_idx; + + _IdxWritePtr[3] = (ImDrawIdx)prev_outer_idx; + _IdxWritePtr[4] = (ImDrawIdx)inner_idx; + _IdxWritePtr[5] = (ImDrawIdx)prev_inner_idx; + + _IdxWritePtr += 6; + + // Fill bevel + if (bevel) + { + _IdxWritePtr[0] = (ImDrawIdx)outer_idx; + _IdxWritePtr[1] = (ImDrawIdx)outer_idx+1; + _IdxWritePtr[2] = (ImDrawIdx)inner_idx; + _IdxWritePtr += 3; + outer_idx++; + } + + prev_outer_idx = outer_idx; + } + + // Patch first segment + start_idx_ptr[0] = (ImDrawIdx)prev_outer_idx; + start_idx_ptr[3] = (ImDrawIdx)prev_outer_idx; + + // Add indices for fill + for (int i = 2; i < points_count; i++) + { + _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx + i - 1); _IdxWritePtr[2] = (ImDrawIdx)(vtx_inner_idx + i); + _IdxWritePtr += 3; + } + + const int idx_used = (int)(_IdxWritePtr - start_idx_ptr); + const int vtx_used = (int)(_VtxWritePtr - start_vtx_ptr); + IM_ASSERT(idx_used <= idx_count && vtx_used <= vtx_count); + + if (idx_used < idx_count || vtx_used < vtx_count) + PrimUnreserve(idx_count - idx_used, vtx_count - vtx_used); +} + +void ImDrawList::AddConvexPolyFilledLegacy(const ImVec2* points, const int points_count, ImU32 col) { if (points_count < 3 || (col & IM_COL32_A_MASK) == 0) return; @@ -1907,6 +2037,7 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun } } + void ImDrawList::_PathArcToFastEx(const ImVec2& center, float radius, int a_min_sample, int a_max_sample, int a_step) { if (radius < 0.5f) @@ -2501,10 +2632,10 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, const ImVec2 inner_uv1(tex_uvs.x + ((0.5f + thickness - stem_offset) / _FringeScale) * _Data->FontAtlas->TexUvScale.x, tex_uvs.y); int base_idx = (int)_VtxCurrentIdx; - IM_POLYLINE_APPEND_VERTEX(0, 0, outer_uv0, col); - IM_POLYLINE_APPEND_VERTEX(0, 0, outer_uv1, col); - IM_POLYLINE_APPEND_VERTEX(0, 0, inner_uv1, col); - IM_POLYLINE_APPEND_VERTEX(0, 0, inner_uv0, col); + IM_APPEND_VTX(0, 0, outer_uv0, col); + IM_APPEND_VTX(0, 0, outer_uv1, col); + IM_APPEND_VTX(0, 0, inner_uv1, col); + IM_APPEND_VTX(0, 0, inner_uv0, col); for (int corner = 0; corner < 4; corner++) { @@ -2520,34 +2651,34 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, for (int i = 0; i < arc_point_count; i++) { const ImVec2 dir = _Data->ArcFastVtx[a]; - IM_POLYLINE_APPEND_VERTEX(center.x - dir.x * rounding, center.y - dir.y * rounding, outer_uv0, col); + IM_APPEND_VTX(center.x - dir.x * rounding, center.y - dir.y * rounding, outer_uv0, col); a = (a + arc_step) % IM_DRAWLIST_ARCFAST_TABLE_SIZE; } } else { - IM_POLYLINE_APPEND_VERTEX(corner_pos[corner].x, corner_pos[corner].y, outer_uv0, col); + IM_APPEND_VTX(corner_pos[corner].x, corner_pos[corner].y, outer_uv0, col); } const int stem0_idx = (int)_VtxCurrentIdx; - IM_POLYLINE_APPEND_VERTEX(stem.x, stem.y, outer_uv1, col); + IM_APPEND_VTX(stem.x, stem.y, outer_uv1, col); const int stem1_idx = (int)_VtxCurrentIdx; - IM_POLYLINE_APPEND_VERTEX(stem.x, stem.y, inner_uv1, col); + IM_APPEND_VTX(stem.x, stem.y, inner_uv1, col); const int inner_idx = (int)_VtxCurrentIdx; - IM_POLYLINE_APPEND_VERTEX(inner.x, inner.y, inner_uv0, col); + IM_APPEND_VTX(inner.x, inner.y, inner_uv0, col); // Arc if (is_rounded) { for (int i = 0; i < arc_point_count-1; i++) - IM_POLYLINE_APPEND_TRI(stem0_idx, arc_idx + i, arc_idx + i + 1); + IM_APPEND_TRI(stem0_idx, arc_idx + i, arc_idx + i + 1); } // Connect with previous - IM_POLYLINE_APPEND_TRI(base_idx+0, arc_idx, stem0_idx); - IM_POLYLINE_APPEND_TRI(base_idx+0, stem0_idx, base_idx+1); - IM_POLYLINE_APPEND_TRI(base_idx+2, stem1_idx, inner_idx); - IM_POLYLINE_APPEND_TRI(base_idx+2, inner_idx, base_idx+3); + IM_APPEND_TRI(base_idx + 0, arc_idx, stem0_idx); + IM_APPEND_TRI(base_idx + 0, stem0_idx, base_idx + 1); + IM_APPEND_TRI(base_idx + 2, stem1_idx, inner_idx); + IM_APPEND_TRI(base_idx + 2, inner_idx, base_idx + 3); base_idx = stem0_idx - 1; @@ -2862,7 +2993,7 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c else { PathRect(p_min, p_max, rounding, flags); - PathFillConvex(col); + PathFillConvex(col, ImDrawFlags_MiterOnly); } } @@ -3017,7 +3148,7 @@ void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col, PathArcTo(center, radius, 0.0f, a_max, num_segments - 1); } - PathFillConvex(col); + PathFillConvex(col, ImDrawFlags_MiterOnly); } // Guaranteed to honor 'num_segments' @@ -3072,7 +3203,7 @@ void ImDrawList::AddNgonFilled(const ImVec2& center, float radius, ImU32 col, in // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; PathArcTo(center, radius, 0.0f, a_max, num_segments - 1); - PathFillConvex(col); + PathFillConvex(col, ImDrawFlags_MiterOnly); } // Ellipse @@ -3277,7 +3408,7 @@ void ImDrawList::AddImageRounded(ImTextureRef tex_ref, const ImVec2& p_min, cons int vert_start_idx = VtxBuffer.Size; PathRect(p_min, p_max, rounding, flags); - PathFillConvex(col); + PathFillConvex(col, ImDrawFlags_MiterOnly); int vert_end_idx = VtxBuffer.Size; ImGui::ShadeVertsLinearUV(this, vert_start_idx, vert_end_idx, p_min, p_max, uv_min, uv_max, true); @@ -7857,7 +7988,7 @@ void ImGui::RenderRectFilledInRangeH(ImDrawList* draw_list, const ImRect& rect, draw_list->PathArcTo(ImVec2(x1, p1.y - rounding), rounding, +arc1_b, +arc1_e); // BR } } - draw_list->PathFillConvex(col); + draw_list->PathFillConvex(col, ImDrawFlags_MiterOnly); } void ImGui::RenderRectFilledWithHole(ImDrawList* draw_list, const ImRect& outer, const ImRect& inner, ImU32 col, float rounding) From ea2e5c79c06fe8a4f87c31307554f00ee924b266 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Mon, 27 Apr 2026 08:36:18 +0300 Subject: [PATCH 024/120] DrawList: added square cap rendering. - Added square cap rendering to the DrawPolyline, which can be turned on by ImDrawFlags_SquareCap - Added draw flags to AddBezierCubic() and AddBezierQuadratic() --- imgui.h | 5 +++-- imgui_demo.cpp | 8 +++++--- imgui_draw.cpp | 24 ++++++++++++++++-------- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/imgui.h b/imgui.h index 0f5edb89a..95718866f 100644 --- a/imgui.h +++ b/imgui.h @@ -3328,6 +3328,7 @@ enum ImDrawFlags_ // Stroke options ImDrawFlags_Closed = 1 << 9, // PathStroke(), AddPolyline(): specify that shape should be closed. ImDrawFlags_MiterOnly = 1 << 10, // PathStroke(), AddPolyline(): use miter corners only. This assumes that the input polyline does not have corners sharper than 90 degrees. Slightly faster. + ImDrawFlags_SquareCap = 1 << 11, // PathStroke(), AddPolyline(): use square cap line ends. // Stroke position relative to the shape outline ImDrawFlags_StrokeInside = 1 << 16, // Draw stroke inside of the shape outline (Default) @@ -3424,8 +3425,8 @@ struct ImDrawList IMGUI_API void AddEllipseFilled(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot = 0.0f, int num_segments = 0); IMGUI_API void AddText(const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL); IMGUI_API void AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end = NULL, float wrap_width = 0.0f, const ImVec4* cpu_fine_clip_rect = NULL); - IMGUI_API void AddBezierCubic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments = 0); // Cubic Bezier (4 control points) - IMGUI_API void AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness, int num_segments = 0); // Quadratic Bezier (3 control points) + IMGUI_API void AddBezierCubic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments = 0, ImDrawFlags flags = 0); // Cubic Bezier (4 control points) + IMGUI_API void AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness, int num_segments = 0, ImDrawFlags flags = 0); // Quadratic Bezier (3 control points) // General polygon // - Only simple polygons are supported by filling functions (no self-intersections, no holes). diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 14de91638..45eff63c3 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10395,6 +10395,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) static bool curve_segments_override = false; static int curve_segments_override_v = 8; static ImVec4 colf = ImVec4(1.0f, 1.0f, 0.4f, 1.0f); + static bool square_caps = false; ImGui::DragFloat("Size", &sz, 0.2f, 0.2f, 100.0f, "%.0f"); ImGui::DragFloat("Thickness", &thickness, 0.05f, 0.0f, 32.0f, "%.02f"); ImGui::SliderInt("N-gon sides", &ngon_sides, 3, 12); @@ -10405,6 +10406,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); curve_segments_override |= ImGui::SliderInt("Curves segments override", &curve_segments_override_v, 3, 40); ImGui::ColorEdit4("Color", &colf.x); + ImGui::Checkbox("Square caps", &square_caps); static ImDrawFlags flags = ImDrawFlags_None; ImGui::AlignTextToFramePadding(); @@ -10420,6 +10422,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) const ImU32 col = ImColor(colf); const float spacing = 10.0f; const ImDrawFlags corners_tl_br = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersBottomRight; + const ImDrawFlags stroke_flags = square_caps ? ImDrawFlags_SquareCap : ImDrawFlags_None; const float rounding = sz / 5.0f; const int circle_segments = circle_segments_override ? circle_segments_override_v : 0; const int curve_segments = curve_segments_override ? curve_segments_override_v : 0; @@ -10444,7 +10447,6 @@ static void ShowExampleAppCustomRendering(bool* p_open) //draw_list->AddPolyline(concave_shape, IM_COUNTOF(concave_shape), col, ImDrawFlags_Closed, th); draw_list->AddLineH(x, x + sz, y, col, th, flags); x += sz + spacing; // Horizontal line (note: drawing a filled rectangle will be faster!) draw_list->AddLineV(x, y, y + sz, col, th, flags); x += spacing; // Vertical line (note: drawing a filled rectangle will be faster!) - draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y), col, th, flags); x += sz + spacing; draw_list->AddLine(ImVec2(x, y + sz), ImVec2(x, y), col, th, flags); x += spacing; @@ -10456,11 +10458,11 @@ static void ShowExampleAppCustomRendering(bool* p_open) x += sz + spacing; // Quadratic Bezier Curve (3 control points) - draw_list->AddBezierQuadratic(ImVec2(x + cp3[0].x, y + cp3[0].y), ImVec2(x + cp3[1].x, y + cp3[1].y), ImVec2(x + cp3[2].x, y + cp3[2].y), col, th, curve_segments); + draw_list->AddBezierQuadratic(ImVec2(x + cp3[0].x, y + cp3[0].y), ImVec2(x + cp3[1].x, y + cp3[1].y), ImVec2(x + cp3[2].x, y + cp3[2].y), col, th, curve_segments, stroke_flags); x += sz + spacing; // Cubic Bezier Curve (4 control points) - draw_list->AddBezierCubic(ImVec2(x + cp4[0].x, y + cp4[0].y), ImVec2(x + cp4[1].x, y + cp4[1].y), ImVec2(x + cp4[2].x, y + cp4[2].y), ImVec2(x + cp4[3].x, y + cp4[3].y), col, th, curve_segments); + draw_list->AddBezierCubic(ImVec2(x + cp4[0].x, y + cp4[0].y), ImVec2(x + cp4[1].x, y + cp4[1].y), ImVec2(x + cp4[2].x, y + cp4[2].y), ImVec2(x + cp4[3].x, y + cp4[3].y), col, th, curve_segments, stroke_flags); x = p.x + 4; y += sz + spacing; diff --git a/imgui_draw.cpp b/imgui_draw.cpp index e3ee377db..c6f84a232 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -928,6 +928,8 @@ void ImDrawList::_AddPolylineThin(const ImVec2* points, ImVec2* normals, float* len_sqr1 = sqr_lengths[0]; const ImVec2 dir(n1.y, -n1.x); + if (flags & ImDrawFlags_SquareCap) + p1 -= dir * (half_thickness - half_aa); // At this point half_thickness has half fringe width baked in. const ImVec2 pa = p1 - dir * half_aa; const ImVec2 pb = p1 + dir * half_aa; @@ -1147,6 +1149,8 @@ void ImDrawList::_AddPolylineThin(const ImVec2* points, ImVec2* normals, float* // End cap const ImVec2 dir(n1.y, -n1.x); + if (flags & ImDrawFlags_SquareCap) + p1 += dir * (half_thickness - half_aa); // At this point half_thickness has half fringe width baked in. const ImVec2 pa = p1 - dir * half_aa; const ImVec2 pb = p1 + dir * half_aa; @@ -1241,6 +1245,8 @@ void ImDrawList::_AddPolylineThick(const ImVec2* points, ImVec2* normals, float* // Start cap const ImVec2 dir(n1.y, -n1.x); + if (flags & ImDrawFlags_SquareCap) + p1 -= dir * (half_thickness - half_aa); // At this point half_thickness has half fringe width baked in. const ImVec2 pa = p1 - dir * half_aa; const ImVec2 pb = p1 + dir * half_aa; @@ -1458,6 +1464,8 @@ void ImDrawList::_AddPolylineThick(const ImVec2* points, ImVec2* normals, float* // End cap const ImVec2 dir(n1.y, -n1.x); + if (flags & ImDrawFlags_SquareCap) + p1 += dir * (half_thickness - half_aa); // At this point half_thickness has half fringe width baked in. const ImVec2 pa = p1 - dir * half_aa; const ImVec2 pb = p1 + dir * half_aa; @@ -1851,7 +1859,7 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun _VtxWritePtr += points_count; _VtxCurrentIdx += points_count; - unsigned int prev_outer_idx = (unsigned int )-1; // We dont know oueter vert could yet, will need to patch once we're done. + unsigned int prev_outer_idx = 0; // We dont know outer vert could yet, will need to patch once we're done. const float miter_distance_limit = half_aa * IM_POLYLINE_MITER_LIMIT; const float miter_distance_limit_sqr = miter_distance_limit * miter_distance_limit; @@ -2389,7 +2397,7 @@ void ImDrawList::AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float th if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) { const ImVec2 points[2] = { ImVec2(p1.x + 0.5f, p1.y + 0.5f), ImVec2(p2.x + 0.5f, p2.y + 0.5f) }; - AddPolyline(points, 2, col, thickness); + AddPolyline(points, 2, col, thickness, ImDrawFlags_StrokeLegacy); return; } @@ -2423,7 +2431,7 @@ void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float th if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) { const ImVec2 points[2] = { ImVec2(min_x + 0.5f, y + 0.5f), ImVec2(max_x + 0.5f, y + 0.5f) }; // Same as AddLine() above. - AddPolyline(points, 2, col, thickness); + AddPolyline(points, 2, col, thickness, ImDrawFlags_StrokeLegacy); return; } @@ -2454,7 +2462,7 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) { const ImVec2 points[2] = { ImVec2(x + 0.5f, max_y + 0.5f), ImVec2(x + 0.5f, min_y + 0.5f) }; // Same as AddLine() above. - AddPolyline(points, 2, col, thickness); + AddPolyline(points, 2, col, thickness, ImDrawFlags_StrokeLegacy); return; } @@ -3302,25 +3310,25 @@ void ImDrawList::AddEllipseFilled(const ImVec2& center, const ImVec2& radius, Im } // Cubic Bezier takes 4 controls points -void ImDrawList::AddBezierCubic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments) +void ImDrawList::AddBezierCubic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, int num_segments, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) return; PathLineTo(p1); PathBezierCubicCurveTo(p2, p3, p4, num_segments); - PathStroke(col, thickness); + PathStroke(col, thickness, flags); } // Quadratic Bezier takes 3 controls points -void ImDrawList::AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness, int num_segments) +void ImDrawList::AddBezierQuadratic(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness, int num_segments, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) return; PathLineTo(p1); PathBezierQuadraticCurveTo(p2, p3, num_segments); - PathStroke(col, thickness); + PathStroke(col, thickness, flags); } void ImDrawList::AddText(ImFont* font, float font_size, const ImVec2& pos, ImU32 col, const char* text_begin, const char* text_end, float wrap_width, const ImVec4* cpu_fine_clip_rect) From d1d2f5aead3f121622bf624846c4c5e3eb0a668a Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Mon, 27 Apr 2026 15:00:04 +0300 Subject: [PATCH 025/120] DrawList: use dedicated larger texture for fractional thickness lines. --- imgui.h | 6 +++-- imgui_draw.cpp | 69 +++++++++++++++++++++++++++++++++++++++--------- imgui_internal.h | 4 ++- 3 files changed, 63 insertions(+), 16 deletions(-) diff --git a/imgui.h b/imgui.h index 95718866f..74feafc8b 100644 --- a/imgui.h +++ b/imgui.h @@ -3205,6 +3205,7 @@ struct ImGuiSelectionExternalStorage // The maximum line width to bake anti-aliased textures for. Build atlas with ImFontAtlasFlags_NoBakedLines to disable baking. #ifndef IM_DRAWLIST_TEX_LINES_WIDTH_MAX #define IM_DRAWLIST_TEX_LINES_WIDTH_MAX (32) +#define IM_DRAWLIST_TEX_LINE_FRACT_WIDTH_MAX (128) #endif #ifndef IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX @@ -3534,8 +3535,8 @@ struct ImDrawList IMGUI_API void _PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments); IMGUI_API void _AddRectFilledBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, ImVec4 tex_uvs, ImDrawFlags flags); IMGUI_API void _AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, float t, ImVec4 tex_uvs, ImDrawFlags flags); - IMGUI_API void _AddPolylineThin(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, ImVec4 tex_uvs); - IMGUI_API void _AddPolylineThick(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags); + IMGUI_API void _AddPolylineIntThickness(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, ImVec4 tex_uvs); + IMGUI_API void _AddPolylineFractThickness(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags); IMGUI_API void _AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags); }; @@ -3899,6 +3900,7 @@ struct ImFontAtlas ImVector Fonts; // Hold all the fonts returned by AddFont*. Fonts[0] is the default font upon calling ImGui::NewFrame(), use ImGui::PushFont()/PopFont() to change the current font. ImVector Sources; // Source/configuration data ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines + ImVec4 TexUvLineFract; // UVs for fraction thickness baked anti-aliased lines ImVec4 TexUvCornerFills[IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX]; // UVs for baked anti-aliased corners ImVec4 TexUvCornerStrokes[IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX * IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX]; // UVs for baked anti-aliased corners int TexNextUniqueID; // Next value to be stored in TexData->UniqueID diff --git a/imgui_draw.cpp b/imgui_draw.cpp index c6f84a232..818bd5b0c 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -871,7 +871,7 @@ static void CalcSegmentNormals(const ImVec2* points, const int points_count, ImV } } -void ImDrawList::_AddPolylineThin(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, ImVec4 tex_uvs) +void ImDrawList::_AddPolylineIntThickness(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, ImVec4 tex_uvs) { const bool closed = (flags & ImDrawFlags_Closed) != 0; const bool miters_only = (flags & ImDrawFlags_MiterOnly) != 0; @@ -1184,7 +1184,7 @@ void ImDrawList::_AddPolylineThin(const ImVec2* points, ImVec2* normals, float* PrimUnreserve(idx_count - idx_used, vtx_count - vtx_used); } -void ImDrawList::_AddPolylineThick(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags) +void ImDrawList::_AddPolylineFractThickness(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags) { const bool closed = (flags & ImDrawFlags_Closed) != 0; const bool miters_only = (flags & ImDrawFlags_MiterOnly) != 0; @@ -1193,7 +1193,7 @@ void ImDrawList::_AddPolylineThick(const ImVec2* points, ImVec2* normals, float* // TODO: we can expand this by having one very long texture strip for the lines, // or if place black 2x2 pixel next to the while pixel, and use texture clamping (that thickness would expand out of the texture). - const float max_width = (IM_DRAWLIST_TEX_LINES_WIDTH_MAX-1) * _FringeScale * 2.0f; + const float max_width = IM_DRAWLIST_TEX_LINE_FRACT_WIDTH_MAX * _FringeScale * 2.0f; thickness = ImMin(thickness, max_width); thickness += _FringeScale; // Place half of AA fringe each side of the line. @@ -1229,7 +1229,7 @@ void ImDrawList::_AddPolylineThick(const ImVec2* points, ImVec2* normals, float* ImVec2 n1; float len_sqr1; - const ImVec4 tex_uvs = _Data->TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX]; + const ImVec4 tex_uvs = _Data->TexUvLineFract; const ImVec2 uv_out(tex_uvs.x + (0.5f / _FringeScale) * _Data->FontAtlas->TexUvScale.x, tex_uvs.y); const ImVec2 uv_in(tex_uvs.x + ((half_thickness + 0.5f) / _FringeScale) * _Data->FontAtlas->TexUvScale.x, tex_uvs.y); @@ -1526,22 +1526,22 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 // TODO: support splitting very long lines to multiple draw calls. // We can use cheaper rendering if the thickness is integer size. - const int int_thickness = (int)screen_thickness; - const bool can_use_thin = ImAbs(screen_thickness - (float)int_thickness) < 0.01f && (int_thickness >= 1 && int_thickness < IM_DRAWLIST_TEX_LINES_WIDTH_MAX); + const int truncated_thickness = (int)screen_thickness; + const bool is_int_thickness = ImAbs(screen_thickness - (float)truncated_thickness) < 0.01f && (truncated_thickness >= 1 && truncated_thickness < IM_DRAWLIST_TEX_LINES_WIDTH_MAX); _Data->TempBuffer.reserve_discard(points_count * 2); ImVec2* normals = _Data->TempBuffer.Data; float* sqr_lengths = (float*)(normals + points_count); CalcSegmentNormals(points, points_count, normals, sqr_lengths, (flags & ImDrawFlags_Closed) != 0); - if (can_use_thin) + if (is_int_thickness) { - const ImVec4 tex_uvs = _Data->TexUvLines[int_thickness]; - _AddPolylineThin(points, normals, sqr_lengths, points_count, col, (float)int_thickness * _FringeScale, flags, tex_uvs); + const ImVec4 tex_uvs = _Data->TexUvLines[truncated_thickness]; + _AddPolylineIntThickness(points, normals, sqr_lengths, points_count, col, (float)truncated_thickness * _FringeScale, flags, tex_uvs); } else { - _AddPolylineThick(points, normals, sqr_lengths, points_count, col, thickness, flags); + _AddPolylineFractThickness(points, normals, sqr_lengths, points_count, col, thickness, flags); } } @@ -1842,7 +1842,7 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun float dx = p1.x - p0.x; float dy = p1.y - p0.y; float d2 = dx*dx + dy*dy; - if (d2 > 0.f) + if (d2 > 0.0f) { float inv_len = ImRsqrt(d2); dx *= inv_len; @@ -1873,7 +1873,7 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun // theta is the angle between two segments const float cos_theta = n0.x * n1.x + n0.y * n1.y; // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm - float miter_scale_factor = ImMin(1000.f, (cos_theta > IM_POLYLINE_MITER_ANGLE_LIMIT) ? (half_aa / (1.0f + cos_theta)) : FLT_MAX); // avoid division by zero + float miter_scale_factor = ImMin(1000.0f, (cos_theta > IM_POLYLINE_MITER_ANGLE_LIMIT) ? (half_aa / (1.0f + cos_theta)) : FLT_MAX); // avoid division by zero float dm_x = (n0.x + n1.x) * miter_scale_factor; float dm_y = (n0.y + n1.y) * miter_scale_factor; @@ -2606,6 +2606,10 @@ void ImDrawList::_AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 c // The stroke is positioned inside the rectangle. void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags) { + // Note: this is smaller than in AddPolyline(), since the stem offset can be biased all the way to one side. + const float max_width = IM_DRAWLIST_TEX_LINE_FRACT_WIDTH_MAX * _FringeScale; + thickness = ImMin(thickness, max_width); + thickness += _FringeScale; // Adjust the rounding to be the outside radius. @@ -2633,7 +2637,7 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, const float stem_offset = thickness - ImMax(thickness - rounding, _FringeScale); - const ImVec4 tex_uvs = _Data->TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX]; // TODO: max uv + const ImVec4 tex_uvs = _Data->TexUvLineFract; // TODO: max uv const ImVec2 outer_uv0(tex_uvs.x + (0.5f / _FringeScale) * _Data->FontAtlas->TexUvScale.x, tex_uvs.y); const ImVec2 outer_uv1(tex_uvs.x + ((0.5f + stem_offset) / _FringeScale) * _Data->FontAtlas->TexUvScale.x, tex_uvs.y); const ImVec2 inner_uv0(tex_uvs.x + (0.5f / _FringeScale) * _Data->FontAtlas->TexUvScale.x, tex_uvs.y); @@ -5470,6 +5474,44 @@ static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts atlas->TexUvLines[n] = ImVec4(uv0.x, half_v, uv1.x, half_v); } + + // Single larger line for fraction width textured lines. + // Alternative solution could be to fix while and black pixel next to the texture border + // and use the texture clamping to expand the opaque pixel. + // : + // [*][ ] + // : + // <-- width would expand out of the texture expanding the while pixel to as far as needed. + add_and_draw = atlas->GetCustomRect(builder->PackIdLineFractTexData, &r) == false; + if (add_and_draw) + { + ImVec2i pack_size = ImVec2i(IM_DRAWLIST_TEX_LINE_FRACT_WIDTH_MAX + 1, 1); + builder->PackIdLineFractTexData = atlas->AddCustomRect(pack_size.x, pack_size.y, &r); + IM_ASSERT(builder->PackIdLineFractTexData != ImFontAtlasRectId_Invalid); + } + + // Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle + // Write each slice + if (add_and_draw && tex->Format == ImTextureFormat_Alpha8) + { + ImU8* write_ptr = (ImU8*)tex->GetPixelsAt(r.x, r.y); + *write_ptr++ = 0x00; + for (int i = 0; i < IM_DRAWLIST_TEX_LINE_FRACT_WIDTH_MAX; i++) + *write_ptr++ = 0xFF; + } + else if (add_and_draw && tex->Format == ImTextureFormat_RGBA32) + { + ImU32* write_ptr = (ImU32*)(void*)tex->GetPixelsAt(r.x, r.y); + *write_ptr++ = IM_COL32(255, 255, 255, 0); + for (int i = 0; i < IM_DRAWLIST_TEX_LINE_FRACT_WIDTH_MAX; i++) + *write_ptr++ = IM_COL32_WHITE; + } + + // Refresh UV coordinates + ImVec2 uv0 = ImVec2((float)r.x, (float)r.y) * atlas->TexUvScale; + ImVec2 uv1 = ImVec2((float)r.x + 1.0f + IM_DRAWLIST_TEX_LINE_FRACT_WIDTH_MAX, (float)r.y) * atlas->TexUvScale; + float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts + atlas->TexUvLineFract = ImVec4(uv0.x, half_v, uv1.x, half_v); } //----------------------------------------------------------------------------------------------------------------------------- @@ -5843,6 +5885,7 @@ void ImFontAtlasUpdateDrawListsSharedData(ImFontAtlas* atlas) { shared_data->TexUvWhitePixel = atlas->TexUvWhitePixel; shared_data->TexUvLines = atlas->TexUvLines; + shared_data->TexUvLineFract = atlas->TexUvLineFract; shared_data->TexUvCornerFills = atlas->TexUvCornerFills; shared_data->TexUvCornerStrokes = atlas->TexUvCornerStrokes; } diff --git a/imgui_internal.h b/imgui_internal.h index 9db27ab74..547f4ddc4 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -921,6 +921,7 @@ struct IMGUI_API ImDrawListSharedData { ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas (== FontAtlas->TexUvWhitePixel) const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas (== FontAtlas->TexUvLines) + ImVec4 TexUvLineFract; // UV of fractional anti-aliased lines in the atlas (== FontAtlas->TexUvLineFract) const ImVec4* TexUvCornerFills; // UV of rounded corner (== FontAtlas->TexUvCornerFills) const ImVec4* TexUvCornerStrokes; ImFontAtlas* FontAtlas; // Current font atlas @@ -4058,9 +4059,10 @@ struct ImFontAtlasBuilder // Custom rectangle identifiers ImFontAtlasRectId PackIdMouseCursors; // White pixel + mouse cursors. Also happen to be fallback in case of packing failure. ImFontAtlasRectId PackIdLinesTexData; + ImFontAtlasRectId PackIdLineFractTexData; ImFontAtlasRectId PackIdCornersTexData; - ImFontAtlasBuilder() { memset((void*)this, 0, sizeof(*this)); FrameCount = -1; RectsIndexFreeListStart = -1; PackIdMouseCursors = PackIdLinesTexData = PackIdCornersTexData = -1; } + ImFontAtlasBuilder() { memset((void*)this, 0, sizeof(*this)); FrameCount = -1; RectsIndexFreeListStart = -1; PackIdMouseCursors = PackIdLinesTexData = PackIdLineFractTexData = PackIdCornersTexData = -1; } }; IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas); From c2e029fb98356a04ce1b79236e815961e9305127 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Thu, 7 May 2026 09:25:46 +0300 Subject: [PATCH 026/120] DrawList: added stroke position to path rendering. - added generic stroke position to all path rendering - removed stroke folding in favor of limiting the miter width (simplifies the geometry cases) - removed fractional stroke rendering in favor of using the integer sized rendering for everything - the rationale is that the in-between sizes are a bit blurry anyways, and the error we get from scaling is visually acceptable - added stroke position option for the demo - updated the degenerate shape handling - remove AddLine() stroke pos handling - removed up commented out code. --- imgui.h | 9 +- imgui_demo.cpp | 12 +- imgui_draw.cpp | 922 ++++++++++++++----------------------------------- 3 files changed, 260 insertions(+), 683 deletions(-) diff --git a/imgui.h b/imgui.h index 74feafc8b..6b777437a 100644 --- a/imgui.h +++ b/imgui.h @@ -3414,9 +3414,9 @@ struct ImDrawList IMGUI_API void AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding = 0.0f, float thickness = 1.0f, ImDrawFlags flags = 0); // a: upper-left, b: lower-right (== upper-left + size) IMGUI_API void AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding = 0.0f, ImDrawFlags flags = 0); // a: upper-left, b: lower-right (== upper-left + size) IMGUI_API void AddRectFilledMultiColor(const ImVec2& p_min, const ImVec2& p_max, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left); - IMGUI_API void AddQuad(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddQuad(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness = 1.0f, ImDrawFlags flags = 0); IMGUI_API void AddQuadFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col); - IMGUI_API void AddTriangle(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness = 1.0f); + IMGUI_API void AddTriangle(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness = 1.0f, ImDrawFlags = 0); IMGUI_API void AddTriangleFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col); IMGUI_API void AddCircle(const ImVec2& center, float radius, ImU32 col, int num_segments = 0, float thickness = 1.0f, ImDrawFlags flags = 0); IMGUI_API void AddCircleFilled(const ImVec2& center, float radius, ImU32 col, int num_segments = 0); @@ -3454,7 +3454,7 @@ struct ImDrawList inline void PathLineToMergeDuplicate(const ImVec2& pos) { if (_Path.Size == 0 || memcmp(&_Path.Data[_Path.Size - 1], &pos, 8) != 0) _Path.push_back(pos); } inline void PathFillConvex(ImU32 col, ImDrawFlags flags = 0) { AddConvexPolyFilled(_Path.Data, _Path.Size, col, flags); _Path.Size = 0; } inline void PathFillConcave(ImU32 col) { AddConcavePolyFilled(_Path.Data, _Path.Size, col); _Path.Size = 0; } - inline void PathStroke(ImU32 col, float thickness = 1.0f, ImDrawFlags flags = 0) { AddPolyline(_Path.Data, _Path.Size, col, thickness, flags); _Path.Size = 0; } + inline void PathStroke(ImU32 col, float thickness = 1.0f, ImDrawFlags flags = ImDrawFlags_StrokeCenter) { AddPolyline(_Path.Data, _Path.Size, col, thickness, flags); _Path.Size = 0; } IMGUI_API void PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments = 0); IMGUI_API void PathArcToFast(const ImVec2& center, float radius, int a_min_of_12, int a_max_of_12); // Use precomputed angles for a 12 steps circle IMGUI_API void PathEllipticalArcTo(const ImVec2& center, const ImVec2& radius, float rot, float a_min, float a_max, int num_segments = 0); // Ellipse @@ -3535,8 +3535,7 @@ struct ImDrawList IMGUI_API void _PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments); IMGUI_API void _AddRectFilledBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, ImVec4 tex_uvs, ImDrawFlags flags); IMGUI_API void _AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, float t, ImVec4 tex_uvs, ImDrawFlags flags); - IMGUI_API void _AddPolylineIntThickness(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, ImVec4 tex_uvs); - IMGUI_API void _AddPolylineFractThickness(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags); + IMGUI_API void _AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, ImVec4 tex_uvs, float fringe); IMGUI_API void _AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags); }; diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 45eff63c3..392609ce3 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10397,7 +10397,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) static ImVec4 colf = ImVec4(1.0f, 1.0f, 0.4f, 1.0f); static bool square_caps = false; ImGui::DragFloat("Size", &sz, 0.2f, 0.2f, 100.0f, "%.0f"); - ImGui::DragFloat("Thickness", &thickness, 0.05f, 0.0f, 32.0f, "%.02f"); + ImGui::DragFloat("Thickness", &thickness, 0.1f, 0.0f, 30.0f, "%.02f"); ImGui::SliderInt("N-gon sides", &ngon_sides, 3, 12); ImGui::Checkbox("##circlesegmentoverride", &circle_segments_override); ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); @@ -10422,7 +10422,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) const ImU32 col = ImColor(colf); const float spacing = 10.0f; const ImDrawFlags corners_tl_br = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersBottomRight; - const ImDrawFlags stroke_flags = square_caps ? ImDrawFlags_SquareCap : ImDrawFlags_None; + const ImDrawFlags cap_flags = square_caps ? ImDrawFlags_SquareCap : ImDrawFlags_None; const float rounding = sz / 5.0f; const int circle_segments = circle_segments_override ? circle_segments_override_v : 0; const int curve_segments = curve_segments_override ? curve_segments_override_v : 0; @@ -10450,19 +10450,19 @@ static void ShowExampleAppCustomRendering(bool* p_open) draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y), col, th, flags); x += sz + spacing; draw_list->AddLine(ImVec2(x, y + sz), ImVec2(x, y), col, th, flags); x += spacing; - draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y + sz), col, th, flags); x += sz + spacing; // Diagonal line + draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y + sz), col, th, flags | cap_flags); x += sz + spacing; // Diagonal line // Path draw_list->PathArcTo(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, 3.141592f, 3.141592f * -0.5f); - draw_list->PathStroke(col, th, flags); + draw_list->PathStroke(col, th, flags | cap_flags); x += sz + spacing; // Quadratic Bezier Curve (3 control points) - draw_list->AddBezierQuadratic(ImVec2(x + cp3[0].x, y + cp3[0].y), ImVec2(x + cp3[1].x, y + cp3[1].y), ImVec2(x + cp3[2].x, y + cp3[2].y), col, th, curve_segments, stroke_flags); + draw_list->AddBezierQuadratic(ImVec2(x + cp3[0].x, y + cp3[0].y), ImVec2(x + cp3[1].x, y + cp3[1].y), ImVec2(x + cp3[2].x, y + cp3[2].y), col, th, curve_segments, flags | cap_flags); x += sz + spacing; // Cubic Bezier Curve (4 control points) - draw_list->AddBezierCubic(ImVec2(x + cp4[0].x, y + cp4[0].y), ImVec2(x + cp4[1].x, y + cp4[1].y), ImVec2(x + cp4[2].x, y + cp4[2].y), ImVec2(x + cp4[3].x, y + cp4[3].y), col, th, curve_segments, stroke_flags); + draw_list->AddBezierCubic(ImVec2(x + cp4[0].x, y + cp4[0].y), ImVec2(x + cp4[1].x, y + cp4[1].y), ImVec2(x + cp4[2].x, y + cp4[2].y), ImVec2(x + cp4[3].x, y + cp4[3].y), col, th, curve_segments, flags | cap_flags); x = p.x + 4; y += sz + spacing; diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 818bd5b0c..3d64cb7b7 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -841,53 +841,31 @@ void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, c #define IM_POLYLINE_MITER_ANGLE_LIMIT (-0.9999619f) // cos(179.5) #define IM_POLYLINE_MITER_LIMIT (4.0f) -static void CalcSegmentNormals(const ImVec2* points, const int points_count, ImVec2* normals, float* sqr_lengths, bool closed) +void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, ImVec4 tex_uvs, float fringe) { - // Calculate normals for each line segment - for (int i = 0; i < points_count - 1; i++) - { - float dx = points[i+1].x - points[i].x; - float dy = points[i+1].y - points[i].y; - const float d2 = dx*dx + dy*dy; - const float inv_len = (d2 > 0.0f) ? 1.0f / sqrtf(d2) : 0.0f; //ImRsqrt(d2) : 0.0f; - normals[i].x = -dy * inv_len; - normals[i].y = dx * inv_len; - sqr_lengths[i] = d2; - } - if (closed) - { - const float dx = points[0].x - points[points_count - 1].x; - const float dy = points[0].y - points[points_count - 1].y; - const float d2 = dx*dx + dy*dy; - const float inv_len = (d2 > 0.0f) ? 1.0f / sqrtf(d2) : 0.0f; //ImRsqrt(d2) : 0.0f; - normals[points_count - 1].x = -dy * inv_len; - normals[points_count - 1].y = dx * inv_len; - sqr_lengths[points_count - 1] = d2; - } - else - { - normals[points_count - 1] = normals[points_count - 2]; - sqr_lengths[points_count - 1] = 0.0f; - } -} - -void ImDrawList::_AddPolylineIntThickness(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, ImVec4 tex_uvs) -{ - const bool closed = (flags & ImDrawFlags_Closed) != 0; - const bool miters_only = (flags & ImDrawFlags_MiterOnly) != 0; const ImU32 col_trans = col & ~IM_COL32_A_MASK; - thickness += _FringeScale; - const float half_thickness = thickness * 0.5f; - const float miter_distance_limit = half_thickness * IM_POLYLINE_MITER_LIMIT; - const float miter_distance_limit_sqr = miter_distance_limit * miter_distance_limit; - const float half_aa = _FringeScale * 0.5f; + const ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); + const bool closed = (flags & ImDrawFlags_Closed) != 0; + const bool miters_only = (flags & ImDrawFlags_MiterOnly) != 0; + const float miter_distance_limit_sqr = IM_POLYLINE_MITER_LIMIT * IM_POLYLINE_MITER_LIMIT; + + float thickness0 = (thickness + fringe) * 0.5f; + float thickness1 = thickness0; + if (stroke_pos == ImDrawFlags_StrokeOutside) + { + thickness0 = thickness + fringe * 0.5f; + thickness1 = fringe * 0.5f; + } + else if (stroke_pos == ImDrawFlags_StrokeInside) + { + thickness0 = fringe * 0.5f; + thickness1 = thickness + fringe * 0.5f; + } - // TODO: Each point will generate 2-7 vertices. The worst case happens really rarely. This can be issue with large polylines and 16it indices. - // One option could be to calculate the miter values and overl & bevel flags before, then alloc, and finally commit. int idx_count = 0; int vtx_count = 0; - const int max_verts_per_point = miters_only ? 2 : 7; + const int max_verts_per_point = miters_only ? 2 : 4; const int max_tris_per_point = miters_only ? 2 : 5; if (closed) { @@ -911,10 +889,14 @@ void ImDrawList::_AddPolylineIntThickness(const ImVec2* points, ImVec2* normals, ImVec2 n1; float len_sqr1; - const float half_texel = (0.5f / _FringeScale) * _Data->FontAtlas->TexUvScale.x; + const float half_texel = 0.5f * _Data->FontAtlas->TexUvScale.x; const ImVec2 uv0(tex_uvs.x + half_texel, tex_uvs.y); const ImVec2 uv1(tex_uvs.z - half_texel, tex_uvs.y); - const ImVec2 uv2((uv0.x+uv1.x)*0.5f, tex_uvs.y); + const float ratio = thickness0 / (thickness0 + thickness1); // The points using uv2 are placed on the path, calculate the position from stroke offets. + const ImVec2 uv2(uv0.x + (uv1.x - uv0.x) * ratio, tex_uvs.y); + + const float half_aa = _FringeScale * 0.5f; // Used for end caps, does not use "fringe" since end cap AA is not using the texture. + const float half_thickness = thickness * 0.5f; int point_idx = 0; int point_end = points_count; @@ -929,17 +911,17 @@ void ImDrawList::_AddPolylineIntThickness(const ImVec2* points, ImVec2* normals, const ImVec2 dir(n1.y, -n1.x); if (flags & ImDrawFlags_SquareCap) - p1 -= dir * (half_thickness - half_aa); // At this point half_thickness has half fringe width baked in. + p1 -= dir * half_thickness; const ImVec2 pa = p1 - dir * half_aa; const ImVec2 pb = p1 + dir * half_aa; base_idx = (int)_VtxCurrentIdx; - IM_APPEND_VTX(pa.x - n1.x * half_thickness , pa.y - n1.y * half_thickness , uv0, col_trans); - IM_APPEND_VTX(pa.x + n1.x * half_thickness , pa.y + n1.y * half_thickness , uv1, col_trans); + IM_APPEND_VTX(pa.x - n1.x * thickness0, pa.y - n1.y * thickness0, uv0, col_trans); + IM_APPEND_VTX(pa.x + n1.x * thickness1, pa.y + n1.y * thickness1, uv1, col_trans); int next_base_idx = (int)_VtxCurrentIdx; - IM_APPEND_VTX(pb.x - n1.x * half_thickness , pb.y - n1.y * half_thickness , uv0, col); - IM_APPEND_VTX(pb.x + n1.x * half_thickness , pb.y + n1.y * half_thickness , uv1, col); + IM_APPEND_VTX(pb.x - n1.x * thickness0, pb.y - n1.y * thickness0, uv0, col); + IM_APPEND_VTX(pb.x + n1.x * thickness1, pb.y + n1.y * thickness1 , uv1, col); // AA cap IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); @@ -959,95 +941,90 @@ void ImDrawList::_AddPolylineIntThickness(const ImVec2* points, ImVec2* normals, IM_APPEND_VTX(0, 0, uv1, col); } - while (point_idx < point_end) + if (miters_only) { - ImVec2 n0 = n1; - float len_sqr0 = len_sqr1; - p1 = points[point_idx]; - n1 = normals[point_idx]; - len_sqr1 = sqr_lengths[point_idx]; - - // theta is the angle between two segments - const float cos_theta = n0.x * n1.x + n0.y * n1.y; - - // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm - const float miter_scale_factor = (cos_theta > IM_POLYLINE_MITER_ANGLE_LIMIT) ? half_thickness / (1.0f + cos_theta) : FLT_MAX; // avoid division by zero - const float miter_offset_x = (n0.x + n1.x) * miter_scale_factor; - const float miter_offset_y = (n0.y + n1.y) * miter_scale_factor; - const float miter_distance_sqr = miter_offset_x * miter_offset_x + miter_offset_y * miter_offset_y; - - const bool overlap = !miters_only && ((len_sqr0 < miter_distance_sqr) || (len_sqr1 < miter_distance_sqr) || (cos_theta <= IM_POLYLINE_MITER_ANGLE_LIMIT)); - const bool bevel = !miters_only && (miter_distance_sqr > miter_distance_limit_sqr); - - if (bevel) + while (point_idx < point_end) { - // Clipped bevel + ImVec2 n0 = n1; + p1 = points[point_idx]; + n1 = normals[point_idx]; + + // theta is the angle between two segments + const float cos_theta = n0.x * n1.x + n0.y * n1.y; + + // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm + const float miter_scale_factor = ImMin(1000.0f, (cos_theta > IM_POLYLINE_MITER_ANGLE_LIMIT) ? 1.0f / (1.0f + cos_theta) : FLT_MAX); // avoid division by zero + const float miter_offset_x = (n0.x + n1.x) * miter_scale_factor; + const float miter_offset_y = (n0.y + n1.y) * miter_scale_factor; + + // Miter + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - miter_offset_x * thickness0, p1.y - miter_offset_y * thickness0, uv0, col); // 2 + IM_APPEND_VTX(p1.x + miter_offset_x * thickness1, p1.y + miter_offset_y * thickness1, uv1, col); // 3 + + // Connect prev to next + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); + IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); + + base_idx = next_base_idx; + + point_idx++; + } + } + else + { + while (point_idx < point_end) + { + ImVec2 n0 = n1; + float len_sqr0 = len_sqr1; + p1 = points[point_idx]; + n1 = normals[point_idx]; + len_sqr1 = sqr_lengths[point_idx]; + + // theta is the angle between two segments + const float cos_theta = n0.x * n1.x + n0.y * n1.y; const float sin_theta = n0.y * n1.x - n0.x * n1.y; - float bevel_normal_x = n0.x + n1.x; - float bevel_normal_y = n0.y + n1.y; - IM_NORMALIZE2F_OVER_ZERO(bevel_normal_x, bevel_normal_y); - const float signed_miter_offset = sin_theta < 0.0f ? half_thickness : -half_thickness; - const float side_offset = half_thickness * ((n0.x * bevel_normal_x + n0.y * bevel_normal_y) - 1.0f) / (n0.y * bevel_normal_x - n0.x * bevel_normal_y); - const float pt_x = p1.x - bevel_normal_x * signed_miter_offset; - const float pt_y = p1.y - bevel_normal_y * signed_miter_offset; - const float sd_x = bevel_normal_y * side_offset; - const float sd_y = -bevel_normal_x * side_offset; + // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm + const float miter_scale_factor = (cos_theta > IM_POLYLINE_MITER_ANGLE_LIMIT) ? 1.0f / (1.0f + cos_theta) : FLT_MAX; // avoid division by zero - if (overlap) + float miter_offset_x = (n0.x + n1.x) * miter_scale_factor; + float miter_offset_y = (n0.y + n1.y) * miter_scale_factor; + const float miter_distance_sqr = miter_offset_x * miter_offset_x + miter_offset_y * miter_offset_y; + + if (miter_distance_sqr > miter_distance_limit_sqr) { - // Dislocated bevel. - if (sin_theta < 0.0f) - { - IM_APPEND_VTX(p1.x - n0.x * half_thickness, p1.y - n0.y * half_thickness, uv0, col); // 2 - IM_APPEND_VTX(p1.x + n0.x * half_thickness, p1.y + n0.y * half_thickness, uv1, col); // 3 - IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv0, col); // 4 - IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv0, col); // 5 - IM_APPEND_VTX(p1.x, p1.y, uv2, col); // 6 - const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; - IM_APPEND_VTX(p1.x - n1.x * half_thickness, p1.y - n1.y * half_thickness, uv0, col); // 7 - IM_APPEND_VTX(p1.x + n1.x * half_thickness, p1.y + n1.y * half_thickness, uv1, col); // 8 + // Clipped bevel - // Connect prev to next - IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); - IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); - // Bevel tris - IM_APPEND_TRI(base_idx + 6, base_idx + 2, base_idx + 4); - IM_APPEND_TRI(base_idx + 6, base_idx + 4, base_idx + 5); - IM_APPEND_TRI(base_idx + 6, base_idx + 5, base_idx + 7); - base_idx = next_base_idx; - } - else + // Limit the inner miter offset to avoid overshooting. + const float ref_thickness = (sin_theta < 0.0f) ? thickness1 : thickness0; + const float ref_thickness_sqr = ref_thickness * ref_thickness; + const float limit_sqr = ImMax(ImMax(len_sqr0, len_sqr1), ref_thickness_sqr); + const float ref_miter_dist_sqr = miter_distance_sqr * ref_thickness_sqr; + if (ref_miter_dist_sqr > limit_sqr) { - IM_APPEND_VTX(p1.x - n0.x * half_thickness, p1.y - n0.y * half_thickness, uv0, col); // 2 - IM_APPEND_VTX(p1.x + n0.x * half_thickness, p1.y + n0.y * half_thickness, uv1, col); // 3 - IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv1, col); // 4 - IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv1, col); // 5 - IM_APPEND_VTX(p1.x, p1.y, uv2, col); // 6 - const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; - IM_APPEND_VTX(p1.x - n1.x * half_thickness, p1.y - n1.y * half_thickness, uv0, col); // 7 - IM_APPEND_VTX(p1.x + n1.x * half_thickness, p1.y + n1.y * half_thickness, uv1, col); // 8 - - // Connect prev to next - IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); - IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); - // Bevel - IM_APPEND_TRI(base_idx + 6, base_idx + 7, base_idx + 5); - IM_APPEND_TRI(base_idx + 6, base_idx + 5, base_idx + 4); - IM_APPEND_TRI(base_idx + 6, base_idx + 4, base_idx + 3); - base_idx = next_base_idx; + const float scale = ImSqrt(limit_sqr / ref_miter_dist_sqr); + miter_offset_x *= scale; + miter_offset_y *= scale; } - } - else - { + + float bn_x = n0.x + n1.x; + float bn_y = n0.y + n1.y; + IM_NORMALIZE2F_OVER_ZERO(bn_x, bn_y); + + const float side_offset = ((n0.x * bn_x + n0.y * bn_y) - 1.0f) / (n0.y * bn_x - n0.x * bn_y); + const float sd_x = bn_y * side_offset; + const float sd_y = -bn_x * side_offset; + // Bevel if (sin_theta < 0.0f) { - IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv0, col); // 2 + IM_APPEND_VTX(p1.x - (bn_x + sd_x) * thickness0, p1.y - (bn_y + sd_y) * thickness0, uv0, col); // 2 + // TODO: calc uv2 correctly. IM_APPEND_VTX(p1.x, p1.y, uv2, col); // 3 const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; - IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv0, col); // 4 - IM_APPEND_VTX(p1.x + miter_offset_x, p1.y + miter_offset_y, uv1, col); // 5 + IM_APPEND_VTX(p1.x - (bn_x - sd_x) * thickness0, p1.y - (bn_y - sd_y) * thickness0, uv0, col); // 4 + IM_APPEND_VTX(p1.x + miter_offset_x * thickness1, p1.y + miter_offset_y * thickness1, uv1, col); // 5 // Connect prev to next IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 5); @@ -1060,11 +1037,12 @@ void ImDrawList::_AddPolylineIntThickness(const ImVec2* points, ImVec2* normals, } else { - IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv1, col); // 2 + IM_APPEND_VTX(p1.x + (bn_x + sd_x) * thickness1, p1.y + (bn_y + sd_y) * thickness1, uv1, col); // 2 + // TODO: calc uv2 correctly. IM_APPEND_VTX(p1.x, p1.y, uv2, col); // 3 const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; - IM_APPEND_VTX(p1.x - miter_offset_x, p1.y - miter_offset_y, uv0, col); // 4 - IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv1, col); // 5 + IM_APPEND_VTX(p1.x - miter_offset_x * thickness0, p1.y - miter_offset_y * thickness0, uv0, col); // 4 + IM_APPEND_VTX(p1.x + (bn_x - sd_x) * thickness1, p1.y + (bn_y - sd_y) * thickness1, uv1, col); // 5 // Connect prev to next IM_APPEND_TRI(base_idx + 0, base_idx + 4, base_idx + 2); @@ -1076,69 +1054,22 @@ void ImDrawList::_AddPolylineIntThickness(const ImVec2* points, ImVec2* normals, base_idx = next_base_idx; } } - } - else - { - if (overlap) - { - // Dislocated Miter - const float sin_theta = n0.y * n1.x - n0.x * n1.y; - if (sin_theta < 0.0f) - { - - IM_APPEND_VTX(p1.x - n0.x * half_thickness, p1.y - n0.y * half_thickness, uv0, col); // 2 - IM_APPEND_VTX(p1.x + n0.x * half_thickness, p1.y + n0.y * half_thickness, uv1, col); // 3 - IM_APPEND_VTX(p1.x - miter_offset_x, p1.y - miter_offset_y, uv0, col); // 4 - IM_APPEND_VTX(p1.x, p1.y, uv2, col); // 5 - const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; - IM_APPEND_VTX(p1.x - n1.x * half_thickness, p1.y - n1.y * half_thickness, uv0, col); // 6 - IM_APPEND_VTX(p1.x + n1.x * half_thickness, p1.y + n1.y * half_thickness, uv1, col); // 7 - - // Connect prev to next - IM_APPEND_TRI(base_idx+0, base_idx+2, base_idx+3); - IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+1); - // Miter - IM_APPEND_TRI(base_idx+5, base_idx+2, base_idx+4); - IM_APPEND_TRI(base_idx+5, base_idx+4, base_idx+6); - - base_idx = next_base_idx; - } - else - { - IM_APPEND_VTX(p1.x - n0.x * half_thickness, p1.y - n0.y * half_thickness, uv0, col); // 2 - IM_APPEND_VTX(p1.x + n0.x * half_thickness, p1.y + n0.y * half_thickness, uv1, col); // 3 - IM_APPEND_VTX(p1.x + miter_offset_x, p1.y + miter_offset_y, uv1, col); // 4 - IM_APPEND_VTX(p1.x, p1.y, uv2, col); // 5 - const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; - IM_APPEND_VTX(p1.x - n1.x * half_thickness, p1.y - n1.y * half_thickness, uv0, col); // 6 - IM_APPEND_VTX(p1.x + n1.x * half_thickness, p1.y + n1.y * half_thickness, uv1, col); // 7 - - // Connect prev to next - IM_APPEND_TRI(base_idx+0, base_idx+2, base_idx+3); - IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+1); - // Miter - IM_APPEND_TRI(base_idx+5, base_idx+4, base_idx+3); - IM_APPEND_TRI(base_idx+5, base_idx+7, base_idx+4); - - base_idx = next_base_idx; - } - } else { // Miter const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; - IM_APPEND_VTX(p1.x - miter_offset_x, p1.y - miter_offset_y, uv0, col); // 2 - IM_APPEND_VTX(p1.x + miter_offset_x, p1.y + miter_offset_y, uv1, col); // 3 + IM_APPEND_VTX(p1.x - miter_offset_x * thickness0, p1.y - miter_offset_y * thickness0, uv0, col); // 2 + IM_APPEND_VTX(p1.x + miter_offset_x * thickness1, p1.y + miter_offset_y * thickness1, uv1, col); // 3 // Connect prev to next - IM_APPEND_TRI(base_idx+0, base_idx+2, base_idx+3); - IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+1); + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); + IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); base_idx = next_base_idx; } - } - point_idx++; + point_idx++; + } } // End cap @@ -1150,16 +1081,16 @@ void ImDrawList::_AddPolylineIntThickness(const ImVec2* points, ImVec2* normals, // End cap const ImVec2 dir(n1.y, -n1.x); if (flags & ImDrawFlags_SquareCap) - p1 += dir * (half_thickness - half_aa); // At this point half_thickness has half fringe width baked in. + p1 += dir * half_thickness; const ImVec2 pa = p1 - dir * half_aa; const ImVec2 pb = p1 + dir * half_aa; int next_base_idx = (int)_VtxCurrentIdx; - IM_APPEND_VTX(pa.x - n1.x * half_thickness, pa.y - n1.y * half_thickness, uv0, col); - IM_APPEND_VTX(pa.x + n1.x * half_thickness, pa.y + n1.y * half_thickness, uv1, col); + IM_APPEND_VTX(pa.x - n1.x * thickness0, pa.y - n1.y * thickness0, uv0, col); + IM_APPEND_VTX(pa.x + n1.x * thickness1, pa.y + n1.y * thickness1, uv1, col); - IM_APPEND_VTX(pb.x - n1.x * half_thickness, pb.y - n1.y * half_thickness, uv0, col_trans); - IM_APPEND_VTX(pb.x + n1.x * half_thickness, pb.y + n1.y * half_thickness, uv1, col_trans); + IM_APPEND_VTX(pb.x - n1.x * thickness0, pb.y - n1.y * thickness0, uv0, col_trans); + IM_APPEND_VTX(pb.x + n1.x * thickness1, pb.y + n1.y * thickness1, uv1, col_trans); // Connect IM_APPEND_TRI(base_idx+0, base_idx+2, base_idx+3); @@ -1184,328 +1115,6 @@ void ImDrawList::_AddPolylineIntThickness(const ImVec2* points, ImVec2* normals, PrimUnreserve(idx_count - idx_used, vtx_count - vtx_used); } -void ImDrawList::_AddPolylineFractThickness(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags) -{ - const bool closed = (flags & ImDrawFlags_Closed) != 0; - const bool miters_only = (flags & ImDrawFlags_MiterOnly) != 0; - - const ImU32 col_trans = col & ~IM_COL32_A_MASK; - - // TODO: we can expand this by having one very long texture strip for the lines, - // or if place black 2x2 pixel next to the while pixel, and use texture clamping (that thickness would expand out of the texture). - const float max_width = IM_DRAWLIST_TEX_LINE_FRACT_WIDTH_MAX * _FringeScale * 2.0f; - thickness = ImMin(thickness, max_width); - - thickness += _FringeScale; // Place half of AA fringe each side of the line. - const float half_thickness = thickness * 0.5f; - const float miter_distance_limit = half_thickness * IM_POLYLINE_MITER_LIMIT; - const float miter_distance_limit_sqr = miter_distance_limit * miter_distance_limit; - const float half_aa = _FringeScale * 0.5f; - - int idx_count = 0; - int vtx_count = 0; - const int max_verts_per_point = miters_only ? 3 : 5; - const int max_tris_per_point = miters_only ? 4 : 5; - if (closed) - { - vtx_count = /*body*/points_count * max_verts_per_point + /*closing*/3; - idx_count = (/*body*/points_count * max_tris_per_point + /*closing*/4) * 3; - } - else - { - // Body + caps - vtx_count = /*body*/(points_count - 2) * max_verts_per_point + /*caps*/(6 * 2); - idx_count = (/*body*/(points_count - 2) * max_tris_per_point + /*last seg*/4 + /*caps*/(4 * 2)) * 3; - } - - PrimReserve(idx_count, vtx_count); - - ImDrawVert* start_vtx_ptr = _VtxWritePtr; - ImDrawIdx* start_idx_ptr = _IdxWritePtr; - - int base_idx = (int)_VtxCurrentIdx; - - ImVec2 p1; - ImVec2 n1; - float len_sqr1; - - const ImVec4 tex_uvs = _Data->TexUvLineFract; - const ImVec2 uv_out(tex_uvs.x + (0.5f / _FringeScale) * _Data->FontAtlas->TexUvScale.x, tex_uvs.y); - const ImVec2 uv_in(tex_uvs.x + ((half_thickness + 0.5f) / _FringeScale) * _Data->FontAtlas->TexUvScale.x, tex_uvs.y); - - int point_idx = 0; - int point_end = points_count; - if (!closed) - { - point_idx++; - point_end--; - p1 = points[0]; - n1 = normals[0]; - len_sqr1 = sqr_lengths[0]; - - // Start cap - const ImVec2 dir(n1.y, -n1.x); - if (flags & ImDrawFlags_SquareCap) - p1 -= dir * (half_thickness - half_aa); // At this point half_thickness has half fringe width baked in. - const ImVec2 pa = p1 - dir * half_aa; - const ImVec2 pb = p1 + dir * half_aa; - - base_idx = (int)_VtxCurrentIdx; - IM_APPEND_VTX(pa.x - n1.x * half_thickness , pa.y - n1.y * half_thickness , uv_out, col_trans); - IM_APPEND_VTX(pa.x, pa.y, uv_in, col_trans); - IM_APPEND_VTX(pa.x + n1.x * half_thickness , pa.y + n1.y * half_thickness , uv_out, col_trans); - - int next_base_idx = (int)_VtxCurrentIdx; - IM_APPEND_VTX(pb.x - n1.x * half_thickness , pb.y - n1.y * half_thickness , uv_out, col); - IM_APPEND_VTX(pb.x, pb.y, uv_in, col); - IM_APPEND_VTX(pb.x + n1.x * half_thickness , pb.y + n1.y * half_thickness , uv_out, col); - - // AA cap - IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+4); - IM_APPEND_TRI(base_idx+0, base_idx+4, base_idx+1); - IM_APPEND_TRI(base_idx+1, base_idx+4, base_idx+5); - IM_APPEND_TRI(base_idx+1, base_idx+5, base_idx+2); - base_idx = next_base_idx; - } - else - { - p1 = points[points_count-1]; - n1 = normals[points_count-1]; - len_sqr1 = sqr_lengths[points_count-1]; - - // This will be filled later, allocate space. - base_idx = (int)_VtxCurrentIdx; - IM_APPEND_VTX(0, 0, uv_out, col); - IM_APPEND_VTX(0, 0, uv_in, col); - IM_APPEND_VTX(0, 0, uv_out, col); - } - - while (point_idx < point_end) - { - ImVec2 n0 = n1; - float len_sqr0 = len_sqr1; - p1 = points[point_idx]; - n1 = normals[point_idx]; - len_sqr1 = sqr_lengths[point_idx]; - - // theta is the angle between two segments - const float cos_theta = n0.x * n1.x + n0.y * n1.y; - - // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm - const float miter_scale_factor = (cos_theta > IM_POLYLINE_MITER_ANGLE_LIMIT) ? half_thickness / (1.0f + cos_theta) : FLT_MAX; // avoid division by zero - const float miter_offset_x = (n0.x + n1.x) * miter_scale_factor; - const float miter_offset_y = (n0.y + n1.y) * miter_scale_factor; - const float miter_distance_sqr = miter_offset_x * miter_offset_x + miter_offset_y * miter_offset_y; - - const bool overlap = !miters_only && ((len_sqr0 < miter_distance_sqr) || (len_sqr1 < miter_distance_sqr) || (cos_theta <= IM_POLYLINE_MITER_ANGLE_LIMIT)); - const bool bevel = !miters_only && (miter_distance_sqr > miter_distance_limit_sqr); - - if (bevel) - { - IM_ASSERT(!miters_only); - // Clipped bevel - const float sin_theta = n0.y * n1.x - n0.x * n1.y; - float bevel_normal_x = n0.x + n1.x; - float bevel_normal_y = n0.y + n1.y; - IM_NORMALIZE2F_OVER_ZERO(bevel_normal_x, bevel_normal_y); - - const float signed_miter_offset = sin_theta < 0.0f ? half_thickness : -half_thickness; - const float side_offset = half_thickness * ((n0.x * bevel_normal_x + n0.y * bevel_normal_y) - 1.0f) / (n0.y * bevel_normal_x - n0.x * bevel_normal_y); - const float pt_x = p1.x - bevel_normal_x * signed_miter_offset; - const float pt_y = p1.y - bevel_normal_y * signed_miter_offset; - const float sd_x = bevel_normal_y * side_offset; - const float sd_y = -bevel_normal_x * side_offset; - - if (overlap) - { - // Dislocated bevel. - if (sin_theta < 0.0f) - { - IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv_out, col); - IM_APPEND_VTX(p1.x + n0.x * half_thickness, p1.y + n0.y * half_thickness, uv_out, col); - const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; - IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv_out, col); - IM_APPEND_VTX(p1.x, p1.y, uv_in, col); - IM_APPEND_VTX(p1.x + n1.x * half_thickness, p1.y + n1.y * half_thickness, uv_out, col); - - // Bevel tri - IM_APPEND_TRI(base_idx+3, base_idx+5, base_idx+6); - // Connect prev to next - IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+6); - IM_APPEND_TRI(base_idx+0, base_idx+6, base_idx+1); - IM_APPEND_TRI(base_idx+1, base_idx+6, base_idx+4); - IM_APPEND_TRI(base_idx+1, base_idx+4, base_idx+2); - base_idx = next_base_idx; - } - else - { - IM_APPEND_VTX(p1.x - n0.x * half_thickness, p1.y - n0.y * half_thickness, uv_out, col); - IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv_out, col); - const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; - IM_APPEND_VTX(p1.x - n1.x * half_thickness, p1.y - n1.y * half_thickness, uv_out, col); - IM_APPEND_VTX(p1.x, p1.y, uv_in, col); - IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv_out, col); - - // Bevel tri - IM_APPEND_TRI(base_idx+6, base_idx+7, base_idx+4); - // Connect prev to next - IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+6); - IM_APPEND_TRI(base_idx+0, base_idx+6, base_idx+1); - IM_APPEND_TRI(base_idx+1, base_idx+6, base_idx+4); - IM_APPEND_TRI(base_idx+1, base_idx+4, base_idx+2); - base_idx = next_base_idx; - } - } - else - { - // Bevel - if (sin_theta < 0.0f) - { - IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv_out, col); - const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; - IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv_out, col); - IM_APPEND_VTX(p1.x, p1.y, uv_in, col); - IM_APPEND_VTX(p1.x + miter_offset_x, p1.y + miter_offset_y, uv_out, col); - - // Bevel tri - IM_APPEND_TRI(base_idx+3, base_idx+4, base_idx+5); - // Connect prev to next - IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+5); - IM_APPEND_TRI(base_idx+0, base_idx+5, base_idx+1); - IM_APPEND_TRI(base_idx+1, base_idx+5, base_idx+6); - IM_APPEND_TRI(base_idx+1, base_idx+6, base_idx+2); - base_idx = next_base_idx; - } - else - { - IM_APPEND_VTX(pt_x + sd_x, pt_y + sd_y, uv_out, col); - const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; - IM_APPEND_VTX(p1.x - miter_offset_x, p1.y - miter_offset_y, uv_out, col); - IM_APPEND_VTX(p1.x, p1.y, uv_in, col); - IM_APPEND_VTX(pt_x - sd_x, pt_y - sd_y, uv_out, col); - - // Bevel tri - IM_APPEND_TRI(base_idx+5, base_idx+6, base_idx+3); - // Connect prev to next - IM_APPEND_TRI(base_idx+0, base_idx+4, base_idx+5); - IM_APPEND_TRI(base_idx+0, base_idx+5, base_idx+1); - IM_APPEND_TRI(base_idx+1, base_idx+5, base_idx+3); - IM_APPEND_TRI(base_idx+1, base_idx+3, base_idx+2); - base_idx = next_base_idx; - } - } - } - else - { - if (overlap) - { - IM_ASSERT(!miters_only); - // Dislocated miter - const float sin_theta = n0.y * n1.x - n0.x * n1.y; - if (sin_theta < 0.0f) - { - IM_APPEND_VTX(p1.x + n0.x * half_thickness, p1.y + n0.y * half_thickness, uv_out, col); - const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; - IM_APPEND_VTX(p1.x - miter_offset_x, p1.y - miter_offset_y, uv_out, col); - IM_APPEND_VTX(p1.x, p1.y, uv_in, col); - IM_APPEND_VTX(p1.x + n1.x * half_thickness, p1.y + n1.y * half_thickness, uv_out, col); - - // Connect prev to next - IM_APPEND_TRI(base_idx+0, base_idx+4, base_idx+5); - IM_APPEND_TRI(base_idx+0, base_idx+5, base_idx+1); - IM_APPEND_TRI(base_idx+1, base_idx+5, base_idx+3); - IM_APPEND_TRI(base_idx+1, base_idx+3, base_idx+2); - - base_idx = next_base_idx; - } - else - { - IM_APPEND_VTX(p1.x - n0.x * half_thickness, p1.y - n0.y * half_thickness, uv_out, col); - const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; - IM_APPEND_VTX(p1.x - n1.x * half_thickness, p1.y - n1.y * half_thickness, uv_out, col); - IM_APPEND_VTX(p1.x, p1.y, uv_in, col); - IM_APPEND_VTX(p1.x + miter_offset_x, p1.y + miter_offset_y, uv_out, col); - - // Connect prev to next - IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+5); - IM_APPEND_TRI(base_idx+0, base_idx+5, base_idx+1); - IM_APPEND_TRI(base_idx+1, base_idx+5, base_idx+6); - IM_APPEND_TRI(base_idx+1, base_idx+6, base_idx+2); - - base_idx = next_base_idx; - } - } - else - { - // Miter - const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; - IM_APPEND_VTX(p1.x - miter_offset_x, p1.y - miter_offset_y, uv_out, col); - IM_APPEND_VTX(p1.x, p1.y, uv_in, col); - IM_APPEND_VTX(p1.x + miter_offset_x, p1.y + miter_offset_y, uv_out, col); - - // Connect prev to next - IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+4); - IM_APPEND_TRI(base_idx+0, base_idx+4, base_idx+1); - IM_APPEND_TRI(base_idx+1, base_idx+4, base_idx+5); - IM_APPEND_TRI(base_idx+1, base_idx+5, base_idx+2); - - base_idx = next_base_idx; - } - } - - point_idx++; - } - - // End cap - if (!closed) - { - p1 = points[points_count-1]; - n1 = normals[points_count-1]; - - // End cap - const ImVec2 dir(n1.y, -n1.x); - if (flags & ImDrawFlags_SquareCap) - p1 += dir * (half_thickness - half_aa); // At this point half_thickness has half fringe width baked in. - const ImVec2 pa = p1 - dir * half_aa; - const ImVec2 pb = p1 + dir * half_aa; - - int next_base_idx = (int)_VtxCurrentIdx; - IM_APPEND_VTX(pa.x - n1.x * half_thickness, pa.y - n1.y * half_thickness, uv_out, col); - IM_APPEND_VTX(pa.x, pa.y, uv_in, col); - IM_APPEND_VTX(pa.x + n1.x * half_thickness, pa.y + n1.y * half_thickness, uv_out, col); - - IM_APPEND_VTX(pb.x - n1.x * half_thickness, pb.y - n1.y * half_thickness, uv_out, col_trans); - IM_APPEND_VTX(pb.x, pb.y, uv_in, col_trans); - IM_APPEND_VTX(pb.x + n1.x * half_thickness, pb.y + n1.y * half_thickness, uv_out, col_trans); - - // Connect - IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+4); - IM_APPEND_TRI(base_idx+0, base_idx+4, base_idx+1); - IM_APPEND_TRI(base_idx+1, base_idx+4, base_idx+5); - IM_APPEND_TRI(base_idx+1, base_idx+5, base_idx+2); - base_idx = next_base_idx; - - // AA cap - IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+4); - IM_APPEND_TRI(base_idx+0, base_idx+4, base_idx+1); - IM_APPEND_TRI(base_idx+1, base_idx+4, base_idx+5); - IM_APPEND_TRI(base_idx+1, base_idx+5, base_idx+2); - } - else - { - // Connect the path. - start_vtx_ptr[0] = VtxBuffer.Data[base_idx+0]; - start_vtx_ptr[1] = VtxBuffer.Data[base_idx+1]; - start_vtx_ptr[2] = VtxBuffer.Data[base_idx+2]; - } - - // Restore unused memory - const int vtx_used = (int)(_VtxWritePtr - start_vtx_ptr); - const int idx_used = (int)(_IdxWritePtr - start_idx_ptr); - IM_ASSERT(vtx_used <= vtx_count && idx_used <= idx_count); - PrimUnreserve(idx_count - idx_used, vtx_count - vtx_used); -} - void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags) { if (points_count < 2 || (col & IM_COL32_A_MASK) == 0) @@ -1522,27 +1131,46 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 thickness = _FringeScale; } - // TODO: square vs miter cap // TODO: support splitting very long lines to multiple draw calls. - // We can use cheaper rendering if the thickness is integer size. - const int truncated_thickness = (int)screen_thickness; - const bool is_int_thickness = ImAbs(screen_thickness - (float)truncated_thickness) < 0.01f && (truncated_thickness >= 1 && truncated_thickness < IM_DRAWLIST_TEX_LINES_WIDTH_MAX); - + // Allocate data for temp buffers _Data->TempBuffer.reserve_discard(points_count * 2); ImVec2* normals = _Data->TempBuffer.Data; float* sqr_lengths = (float*)(normals + points_count); - CalcSegmentNormals(points, points_count, normals, sqr_lengths, (flags & ImDrawFlags_Closed) != 0); - if (is_int_thickness) + // Calculate normals for each line segment + for (int i = 0; i < points_count - 1; i++) { - const ImVec4 tex_uvs = _Data->TexUvLines[truncated_thickness]; - _AddPolylineIntThickness(points, normals, sqr_lengths, points_count, col, (float)truncated_thickness * _FringeScale, flags, tex_uvs); + float dx = points[i + 1].x - points[i].x; + float dy = points[i + 1].y - points[i].y; + const float d2 = dx * dx + dy * dy; + const float inv_len = (d2 > 0.0f) ? 1.0f / sqrtf(d2) : 0.0f; //ImRsqrt(d2) : 0.0f; TODO: ImRsqrt is not accurate enough. + normals[i].x = -dy * inv_len; + normals[i].y = dx * inv_len; + sqr_lengths[i] = d2; + } + if ((flags & ImDrawFlags_Closed) != 0) + { + const float dx = points[0].x - points[points_count - 1].x; + const float dy = points[0].y - points[points_count - 1].y; + const float d2 = dx * dx + dy * dy; + const float inv_len = (d2 > 0.0f) ? 1.0f / sqrtf(d2) : 0.0f; //ImRsqrt(d2) : 0.0f; TODO: ImRsqrt is not accurate enough. + normals[points_count - 1].x = -dy * inv_len; + normals[points_count - 1].y = dx * inv_len; + sqr_lengths[points_count - 1] = d2; } else { - _AddPolylineFractThickness(points, normals, sqr_lengths, points_count, col, thickness, flags); + normals[points_count - 1] = normals[points_count - 2]; + sqr_lengths[points_count - 1] = 0.0f; } + + // Bias the texture selection towards higher resolution to avoid visual pops when the texture change. + const int texture_idx = ImClamp((int)(screen_thickness + 0.95f), 1, IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1); + const float fringe = _FringeScale * (screen_thickness / (float)texture_idx); // Scale the fringe to cover the discrepancy between the texture and requested size. + const ImVec4 tex_uvs = _Data->TexUvLines[texture_idx]; + + _AddPolyline(points, normals, sqr_lengths, points_count, col, thickness, flags, tex_uvs, fringe); } void ImDrawList::AddPolylineLegacy(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags) @@ -1884,14 +1512,16 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun const float miter_distance_sqr = dm_x * dm_x + dm_y * dm_y; bevel = miter_distance_sqr > miter_distance_limit_sqr; - const float segment_limit_sqr = ImMax(temp_sqr_lengths[i0], temp_sqr_lengths[i1]); - const bool overshoot = miter_distance_sqr > segment_limit_sqr; - if (overshoot) + if (bevel) { - // Make sure the miter stays within the polygon bounds. - const float scale = ImSqrt(segment_limit_sqr / miter_distance_sqr); - dm_x *= scale; - dm_y *= scale; + // Limit inner bevel so that it is does not shoot out outside the polygon. + const float segment_limit_sqr = ImMax(temp_sqr_lengths[i0], temp_sqr_lengths[i1]); + if (miter_distance_sqr > segment_limit_sqr) + { + const float scale = ImSqrt(segment_limit_sqr / miter_distance_sqr); + dm_x *= scale; + dm_y *= scale; + } } } @@ -2393,32 +2023,14 @@ void ImDrawList::AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float th if ((col & IM_COL32_A_MASK) == 0) return; - ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeCenter); // WIP: only Legacy is used. - if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) + if (g_LEGACY_STROKES) { const ImVec2 points[2] = { ImVec2(p1.x + 0.5f, p1.y + 0.5f), ImVec2(p2.x + 0.5f, p2.y + 0.5f) }; AddPolyline(points, 2, col, thickness, ImDrawFlags_StrokeLegacy); return; } - ImVec2 off(0, 0); - if (stroke_pos != ImDrawFlags_StrokeCenter) - { - // FIXME: this should be removed when the generic PathStroke() stroke pos is merged. - float nx = -(p1.y - p2.y); - float ny = p1.x - p2.x; - IM_NORMALIZE2F_OVER_ZERO(nx, ny); - float offset = 0.0f; - if (stroke_pos == ImDrawFlags_StrokeInside) - offset = -thickness * 0.5f; - else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) - offset = -(thickness * 0.5f - CalculateCenterPixelAlignedOffset(thickness, _FringeScale)); - else if (stroke_pos == ImDrawFlags_StrokeOutside) - offset = thickness * 0.5f; - off.x = nx * offset; - off.y = ny * offset; - } - const ImVec2 points[2] = { p1 + off, p2 + off }; + const ImVec2 points[2] = { p1, p2 }; AddPolyline(points, 2, col, thickness, flags); } @@ -2612,9 +2224,6 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, thickness += _FringeScale; - // Adjust the rounding to be the outside radius. - rounding += thickness * 0.5f; - const int arc_step = ImClamp(IM_DRAWLIST_ARCFAST_SAMPLE_MAX / this->_CalcCircleAutoSegmentCount(rounding), 1, IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4); const int arc_point_count = (IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4) / arc_step + 1; @@ -2625,12 +2234,11 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, ImDrawVert* start_vtx_ptr = _VtxWritePtr; ImDrawIdx* start_idx_ptr = _IdxWritePtr; - const float ht = thickness * 0.5f; const ImVec2 corner_pos[4] = { - ImVec2(p_min.x - ht, p_min.y - ht), - ImVec2(p_max.x + ht, p_min.y - ht), - ImVec2(p_max.x + ht, p_max.y + ht), - ImVec2(p_min.x - ht, p_max.y + ht) + ImVec2(p_min.x, p_min.y), + ImVec2(p_max.x, p_min.y), + ImVec2(p_max.x, p_max.y), + ImVec2(p_min.x, p_max.y) }; static const ImVec2 corner_offset[4] = { {1,1}, {-1,1}, {-1,-1}, {1,-1} }; static const ImDrawFlags corner_flags[4] = { ImDrawFlags_RoundCornersTopLeft, ImDrawFlags_RoundCornersTopRight, ImDrawFlags_RoundCornersBottomRight, ImDrawFlags_RoundCornersBottomLeft }; @@ -2742,38 +2350,41 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl // FIXME-POLYLINE const bool has_rounding = (rounding >= _FringeScale); - ImVec2 adjusted_min = p_min; - ImVec2 adjusted_max = p_max; - float adjusted_rounding = rounding; + ImVec2 outer_min = p_min; + ImVec2 outer_max = p_max; + float outer_rounding = rounding; - const ImVec2 offset(thickness * 0.5f, thickness * 0.5f); - if (stroke_pos == ImDrawFlags_StrokeInside) + if (stroke_pos == ImDrawFlags_StrokeLegacy) { - adjusted_min += offset; - adjusted_max -= offset; - adjusted_rounding -= thickness * 0.5f; + outer_min += ImVec2(0.50f, 0.50f); + if (Flags & ImDrawListFlags_AntiAliasedLines) + outer_max -= ImVec2(0.50f, 0.50f); + else + outer_max -= ImVec2(0.49f, 0.49f); // Better looking lower-right corner and rounded non-AA shapes. + stroke_pos = ImDrawFlags_StrokeCenter; + } + + if (stroke_pos == ImDrawFlags_StrokeCenter) + { + const ImVec2 offset(thickness * 0.5f, thickness * 0.5f); + outer_min -= offset; + outer_max += offset; + outer_rounding += has_rounding ? thickness * 0.5f : 0.0f; } else if (stroke_pos == ImDrawFlags_StrokeOutside) { - adjusted_min -= offset; - adjusted_max += offset; - adjusted_rounding += thickness * 0.5f; - } - else if (stroke_pos == ImDrawFlags_StrokeLegacy) - { - adjusted_min += ImVec2(0.50f, 0.50f); - if (Flags & ImDrawListFlags_AntiAliasedLines) - adjusted_max -= ImVec2(0.50f, 0.50f); - else - adjusted_max -= ImVec2(0.49f, 0.49f); // Better looking lower-right corner and rounded non-AA shapes. + const ImVec2 offset(thickness, thickness); + outer_min -= offset; + outer_max += offset; + outer_rounding += has_rounding ? thickness : 0.0f; } - const float width = adjusted_max.x - adjusted_min.x; - const float height = adjusted_max.y - adjusted_min.y; - if (ImMin(width, height) < (thickness + _FringeScale)) + const float width = outer_max.x - outer_min.x; + const float height = outer_max.y - outer_min.y; + if (ImMin(width, height) < (thickness + _FringeScale) * 2.0f) { // Rectangle has collapsed into filled rectangle. - AddRectFilled(p_min, p_max, col, rounding, flags); + AddRectFilled(outer_min, outer_max, col, outer_rounding, flags); return; } @@ -2782,36 +2393,35 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl if (!has_rounding || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) { - PathLineTo(adjusted_min); - PathLineTo(ImVec2(adjusted_max.x, adjusted_min.y)); - PathLineTo(adjusted_max); - PathLineTo(ImVec2(adjusted_min.x, adjusted_max.y)); + PathLineTo(outer_min); + PathLineTo(ImVec2(outer_max.x, outer_min.y)); + PathLineTo(outer_max); + PathLineTo(ImVec2(outer_min.x, outer_max.y)); } else { // Constrain rounding to rect dimensions - adjusted_rounding = ImMin(adjusted_rounding, ImFabs(width) * (((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f) - 1.0f); - adjusted_rounding = ImMin(adjusted_rounding, ImFabs(height) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f); + outer_rounding = ImMin(outer_rounding, ImFabs(width) * (((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f) - 1.0f); + outer_rounding = ImMin(outer_rounding, ImFabs(height) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f); - if (thickness * 0.5f > adjusted_rounding) + if (thickness > outer_rounding) { // Special case rendering to avoid rendering artifacts at the corners. // If rendered using the regular polyline, the stroke will fold and leave artifact on the corner if rendered with transparency. - _AddRectTinyRounding(adjusted_min, adjusted_max, col, adjusted_rounding, thickness, flags); + _AddRectTinyRounding(outer_min, outer_max, col, outer_rounding, thickness, flags); return; } - - const float rounding_tl = (flags & ImDrawFlags_RoundCornersTopLeft) ? adjusted_rounding : 0.0f; - const float rounding_tr = (flags & ImDrawFlags_RoundCornersTopRight) ? adjusted_rounding : 0.0f; - const float rounding_br = (flags & ImDrawFlags_RoundCornersBottomRight) ? adjusted_rounding : 0.0f; - const float rounding_bl = (flags & ImDrawFlags_RoundCornersBottomLeft) ? adjusted_rounding : 0.0f; - PathArcToFast(ImVec2(adjusted_min.x + rounding_tl, adjusted_min.y + rounding_tl), rounding_tl, 6, 9); - PathArcToFast(ImVec2(adjusted_max.x - rounding_tr, adjusted_min.y + rounding_tr), rounding_tr, 9, 12); - PathArcToFast(ImVec2(adjusted_max.x - rounding_br, adjusted_max.y - rounding_br), rounding_br, 0, 3); - PathArcToFast(ImVec2(adjusted_min.x + rounding_bl, adjusted_max.y - rounding_bl), rounding_bl, 3, 6); + const float rounding_tl = (flags & ImDrawFlags_RoundCornersTopLeft) ? outer_rounding : 0.0f; + const float rounding_tr = (flags & ImDrawFlags_RoundCornersTopRight) ? outer_rounding : 0.0f; + const float rounding_br = (flags & ImDrawFlags_RoundCornersBottomRight) ? outer_rounding : 0.0f; + const float rounding_bl = (flags & ImDrawFlags_RoundCornersBottomLeft) ? outer_rounding : 0.0f; + PathArcToFast(ImVec2(outer_min.x + rounding_tl, outer_min.y + rounding_tl), rounding_tl, 6, 9); + PathArcToFast(ImVec2(outer_max.x - rounding_tr, outer_min.y + rounding_tr), rounding_tr, 9, 12); + PathArcToFast(ImVec2(outer_max.x - rounding_br, outer_max.y - rounding_br), rounding_br, 0, 3); + PathArcToFast(ImVec2(outer_min.x + rounding_bl, outer_max.y - rounding_bl), rounding_bl, 3, 6); } - PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly); + PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | ImDrawFlags_StrokeInside); #else const bool is_truncated = _FringeScaleIsInteger && ImIsTruncated4(p_min.x, p_min.y, p_max.x, p_max.y) && ImIsTruncated4(rounding, thickness, 0.0f, 0.0f); if ((stroke_pos == ImDrawFlags_StrokeInside || stroke_pos == ImDrawFlags_StrokeOutside || stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) && is_truncated) @@ -3026,7 +2636,7 @@ void ImDrawList::AddRectFilledMultiColor(const ImVec2& p_min, const ImVec2& p_ma PrimWriteVtx(ImVec2(p_min.x, p_max.y), uv, col_bot_left); } -void ImDrawList::AddQuad(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness) +void ImDrawList::AddQuad(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) return; @@ -3035,7 +2645,7 @@ void ImDrawList::AddQuad(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, c PathLineTo(p2); PathLineTo(p3); PathLineTo(p4); - PathStroke(col, thickness, ImDrawFlags_Closed); + PathStroke(col, thickness, flags | ImDrawFlags_Closed); } void ImDrawList::AddQuadFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col) @@ -3050,7 +2660,7 @@ void ImDrawList::AddQuadFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& PathFillConvex(col); } -void ImDrawList::AddTriangle(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness) +void ImDrawList::AddTriangle(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) return; @@ -3058,7 +2668,7 @@ void ImDrawList::AddTriangle(const ImVec2& p1, const ImVec2& p2, const ImVec2& p PathLineTo(p1); PathLineTo(p2); PathLineTo(p3); - PathStroke(col, thickness, ImDrawFlags_Closed); + PathStroke(col, thickness, flags | ImDrawFlags_Closed); } void ImDrawList::AddTriangleFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col) @@ -3078,42 +2688,34 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu return; ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); - - if (g_LEGACY_STROKES) + if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) { radius -= 0.5f; - } - else - { - if (stroke_pos == ImDrawFlags_StrokeInside) - radius -= thickness * 0.5f; - else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) - radius -= thickness * 0.5f - CalculateCenterPixelAlignedOffset(thickness, _FringeScale); - else if (stroke_pos == ImDrawFlags_StrokeOutside) - radius += thickness * 0.5f; - else if (stroke_pos == ImDrawFlags_StrokeLegacy) - radius -= 0.5f; + stroke_pos = ImDrawFlags_StrokeCenter; } - float adjusted_radius = radius; - if (stroke_pos == ImDrawFlags_StrokeInside) - adjusted_radius -= thickness * 0.5f; + // FIXME-POLYLINE: missing ImDrawFlags_StrokeCenterPixelAligned. + float outer_radius = radius; + if (stroke_pos == ImDrawFlags_StrokeCenter) + outer_radius = radius + thickness * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeInside) + outer_radius = radius; else if (stroke_pos == ImDrawFlags_StrokeOutside) - adjusted_radius += thickness * 0.5f; - else if (stroke_pos == ImDrawFlags_StrokeLegacy) - adjusted_radius -= 0.5f; - if (adjusted_radius < (thickness * 0.5f + _FringeScale * 0.5f)) + outer_radius = radius + thickness; + if (outer_radius < (thickness + _FringeScale)) { // The circle has collapsed into a filled circle. - AddCircleFilled(center, radius, col, num_segments); + AddCircleFilled(center, outer_radius, col, num_segments); return; } if (num_segments <= 0) { // Use arc with automatic segment count - const int a_step = IM_DRAWLIST_ARCFAST_SAMPLE_MAX / _CalcCircleAutoSegmentCount(radius); // Radius used here inteitionally so that the circle matches filled circle of same radius. - _PathArcToFastEx(center, adjusted_radius, 0, IM_DRAWLIST_ARCFAST_SAMPLE_MAX, a_step); + // FIXME-POLYLINE: Review + //const int a_step = IM_DRAWLIST_ARCFAST_SAMPLE_MAX / _CalcCircleAutoSegmentCount(radius); // Radius used here intentionally so that the circle matches filled circle of same radius. + //_PathArcToFastEx(center, radius, 0, IM_DRAWLIST_ARCFAST_SAMPLE_MAX, a_step); + _PathArcToFastEx(center, radius, 0, IM_DRAWLIST_ARCFAST_SAMPLE_MAX, 0); _Path.Size--; } else @@ -3123,10 +2725,10 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; - PathArcTo(center, adjusted_radius, 0.0f, a_max, num_segments - 1); + PathArcTo(center, radius, 0.0f, a_max, num_segments - 1); } - PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly); + PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | stroke_pos); } void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col, int num_segments) @@ -3169,31 +2771,35 @@ void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_ if ((col & IM_COL32_A_MASK) == 0 || num_segments <= 2) return; - if (g_LEGACY_STROKES) + ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); + if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) + { radius -= 0.5f; + stroke_pos = ImDrawFlags_StrokeCenter; + } - const ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); const float unit_apothem = ImCos(IM_PI / (float)num_segments); const float miter_thickness = thickness / unit_apothem; - float adjusted_radius = radius; - if (stroke_pos == ImDrawFlags_StrokeInside) - adjusted_radius -= miter_thickness * 0.5f; + float outer_radius = radius; + if (stroke_pos == ImDrawFlags_StrokeCenter) + outer_radius = radius + miter_thickness * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeInside) + outer_radius = radius; else if (stroke_pos == ImDrawFlags_StrokeOutside) - adjusted_radius += miter_thickness * 0.5f; - else if (stroke_pos == ImDrawFlags_StrokeLegacy) - adjusted_radius -= 0.5f; - if (adjusted_radius < (miter_thickness * 0.5f + _FringeScale * 0.5f)) + outer_radius = radius + miter_thickness; + + if (outer_radius < (miter_thickness + _FringeScale)) { // The polygon has collapsed into a filled polygon. - AddNgonFilled(center, radius, col, num_segments); + AddNgonFilled(center, outer_radius, col, num_segments); return; } // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; - PathArcTo(center, adjusted_radius, 0.0f, a_max, num_segments - 1); - PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly); + PathArcTo(center, radius, 0.0f, a_max, num_segments - 1); + PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | stroke_pos); } // Guaranteed to honor 'num_segments' @@ -3224,8 +2830,9 @@ void ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 co if ((col & IM_COL32_A_MASK) == 0) return; - ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); - + // Note: since offset ellipse is not ellipse anymore, we cannot fall back to filled ellipse when outline covers the whole shape like in other shapes. + // FIXME-POLYLINE + /* ImVec2 r; if (g_LEGACY_STROKES) { @@ -3234,6 +2841,7 @@ void ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 co else { r = radius; + ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); if (stroke_pos == ImDrawFlags_StrokeInside) r -= ImVec2(thickness * 0.5f, thickness * 0.5f); else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) @@ -3244,7 +2852,8 @@ void ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 co else if (stroke_pos == ImDrawFlags_StrokeOutside) r += ImVec2(thickness * 0.5f, thickness * 0.5f); } - + */ + /* float stroke_offset = 0.0f; if (stroke_pos == ImDrawFlags_StrokeInside) stroke_offset = -thickness * 0.5f; @@ -3255,48 +2864,17 @@ void ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 co AddEllipseFilled(center, radius, col, rot, num_segments); return; } + */ if (num_segments <= 0) num_segments = _CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)); // A bit pessimistic, maybe there's a better computation to do here. _Path.reserve(_Path.Size + (num_segments + 1)); - // Custom tessellation, since offset ellipse is not an ellipse anymore. - const float cos_rot = ImCos(rot); - const float sin_rot = ImSin(rot); - if (stroke_offset != 0.0f) - { - const float inv_rx = 1.0f / radius.x; - const float inv_ry = 1.0f / radius.y; - for (int i = 0; i < num_segments; i++) - { - const float a = ((float)i / (float)num_segments) * IM_PI * 2.0f; - const float dir_x = ImCos(a); - const float dir_y = ImSin(a); - float nx = dir_x * inv_rx; - float ny = dir_y * inv_ry; - IM_NORMALIZE2F_OVER_ZERO(nx, ny); - ImVec2 point(dir_x * radius.x + nx * stroke_offset, dir_y * radius.y + ny * stroke_offset); - const ImVec2 rel((point.x * cos_rot) - (point.y * sin_rot), (point.x * sin_rot) + (point.y * cos_rot)); - point.x = rel.x + center.x; - point.y = rel.y + center.y; - _Path.push_back(point); - } - } - else - { - for (int i = 0; i < num_segments; i++) - { - const float a = ((float)i / (float)num_segments) * IM_PI * 2.0f; - ImVec2 point(ImCos(a) * radius.x, ImSin(a) * radius.y); - const ImVec2 rel((point.x * cos_rot) - (point.y * sin_rot), (point.x * sin_rot) + (point.y * cos_rot)); - point.x = rel.x + center.x; - point.y = rel.y + center.y; - _Path.push_back(point); - } - } - - PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly); + // Because we are filling a closed shape we remove 1 from the count of segments/points + const float a_max = IM_PI * 2.0f * ((float)num_segments - 1.0f) / (float)num_segments; + PathEllipticalArcTo(center, radius, rot, 0.0f, a_max, num_segments - 1); + PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | flags); } void ImDrawList::AddEllipseFilled(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot, int num_segments) From 5782f4e0eec8e5eb2b7995ba109f17c43cf00aa4 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Tue, 12 May 2026 11:17:49 +0300 Subject: [PATCH 027/120] DrawList: path rendering optimizations. - added ImRsqrtPrecise() which adds Newton-Raphson iteration for more precision - change calculation of miter_scale_factor so that it does not have a branch - renamed IM_POLYLINE_APPEND_* macros to IM_APPEND_* and also use them in fill convex poly - changed the convex poly miter calculation to match the polyline to make debugging and changes easier - split the convex poly to separate loop when ImDrawFlags_MiterOnly is specified for less branches in common case # Conflicts: # imgui_draw.cpp --- imgui_draw.cpp | 191 ++++++++++++++++++++++++++--------------------- imgui_internal.h | 4 + 2 files changed, 111 insertions(+), 84 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 3d64cb7b7..a37e35b8e 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -924,8 +924,8 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ IM_APPEND_VTX(pb.x + n1.x * thickness1, pb.y + n1.y * thickness1 , uv1, col); // AA cap - IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); - IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); + IM_APPEND_TRI(base_idx+0, base_idx+2, base_idx+3); + IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+1); base_idx = next_base_idx; } else @@ -953,7 +953,8 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ const float cos_theta = n0.x * n1.x + n0.y * n1.y; // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm - const float miter_scale_factor = ImMin(1000.0f, (cos_theta > IM_POLYLINE_MITER_ANGLE_LIMIT) ? 1.0f / (1.0f + cos_theta) : FLT_MAX); // avoid division by zero + const float cos_theta_clamped = ImMax(IM_POLYLINE_MITER_ANGLE_LIMIT, cos_theta); // Avoid div by 0. + const float miter_scale_factor = ImMin(1000.0f, 1.0f / (1.0f + cos_theta_clamped)); const float miter_offset_x = (n0.x + n1.x) * miter_scale_factor; const float miter_offset_y = (n0.y + n1.y) * miter_scale_factor; @@ -986,10 +987,11 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ const float sin_theta = n0.y * n1.x - n0.x * n1.y; // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm - const float miter_scale_factor = (cos_theta > IM_POLYLINE_MITER_ANGLE_LIMIT) ? 1.0f / (1.0f + cos_theta) : FLT_MAX; // avoid division by zero - + const float cos_theta_clamped = ImMax(IM_POLYLINE_MITER_ANGLE_LIMIT, cos_theta); // Avoid div by 0. + const float miter_scale_factor = ImMin(1000.f, 1.f / (1.0f + cos_theta_clamped)); float miter_offset_x = (n0.x + n1.x) * miter_scale_factor; float miter_offset_y = (n0.y + n1.y) * miter_scale_factor; + const float miter_distance_sqr = miter_offset_x * miter_offset_x + miter_offset_y * miter_offset_y; if (miter_distance_sqr > miter_distance_limit_sqr) @@ -1144,7 +1146,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 float dx = points[i + 1].x - points[i].x; float dy = points[i + 1].y - points[i].y; const float d2 = dx * dx + dy * dy; - const float inv_len = (d2 > 0.0f) ? 1.0f / sqrtf(d2) : 0.0f; //ImRsqrt(d2) : 0.0f; TODO: ImRsqrt is not accurate enough. + const float inv_len = (d2 > 0.0f) ? ImRsqrtPrecise(d2) : 0.0f; normals[i].x = -dy * inv_len; normals[i].y = dx * inv_len; sqr_lengths[i] = d2; @@ -1154,7 +1156,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 const float dx = points[0].x - points[points_count - 1].x; const float dy = points[0].y - points[points_count - 1].y; const float d2 = dx * dx + dy * dy; - const float inv_len = (d2 > 0.0f) ? 1.0f / sqrtf(d2) : 0.0f; //ImRsqrt(d2) : 0.0f; TODO: ImRsqrt is not accurate enough. + const float inv_len = (d2 > 0.0f) ? ImRsqrtPrecise(d2) : 0.0f; normals[points_count - 1].x = -dy * inv_len; normals[points_count - 1].y = dx * inv_len; sqr_lengths[points_count - 1] = d2; @@ -1452,6 +1454,7 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun const float half_aa = _FringeScale * 0.5f; ImU32 col_trans = col & ~IM_COL32_A_MASK; const bool miters_only = (flags & ImDrawFlags_MiterOnly) != 0; + const float miter_distance_limit_sqr = IM_POLYLINE_MITER_LIMIT * IM_POLYLINE_MITER_LIMIT; const int idx_count = ((points_count - 2) + points_count * 3) * 3; const int vtx_count = (points_count * 3); @@ -1472,7 +1475,7 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun float d2 = dx*dx + dy*dy; if (d2 > 0.0f) { - float inv_len = ImRsqrt(d2); + const float inv_len = ImRsqrtPrecise(d2); dx *= inv_len; dy *= inv_len; } @@ -1489,100 +1492,121 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun unsigned int prev_outer_idx = 0; // We dont know outer vert could yet, will need to patch once we're done. - const float miter_distance_limit = half_aa * IM_POLYLINE_MITER_LIMIT; - const float miter_distance_limit_sqr = miter_distance_limit * miter_distance_limit; - for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) + if (miters_only) { - // Average normals - const ImVec2& n0 = temp_normals[i0]; - const ImVec2& n1 = temp_normals[i1]; - - // theta is the angle between two segments - const float cos_theta = n0.x * n1.x + n0.y * n1.y; - // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm - float miter_scale_factor = ImMin(1000.0f, (cos_theta > IM_POLYLINE_MITER_ANGLE_LIMIT) ? (half_aa / (1.0f + cos_theta)) : FLT_MAX); // avoid division by zero - - float dm_x = (n0.x + n1.x) * miter_scale_factor; - float dm_y = (n0.y + n1.y) * miter_scale_factor; - bool bevel = false; - - if (!miters_only) + for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) { - const float miter_distance_sqr = dm_x * dm_x + dm_y * dm_y; - bevel = miter_distance_sqr > miter_distance_limit_sqr; + // Average normals + const ImVec2 p1 = points[i1]; + const ImVec2 n0 = temp_normals[i0]; + const ImVec2 n1 = temp_normals[i1]; + // theta is the angle between two segments + const float cos_theta = n0.x * n1.x + n0.y * n1.y; + // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm + const float cos_theta_clamped = ImMax(IM_POLYLINE_MITER_ANGLE_LIMIT, cos_theta); // Avoid div by 0. + const float miter_scale_factor = ImMin(1000.f, 1.f / (1.0f + cos_theta_clamped)); + const float miter_offset_x = (n0.x + n1.x) * miter_scale_factor * half_aa; + const float miter_offset_y = (n0.y + n1.y) * miter_scale_factor * half_aa; + + // Inner + inner_vtx_ptr->pos.x = p1.x - miter_offset_x; + inner_vtx_ptr->pos.y = p1.y - miter_offset_y; + inner_vtx_ptr->uv = uv; inner_vtx_ptr->col = col; + inner_vtx_ptr++; + + const unsigned int prev_inner_idx = vtx_inner_idx + i0; + const unsigned int inner_idx = vtx_inner_idx + i1; + const unsigned int outer_idx = _VtxCurrentIdx; + + // Outer + IM_APPEND_VTX(p1.x + miter_offset_x, p1.y + miter_offset_y, uv, col_trans); + // Connect with previous + IM_APPEND_TRI(prev_outer_idx, outer_idx, inner_idx); + IM_APPEND_TRI(prev_outer_idx, inner_idx, prev_inner_idx); + + prev_outer_idx = outer_idx; + } + } + else + { + for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) + { + // Average normals + const ImVec2 p1 = points[i1]; + const ImVec2 n0 = temp_normals[i0]; + const ImVec2 n1 = temp_normals[i1]; + + // theta is the angle between two segments + const float cos_theta = n0.x * n1.x + n0.y * n1.y; + // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm + const float cos_theta_clamped = ImMax(IM_POLYLINE_MITER_ANGLE_LIMIT, cos_theta); // Avoid div by 0. + const float miter_scale_factor = ImMin(1000.f, 1.f / (1.0f + cos_theta_clamped)); + float miter_offset_x = (n0.x + n1.x) * miter_scale_factor; + float miter_offset_y = (n0.y + n1.y) * miter_scale_factor; + + const float miter_distance_sqr = miter_offset_x * miter_offset_x + miter_offset_y * miter_offset_y; + bool bevel = miter_distance_sqr > miter_distance_limit_sqr; if (bevel) { // Limit inner bevel so that it is does not shoot out outside the polygon. - const float segment_limit_sqr = ImMax(temp_sqr_lengths[i0], temp_sqr_lengths[i1]); - if (miter_distance_sqr > segment_limit_sqr) + const float ref_thickness_sqr = half_aa * half_aa; + const float limit_sqr = ImMax(temp_sqr_lengths[i0], temp_sqr_lengths[i1]); + const float ref_miter_dist_sqr = miter_distance_sqr * ref_thickness_sqr; + if (ref_miter_dist_sqr > limit_sqr) { - const float scale = ImSqrt(segment_limit_sqr / miter_distance_sqr); - dm_x *= scale; - dm_y *= scale; + const float scale = ImSqrt(limit_sqr / ref_miter_dist_sqr); + miter_offset_x *= scale; + miter_offset_y *= scale; } } + + miter_offset_x *= half_aa; + miter_offset_y *= half_aa; + + // Inner + inner_vtx_ptr->pos.x = p1.x - miter_offset_x; + inner_vtx_ptr->pos.y = p1.y - miter_offset_y; + inner_vtx_ptr->uv = uv; inner_vtx_ptr->col = col; + inner_vtx_ptr++; + + const unsigned int prev_inner_idx = vtx_inner_idx + i0; + const unsigned int inner_idx = vtx_inner_idx + i1; + unsigned int outer_idx = _VtxCurrentIdx; + + // Outer + if (bevel) + { + IM_APPEND_VTX(p1.x + n0.x * half_aa, p1.y + n0.y * half_aa, uv, col_trans); + IM_APPEND_VTX(p1.x + n1.x * half_aa, p1.y + n1.y * half_aa, uv, col_trans); + // Connect with previous + IM_APPEND_TRI(prev_outer_idx, outer_idx, inner_idx); + IM_APPEND_TRI(prev_outer_idx, inner_idx, prev_inner_idx); + // Fill bevel + IM_APPEND_TRI(outer_idx, outer_idx + 1, inner_idx); + outer_idx++; + } + else + { + IM_APPEND_VTX(p1.x + miter_offset_x, p1.y + miter_offset_y, uv, col_trans); + // Connect with previous + IM_APPEND_TRI(prev_outer_idx, outer_idx, inner_idx); + IM_APPEND_TRI(prev_outer_idx, inner_idx, prev_inner_idx); + } + + prev_outer_idx = outer_idx; } - - // Inner - inner_vtx_ptr->pos.x = (points[i1].x - dm_x); inner_vtx_ptr->pos.y = (points[i1].y - dm_y); inner_vtx_ptr->uv = uv; inner_vtx_ptr->col = col; - inner_vtx_ptr++; - - unsigned int prev_inner_idx = vtx_inner_idx + i0; - unsigned int inner_idx = vtx_inner_idx + i1; - unsigned int outer_idx = _VtxCurrentIdx; - - // Outer - if (bevel) - { - _VtxWritePtr->pos.x = (points[i1].x + n0.x * half_aa); _VtxWritePtr->pos.y = (points[i1].y + n0.y * half_aa); _VtxWritePtr->uv = uv; _VtxWritePtr->col = col_trans; - _VtxWritePtr++; - _VtxCurrentIdx++; - _VtxWritePtr->pos.x = (points[i1].x + n1.x * half_aa); _VtxWritePtr->pos.y = (points[i1].y + n1.y * half_aa); _VtxWritePtr->uv = uv; _VtxWritePtr->col = col_trans; - _VtxWritePtr++; - _VtxCurrentIdx++; - } - else - { - _VtxWritePtr->pos.x = (points[i1].x + dm_x); _VtxWritePtr->pos.y = (points[i1].y + dm_y); _VtxWritePtr->uv = uv; _VtxWritePtr->col = col_trans; - _VtxWritePtr++; - _VtxCurrentIdx++; - } - - // Connect with previous - _IdxWritePtr[0] = (ImDrawIdx)prev_outer_idx; - _IdxWritePtr[1] = (ImDrawIdx)outer_idx; - _IdxWritePtr[2] = (ImDrawIdx)inner_idx; - - _IdxWritePtr[3] = (ImDrawIdx)prev_outer_idx; - _IdxWritePtr[4] = (ImDrawIdx)inner_idx; - _IdxWritePtr[5] = (ImDrawIdx)prev_inner_idx; - - _IdxWritePtr += 6; - - // Fill bevel - if (bevel) - { - _IdxWritePtr[0] = (ImDrawIdx)outer_idx; - _IdxWritePtr[1] = (ImDrawIdx)outer_idx+1; - _IdxWritePtr[2] = (ImDrawIdx)inner_idx; - _IdxWritePtr += 3; - outer_idx++; - } - - prev_outer_idx = outer_idx; } - // Patch first segment + // Patch first segment to wrap around start_idx_ptr[0] = (ImDrawIdx)prev_outer_idx; start_idx_ptr[3] = (ImDrawIdx)prev_outer_idx; // Add indices for fill for (int i = 2; i < points_count; i++) { - _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx + i - 1); _IdxWritePtr[2] = (ImDrawIdx)(vtx_inner_idx + i); - _IdxWritePtr += 3; + IM_APPEND_TRI(vtx_inner_idx, vtx_inner_idx + i - 1, vtx_inner_idx + i); } const int idx_used = (int)(_IdxWritePtr - start_idx_ptr); @@ -2300,7 +2324,6 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, IM_APPEND_TRI(base_idx + 2, stem1_idx, inner_idx); IM_APPEND_TRI(base_idx + 2, inner_idx, base_idx + 3); - base_idx = stem0_idx - 1; } diff --git a/imgui_internal.h b/imgui_internal.h index 547f4ddc4..5d63fe902 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -506,11 +506,15 @@ inline float ImSign(float x) { return (x < 0.0f) ? -1.0f : (x > 0.0f inline double ImSign(double x) { return (x < 0.0) ? -1.0 : (x > 0.0) ? 1.0 : 0.0; } #ifdef IMGUI_ENABLE_SSE inline float ImRsqrt(float x) { return _mm_cvtss_f32(_mm_rsqrt_ss(_mm_set_ss(x))); } +// Converge to more precise solution using single step of Newton-Raphson method, repeating increase precision +inline float ImRsqrtPrecise(float x) { const float r = _mm_cvtss_f32(_mm_rsqrt_ss(_mm_set_ss(x))); return r * (1.5f - x * 0.5f * r * r); } #else inline float ImRsqrt(float x) { return 1.0f / sqrtf(x); } +inline float ImRsqrtPrecise(float x) { return 1.0f / sqrtf(x); } #endif inline double ImRsqrt(double x) { return 1.0 / sqrt(x); } #endif + // - ImMin/ImMax/ImClamp/ImLerp/ImSwap are used by widgets which support variety of types: signed/unsigned int/long long float/double // (Exceptionally using templates here but we could also redefine them for those types) template T ImMin(T lhs, T rhs) { return lhs < rhs ? lhs : rhs; } From 5d9b691248183a4ed3838474c5e8463e7c0f7cf8 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 18 May 2026 17:17:15 +0200 Subject: [PATCH 028/120] DrawList: misc fixes, amends (replace uses of GetColorU32, macros fixes, default flags). Demo: added rotating square, added MiterOnly checkbox. --- imgui.h | 2 +- imgui_demo.cpp | 29 +++++++++++++++++++---------- imgui_draw.cpp | 30 +++++++++++++++++++----------- 3 files changed, 39 insertions(+), 22 deletions(-) diff --git a/imgui.h b/imgui.h index 6b777437a..4bebc2bd4 100644 --- a/imgui.h +++ b/imgui.h @@ -3454,7 +3454,7 @@ struct ImDrawList inline void PathLineToMergeDuplicate(const ImVec2& pos) { if (_Path.Size == 0 || memcmp(&_Path.Data[_Path.Size - 1], &pos, 8) != 0) _Path.push_back(pos); } inline void PathFillConvex(ImU32 col, ImDrawFlags flags = 0) { AddConvexPolyFilled(_Path.Data, _Path.Size, col, flags); _Path.Size = 0; } inline void PathFillConcave(ImU32 col) { AddConcavePolyFilled(_Path.Data, _Path.Size, col); _Path.Size = 0; } - inline void PathStroke(ImU32 col, float thickness = 1.0f, ImDrawFlags flags = ImDrawFlags_StrokeCenter) { AddPolyline(_Path.Data, _Path.Size, col, thickness, flags); _Path.Size = 0; } + inline void PathStroke(ImU32 col, float thickness = 1.0f, ImDrawFlags flags = 0) { AddPolyline(_Path.Data, _Path.Size, col, thickness, flags); _Path.Size = 0; } IMGUI_API void PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments = 0); IMGUI_API void PathArcToFast(const ImVec2& center, float radius, int a_min_of_12, int a_max_of_12); // Use precomputed angles for a 12 steps circle IMGUI_API void PathEllipticalArcTo(const ImVec2& center, const ImVec2& radius, float rot, float a_min, float a_max, int num_segments = 0); // Ellipse diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 392609ce3..74ce27ef8 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10395,7 +10395,6 @@ static void ShowExampleAppCustomRendering(bool* p_open) static bool curve_segments_override = false; static int curve_segments_override_v = 8; static ImVec4 colf = ImVec4(1.0f, 1.0f, 0.4f, 1.0f); - static bool square_caps = false; ImGui::DragFloat("Size", &sz, 0.2f, 0.2f, 100.0f, "%.0f"); ImGui::DragFloat("Thickness", &thickness, 0.1f, 0.0f, 30.0f, "%.02f"); ImGui::SliderInt("N-gon sides", &ngon_sides, 3, 12); @@ -10406,7 +10405,6 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); curve_segments_override |= ImGui::SliderInt("Curves segments override", &curve_segments_override_v, 3, 40); ImGui::ColorEdit4("Color", &colf.x); - ImGui::Checkbox("Square caps", &square_caps); static ImDrawFlags flags = ImDrawFlags_None; ImGui::AlignTextToFramePadding(); @@ -10417,12 +10415,13 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::SameLine(); ImGui::RadioButton("CenterPixelAligned", &flags, ImDrawFlags_StrokeCenterPixelAligned); ImGui::SameLine(); ImGui::RadioButton("Outside", &flags, ImDrawFlags_StrokeOutside); ImGui::SameLine(); ImGui::RadioButton("Legacy", &flags, ImDrawFlags_StrokeLegacy); + ImGui::CheckboxFlags("MiterOnly", &flags, ImDrawFlags_MiterOnly); + ImGui::SameLine(); ImGui::CheckboxFlags("SquareCap", &flags, ImDrawFlags_SquareCap); const ImVec2 p = ImGui::GetCursorScreenPos(); const ImU32 col = ImColor(colf); const float spacing = 10.0f; const ImDrawFlags corners_tl_br = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersBottomRight; - const ImDrawFlags cap_flags = square_caps ? ImDrawFlags_SquareCap : ImDrawFlags_None; const float rounding = sz / 5.0f; const int circle_segments = circle_segments_override ? circle_segments_override_v : 0; const int curve_segments = curve_segments_override ? curve_segments_override_v : 0; @@ -10441,8 +10440,8 @@ static void ShowExampleAppCustomRendering(bool* p_open) draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 0.0f, th, flags); x += sz + spacing; // Square draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, th, flags); x += sz + spacing; // Square with all rounded corners draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, th, flags | corners_tl_br); x += sz + spacing; // Square with two rounded corners - draw_list->AddTriangle(ImVec2(x+sz*0.5f,y), ImVec2(x+sz, y+sz-0.5f), ImVec2(x, y+sz-0.5f), col, th/*, flags*/);x += sz + spacing; // Triangle - //draw_list->AddTriangle(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col, th);x+= sz*0.4f + spacing; // Thin triangle + draw_list->AddTriangle(ImVec2(x+sz*0.5f,y), ImVec2(x+sz, y+sz-0.5f), ImVec2(x, y+sz-0.5f), col, th, flags);x += sz + spacing; // Triangle + //draw_list->AddTriangle(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col, th, flags); x+= sz*0.4f + spacing; // Thin triangle PathConcaveShape(draw_list, x, y, sz); draw_list->PathStroke(col, th, flags | ImDrawFlags_Closed); x += sz + spacing; // Concave Shape //draw_list->AddPolyline(concave_shape, IM_COUNTOF(concave_shape), col, ImDrawFlags_Closed, th); draw_list->AddLineH(x, x + sz, y, col, th, flags); x += sz + spacing; // Horizontal line (note: drawing a filled rectangle will be faster!) @@ -10450,19 +10449,29 @@ static void ShowExampleAppCustomRendering(bool* p_open) draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y), col, th, flags); x += sz + spacing; draw_list->AddLine(ImVec2(x, y + sz), ImVec2(x, y), col, th, flags); x += spacing; - draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y + sz), col, th, flags | cap_flags); x += sz + spacing; // Diagonal line + draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y + sz), col, th, flags); x += sz + spacing; // Diagonal line + + // Rotating square made of lines + float side_step = 3.141592f * 0.5f; + for (int side = 0; side < 4; side++) + { + float t = (float)ImGui::GetTime() * 0.3f + side * side_step; + ImVec2 center(x + sz * 0.5f, y + sz * 0.5f); + draw_list->AddLine({ center.x + cosf(t) * sz * 0.5f, center.y + sinf(t) * sz * 0.5f }, { center.x + cosf(t + side_step) * sz * 0.5f, center.y + sinf(t + side_step) * sz * 0.5f }, col, th, flags); + } + x += sz + spacing; // Path - draw_list->PathArcTo(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, 3.141592f, 3.141592f * -0.5f); - draw_list->PathStroke(col, th, flags | cap_flags); + draw_list->PathArcTo(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, 3.141592f * -0.5f, 3.141592f); + draw_list->PathStroke(col, th, flags); x += sz + spacing; // Quadratic Bezier Curve (3 control points) - draw_list->AddBezierQuadratic(ImVec2(x + cp3[0].x, y + cp3[0].y), ImVec2(x + cp3[1].x, y + cp3[1].y), ImVec2(x + cp3[2].x, y + cp3[2].y), col, th, curve_segments, flags | cap_flags); + draw_list->AddBezierQuadratic(ImVec2(x + cp3[0].x, y + cp3[0].y), ImVec2(x + cp3[1].x, y + cp3[1].y), ImVec2(x + cp3[2].x, y + cp3[2].y), col, th, curve_segments, flags); x += sz + spacing; // Cubic Bezier Curve (4 control points) - draw_list->AddBezierCubic(ImVec2(x + cp4[0].x, y + cp4[0].y), ImVec2(x + cp4[1].x, y + cp4[1].y), ImVec2(x + cp4[2].x, y + cp4[2].y), ImVec2(x + cp4[3].x, y + cp4[3].y), col, th, curve_segments, flags | cap_flags); + draw_list->AddBezierCubic(ImVec2(x + cp4[0].x, y + cp4[0].y), ImVec2(x + cp4[1].x, y + cp4[1].y), ImVec2(x + cp4[2].x, y + cp4[2].y), ImVec2(x + cp4[3].x, y + cp4[3].y), col, th, curve_segments, flags); x = p.x + 4; y += sz + spacing; diff --git a/imgui_draw.cpp b/imgui_draw.cpp index a37e35b8e..06be9491d 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -819,6 +819,7 @@ void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, c #define IM_FIXNORMAL2F_MAX_INVLEN2 100.0f // 500.0f (see #4053, #3366) #define IM_FIXNORMAL2F(VX,VY) { float d2 = VX*VX + VY*VY; if (d2 > 0.000001f) { float inv_len2 = 1.0f / d2; if (inv_len2 > IM_FIXNORMAL2F_MAX_INVLEN2) inv_len2 = IM_FIXNORMAL2F_MAX_INVLEN2; VX *= inv_len2; VY *= inv_len2; } } (void)0 +// We avoid using the ImVec2 math operators here to reduce cost to a minimum for debug/non-inlined builds. // We avoid using the 'do { } while (false)` idiom in those macros as they typically have overhead in debug builds. #define IM_APPEND_VTX(PX, PY, UV, COL) \ { \ @@ -845,7 +846,7 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ { const ImU32 col_trans = col & ~IM_COL32_A_MASK; - const ImDrawFlags stroke_pos = (flags & ImDrawFlags_StrokeMask_); + const ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeCenter); const bool closed = (flags & ImDrawFlags_Closed) != 0; const bool miters_only = (flags & ImDrawFlags_MiterOnly) != 0; const float miter_distance_limit_sqr = IM_POLYLINE_MITER_LIMIT * IM_POLYLINE_MITER_LIMIT; @@ -988,7 +989,7 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm const float cos_theta_clamped = ImMax(IM_POLYLINE_MITER_ANGLE_LIMIT, cos_theta); // Avoid div by 0. - const float miter_scale_factor = ImMin(1000.f, 1.f / (1.0f + cos_theta_clamped)); + const float miter_scale_factor = ImMin(1000.0f, 1.0f / (1.0f + cos_theta_clamped)); float miter_offset_x = (n0.x + n1.x) * miter_scale_factor; float miter_offset_y = (n0.y + n1.y) * miter_scale_factor; @@ -1117,6 +1118,14 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ PrimUnreserve(idx_count - idx_used, vtx_count - vtx_used); } +static ImU32 ImAlphaMultiply(ImU32 col, float alpha_mul) +{ + IM_ASSERT_PARANOID(a >= 0.0f && a < 1.0f); // We don't clamp! + ImU32 a = (col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT; + a = (ImU32)(a * alpha_mul); // We don't need to clamp 0..255 because alpha is in 0..1 range. + return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT); +} + void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags) { if (points_count < 2 || (col & IM_COL32_A_MASK) == 0) @@ -1127,8 +1136,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 return; if (screen_thickness < 1.0f) { - const float alpha = screen_thickness; - col = ImGui::GetColorU32(col, alpha); + col = ImAlphaMultiply(col, screen_thickness); screen_thickness = 1.0f; thickness = _FringeScale; } @@ -1506,7 +1514,7 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun const float cos_theta = n0.x * n1.x + n0.y * n1.y; // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm const float cos_theta_clamped = ImMax(IM_POLYLINE_MITER_ANGLE_LIMIT, cos_theta); // Avoid div by 0. - const float miter_scale_factor = ImMin(1000.f, 1.f / (1.0f + cos_theta_clamped)); + const float miter_scale_factor = ImMin(1000.0f, 1.0f / (1.0f + cos_theta_clamped)); const float miter_offset_x = (n0.x + n1.x) * miter_scale_factor * half_aa; const float miter_offset_y = (n0.y + n1.y) * miter_scale_factor * half_aa; @@ -1542,7 +1550,7 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun const float cos_theta = n0.x * n1.x + n0.y * n1.y; // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm const float cos_theta_clamped = ImMax(IM_POLYLINE_MITER_ANGLE_LIMIT, cos_theta); // Avoid div by 0. - const float miter_scale_factor = ImMin(1000.f, 1.f / (1.0f + cos_theta_clamped)); + const float miter_scale_factor = ImMin(1000.0f, 1.0f / (1.0f + cos_theta_clamped)); float miter_offset_x = (n0.x + n1.x) * miter_scale_factor; float miter_offset_y = (n0.y + n1.y) * miter_scale_factor; @@ -2085,6 +2093,7 @@ void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float th } else { + // FIXME-POLYLINE: Implement square caps AddRectFilled(ImVec2(min_x, y), ImVec2(max_x, y + thickness), col); } } @@ -2116,6 +2125,7 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th } else { + // FIXME-POLYLINE: Implement square caps AddRectFilled(ImVec2(x, min_y), ImVec2(x + thickness, max_y), col); } } @@ -2446,6 +2456,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | ImDrawFlags_StrokeInside); #else + // textured_round_corners const bool is_truncated = _FringeScaleIsInteger && ImIsTruncated4(p_min.x, p_min.y, p_max.x, p_max.y) && ImIsTruncated4(rounding, thickness, 0.0f, 0.0f); if ((stroke_pos == ImDrawFlags_StrokeInside || stroke_pos == ImDrawFlags_StrokeOutside || stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) && is_truncated) { @@ -2764,8 +2775,7 @@ void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col, return; if (screen_diameter < 1.0f) { - const float alpha = screen_diameter; - col = ImGui::GetColorU32(col, alpha); + col = ImAlphaMultiply(col, screen_diameter); radius = _FringeScale * 0.5f; } @@ -2811,7 +2821,6 @@ void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_ outer_radius = radius; else if (stroke_pos == ImDrawFlags_StrokeOutside) outer_radius = radius + miter_thickness; - if (outer_radius < (miter_thickness + _FringeScale)) { // The polygon has collapsed into a filled polygon. @@ -2836,8 +2845,7 @@ void ImDrawList::AddNgonFilled(const ImVec2& center, float radius, ImU32 col, in return; if (screen_diameter < 1.0f) { - const float alpha = screen_diameter; - col = ImGui::GetColorU32(col, alpha); + col = ImAlphaMultiply(col, screen_diameter); radius = _FringeScale * 0.5f; } From c1f781d7af4abe61b4c6bcea0e0d9f9f2edd7a57 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Fri, 22 May 2026 09:59:29 +0300 Subject: [PATCH 029/120] DrawList: manually fix cases from merge, added missing cases of ImDrawFlags_StrokeCenterPixelAligned. --- imgui_draw.cpp | 347 +++++++++++++++++++------------------------------ 1 file changed, 133 insertions(+), 214 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 06be9491d..c88fb2440 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -812,6 +812,20 @@ void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, c _IdxWritePtr += 6; } + +static float CalculateCenterPixelAlignedOffset(float thickness, float _FringeScale) +{ + // Old jumpy one. + // return IM_TRUNC(thickness * 0.5f / _FringeScale) * _FringeScale; + + // Calculate outside offset for stroke position CenterPixelAligned + // so that one side the of the line is always at pixel boundary. + // On integer thickness both sides of the line are on pixel boundary. + const float screen_thickness = thickness / _FringeScale; + const int s_thickness = (int)screen_thickness; + return ((float)(s_thickness / 2) + (s_thickness & 1) * (screen_thickness - s_thickness)) * _FringeScale; +} + // On AddPolyline() and AddConvexPolyFilled() we intentionally avoid using ImVec2 and superfluous function calls to optimize debug/non-inlined builds. // - Those macros expects l-values and need to be used as their own statement. // - Those macros are intentionally not surrounded by the 'do {} while (0)' idiom because even that translates to runtime with debug compilers. @@ -851,18 +865,14 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ const bool miters_only = (flags & ImDrawFlags_MiterOnly) != 0; const float miter_distance_limit_sqr = IM_POLYLINE_MITER_LIMIT * IM_POLYLINE_MITER_LIMIT; - float thickness0 = (thickness + fringe) * 0.5f; - float thickness1 = thickness0; + float thickness0 = (thickness + fringe) * 0.5f; // Center (or legacy) if (stroke_pos == ImDrawFlags_StrokeOutside) - { thickness0 = thickness + fringe * 0.5f; - thickness1 = fringe * 0.5f; - } + else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) + thickness0 = CalculateCenterPixelAlignedOffset(thickness, _FringeScale) + fringe * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeInside) - { thickness0 = fringe * 0.5f; - thickness1 = thickness + fringe * 0.5f; - } + float thickness1 = (thickness + fringe) - thickness0; int idx_count = 0; int vtx_count = 0; @@ -2026,19 +2036,6 @@ void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDr } } -static float CalculateCenterPixelAlignedOffset(float thickness, float _FringeScale) -{ - // Old jumpy one. - // return IM_TRUNC(thickness * 0.5f / _FringeScale) * _FringeScale; - - // Calculate outside offset for stroke position CenterPixelAligned - // so that one side the of the line is always at pixel boundary. - // On integer thickness both sides of the line are on pixel boundary. - const float screen_thickness = thickness / _FringeScale; - const int s_thickness = (int)screen_thickness; - return ((float)(s_thickness / 2) + (s_thickness & 1) * (screen_thickness - s_thickness)) * _FringeScale; -} - extern bool g_LEGACY_STROKES; // We intently don't turn g_LEGACY_STROKES into ImDrawFlags_StrokeLegacy here. @@ -2055,7 +2052,8 @@ void ImDrawList::AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float th if ((col & IM_COL32_A_MASK) == 0) return; - if (g_LEGACY_STROKES) + ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeCenter); + if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) { const ImVec2 points[2] = { ImVec2(p1.x + 0.5f, p1.y + 0.5f), ImVec2(p2.x + 0.5f, p2.y + 0.5f) }; AddPolyline(points, 2, col, thickness, ImDrawFlags_StrokeLegacy); @@ -2075,26 +2073,33 @@ void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float th if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) { const ImVec2 points[2] = { ImVec2(min_x + 0.5f, y + 0.5f), ImVec2(max_x + 0.5f, y + 0.5f) }; // Same as AddLine() above. - AddPolyline(points, 2, col, thickness, ImDrawFlags_StrokeLegacy); + AddPolyline(points, 2, col, thickness, stroke_pos); return; } + float top_y = y; // Inside if (stroke_pos == ImDrawFlags_StrokeCenter) - y -= thickness * 0.5f; + top_y -= thickness * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) - y -= CalculateCenterPixelAlignedOffset(thickness, _FringeScale); + top_y -= CalculateCenterPixelAlignedOffset(thickness, _FringeScale); else if (stroke_pos == ImDrawFlags_StrokeOutside) - y -= thickness; + top_y -= thickness; - if (_FringeScaleIsInteger && ImIsTruncated4(min_x, max_x, y, thickness)) + if (_FringeScaleIsInteger && ImIsTruncated4(min_x, max_x, top_y, thickness) && (flags & ImDrawFlags_SquareCap) == 0) { + // For pixel aligned case, use simple rectangle. PrimReserve(6, 4); - PrimRect(ImVec2(min_x, y), ImVec2(max_x, y + thickness), col); + PrimRect(ImVec2(min_x, top_y), ImVec2(max_x, top_y + thickness), col); } else { - // FIXME-POLYLINE: Implement square caps - AddRectFilled(ImVec2(min_x, y), ImVec2(max_x, y + thickness), col); + // Replace stroke pos with the already calculated one. + flags = (flags & ~ImDrawFlags_StrokeMask_) | stroke_pos; + + // For generic case use line, since it needs less triangles than AA rectangle. + PathLineTo(ImVec2(min_x, y)); + PathLineTo(ImVec2(max_x, y)); + PathStroke(col, thickness, flags); } } @@ -2107,26 +2112,34 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) { const ImVec2 points[2] = { ImVec2(x + 0.5f, max_y + 0.5f), ImVec2(x + 0.5f, min_y + 0.5f) }; // Same as AddLine() above. - AddPolyline(points, 2, col, thickness, ImDrawFlags_StrokeLegacy); + AddPolyline(points, 2, col, thickness, stroke_pos); return; } + float left_x = x; // Inside if (stroke_pos == ImDrawFlags_StrokeCenter) - x -= thickness * 0.5f; + left_x -= thickness * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) - x -= CalculateCenterPixelAlignedOffset(thickness, _FringeScale); + left_x -= CalculateCenterPixelAlignedOffset(thickness, _FringeScale); else if (stroke_pos == ImDrawFlags_StrokeOutside) - x -= thickness; + left_x -= thickness; - if (_FringeScaleIsInteger && ImIsTruncated4(x, min_y, max_y, thickness)) + if (_FringeScaleIsInteger && ImIsTruncated4(left_x, min_y, max_y, thickness) && (flags & ImDrawFlags_SquareCap) == 0) { + // For pixel aligned case, use simple rectangle. PrimReserve(6, 4); - PrimRect(ImVec2(x, min_y), ImVec2(x + thickness, max_y), col); + PrimRect(ImVec2(left_x, min_y), ImVec2(left_x + thickness, max_y), col); } else { - // FIXME-POLYLINE: Implement square caps - AddRectFilled(ImVec2(x, min_y), ImVec2(x + thickness, max_y), col); + // Replace stroke pos with the already calculated one. + flags = (flags & ~ImDrawFlags_StrokeMask_) | stroke_pos; + + // For generic case use line, since it needs less triangles than AA rectangle. + // Drawing for bottom to top, so that the inside stroke pos expands to right. + PathLineTo(ImVec2(x, max_y)); + PathLineTo(ImVec2(x, min_y)); + PathStroke(col, thickness, flags); } } @@ -2368,49 +2381,37 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl if ((col & IM_COL32_A_MASK) == 0) return; - if (g_LEGACY_STROKES) + ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); + + if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) { if (Flags & ImDrawListFlags_AntiAliasedLines) PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.50f, 0.50f), rounding, flags); else PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.49f, 0.49f), rounding, flags); // Better looking lower-right corner and rounded non-AA shapes. - PathStroke(col, thickness, ImDrawFlags_Closed); + PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | ImDrawFlags_StrokeCenter); return; } - ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); -#if 0 // shiny_polyline - // FIXME-POLYLINE - const bool has_rounding = (rounding >= _FringeScale); + const bool has_rounding = (rounding >= _FringeScale * 0.5f); + // Calculate the outer boundary of the rectangle based on the stroke position. ImVec2 outer_min = p_min; ImVec2 outer_max = p_max; float outer_rounding = rounding; - if (stroke_pos == ImDrawFlags_StrokeLegacy) - { - outer_min += ImVec2(0.50f, 0.50f); - if (Flags & ImDrawListFlags_AntiAliasedLines) - outer_max -= ImVec2(0.50f, 0.50f); - else - outer_max -= ImVec2(0.49f, 0.49f); // Better looking lower-right corner and rounded non-AA shapes. - stroke_pos = ImDrawFlags_StrokeCenter; - } - + float offset = 0.0f; // Inside if (stroke_pos == ImDrawFlags_StrokeCenter) - { - const ImVec2 offset(thickness * 0.5f, thickness * 0.5f); - outer_min -= offset; - outer_max += offset; - outer_rounding += has_rounding ? thickness * 0.5f : 0.0f; - } + offset = thickness * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) + offset = CalculateCenterPixelAlignedOffset(thickness, _FringeScale); else if (stroke_pos == ImDrawFlags_StrokeOutside) - { - const ImVec2 offset(thickness, thickness); - outer_min -= offset; - outer_max += offset; - outer_rounding += has_rounding ? thickness : 0.0f; - } + offset = thickness; + outer_min.x -= offset; + outer_min.y -= offset; + outer_max.x += offset; + outer_max.y += offset; + outer_rounding += has_rounding ? offset : 0.0f; const float width = outer_max.x - outer_min.x; const float height = outer_max.y - outer_min.y; @@ -2423,6 +2424,61 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl if ((flags & ImDrawFlags_RoundCornersMask_) == 0) flags |= ImDrawFlags_RoundCornersAll; + if (has_rounding) + { + // Constrain rounding to rect dimensions + outer_rounding = ImMin(outer_rounding, ImFabs(width) * (((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f) - 1.0f); + outer_rounding = ImMin(outer_rounding, ImFabs(height) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f); + } + + if (_FringeScaleIsInteger && ImIsTruncated4(outer_min.x, outer_min.y, outer_max.x, outer_max.y) && ImIsTruncated4(outer_rounding, thickness, 0.0f, 0.0f)) + { + int s_rounding = (int)(outer_rounding / _FringeScale); + int s_thickness = (int)(thickness / _FringeScale); + + if (s_rounding <= 0 || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) + { + // Pixel aligned non-rounded rect. + const ImVec2 opaque_uv = _Data->TexUvWhitePixel; + const float t = (float)s_thickness * _FringeScale; + + PrimReserve(8*3, 8); + const ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(outer_min.x, outer_min.y, opaque_uv, col); + IM_APPEND_VTX(outer_min.x + t, outer_min.y + t, opaque_uv, col); + IM_APPEND_VTX(outer_max.x, outer_min.y, opaque_uv, col); + IM_APPEND_VTX(outer_max.x - t, outer_min.y + t, opaque_uv, col); + IM_APPEND_VTX(outer_max.x, outer_max.y, opaque_uv, col); + IM_APPEND_VTX(outer_max.x - t, outer_max.y - t, opaque_uv, col); + IM_APPEND_VTX(outer_min.x, outer_max.y, opaque_uv, col); + IM_APPEND_VTX(outer_min.x + t, outer_max.y - t, opaque_uv, col); + + IM_APPEND_TRI(idx + 0, idx + 3, idx + 1); + IM_APPEND_TRI(idx + 0, idx + 2, idx + 3); + + IM_APPEND_TRI(idx + 2, idx + 5, idx + 3); + IM_APPEND_TRI(idx + 2, idx + 4, idx + 5); + + IM_APPEND_TRI(idx + 4, idx + 7, idx + 5); + IM_APPEND_TRI(idx + 4, idx + 6, idx + 7); + + IM_APPEND_TRI(idx + 6, idx + 1, idx + 7); + IM_APPEND_TRI(idx + 6, idx + 0, idx + 1); + + return; + } + if ((Flags & ImDrawListFlags_RoundCornersUseTex) && s_thickness <= IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX && s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) + { + // Pixel aligned rect with round corners rendered using baked textures. + IM_ASSERT_PARANOID(!(_Data->Font->OwnerAtlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners)); + const int size = ImMax(s_rounding, s_thickness); // This is matching the baking calculations. + const int idx = (s_thickness - 1) * IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX + s_rounding - 1; + IM_ASSERT_PARANOID(idx >= 0 && idx < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX * IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX); + const ImVec4 tex_uvs = _Data->TexUvCornerStrokes[idx]; + _AddRectBaked(outer_min, outer_max, col, (float)size * _FringeScale, (float)thickness, tex_uvs, flags); + return; + } + } if (!has_rounding || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) { @@ -2433,10 +2489,6 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl } else { - // Constrain rounding to rect dimensions - outer_rounding = ImMin(outer_rounding, ImFabs(width) * (((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f) - 1.0f); - outer_rounding = ImMin(outer_rounding, ImFabs(height) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f); - if (thickness > outer_rounding) { // Special case rendering to avoid rendering artifacts at the corners. @@ -2455,111 +2507,6 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl } PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | ImDrawFlags_StrokeInside); -#else - // textured_round_corners - const bool is_truncated = _FringeScaleIsInteger && ImIsTruncated4(p_min.x, p_min.y, p_max.x, p_max.y) && ImIsTruncated4(rounding, thickness, 0.0f, 0.0f); - if ((stroke_pos == ImDrawFlags_StrokeInside || stroke_pos == ImDrawFlags_StrokeOutside || stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) && is_truncated) - { - if ((flags & ImDrawFlags_RoundCornersMask_) == 0) - flags |= ImDrawFlags_RoundCornersAll; - - rounding = ImMin(rounding, ImFabs(p_max.x - p_min.x) * (((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f) - 1.0f); - rounding = ImMin(rounding, ImFabs(p_max.y - p_min.y) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f); - - int s_rounding = (int)(rounding / _FringeScale); - int s_thickness = (int)(thickness / _FringeScale); - - // AddRectBaked() renders the stroke inside the rectangle, so we need to adjust the outside case - // This branch does not handle the center, since odd thickness strokes are not pixel aligned, even if the rectangle is. - // Pixel aligned center works, since the stroke is aligned to pixels. - ImVec2 s_min = p_min; - ImVec2 s_max = p_max; - if (stroke_pos == ImDrawFlags_StrokeOutside) - { - s_min.x -= thickness; - s_min.y -= thickness; - s_max.x += thickness; - s_max.y += thickness; - if (s_rounding != 0) - s_rounding += s_thickness; - } - else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) - { - const float offset = (float)(s_thickness / 2) * _FringeScale; // Using integer div, since we want IM_TRUNC(thickness/2). - s_min.x -= offset; - s_min.y -= offset; - s_max.x += offset; - s_max.y += offset; - if (s_rounding != 0) - s_rounding += s_thickness / 2; - } - - if (s_rounding <= 0 || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) - { - const ImVec2 opaque_uv = _Data->TexUvWhitePixel; - const float t = (float)s_thickness * _FringeScale; - - PrimReserve(8*3, 8); - const ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; - IM_APPEND_VTX(s_min.x, s_min.y, opaque_uv, col); - IM_APPEND_VTX(s_min.x + t, s_min.y + t, opaque_uv, col); - IM_APPEND_VTX(s_max.x, s_min.y, opaque_uv, col); - IM_APPEND_VTX(s_max.x - t, s_min.y + t, opaque_uv, col); - IM_APPEND_VTX(s_max.x, s_max.y, opaque_uv, col); - IM_APPEND_VTX(s_max.x - t, s_max.y - t, opaque_uv, col); - IM_APPEND_VTX(s_min.x, s_max.y, opaque_uv, col); - IM_APPEND_VTX(s_min.x + t, s_max.y - t, opaque_uv, col); - - IM_APPEND_TRI(idx + 0, idx + 3, idx + 1); - IM_APPEND_TRI(idx + 0, idx + 2, idx + 3); - - IM_APPEND_TRI(idx + 2, idx + 5, idx + 3); - IM_APPEND_TRI(idx + 2, idx + 4, idx + 5); - - IM_APPEND_TRI(idx + 4, idx + 7, idx + 5); - IM_APPEND_TRI(idx + 4, idx + 6, idx + 7); - - IM_APPEND_TRI(idx + 6, idx + 1, idx + 7); - IM_APPEND_TRI(idx + 6, idx + 0, idx + 1); - - return; - } - if ((Flags & ImDrawListFlags_RoundCornersUseTex) && s_thickness <= IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX && s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) - { - IM_ASSERT_PARANOID(!(_Data->Font->OwnerAtlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners)); - const int size = ImMax(s_rounding, s_thickness); // This is matching the baking calculations. - const int idx = (s_thickness - 1) * IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX + s_rounding - 1; - IM_ASSERT_PARANOID(idx >= 0 && idx < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX * IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX); - const ImVec4 tex_uvs = _Data->TexUvCornerStrokes[idx]; - _AddRectBaked(s_min, s_max, col, (float)size * _FringeScale, (float)thickness, tex_uvs, flags); - return; - } - } - - const ImVec2 offset(thickness * 0.5f, thickness * 0.5f); - if (stroke_pos == ImDrawFlags_StrokeInside) - { - rounding -= (rounding != 0.0f) ? thickness * 0.5f : 0.0f; - PathRect(p_min + offset, p_max - offset, rounding, flags); - } - else if (stroke_pos == ImDrawFlags_StrokeCenter) - { - PathRect(p_min, p_max, rounding, flags); - } - else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) - { - // PathRect() centers the stroke, just calculate the fractional offset. - const float fract = (thickness * 0.5f) - CalculateCenterPixelAlignedOffset(thickness, _FringeScale); - rounding += (rounding != 0.0f) ? fract : 0.0f; - PathRect(ImVec2(p_min.x + fract, p_min.y + fract), ImVec2(p_max.x - fract, p_max.y - fract), rounding, flags); - } - else if (stroke_pos == ImDrawFlags_StrokeOutside) - { - rounding += (rounding != 0.0f) ? thickness * 0.5f : 0.0f; - PathRect(p_min - offset, p_max + offset, rounding, flags); - } - PathStroke(col, thickness, ImDrawFlags_Closed); -#endif } void ImDrawList::_AddRectFilledBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, ImVec4 tex_uvs, ImDrawFlags flags) @@ -2728,10 +2675,11 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu stroke_pos = ImDrawFlags_StrokeCenter; } - // FIXME-POLYLINE: missing ImDrawFlags_StrokeCenterPixelAligned. float outer_radius = radius; if (stroke_pos == ImDrawFlags_StrokeCenter) outer_radius = radius + thickness * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) + outer_radius = radius + CalculateCenterPixelAlignedOffset(thickness, _FringeScale); else if (stroke_pos == ImDrawFlags_StrokeInside) outer_radius = radius; else if (stroke_pos == ImDrawFlags_StrokeOutside) @@ -2746,10 +2694,8 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu if (num_segments <= 0) { // Use arc with automatic segment count - // FIXME-POLYLINE: Review - //const int a_step = IM_DRAWLIST_ARCFAST_SAMPLE_MAX / _CalcCircleAutoSegmentCount(radius); // Radius used here intentionally so that the circle matches filled circle of same radius. - //_PathArcToFastEx(center, radius, 0, IM_DRAWLIST_ARCFAST_SAMPLE_MAX, a_step); - _PathArcToFastEx(center, radius, 0, IM_DRAWLIST_ARCFAST_SAMPLE_MAX, 0); + const int a_step = IM_DRAWLIST_ARCFAST_SAMPLE_MAX / _CalcCircleAutoSegmentCount(outer_radius); // Use outer_radius for segment count to be consistent with rounded rect. + _PathArcToFastEx(center, radius, 0, IM_DRAWLIST_ARCFAST_SAMPLE_MAX, a_step); _Path.Size--; } else @@ -2817,6 +2763,8 @@ void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_ float outer_radius = radius; if (stroke_pos == ImDrawFlags_StrokeCenter) outer_radius = radius + miter_thickness * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) + outer_radius = radius + CalculateCenterPixelAlignedOffset(miter_thickness, _FringeScale); else if (stroke_pos == ImDrawFlags_StrokeInside) outer_radius = radius; else if (stroke_pos == ImDrawFlags_StrokeOutside) @@ -2861,41 +2809,12 @@ void ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 co if ((col & IM_COL32_A_MASK) == 0) return; - // Note: since offset ellipse is not ellipse anymore, we cannot fall back to filled ellipse when outline covers the whole shape like in other shapes. - // FIXME-POLYLINE - /* - ImVec2 r; - if (g_LEGACY_STROKES) + ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); + + if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) { - r = radius; + stroke_pos = ImDrawFlags_StrokeCenter; } - else - { - r = radius; - ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); - if (stroke_pos == ImDrawFlags_StrokeInside) - r -= ImVec2(thickness * 0.5f, thickness * 0.5f); - else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) - { - const float fract = thickness * 0.5f - CalculateCenterPixelAlignedOffset(thickness, _FringeScale); - r -= ImVec2(fract, fract); - } - else if (stroke_pos == ImDrawFlags_StrokeOutside) - r += ImVec2(thickness * 0.5f, thickness * 0.5f); - } - */ - /* - float stroke_offset = 0.0f; - if (stroke_pos == ImDrawFlags_StrokeInside) - stroke_offset = -thickness * 0.5f; - else if (stroke_pos == ImDrawFlags_StrokeOutside) - stroke_offset = thickness * 0.5f; - if ((ImMin(radius.x, radius.y) + stroke_offset) < (thickness * 0.5f + _FringeScale * 0.5f)) - { - AddEllipseFilled(center, radius, col, rot, num_segments); - return; - } - */ if (num_segments <= 0) num_segments = _CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)); // A bit pessimistic, maybe there's a better computation to do here. @@ -2905,7 +2824,7 @@ void ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 co // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = IM_PI * 2.0f * ((float)num_segments - 1.0f) / (float)num_segments; PathEllipticalArcTo(center, radius, rot, 0.0f, a_max, num_segments - 1); - PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | flags); + PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | stroke_pos); } void ImDrawList::AddEllipseFilled(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot, int num_segments) From 170a374f5993170e5161193176cfb2e1f2c7b393 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Fri, 22 May 2026 10:09:57 +0300 Subject: [PATCH 030/120] DrawList: added ImDrawFlags_NoAAEnds which allows to disable line end AA. --- imgui.h | 5 ++-- imgui_demo.cpp | 3 +- imgui_draw.cpp | 75 +++++++++++++++++++++++++++++++++----------------- 3 files changed, 54 insertions(+), 29 deletions(-) diff --git a/imgui.h b/imgui.h index 4bebc2bd4..0a495a688 100644 --- a/imgui.h +++ b/imgui.h @@ -3330,13 +3330,14 @@ enum ImDrawFlags_ ImDrawFlags_Closed = 1 << 9, // PathStroke(), AddPolyline(): specify that shape should be closed. ImDrawFlags_MiterOnly = 1 << 10, // PathStroke(), AddPolyline(): use miter corners only. This assumes that the input polyline does not have corners sharper than 90 degrees. Slightly faster. ImDrawFlags_SquareCap = 1 << 11, // PathStroke(), AddPolyline(): use square cap line ends. + ImDrawFlags_NoAAEnds = 1 << 12, // PathStroke(), AddPolyline(): do not generate anti-aliased line ends. This can speed up single line rendering. // Stroke position relative to the shape outline - ImDrawFlags_StrokeInside = 1 << 16, // Draw stroke inside of the shape outline (Default) + ImDrawFlags_StrokeInside = 1 << 16, // Draw stroke inside of the shape outline (default for closed shapes and AddLineH, AddLineV functions) ImDrawFlags_StrokeCenter = 2 << 16, // Draw stroke at the center of the shape outline ImDrawFlags_StrokeCenterPixelAligned = 3 << 16, // Draw stroke at the center of the shape outline, so that half thickness (rounded down) will be outside, and rest inside the shape outline. ImDrawFlags_StrokeOutside = 4 << 16, // Draw stroke outside of the shape outline - ImDrawFlags_StrokeLegacy = 5 << 16, // Use legacy position + ImDrawFlags_StrokeLegacy = 5 << 16, // Use legacy positioning + enable MiterOnly and NoAAEnds flags. ImDrawFlags_StrokeMask_ = 0x07 << 16, ImDrawFlags_InvalidMask_ = ~0x7FFFFFF0, // == 0x8000000F, diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 74ce27ef8..dec9d485e 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10417,6 +10417,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::SameLine(); ImGui::RadioButton("Legacy", &flags, ImDrawFlags_StrokeLegacy); ImGui::CheckboxFlags("MiterOnly", &flags, ImDrawFlags_MiterOnly); ImGui::SameLine(); ImGui::CheckboxFlags("SquareCap", &flags, ImDrawFlags_SquareCap); + ImGui::SameLine(); ImGui::CheckboxFlags("NoAAEnds", &flags, ImDrawFlags_NoAAEnds); const ImVec2 p = ImGui::GetCursorScreenPos(); const ImU32 col = ImColor(colf); @@ -10455,7 +10456,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) float side_step = 3.141592f * 0.5f; for (int side = 0; side < 4; side++) { - float t = (float)ImGui::GetTime() * 0.3f + side * side_step; + float t = (float)ImGui::GetTime() * 0.1f + side * side_step; ImVec2 center(x + sz * 0.5f, y + sz * 0.5f); draw_list->AddLine({ center.x + cosf(t) * sz * 0.5f, center.y + sinf(t) * sz * 0.5f }, { center.x + cosf(t + side_step) * sz * 0.5f, center.y + sinf(t + side_step) * sz * 0.5f }, col, th, flags); } diff --git a/imgui_draw.cpp b/imgui_draw.cpp index c88fb2440..d10ec91d0 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -923,21 +923,31 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ const ImVec2 dir(n1.y, -n1.x); if (flags & ImDrawFlags_SquareCap) p1 -= dir * half_thickness; - const ImVec2 pa = p1 - dir * half_aa; - const ImVec2 pb = p1 + dir * half_aa; - base_idx = (int)_VtxCurrentIdx; - IM_APPEND_VTX(pa.x - n1.x * thickness0, pa.y - n1.y * thickness0, uv0, col_trans); - IM_APPEND_VTX(pa.x + n1.x * thickness1, pa.y + n1.y * thickness1, uv1, col_trans); + if (flags & ImDrawFlags_NoAAEnds) + { + base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(p1.x - n1.x * thickness0, p1.y - n1.y * thickness0, uv0, col); + IM_APPEND_VTX(p1.x + n1.x * thickness1, p1.y + n1.y * thickness1, uv1, col); + } + else + { + const ImVec2 pa = p1 - dir * half_aa; + const ImVec2 pb = p1 + dir * half_aa; - int next_base_idx = (int)_VtxCurrentIdx; - IM_APPEND_VTX(pb.x - n1.x * thickness0, pb.y - n1.y * thickness0, uv0, col); - IM_APPEND_VTX(pb.x + n1.x * thickness1, pb.y + n1.y * thickness1 , uv1, col); + base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(pa.x - n1.x * thickness0, pa.y - n1.y * thickness0, uv0, col_trans); + IM_APPEND_VTX(pa.x + n1.x * thickness1, pa.y + n1.y * thickness1, uv1, col_trans); - // AA cap - IM_APPEND_TRI(base_idx+0, base_idx+2, base_idx+3); - IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+1); - base_idx = next_base_idx; + int next_base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(pb.x - n1.x * thickness0, pb.y - n1.y * thickness0, uv0, col); + IM_APPEND_VTX(pb.x + n1.x * thickness1, pb.y + n1.y * thickness1, uv1, col); + + // AA cap + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); + IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); + base_idx = next_base_idx; + } } else { @@ -1095,24 +1105,37 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ const ImVec2 dir(n1.y, -n1.x); if (flags & ImDrawFlags_SquareCap) p1 += dir * half_thickness; - const ImVec2 pa = p1 - dir * half_aa; - const ImVec2 pb = p1 + dir * half_aa; - int next_base_idx = (int)_VtxCurrentIdx; - IM_APPEND_VTX(pa.x - n1.x * thickness0, pa.y - n1.y * thickness0, uv0, col); - IM_APPEND_VTX(pa.x + n1.x * thickness1, pa.y + n1.y * thickness1, uv1, col); + if (flags & ImDrawFlags_NoAAEnds) + { + IM_APPEND_VTX(p1.x - n1.x * thickness0, p1.y - n1.y * thickness0, uv0, col); + IM_APPEND_VTX(p1.x + n1.x * thickness1, p1.y + n1.y * thickness1, uv1, col); - IM_APPEND_VTX(pb.x - n1.x * thickness0, pb.y - n1.y * thickness0, uv0, col_trans); - IM_APPEND_VTX(pb.x + n1.x * thickness1, pb.y + n1.y * thickness1, uv1, col_trans); + // Connect + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); + IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); + } + else + { + const ImVec2 pa = p1 - dir * half_aa; + const ImVec2 pb = p1 + dir * half_aa; - // Connect - IM_APPEND_TRI(base_idx+0, base_idx+2, base_idx+3); - IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+1); - base_idx = next_base_idx; + int next_base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(pa.x - n1.x * thickness0, pa.y - n1.y * thickness0, uv0, col); + IM_APPEND_VTX(pa.x + n1.x * thickness1, pa.y + n1.y * thickness1, uv1, col); - // AA cap - IM_APPEND_TRI(base_idx+0, base_idx+2, base_idx+3); - IM_APPEND_TRI(base_idx+0, base_idx+3, base_idx+1); + IM_APPEND_VTX(pb.x - n1.x * thickness0, pb.y - n1.y * thickness0, uv0, col_trans); + IM_APPEND_VTX(pb.x + n1.x * thickness1, pb.y + n1.y * thickness1, uv1, col_trans); + + // Connect + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); + IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); + base_idx = next_base_idx; + + // AA cap + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); + IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); + } } else { From ce3d970fc7e101a9d61e3cc93547c6f6e4ee765f Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Fri, 22 May 2026 12:36:20 +0300 Subject: [PATCH 031/120] DrawList: optimized AA end overhead in debug builds. - inline some code and reuse vars (ctors get function calls in debug msvc) to reduce the AA line end over in debug builds --- imgui_draw.cpp | 53 ++++++++++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index d10ec91d0..ac8fb7193 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -889,6 +889,20 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ idx_count = (/*body*/(points_count - 2) * max_tris_per_point + /*last seg*/4 + /*caps*/(4 * 2)) * 3; } + + const float half_texel = 0.5f * _Data->FontAtlas->TexUvScale.x; + const float ratio = thickness0 / (thickness0 + thickness1); // The points using uv2 are placed on the path, calculate the position from stroke offets. + ImVec2 uv0, uv1, uv2; + uv0.x = tex_uvs.x + half_texel; + uv0.y = tex_uvs.y; + uv1.x = tex_uvs.z - half_texel; + uv1.y = tex_uvs.y; + uv2.x = uv0.x + (uv1.x - uv0.x) * ratio; + uv2.y = tex_uvs.y; + + const float half_aa = _FringeScale * 0.5f; // Used for end caps, does not use "fringe" since end cap AA is not using the texture. + const float half_thickness = thickness * 0.5f; + PrimReserve(idx_count, vtx_count); ImDrawVert* start_vtx_ptr = _VtxWritePtr; @@ -896,19 +910,11 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ int base_idx = (int)_VtxCurrentIdx; + ImVec2 pa, pb, dir; ImVec2 p1; ImVec2 n1; float len_sqr1; - const float half_texel = 0.5f * _Data->FontAtlas->TexUvScale.x; - const ImVec2 uv0(tex_uvs.x + half_texel, tex_uvs.y); - const ImVec2 uv1(tex_uvs.z - half_texel, tex_uvs.y); - const float ratio = thickness0 / (thickness0 + thickness1); // The points using uv2 are placed on the path, calculate the position from stroke offets. - const ImVec2 uv2(uv0.x + (uv1.x - uv0.x) * ratio, tex_uvs.y); - - const float half_aa = _FringeScale * 0.5f; // Used for end caps, does not use "fringe" since end cap AA is not using the texture. - const float half_thickness = thickness * 0.5f; - int point_idx = 0; int point_end = points_count; if (!closed) @@ -920,9 +926,14 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ n1 = normals[0]; len_sqr1 = sqr_lengths[0]; - const ImVec2 dir(n1.y, -n1.x); + dir.x = n1.y; + dir.y = -n1.x; + if (flags & ImDrawFlags_SquareCap) - p1 -= dir * half_thickness; + { + p1.x -= dir.x * half_thickness; + p1.y -= dir.y * half_thickness; + } if (flags & ImDrawFlags_NoAAEnds) { @@ -932,8 +943,10 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ } else { - const ImVec2 pa = p1 - dir * half_aa; - const ImVec2 pb = p1 + dir * half_aa; + pa.x = p1.x - dir.x * half_aa; + pa.y = p1.y - dir.y * half_aa; + pb.x = p1.x + dir.x * half_aa; + pb.y = p1.y + dir.y * half_aa; base_idx = (int)_VtxCurrentIdx; IM_APPEND_VTX(pa.x - n1.x * thickness0, pa.y - n1.y * thickness0, uv0, col_trans); @@ -1102,9 +1115,13 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ n1 = normals[points_count-1]; // End cap - const ImVec2 dir(n1.y, -n1.x); + dir.x = n1.y; + dir.y = -n1.x; if (flags & ImDrawFlags_SquareCap) - p1 += dir * half_thickness; + { + p1.x += dir.x * half_thickness; + p1.y += dir.y * half_thickness; + } if (flags & ImDrawFlags_NoAAEnds) { @@ -1117,8 +1134,10 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ } else { - const ImVec2 pa = p1 - dir * half_aa; - const ImVec2 pb = p1 + dir * half_aa; + pa.x = p1.x - dir.x * half_aa; + pa.y = p1.y - dir.y * half_aa; + pb.x = p1.x + dir.x * half_aa; + pb.y = p1.y + dir.y * half_aa; int next_base_idx = (int)_VtxCurrentIdx; IM_APPEND_VTX(pa.x - n1.x * thickness0, pa.y - n1.y * thickness0, uv0, col); From d879e9781b6b45aa2c9fae7443a26924dc6cf9f1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 22 May 2026 16:00:55 +0200 Subject: [PATCH 032/120] DrawList: wired AddPolylineLegacy() to g_LEGACY_STROKES. Fixed AddLine(), AddLineV(), AddLineH() legacy path. --- imgui_draw.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index ac8fb7193..400fe6067 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -861,6 +861,9 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ const ImU32 col_trans = col & ~IM_COL32_A_MASK; const ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeCenter); + if (stroke_pos == ImDrawFlags_StrokeLegacy) + flags |= ImDrawFlags_MiterOnly | ImDrawFlags_NoAAEnds; + const bool closed = (flags & ImDrawFlags_Closed) != 0; const bool miters_only = (flags & ImDrawFlags_MiterOnly) != 0; const float miter_distance_limit_sqr = IM_POLYLINE_MITER_LIMIT * IM_POLYLINE_MITER_LIMIT; @@ -1178,8 +1181,16 @@ static ImU32 ImAlphaMultiply(ImU32 col, float alpha_mul) return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT); } +extern bool g_LEGACY_STROKES; + void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags) { + if (g_LEGACY_STROKES) + { + AddPolylineLegacy(points, points_count, col, thickness, flags); + return; + } + if (points_count < 2 || (col & IM_COL32_A_MASK) == 0) return; @@ -2078,8 +2089,6 @@ void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDr } } -extern bool g_LEGACY_STROKES; - // We intently don't turn g_LEGACY_STROKES into ImDrawFlags_StrokeLegacy here. // The earlier should use verbatim legacy code but will be removed before release once we confirm that both matches. ImDrawFlags ImDrawList::_GetStrokePos(ImDrawFlags flags, ImDrawFlags default_stroke_pos) @@ -2688,6 +2697,8 @@ void ImDrawList::AddTriangle(const ImVec2& p1, const ImVec2& p2, const ImVec2& p if ((col & IM_COL32_A_MASK) == 0) return; + ImDrawFlags stroke_pos = g_LEGACY_STROKES ? ImDrawFlags_StrokeLegacy : _GetStrokePos(flags, ImDrawFlags_StrokeInside); + flags = (flags & ~ImDrawFlags_StrokeMask_) | stroke_pos; PathLineTo(p1); PathLineTo(p2); PathLineTo(p3); From 0e219e6c4ddbe16e606d9cfe74357517ab7fa2fa Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 22 May 2026 16:49:40 +0200 Subject: [PATCH 033/120] Demo: rework Custom Rendering's Primitives rendering. Fill + Stroke overlap. Use {} style for ImVec2. --- imgui_demo.cpp | 214 +++++++++++++++++++++++++++++-------------------- 1 file changed, 126 insertions(+), 88 deletions(-) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index dec9d485e..c3a63ac27 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10394,7 +10394,8 @@ static void ShowExampleAppCustomRendering(bool* p_open) static int circle_segments_override_v = 12; static bool curve_segments_override = false; static int curve_segments_override_v = 8; - static ImVec4 colf = ImVec4(1.0f, 1.0f, 0.4f, 1.0f); + static ImVec4 colf_stroke = ImVec4(1.00f, 1.00f, 0.40f, 1.0f); + static ImVec4 colf_fill = ImVec4(0.70f, 0.18f, 0.62f, 1.0f); ImGui::DragFloat("Size", &sz, 0.2f, 0.2f, 100.0f, "%.0f"); ImGui::DragFloat("Thickness", &thickness, 0.1f, 0.0f, 30.0f, "%.02f"); ImGui::SliderInt("N-gon sides", &ngon_sides, 3, 12); @@ -10404,114 +10405,151 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::Checkbox("##curvessegmentoverride", &curve_segments_override); ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); curve_segments_override |= ImGui::SliderInt("Curves segments override", &curve_segments_override_v, 3, 40); - ImGui::ColorEdit4("Color", &colf.x); + ImGui::ColorEdit4("Stroke Color", &colf_stroke.x); + ImGui::ColorEdit4("Fill Color", &colf_fill.x); static ImDrawFlags flags = ImDrawFlags_None; - ImGui::AlignTextToFramePadding(); - ImGui::Text("Stroke:"); - ImGui::SameLine(); ImGui::RadioButton("Default", &flags, ImDrawFlags_None); - ImGui::SameLine(); ImGui::RadioButton("Inside", &flags, ImDrawFlags_StrokeInside); - ImGui::SameLine(); ImGui::RadioButton("Center", &flags, ImDrawFlags_StrokeCenter); - ImGui::SameLine(); ImGui::RadioButton("CenterPixelAligned", &flags, ImDrawFlags_StrokeCenterPixelAligned); - ImGui::SameLine(); ImGui::RadioButton("Outside", &flags, ImDrawFlags_StrokeOutside); - ImGui::SameLine(); ImGui::RadioButton("Legacy", &flags, ImDrawFlags_StrokeLegacy); - ImGui::CheckboxFlags("MiterOnly", &flags, ImDrawFlags_MiterOnly); - ImGui::SameLine(); ImGui::CheckboxFlags("SquareCap", &flags, ImDrawFlags_SquareCap); - ImGui::SameLine(); ImGui::CheckboxFlags("NoAAEnds", &flags, ImDrawFlags_NoAAEnds); + ImGui::Text("Stroke Flags:"); + ImGui::RadioButton("Default", &flags, ImDrawFlags_None); ImGui::SameLine(); + ImGui::RadioButton("Inside", &flags, ImDrawFlags_StrokeInside); ImGui::SameLine(); + ImGui::RadioButton("Center", &flags, ImDrawFlags_StrokeCenter); ImGui::SameLine(); + ImGui::RadioButton("CenterPixelAligned", &flags, ImDrawFlags_StrokeCenterPixelAligned); ImGui::SameLine(); + ImGui::RadioButton("Outside", &flags, ImDrawFlags_StrokeOutside); ImGui::SameLine(); + ImGui::RadioButton("Legacy", &flags, ImDrawFlags_StrokeLegacy); + ImGui::CheckboxFlags("MiterOnly", &flags, ImDrawFlags_MiterOnly); ImGui::SameLine(); + ImGui::CheckboxFlags("SquareCap", &flags, ImDrawFlags_SquareCap); ImGui::SameLine(); + ImGui::CheckboxFlags("NoAAEnds", &flags, ImDrawFlags_NoAAEnds); + ImGui::Spacing(); - const ImVec2 p = ImGui::GetCursorScreenPos(); - const ImU32 col = ImColor(colf); + ImVec2 start_pos = ImGui::GetCursorScreenPos(); + const float pi = 3.141592f; const float spacing = 10.0f; const ImDrawFlags corners_tl_br = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersBottomRight; const float rounding = sz / 5.0f; + const float half_sz = sz * 0.5f; + const float half_szi = (float)(int)(sz * 0.5f); const int circle_segments = circle_segments_override ? circle_segments_override_v : 0; const int curve_segments = curve_segments_override ? curve_segments_override_v : 0; const ImVec2 cp3[3] = { ImVec2(0.0f, sz * 0.6f), ImVec2(sz * 0.5f, -sz * 0.4f), ImVec2(sz, sz) }; // Control points for curves const ImVec2 cp4[4] = { ImVec2(0.0f, 0.0f), ImVec2(sz * 1.3f, sz * 0.3f), ImVec2(sz - sz * 1.3f, sz - sz * 0.3f), ImVec2(sz, sz) }; - - float x = p.x + 4.0f; - float y = p.y + 4.0f; - for (int n = 0; n < 2; n++) + ImVec2 rotating_square[4]; + for (int side = 0; side < 4; side++) { - // First line uses a thickness of 1.0f, second line uses the configurable thickness - float th = (n == 0) ? 1.0f : thickness; - draw_list->AddNgon(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, ngon_sides, th, flags); x += sz + spacing; // N-gon - draw_list->AddCircle(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, col, circle_segments, th, flags); x += sz + spacing; // Circle - draw_list->AddEllipse(ImVec2(x + sz*0.5f, y + sz*0.5f), ImVec2(sz*0.5f, sz*0.3f), col, -0.3f, circle_segments, th, flags); x += sz + spacing; // Ellipse - draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 0.0f, th, flags); x += sz + spacing; // Square - draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, th, flags); x += sz + spacing; // Square with all rounded corners - draw_list->AddRect(ImVec2(x, y), ImVec2(x + sz, y + sz), col, rounding, th, flags | corners_tl_br); x += sz + spacing; // Square with two rounded corners - draw_list->AddTriangle(ImVec2(x+sz*0.5f,y), ImVec2(x+sz, y+sz-0.5f), ImVec2(x, y+sz-0.5f), col, th, flags);x += sz + spacing; // Triangle - //draw_list->AddTriangle(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col, th, flags); x+= sz*0.4f + spacing; // Thin triangle - PathConcaveShape(draw_list, x, y, sz); draw_list->PathStroke(col, th, flags | ImDrawFlags_Closed); x += sz + spacing; // Concave Shape - //draw_list->AddPolyline(concave_shape, IM_COUNTOF(concave_shape), col, ImDrawFlags_Closed, th); - draw_list->AddLineH(x, x + sz, y, col, th, flags); x += sz + spacing; // Horizontal line (note: drawing a filled rectangle will be faster!) - draw_list->AddLineV(x, y, y + sz, col, th, flags); x += spacing; // Vertical line (note: drawing a filled rectangle will be faster!) - draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y), col, th, flags); x += sz + spacing; - draw_list->AddLine(ImVec2(x, y + sz), ImVec2(x, y), col, th, flags); x += spacing; + float t = (float)ImGui::GetTime() * 0.1f + side * pi * 0.5f; + rotating_square[side] = { (float)cosf(t) * half_sz, (float)sin(t) * half_sz }; + } - draw_list->AddLine(ImVec2(x, y), ImVec2(x + sz, y + sz), col, th, flags); x += sz + spacing; // Diagonal line - - // Rotating square made of lines - float side_step = 3.141592f * 0.5f; - for (int side = 0; side < 4; side++) + float x = start_pos.x; + float y = start_pos.y; + for (int row = 0; row < 4; row++) + { + x = start_pos.x; + bool draw_fill = (row == 2 || row == 3); + bool draw_strokes = (row == 0 || row == 1 || row == 3); + if (draw_fill) { - float t = (float)ImGui::GetTime() * 0.1f + side * side_step; - ImVec2 center(x + sz * 0.5f, y + sz * 0.5f); - draw_list->AddLine({ center.x + cosf(t) * sz * 0.5f, center.y + sinf(t) * sz * 0.5f }, { center.x + cosf(t + side_step) * sz * 0.5f, center.y + sinf(t + side_step) * sz * 0.5f }, col, th, flags); + // FILLED SHAPES + const ImU32 col = ImColor(colf_fill); + draw_list->AddNgonFilled({ x + half_sz, y + half_sz }, half_sz, col, ngon_sides); x += sz + spacing; // N-gon + draw_list->AddCircleFilled({ x + half_sz, y + half_sz }, half_sz, col, circle_segments); x += sz + spacing; // Circle + draw_list->AddEllipseFilled({ x + half_sz, y + half_sz }, { sz * 0.5f, sz * 0.3f }, col, -0.3f, circle_segments); x += sz + spacing;// Ellipse + draw_list->AddRectFilled({ x, y }, { x + sz, y + sz }, col); x += sz + spacing; // Square + draw_list->AddRectFilled({ x, y }, { x + sz, y + sz }, col, rounding); x += sz + spacing; // Square with all rounded corners + draw_list->AddRectFilled({ x, y }, { x + sz, y + sz }, col, rounding, corners_tl_br); x += sz + spacing; // Square with two rounded corners + draw_list->AddTriangleFilled({ x + sz * 0.5f, y }, { x + sz, y + sz }, { x, y + sz }, col); x += sz + spacing; // Triangle + draw_list->AddTriangleFilled({ x + sz * 0.2f, y }, { x + sz * 0.4f, y + sz }, { x, y + sz }, col); x += (float)(int)(sz * 0.4f) + spacing; // Thin triangle + PathConcaveShape(draw_list, x, y, sz); draw_list->PathFillConcave(col); x += sz + spacing; // Concave shape + draw_list->AddRectFilled({ x, y }, { x + sz, y + thickness }, col); x += sz + spacing; // Horizontal line (faster than AddLine, but only handle integer thickness) + draw_list->AddRectFilled({ x, y }, { x + thickness, y + sz }, col); x += half_szi + spacing; // Vertical line (faster than AddLine, but only handle integer thickness) + if (!(draw_fill && draw_strokes)) //-V560 + { + draw_list->AddRectFilled({ x + 0, y }, { x + 0 + 1, y + 1 }, col); // Pixels (faster than AddLine) + draw_list->AddRectFilled({ x + 2, y }, { x + 2 + 2, y + 2 }, col); + draw_list->AddRectFilled({ x + 5, y }, { x + 5 + 3, y + 3 }, col); + } + x += spacing + sz; + + // Rotating square + for (int n = 0; n < 4; n++) + draw_list->PathLineTo({ x + half_sz + rotating_square[n].x, y + half_sz + rotating_square[n].y }); + draw_list->PathFillConvex(col, flags); + x += sz + spacing; + + // Path + draw_list->PathArcTo({ x + half_sz, y + half_sz }, half_sz, pi * -0.5f, pi); + draw_list->PathFillConvex(col); + x += sz + spacing; + + // Quadratic Bezier Curve (3 control points) + draw_list->PathLineTo({ x + cp3[0].x, y + cp3[0].y }); + draw_list->PathBezierQuadraticCurveTo({ x + cp3[1].x, y + cp3[1].y }, { x + cp3[2].x, y + cp3[2].y }, curve_segments); + draw_list->PathFillConvex(col); + x += sz + spacing; + + if (!(draw_fill && draw_strokes)) //-V560 + { + draw_list->AddRectFilledMultiColor({ x, y }, { x + sz, y + sz }, IM_COL32(0, 0, 0, 255), IM_COL32(255, 0, 0, 255), IM_COL32(255, 255, 0, 255), IM_COL32(0, 255, 0, 255)); + x += sz + spacing; + } } - x += sz + spacing; + if (draw_fill && draw_strokes) + x = start_pos.x; + if (draw_strokes) + { + // STROKES + // First row uses a thickness of 1.0f, second row uses the configurable thickness + const ImU32 col = ImColor(colf_stroke); + float th = (row == 0) ? 1.0f : thickness; + draw_list->AddNgon({ x + half_sz, y + half_sz }, half_sz, col, ngon_sides, th, flags); x += sz + spacing; // N-gon + draw_list->AddCircle({ x + half_sz, y + half_sz }, half_sz, col, circle_segments, th, flags); x += sz + spacing; // Circle + draw_list->AddEllipse({ x + half_sz, y + half_sz }, { sz * 0.5f, sz * 0.3f }, col, -0.3f, circle_segments, th, flags); x += sz + spacing; // Ellipse + draw_list->AddRect({ x, y }, { x + sz, y + sz }, col, 0.0f, th, flags); x += sz + spacing; // Square + draw_list->AddRect({ x, y }, { x + sz, y + sz }, col, rounding, th, flags); x += sz + spacing; // Square with all rounded corners + draw_list->AddRect({ x, y }, { x + sz, y + sz }, col, rounding, th, flags | corners_tl_br); x += sz + spacing; // Square with two rounded corners + draw_list->AddTriangle({ x + sz * 0.5f, y }, { x + sz, y + sz }, { x, y + sz }, col, th, flags); x += sz + spacing; // Triangle + draw_list->AddTriangle({ x + sz * 0.2f, y }, { x + sz * 0.4f, y + sz }, { x, y + sz }, col, th, flags); x += (float)(int)(sz * 0.4f) + spacing; // Thin triangle + PathConcaveShape(draw_list, x, y, sz); draw_list->PathStroke(col, th, flags | ImDrawFlags_Closed); x += sz + spacing; // Concave Shape + //draw_list->AddPolyline(concave_shape, IM_COUNTOF(concave_shape), col, ImDrawFlags_Closed, th); - // Path - draw_list->PathArcTo(ImVec2(x + sz*0.5f, y + sz*0.5f), sz*0.5f, 3.141592f * -0.5f, 3.141592f); - draw_list->PathStroke(col, th, flags); - x += sz + spacing; + draw_list->AddLineH(x, x + sz, y, col, th, flags); // Horizontal line + draw_list->AddLine({ x, y + half_szi }, { x + sz, y + half_szi }, col, th, flags); + x += sz + spacing; - // Quadratic Bezier Curve (3 control points) - draw_list->AddBezierQuadratic(ImVec2(x + cp3[0].x, y + cp3[0].y), ImVec2(x + cp3[1].x, y + cp3[1].y), ImVec2(x + cp3[2].x, y + cp3[2].y), col, th, curve_segments, flags); - x += sz + spacing; + draw_list->AddLineV(x, y, y + sz, col, th, flags); // Vertical line (note: drawing a filled rectangle will be faster!) + draw_list->AddLine({ x + half_szi, y + sz }, { x + half_szi, y }, col, th, flags); + x += half_sz + spacing; - // Cubic Bezier Curve (4 control points) - draw_list->AddBezierCubic(ImVec2(x + cp4[0].x, y + cp4[0].y), ImVec2(x + cp4[1].x, y + cp4[1].y), ImVec2(x + cp4[2].x, y + cp4[2].y), ImVec2(x + cp4[3].x, y + cp4[3].y), col, th, curve_segments, flags); + if (!(draw_fill && draw_strokes)) //-V560 + draw_list->AddLine({ x, y }, { x + sz, y + sz }, col, th, flags); x += sz + spacing; // Diagonal line - x = p.x + 4; + // Rotating square + for (int n = 0; n < 4; n++) + draw_list->PathLineTo({ x + half_sz + rotating_square[n].x, y + half_sz + rotating_square[n].y }); + draw_list->PathStroke(col, flags | ImDrawFlags_Closed, th); + //for (int n = 0; n < 4; n++) // ...made of individual line + // draw_list->AddLine({ x + half_sz + rotating_square[n].x, y + half_sz + rotating_square[n].y }, { x + half_sz + rotating_square[(n + 1) & 3].x, y + half_sz + rotating_square[(n + 1) & 3].y }, col, th, flags); + x += sz + spacing; + + // Path + draw_list->PathArcTo({ x + half_sz, y + half_sz }, half_sz, pi * -0.5f, pi); + draw_list->PathStroke(col, th, flags); + x += sz + spacing; + + // Quadratic Bezier Curve (3 control points) + draw_list->AddBezierQuadratic({ x + cp3[0].x, y + cp3[0].y }, { x + cp3[1].x, y + cp3[1].y }, { x + cp3[2].x, y + cp3[2].y }, col, th, curve_segments, flags); + x += sz + spacing; + + // Cubic Bezier Curve (4 control points) + if (!(draw_fill && draw_strokes)) //-V560 + { + draw_list->AddBezierCubic({ x + cp4[0].x, y + cp4[0].y }, { x + cp4[1].x, y + cp4[1].y }, { x + cp4[2].x, y + cp4[2].y }, { x + cp4[3].x, y + cp4[3].y }, col, th, curve_segments, flags); + x += sz + spacing; + } + } y += sz + spacing; } - // Filled shapes - draw_list->AddNgonFilled(ImVec2(x + sz * 0.5f, y + sz * 0.5f), sz * 0.5f, col, ngon_sides); x += sz + spacing; // N-gon - draw_list->AddCircleFilled(ImVec2(x + sz * 0.5f, y + sz * 0.5f), sz * 0.5f, col, circle_segments); x += sz + spacing; // Circle - draw_list->AddEllipseFilled(ImVec2(x + sz * 0.5f, y + sz * 0.5f), ImVec2(sz * 0.5f, sz * 0.3f), col, -0.3f, circle_segments); x += sz + spacing;// Ellipse - draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + sz), col); x += sz + spacing; // Square - draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 10.0f); x += sz + spacing; // Square with all rounded corners - draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + sz), col, 10.0f, corners_tl_br); x += sz + spacing; // Square with two rounded corners - draw_list->AddTriangleFilled(ImVec2(x+sz*0.5f,y), ImVec2(x+sz, y+sz-0.5f), ImVec2(x, y+sz-0.5f), col); x += sz + spacing; // Triangle - //draw_list->AddTriangleFilled(ImVec2(x+sz*0.2f,y), ImVec2(x, y+sz-0.5f), ImVec2(x+sz*0.4f, y+sz-0.5f), col); x += sz*0.4f + spacing; // Thin triangle - PathConcaveShape(draw_list, x, y, sz); draw_list->PathFillConcave(col); x += sz + spacing; // Concave shape - draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + sz, y + thickness), col); x += sz + spacing; // Horizontal line (faster than AddLine, but only handle integer thickness) - draw_list->AddRectFilled(ImVec2(x, y), ImVec2(x + thickness, y + sz), col); x += spacing; // Vertical line (faster than AddLine, but only handle integer thickness) - x += sz + spacing; - x += spacing; - draw_list->AddRectFilled(ImVec2(x + 0, y), ImVec2(x + 0 + 1, y + 1), col); // Pixels (faster than AddLine) - draw_list->AddRectFilled(ImVec2(x + 2, y), ImVec2(x + 2 + 2, y + 2), col); - draw_list->AddRectFilled(ImVec2(x + 5, y), ImVec2(x + 5 + 3, y + 3), col); - x += spacing + sz; - - // Path - draw_list->PathArcTo(ImVec2(x + sz * 0.5f, y + sz * 0.5f), sz * 0.5f, 3.141592f * -0.5f, 3.141592f); - draw_list->PathFillConvex(col); - x += sz + spacing; - - // Quadratic Bezier Curve (3 control points) - draw_list->PathLineTo(ImVec2(x + cp3[0].x, y + cp3[0].y)); - draw_list->PathBezierQuadraticCurveTo(ImVec2(x + cp3[1].x, y + cp3[1].y), ImVec2(x + cp3[2].x, y + cp3[2].y), curve_segments); - draw_list->PathFillConvex(col); - x += sz + spacing; - - draw_list->AddRectFilledMultiColor(ImVec2(x, y), ImVec2(x + sz, y + sz), IM_COL32(0, 0, 0, 255), IM_COL32(255, 0, 0, 255), IM_COL32(255, 255, 0, 255), IM_COL32(0, 255, 0, 255)); - x += sz + spacing; - - ImGui::Dummy(ImVec2((sz + spacing) * 13.2f, (sz + spacing) * 3.0f)); + ImGui::Dummy(ImVec2((sz + spacing) * 15.0f, (sz + spacing) * 4.0f)); ImGui::PopItemFlag(); ImGui::PopItemWidth(); ImGui::EndTabItem(); From e3fb7657ba6c7af876ea1f76a36598f66622004c Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 22 May 2026 16:50:57 +0200 Subject: [PATCH 034/120] DrawList: renamed ImDrawFlags_StrokeCenterPixelAligned to ImDrawFlags_StrokeCenterAligned. --- imgui.h | 2 +- imgui_demo.cpp | 2 +- imgui_draw.cpp | 28 ++++++++++++++-------------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/imgui.h b/imgui.h index 0a495a688..f17f1888e 100644 --- a/imgui.h +++ b/imgui.h @@ -3335,7 +3335,7 @@ enum ImDrawFlags_ // Stroke position relative to the shape outline ImDrawFlags_StrokeInside = 1 << 16, // Draw stroke inside of the shape outline (default for closed shapes and AddLineH, AddLineV functions) ImDrawFlags_StrokeCenter = 2 << 16, // Draw stroke at the center of the shape outline - ImDrawFlags_StrokeCenterPixelAligned = 3 << 16, // Draw stroke at the center of the shape outline, so that half thickness (rounded down) will be outside, and rest inside the shape outline. + ImDrawFlags_StrokeCenterAligned = 3 << 16, // Draw stroke at the center of the shape outline, so that half thickness (rounded down) will be outside, and rest inside the shape outline. ImDrawFlags_StrokeOutside = 4 << 16, // Draw stroke outside of the shape outline ImDrawFlags_StrokeLegacy = 5 << 16, // Use legacy positioning + enable MiterOnly and NoAAEnds flags. ImDrawFlags_StrokeMask_ = 0x07 << 16, diff --git a/imgui_demo.cpp b/imgui_demo.cpp index c3a63ac27..77b005711 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10413,7 +10413,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::RadioButton("Default", &flags, ImDrawFlags_None); ImGui::SameLine(); ImGui::RadioButton("Inside", &flags, ImDrawFlags_StrokeInside); ImGui::SameLine(); ImGui::RadioButton("Center", &flags, ImDrawFlags_StrokeCenter); ImGui::SameLine(); - ImGui::RadioButton("CenterPixelAligned", &flags, ImDrawFlags_StrokeCenterPixelAligned); ImGui::SameLine(); + ImGui::RadioButton("CenterAligned", &flags, ImDrawFlags_StrokeCenterAligned); ImGui::SameLine(); ImGui::RadioButton("Outside", &flags, ImDrawFlags_StrokeOutside); ImGui::SameLine(); ImGui::RadioButton("Legacy", &flags, ImDrawFlags_StrokeLegacy); ImGui::CheckboxFlags("MiterOnly", &flags, ImDrawFlags_MiterOnly); ImGui::SameLine(); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 400fe6067..7ed4e695f 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -813,12 +813,12 @@ void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, c } -static float CalculateCenterPixelAlignedOffset(float thickness, float _FringeScale) +static float CalculateCenterAlignedOffset(float thickness, float _FringeScale) { // Old jumpy one. // return IM_TRUNC(thickness * 0.5f / _FringeScale) * _FringeScale; - // Calculate outside offset for stroke position CenterPixelAligned + // Calculate outside offset for stroke position CenterAligned // so that one side the of the line is always at pixel boundary. // On integer thickness both sides of the line are on pixel boundary. const float screen_thickness = thickness / _FringeScale; @@ -871,8 +871,8 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ float thickness0 = (thickness + fringe) * 0.5f; // Center (or legacy) if (stroke_pos == ImDrawFlags_StrokeOutside) thickness0 = thickness + fringe * 0.5f; - else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) - thickness0 = CalculateCenterPixelAlignedOffset(thickness, _FringeScale) + fringe * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeCenterAligned) + thickness0 = CalculateCenterAlignedOffset(thickness, _FringeScale) + fringe * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeInside) thickness0 = fringe * 0.5f; float thickness1 = (thickness + fringe) - thickness0; @@ -2131,8 +2131,8 @@ void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float th float top_y = y; // Inside if (stroke_pos == ImDrawFlags_StrokeCenter) top_y -= thickness * 0.5f; - else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) - top_y -= CalculateCenterPixelAlignedOffset(thickness, _FringeScale); + else if (stroke_pos == ImDrawFlags_StrokeCenterAligned) + top_y -= CalculateCenterAlignedOffset(thickness, _FringeScale); else if (stroke_pos == ImDrawFlags_StrokeOutside) top_y -= thickness; @@ -2170,8 +2170,8 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th float left_x = x; // Inside if (stroke_pos == ImDrawFlags_StrokeCenter) left_x -= thickness * 0.5f; - else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) - left_x -= CalculateCenterPixelAlignedOffset(thickness, _FringeScale); + else if (stroke_pos == ImDrawFlags_StrokeCenterAligned) + left_x -= CalculateCenterAlignedOffset(thickness, _FringeScale); else if (stroke_pos == ImDrawFlags_StrokeOutside) left_x -= thickness; @@ -2454,8 +2454,8 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl float offset = 0.0f; // Inside if (stroke_pos == ImDrawFlags_StrokeCenter) offset = thickness * 0.5f; - else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) - offset = CalculateCenterPixelAlignedOffset(thickness, _FringeScale); + else if (stroke_pos == ImDrawFlags_StrokeCenterAligned) + offset = CalculateCenterAlignedOffset(thickness, _FringeScale); else if (stroke_pos == ImDrawFlags_StrokeOutside) offset = thickness; outer_min.x -= offset; @@ -2731,8 +2731,8 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu float outer_radius = radius; if (stroke_pos == ImDrawFlags_StrokeCenter) outer_radius = radius + thickness * 0.5f; - else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) - outer_radius = radius + CalculateCenterPixelAlignedOffset(thickness, _FringeScale); + else if (stroke_pos == ImDrawFlags_StrokeCenterAligned) + outer_radius = radius + CalculateCenterAlignedOffset(thickness, _FringeScale); else if (stroke_pos == ImDrawFlags_StrokeInside) outer_radius = radius; else if (stroke_pos == ImDrawFlags_StrokeOutside) @@ -2816,8 +2816,8 @@ void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_ float outer_radius = radius; if (stroke_pos == ImDrawFlags_StrokeCenter) outer_radius = radius + miter_thickness * 0.5f; - else if (stroke_pos == ImDrawFlags_StrokeCenterPixelAligned) - outer_radius = radius + CalculateCenterPixelAlignedOffset(miter_thickness, _FringeScale); + else if (stroke_pos == ImDrawFlags_StrokeCenterAligned) + outer_radius = radius + CalculateCenterAlignedOffset(miter_thickness, _FringeScale); else if (stroke_pos == ImDrawFlags_StrokeInside) outer_radius = radius; else if (stroke_pos == ImDrawFlags_StrokeOutside) From 4d5fcdd4baddf02754bbc045de409eefd806daac Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 22 May 2026 20:36:19 +0200 Subject: [PATCH 035/120] DrawList: Textured Round Corners: use same array for fill and strokes. Feels like a little less book-keeping. --- imgui.h | 11 +++++------ imgui_demo.cpp | 2 +- imgui_draw.cpp | 22 ++++++++++------------ imgui_internal.h | 3 +-- 4 files changed, 17 insertions(+), 21 deletions(-) diff --git a/imgui.h b/imgui.h index f17f1888e..96cdaf0c9 100644 --- a/imgui.h +++ b/imgui.h @@ -3204,15 +3204,15 @@ struct ImGuiSelectionExternalStorage // The maximum line width to bake anti-aliased textures for. Build atlas with ImFontAtlasFlags_NoBakedLines to disable baking. #ifndef IM_DRAWLIST_TEX_LINES_WIDTH_MAX -#define IM_DRAWLIST_TEX_LINES_WIDTH_MAX (32) -#define IM_DRAWLIST_TEX_LINE_FRACT_WIDTH_MAX (128) +#define IM_DRAWLIST_TEX_LINES_WIDTH_MAX (32) +#define IM_DRAWLIST_TEX_LINE_FRACT_WIDTH_MAX (128) #endif #ifndef IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX -#define IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX (16) +#define IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX (16) #endif #ifndef IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX -#define IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX (3) +#define IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX (4) // 0: fill, 1-3: strokes thickness #endif // ImDrawIdx: vertex index. [Compile-time configurable type] @@ -3901,8 +3901,7 @@ struct ImFontAtlas ImVector Sources; // Source/configuration data ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines ImVec4 TexUvLineFract; // UVs for fraction thickness baked anti-aliased lines - ImVec4 TexUvCornerFills[IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX]; // UVs for baked anti-aliased corners - ImVec4 TexUvCornerStrokes[IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX * IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX]; // UVs for baked anti-aliased corners + ImVec4 TexUvCorners[IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX * IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX]; // UVs for baked anti-aliased corners (0= fill, 1> stroke thickness) int TexNextUniqueID; // Next value to be stored in TexData->UniqueID int FontNextUniqueID; // Next value to be stored in ImFont->FontID ImVector DrawListSharedDatas; // List of users for this atlas. Typically one per Dear ImGui context. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 77b005711..76da3ec53 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10525,7 +10525,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) // Rotating square for (int n = 0; n < 4; n++) draw_list->PathLineTo({ x + half_sz + rotating_square[n].x, y + half_sz + rotating_square[n].y }); - draw_list->PathStroke(col, flags | ImDrawFlags_Closed, th); + draw_list->PathStroke(col, th, flags | ImDrawFlags_Closed); //for (int n = 0; n < 4; n++) // ...made of individual line // draw_list->AddLine({ x + half_sz + rotating_square[n].x, y + half_sz + rotating_square[n].y }, { x + half_sz + rotating_square[(n + 1) & 3].x, y + half_sz + rotating_square[(n + 1) & 3].y }, col, th, flags); x += sz + spacing; diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 7ed4e695f..f30c378a0 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2523,9 +2523,9 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl // Pixel aligned rect with round corners rendered using baked textures. IM_ASSERT_PARANOID(!(_Data->Font->OwnerAtlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners)); const int size = ImMax(s_rounding, s_thickness); // This is matching the baking calculations. - const int idx = (s_thickness - 1) * IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX + s_rounding - 1; + const int idx = (s_thickness * IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) + s_rounding - 1; IM_ASSERT_PARANOID(idx >= 0 && idx < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX * IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX); - const ImVec4 tex_uvs = _Data->TexUvCornerStrokes[idx]; + const ImVec4 tex_uvs = _Data->TexUvCorners[idx]; _AddRectBaked(outer_min, outer_max, col, (float)size * _FringeScale, (float)thickness, tex_uvs, flags); return; } @@ -2633,9 +2633,9 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c { IM_ASSERT_PARANOID(!(_Data->Font->OwnerAtlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners)); const int size = ImMax(2, s_rounding); // This is matching the baking calculations. - const int idx = s_rounding - 1; + const int idx = (s_rounding - 1); IM_ASSERT_PARANOID(idx >= 0 && idx < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX); - ImVec4 tex_uvs = _Data->TexUvCornerFills[idx]; + ImVec4 tex_uvs = _Data->TexUvCorners[idx]; _AddRectFilledBaked(p_min, p_max, col, (float)size * _FringeScale, tex_uvs, flags); } else @@ -4867,13 +4867,13 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) pack_size.x = ImMax(pack_size.x, row_width); pack_size.y += row_height; // Rounded corners - for (int t = 0; t < IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX; t++) + for (int thickness = 1; thickness < IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX; thickness++) { row_width = 0; row_height = 0; for (int i = 0; i < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX; i++) { - const int size = ImMax(2, ImMax(i+1, t+1)) + 2; + const int size = ImMax(2, ImMax(i + 1, thickness)) + 2; row_width += size; row_height = ImMax(row_height, size); } @@ -4929,7 +4929,7 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) // Refresh UV coordinates ImVec2 uv0 = ImVec2((float)(x + 1), (float)(y + 1)) * atlas->TexUvScale; ImVec2 uv1 = ImVec2((float)(x + 1 + s), (float)(y + 1 + s)) * atlas->TexUvScale; - atlas->TexUvCornerFills[n] = ImVec4(uv0.x, uv0.y, uv1.x, uv1.y); + atlas->TexUvCorners[n] = ImVec4(uv0.x, uv0.y, uv1.x, uv1.y); x += w; row_height = ImMax(row_height, h); @@ -4937,14 +4937,13 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) y += row_height; // Stroked - for (int t = 0; t < IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX; t++) + for (int thickness = 1; thickness < IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX; thickness++) { x = r.x; row_height = 0; for (int n = 0; n < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX; n++) { const int rounding = n+1; - const int thickness = t+1; const int s = ImMax(rounding, thickness); const int w = s + 2; const int h = s + 2; @@ -4984,7 +4983,7 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) // Refresh UV coordinates ImVec2 uv0 = ImVec2((float)(x + 1), (float)(y + 1)) * atlas->TexUvScale; ImVec2 uv1 = ImVec2((float)(x + 1 + s), (float)(y + 1 + s)) * atlas->TexUvScale; - atlas->TexUvCornerStrokes[t*IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX + n] = ImVec4(uv0.x, uv0.y, uv1.x, uv1.y); + atlas->TexUvCorners[thickness * IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX + n] = ImVec4(uv0.x, uv0.y, uv1.x, uv1.y); x += w; row_height = ImMax(row_height, h); @@ -5467,8 +5466,7 @@ void ImFontAtlasUpdateDrawListsSharedData(ImFontAtlas* atlas) shared_data->TexUvWhitePixel = atlas->TexUvWhitePixel; shared_data->TexUvLines = atlas->TexUvLines; shared_data->TexUvLineFract = atlas->TexUvLineFract; - shared_data->TexUvCornerFills = atlas->TexUvCornerFills; - shared_data->TexUvCornerStrokes = atlas->TexUvCornerStrokes; + shared_data->TexUvCorners = atlas->TexUvCorners; } } diff --git a/imgui_internal.h b/imgui_internal.h index 5d63fe902..fb8e37441 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -926,8 +926,7 @@ struct IMGUI_API ImDrawListSharedData ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas (== FontAtlas->TexUvWhitePixel) const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas (== FontAtlas->TexUvLines) ImVec4 TexUvLineFract; // UV of fractional anti-aliased lines in the atlas (== FontAtlas->TexUvLineFract) - const ImVec4* TexUvCornerFills; // UV of rounded corner (== FontAtlas->TexUvCornerFills) - const ImVec4* TexUvCornerStrokes; + const ImVec4* TexUvCorners; // UV of rounded corner (== FontAtlas->TexUvCorners) ImFontAtlas* FontAtlas; // Current font atlas ImFont* Font; // Current font (used for simplified AddText overload) float FontSize; // Current font size (used for simplified AddText overload) From 81f316a3bf566c651944f3226500746fe464525f Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Mon, 25 May 2026 10:40:13 +0300 Subject: [PATCH 036/120] DrawList: fixed and optimized _AddRectTinyRounding. - fixed bug where fringe size was not properly applied in _AddRectTinyRounding - optimized _AddRectTinyRounding to use less vertices - changed _AddRectTinyRounding to use similar texturing logic as lines - remove unused fractional line width texture --- imgui.h | 2 - imgui_draw.cpp | 143 ++++++++++++++++++++--------------------------- imgui_internal.h | 1 - 3 files changed, 62 insertions(+), 84 deletions(-) diff --git a/imgui.h b/imgui.h index 96cdaf0c9..dbecdca92 100644 --- a/imgui.h +++ b/imgui.h @@ -3205,7 +3205,6 @@ struct ImGuiSelectionExternalStorage // The maximum line width to bake anti-aliased textures for. Build atlas with ImFontAtlasFlags_NoBakedLines to disable baking. #ifndef IM_DRAWLIST_TEX_LINES_WIDTH_MAX #define IM_DRAWLIST_TEX_LINES_WIDTH_MAX (32) -#define IM_DRAWLIST_TEX_LINE_FRACT_WIDTH_MAX (128) #endif #ifndef IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX @@ -3900,7 +3899,6 @@ struct ImFontAtlas ImVector Fonts; // Hold all the fonts returned by AddFont*. Fonts[0] is the default font upon calling ImGui::NewFrame(), use ImGui::PushFont()/PopFont() to change the current font. ImVector Sources; // Source/configuration data ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines - ImVec4 TexUvLineFract; // UVs for fraction thickness baked anti-aliased lines ImVec4 TexUvCorners[IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX * IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX]; // UVs for baked anti-aliased corners (0= fill, 1> stroke thickness) int TexNextUniqueID; // Next value to be stored in TexData->UniqueID int FontNextUniqueID; // Next value to be stored in ImFont->FontID diff --git a/imgui_draw.cpp b/imgui_draw.cpp index f30c378a0..3ea9192eb 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2316,96 +2316,116 @@ void ImDrawList::_AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 c // The stroke is positioned inside the rectangle. void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags) { - // Note: this is smaller than in AddPolyline(), since the stem offset can be biased all the way to one side. - const float max_width = IM_DRAWLIST_TEX_LINE_FRACT_WIDTH_MAX * _FringeScale; - thickness = ImMin(thickness, max_width); + float screen_thickness = thickness / _FringeScale; + if (screen_thickness < 1.0f / 255.0f) + return; + if (screen_thickness < 1.0f) + { + col = ImAlphaMultiply(col, screen_thickness); + screen_thickness = 1.0f; + thickness = _FringeScale; + } - thickness += _FringeScale; + // Bias the texture selection towards higher resolution to avoid visual pops when the texture change. + const int texture_idx = ImClamp((int)(screen_thickness + 0.95f), 1, IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1); + const float fringe = _FringeScale * (screen_thickness / (float)texture_idx); // Scale the fringe to cover the discrepancy between the texture and requested size. + const ImVec4 tex_uvs = _Data->TexUvLines[texture_idx]; const int arc_step = ImClamp(IM_DRAWLIST_ARCFAST_SAMPLE_MAX / this->_CalcCircleAutoSegmentCount(rounding), 1, IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4); const int arc_point_count = (IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4) / arc_step + 1; - int vtx_count = (4 + arc_point_count) * 4 + 4; + int vtx_count = (4 + arc_point_count) * 3 + 3; int idx_count = (4 + arc_point_count - 1) * 4 * 3; PrimReserve(idx_count, vtx_count); ImDrawVert* start_vtx_ptr = _VtxWritePtr; ImDrawIdx* start_idx_ptr = _IdxWritePtr; - const ImVec2 corner_pos[4] = { - ImVec2(p_min.x, p_min.y), - ImVec2(p_max.x, p_min.y), - ImVec2(p_max.x, p_max.y), - ImVec2(p_min.x, p_max.y) - }; static const ImVec2 corner_offset[4] = { {1,1}, {-1,1}, {-1,-1}, {1,-1} }; static const ImDrawFlags corner_flags[4] = { ImDrawFlags_RoundCornersTopLeft, ImDrawFlags_RoundCornersTopRight, ImDrawFlags_RoundCornersBottomRight, ImDrawFlags_RoundCornersBottomLeft }; + const float half_fringe = fringe * 0.5f; + const ImVec2 corner_pos[4] = { + ImVec2(p_min.x - half_fringe, p_min.y - half_fringe), + ImVec2(p_max.x + half_fringe, p_min.y - half_fringe), + ImVec2(p_max.x + half_fringe, p_max.y + half_fringe), + ImVec2(p_min.x - half_fringe, p_max.y + half_fringe) + }; + thickness += fringe; + rounding += half_fringe; + // We need to stem offset to be atleast fringe scale away, to maintain the fringe size. const float stem_offset = thickness - ImMax(thickness - rounding, _FringeScale); - const ImVec4 tex_uvs = _Data->TexUvLineFract; // TODO: max uv - const ImVec2 outer_uv0(tex_uvs.x + (0.5f / _FringeScale) * _Data->FontAtlas->TexUvScale.x, tex_uvs.y); - const ImVec2 outer_uv1(tex_uvs.x + ((0.5f + stem_offset) / _FringeScale) * _Data->FontAtlas->TexUvScale.x, tex_uvs.y); - const ImVec2 inner_uv0(tex_uvs.x + (0.5f / _FringeScale) * _Data->FontAtlas->TexUvScale.x, tex_uvs.y); - const ImVec2 inner_uv1(tex_uvs.x + ((0.5f + thickness - stem_offset) / _FringeScale) * _Data->FontAtlas->TexUvScale.x, tex_uvs.y); + const float half_texel = 0.5f * _Data->FontAtlas->TexUvScale.x; + const float ratio = stem_offset / thickness; + ImVec2 inner_uv, outer_uv, stem_uv; + inner_uv.x = tex_uvs.x + half_texel; + inner_uv.y = tex_uvs.y; + outer_uv.x = tex_uvs.z - half_texel; + outer_uv.y = tex_uvs.y; + stem_uv.x = inner_uv.x + (outer_uv.x - inner_uv.x) * ratio; + stem_uv.y = tex_uvs.y; + // Previous vertices, copied from the last vertices. int base_idx = (int)_VtxCurrentIdx; - IM_APPEND_VTX(0, 0, outer_uv0, col); - IM_APPEND_VTX(0, 0, outer_uv1, col); - IM_APPEND_VTX(0, 0, inner_uv1, col); - IM_APPEND_VTX(0, 0, inner_uv0, col); + IM_APPEND_VTX(0, 0, inner_uv, col); + IM_APPEND_VTX(0, 0, stem_uv, col); + IM_APPEND_VTX(0, 0, outer_uv, col); + + ImVec2 outer, stem, inner, center; for (int corner = 0; corner < 4; corner++) { - const ImVec2 stem = corner_pos[corner] + corner_offset[corner] * stem_offset; - const ImVec2 inner = corner_pos[corner] + corner_offset[corner] * thickness; + outer = corner_pos[corner]; + stem.x = corner_pos[corner].x + corner_offset[corner].x * stem_offset; + stem.y = corner_pos[corner].y + corner_offset[corner].y * stem_offset; + inner.x = corner_pos[corner].x + corner_offset[corner].x * thickness; + inner.y = corner_pos[corner].y + corner_offset[corner].y * thickness; + const bool is_rounded = flags & corner_flags[corner]; const int arc_idx = (int)_VtxCurrentIdx; if (is_rounded) { - const ImVec2 center = corner_pos[corner] + corner_offset[corner] * rounding; + center.x = outer.x + corner_offset[corner].x * rounding; + center.y = outer.y + corner_offset[corner].y * rounding; int a = (IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4) * corner; for (int i = 0; i < arc_point_count; i++) { - const ImVec2 dir = _Data->ArcFastVtx[a]; - IM_APPEND_VTX(center.x - dir.x * rounding, center.y - dir.y * rounding, outer_uv0, col); + IM_APPEND_VTX(center.x - _Data->ArcFastVtx[a].x * rounding, center.y - _Data->ArcFastVtx[a].y * rounding, inner_uv, col); a = (a + arc_step) % IM_DRAWLIST_ARCFAST_TABLE_SIZE; } } else { - IM_APPEND_VTX(corner_pos[corner].x, corner_pos[corner].y, outer_uv0, col); + IM_APPEND_VTX(outer.x, outer.y, inner_uv, col); } - const int stem0_idx = (int)_VtxCurrentIdx; - IM_APPEND_VTX(stem.x, stem.y, outer_uv1, col); - const int stem1_idx = (int)_VtxCurrentIdx; - IM_APPEND_VTX(stem.x, stem.y, inner_uv1, col); + const int stem_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(stem.x, stem.y, stem_uv, col); const int inner_idx = (int)_VtxCurrentIdx; - IM_APPEND_VTX(inner.x, inner.y, inner_uv0, col); + IM_APPEND_VTX(inner.x, inner.y, outer_uv, col); // Arc if (is_rounded) { - for (int i = 0; i < arc_point_count-1; i++) - IM_APPEND_TRI(stem0_idx, arc_idx + i, arc_idx + i + 1); + for (int i = 0; i < arc_point_count - 1; i++) + IM_APPEND_TRI(stem_idx, arc_idx + i, arc_idx + i + 1); } // Connect with previous - IM_APPEND_TRI(base_idx + 0, arc_idx, stem0_idx); - IM_APPEND_TRI(base_idx + 0, stem0_idx, base_idx + 1); - IM_APPEND_TRI(base_idx + 2, stem1_idx, inner_idx); - IM_APPEND_TRI(base_idx + 2, inner_idx, base_idx + 3); + IM_APPEND_TRI(base_idx + 0, arc_idx, stem_idx); + IM_APPEND_TRI(base_idx + 0, stem_idx, base_idx + 1); + IM_APPEND_TRI(base_idx + 1, stem_idx, inner_idx); + IM_APPEND_TRI(base_idx + 1, inner_idx, base_idx + 2); - base_idx = stem0_idx - 1; + base_idx = stem_idx - 1; } // Close - start_vtx_ptr[0] = VtxBuffer.Data[base_idx+0]; - start_vtx_ptr[1] = VtxBuffer.Data[base_idx+1]; - start_vtx_ptr[2] = VtxBuffer.Data[base_idx+2]; - start_vtx_ptr[3] = VtxBuffer.Data[base_idx+3]; + start_vtx_ptr[0] = VtxBuffer.Data[base_idx + 0]; + start_vtx_ptr[1] = VtxBuffer.Data[base_idx + 1]; + start_vtx_ptr[2] = VtxBuffer.Data[base_idx + 2]; const int idx_used = (int)(_IdxWritePtr - start_idx_ptr); const int vtx_used = (int)(_VtxWritePtr - start_vtx_ptr); @@ -5054,44 +5074,6 @@ static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts atlas->TexUvLines[n] = ImVec4(uv0.x, half_v, uv1.x, half_v); } - - // Single larger line for fraction width textured lines. - // Alternative solution could be to fix while and black pixel next to the texture border - // and use the texture clamping to expand the opaque pixel. - // : - // [*][ ] - // : - // <-- width would expand out of the texture expanding the while pixel to as far as needed. - add_and_draw = atlas->GetCustomRect(builder->PackIdLineFractTexData, &r) == false; - if (add_and_draw) - { - ImVec2i pack_size = ImVec2i(IM_DRAWLIST_TEX_LINE_FRACT_WIDTH_MAX + 1, 1); - builder->PackIdLineFractTexData = atlas->AddCustomRect(pack_size.x, pack_size.y, &r); - IM_ASSERT(builder->PackIdLineFractTexData != ImFontAtlasRectId_Invalid); - } - - // Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle - // Write each slice - if (add_and_draw && tex->Format == ImTextureFormat_Alpha8) - { - ImU8* write_ptr = (ImU8*)tex->GetPixelsAt(r.x, r.y); - *write_ptr++ = 0x00; - for (int i = 0; i < IM_DRAWLIST_TEX_LINE_FRACT_WIDTH_MAX; i++) - *write_ptr++ = 0xFF; - } - else if (add_and_draw && tex->Format == ImTextureFormat_RGBA32) - { - ImU32* write_ptr = (ImU32*)(void*)tex->GetPixelsAt(r.x, r.y); - *write_ptr++ = IM_COL32(255, 255, 255, 0); - for (int i = 0; i < IM_DRAWLIST_TEX_LINE_FRACT_WIDTH_MAX; i++) - *write_ptr++ = IM_COL32_WHITE; - } - - // Refresh UV coordinates - ImVec2 uv0 = ImVec2((float)r.x, (float)r.y) * atlas->TexUvScale; - ImVec2 uv1 = ImVec2((float)r.x + 1.0f + IM_DRAWLIST_TEX_LINE_FRACT_WIDTH_MAX, (float)r.y) * atlas->TexUvScale; - float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts - atlas->TexUvLineFract = ImVec4(uv0.x, half_v, uv1.x, half_v); } //----------------------------------------------------------------------------------------------------------------------------- @@ -5465,7 +5447,6 @@ void ImFontAtlasUpdateDrawListsSharedData(ImFontAtlas* atlas) { shared_data->TexUvWhitePixel = atlas->TexUvWhitePixel; shared_data->TexUvLines = atlas->TexUvLines; - shared_data->TexUvLineFract = atlas->TexUvLineFract; shared_data->TexUvCorners = atlas->TexUvCorners; } } diff --git a/imgui_internal.h b/imgui_internal.h index fb8e37441..d632533f3 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -925,7 +925,6 @@ struct IMGUI_API ImDrawListSharedData { ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas (== FontAtlas->TexUvWhitePixel) const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas (== FontAtlas->TexUvLines) - ImVec4 TexUvLineFract; // UV of fractional anti-aliased lines in the atlas (== FontAtlas->TexUvLineFract) const ImVec4* TexUvCorners; // UV of rounded corner (== FontAtlas->TexUvCorners) ImFontAtlas* FontAtlas; // Current font atlas ImFont* Font; // Current font (used for simplified AddText overload) From 3f7da1e184334b5210bb81c05b230b1a521bac67 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Tue, 26 May 2026 15:48:28 +0300 Subject: [PATCH 037/120] DrawList: improved shape drawing input validation for thin shapes and small thickness. --- imgui_draw.cpp | 82 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 76 insertions(+), 6 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 3ea9192eb..3812880d3 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2138,6 +2138,16 @@ void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float th if (_FringeScaleIsInteger && ImIsTruncated4(min_x, max_x, top_y, thickness) && (flags & ImDrawFlags_SquareCap) == 0) { + float screen_thickness = thickness / _FringeScale; + if (screen_thickness < 1.f / 255.f) + return; + if (screen_thickness < 1.f) + { + col = ImAlphaMultiply(col, screen_thickness); + screen_thickness = 1.f; + thickness = _FringeScale; + } + // For pixel aligned case, use simple rectangle. PrimReserve(6, 4); PrimRect(ImVec2(min_x, top_y), ImVec2(max_x, top_y + thickness), col); @@ -2177,6 +2187,16 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th if (_FringeScaleIsInteger && ImIsTruncated4(left_x, min_y, max_y, thickness) && (flags & ImDrawFlags_SquareCap) == 0) { + float screen_thickness = thickness / _FringeScale; + if (screen_thickness < 1.f / 255.f) + return; + if (screen_thickness < 1.f) + { + col = ImAlphaMultiply(col, screen_thickness); + screen_thickness = 1.f; + thickness = _FringeScale; + } + // For pixel aligned case, use simple rectangle. PrimReserve(6, 4); PrimRect(ImVec2(left_x, min_y), ImVec2(left_x + thickness, max_y), col); @@ -2506,6 +2526,8 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl { int s_rounding = (int)(outer_rounding / _FringeScale); int s_thickness = (int)(thickness / _FringeScale); + if (s_thickness <= 0) + return; if (s_rounding <= 0 || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) { @@ -2634,14 +2656,35 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c if ((col & IM_COL32_A_MASK) == 0) return; + ImVec2 b_min = p_min; + ImVec2 b_max = p_max; + const float width = b_max.x - b_min.x; + const float height = b_max.y - b_min.y; + + const float inv_fringe_scale = 1.f / _FringeScale; + const float screen_width = width * inv_fringe_scale; + const float screen_height = height * inv_fringe_scale; + + if (ImMin(screen_width, screen_height) < 1.f / 255.f) + return; + if (screen_width < 1.f) + { + col = ImAlphaMultiply(col, screen_width); + b_max.x = b_min.x + _FringeScale; + } + if (screen_height < 1.f) + { + col = ImAlphaMultiply(col, screen_height); + b_max.y = b_min.y + _FringeScale; + } + if (ImIsTruncated4(p_min.x, p_min.y, p_max.x, p_max.y) && ImIsTruncated(rounding)) { if ((flags & ImDrawFlags_RoundCornersMask_) == 0) flags |= ImDrawFlags_RoundCornersAll; - rounding = ImMin(rounding, ImFabs(p_max.x - p_min.x) * (((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f) - 1.0f); - rounding = ImMin(rounding, ImFabs(p_max.y - p_min.y) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f); - + rounding = ImMin(rounding, ImFabs(width) * (((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f) - 1.0f); + rounding = ImMin(rounding, ImFabs(height) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f); const int s_rounding = (int)(rounding / _FringeScale); if (s_rounding <= 0 || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) @@ -2738,7 +2781,7 @@ void ImDrawList::AddTriangleFilled(const ImVec2& p1, const ImVec2& p2, const ImV void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness, ImDrawFlags flags) { - if ((col & IM_COL32_A_MASK) == 0 || radius < 0.01f) + if ((col & IM_COL32_A_MASK) == 0) return; ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); @@ -2748,6 +2791,8 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu stroke_pos = ImDrawFlags_StrokeCenter; } + radius = ImMax(0.01f, radius); + float outer_radius = radius; if (stroke_pos == ImDrawFlags_StrokeCenter) outer_radius = radius + thickness * 0.5f; @@ -2830,6 +2875,8 @@ void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_ stroke_pos = ImDrawFlags_StrokeCenter; } + radius = ImMax(0.01f, radius); + const float unit_apothem = ImCos(IM_PI / (float)num_segments); const float miter_thickness = thickness / unit_apothem; @@ -2889,14 +2936,18 @@ void ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 co stroke_pos = ImDrawFlags_StrokeCenter; } + ImVec2 rad = radius; + rad.x = ImMax(0.01f, rad.x); + rad.y = ImMax(0.01f, rad.y); + if (num_segments <= 0) - num_segments = _CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)); // A bit pessimistic, maybe there's a better computation to do here. + num_segments = _CalcCircleAutoSegmentCount(ImMax(rad.x, rad.y)); // A bit pessimistic, maybe there's a better computation to do here. _Path.reserve(_Path.Size + (num_segments + 1)); // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = IM_PI * 2.0f * ((float)num_segments - 1.0f) / (float)num_segments; - PathEllipticalArcTo(center, radius, rot, 0.0f, a_max, num_segments - 1); + PathEllipticalArcTo(center, rad, rot, 0.0f, a_max, num_segments - 1); PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | stroke_pos); } @@ -2905,6 +2956,25 @@ void ImDrawList::AddEllipseFilled(const ImVec2& center, const ImVec2& radius, Im if ((col & IM_COL32_A_MASK) == 0) return; + ImVec2 rad = radius; + + const float inv_fringe_scale = 1.f / _FringeScale; + float screen_diameter_x = rad.x* 2.f * inv_fringe_scale; + float screen_diameter_y = rad.y * 2.f * inv_fringe_scale; + if (screen_diameter_x < 1.f / 255.f || screen_diameter_y < 1.f / 255.f) + return; + + if (screen_diameter_x < 1.f) + { + col = ImAlphaMultiply(col, screen_diameter_x); + rad.x = _FringeScale * 0.5f; + } + if (screen_diameter_y < 1.f) + { + col = ImAlphaMultiply(col, screen_diameter_y); + rad.y = _FringeScale * 0.5f; + } + if (num_segments <= 0) num_segments = _CalcCircleAutoSegmentCount(ImMax(radius.x, radius.y)); // A bit pessimistic, maybe there's a better computation to do here. From 91e769fbba0cab2f3fdd4632d6f83d6dee69c8d1 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Tue, 26 May 2026 15:52:59 +0300 Subject: [PATCH 038/120] DrawList: fixed visual pop in stroke rendering when thickness is between 1 and 2 pixels. - added animated thickness to demo to make it easier to evaluate thickness visual progression - added super sampled textures in thickness range [1..3] to avoid visual pop --- imgui.h | 7 ++- imgui_demo.cpp | 20 ++++-- imgui_draw.cpp | 165 ++++++++++++++++++++++++++++++++++++------------- 3 files changed, 142 insertions(+), 50 deletions(-) diff --git a/imgui.h b/imgui.h index dbecdca92..5c2833141 100644 --- a/imgui.h +++ b/imgui.h @@ -3207,6 +3207,10 @@ struct ImGuiSelectionExternalStorage #define IM_DRAWLIST_TEX_LINES_WIDTH_MAX (32) #endif +#ifndef IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX +#define IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX (2) // How many samples for lines between [1..2) thickness. +#endif + #ifndef IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX #define IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX (16) #endif @@ -3537,6 +3541,7 @@ struct ImDrawList IMGUI_API void _AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, float t, ImVec4 tex_uvs, ImDrawFlags flags); IMGUI_API void _AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, ImVec4 tex_uvs, float fringe); IMGUI_API void _AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags); + IMGUI_API void _SelectFringeTexture(float screen_thickness, ImVec4& tex_uvs, float& fringe); }; // All draw data to render a Dear ImGui frame @@ -3898,7 +3903,7 @@ struct ImFontAtlas ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel. May change as new texture gets created. ImVector Fonts; // Hold all the fonts returned by AddFont*. Fonts[0] is the default font upon calling ImGui::NewFrame(), use ImGui::PushFont()/PopFont() to change the current font. ImVector Sources; // Source/configuration data - ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines + ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX + 1]; // UVs for baked anti-aliased lines ImVec4 TexUvCorners[IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX * IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX]; // UVs for baked anti-aliased corners (0= fill, 1> stroke thickness) int TexNextUniqueID; // Next value to be stored in TexData->UniqueID int FontNextUniqueID; // Next value to be stored in ImFont->FontID diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 76da3ec53..bd99567e3 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10388,17 +10388,25 @@ static void ShowExampleAppCustomRendering(bool* p_open) // Draw a bunch of primitives ImGui::Text("All primitives"); static float sz = 42.0f; - static float thickness = 3.0f; - static int ngon_sides = 6; + static bool animate_thickness = false; + static float base_thickness = 3.0f; + static int ngon_segments = 6; static bool circle_segments_override = false; static int circle_segments_override_v = 12; static bool curve_segments_override = false; static int curve_segments_override_v = 8; static ImVec4 colf_stroke = ImVec4(1.00f, 1.00f, 0.40f, 1.0f); static ImVec4 colf_fill = ImVec4(0.70f, 0.18f, 0.62f, 1.0f); + const float thickness_anim_period = 5.f; + float thickness_wave = 1.0f - fabs(fmod((float)ImGui::GetTime(), thickness_anim_period) * (2.0f / thickness_anim_period) - 1.0f); + float thickness = animate_thickness ? thickness_wave * base_thickness : base_thickness; + ImGui::DragFloat("Size", &sz, 0.2f, 0.2f, 100.0f, "%.0f"); - ImGui::DragFloat("Thickness", &thickness, 0.1f, 0.0f, 30.0f, "%.02f"); - ImGui::SliderInt("N-gon sides", &ngon_sides, 3, 12); + ImGui::DragFloat("Thickness", &base_thickness, 0.1f, 0.0f, 30.0f, "%.02f"); + ImGui::Checkbox("Animate Thickness", &animate_thickness); + ImGui::SameLine(); + ImGui::Text("%.2f", thickness); + ImGui::SliderInt("N-gon sides", &ngon_segments, 3, 12); ImGui::Checkbox("##circlesegmentoverride", &circle_segments_override); ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); circle_segments_override |= ImGui::SliderInt("Circle segments override", &circle_segments_override_v, 3, 40); @@ -10450,7 +10458,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) { // FILLED SHAPES const ImU32 col = ImColor(colf_fill); - draw_list->AddNgonFilled({ x + half_sz, y + half_sz }, half_sz, col, ngon_sides); x += sz + spacing; // N-gon + draw_list->AddNgonFilled({ x + half_sz, y + half_sz }, half_sz, col, ngon_segments); x += sz + spacing; // N-gon draw_list->AddCircleFilled({ x + half_sz, y + half_sz }, half_sz, col, circle_segments); x += sz + spacing; // Circle draw_list->AddEllipseFilled({ x + half_sz, y + half_sz }, { sz * 0.5f, sz * 0.3f }, col, -0.3f, circle_segments); x += sz + spacing;// Ellipse draw_list->AddRectFilled({ x, y }, { x + sz, y + sz }, col); x += sz + spacing; // Square @@ -10500,7 +10508,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) // First row uses a thickness of 1.0f, second row uses the configurable thickness const ImU32 col = ImColor(colf_stroke); float th = (row == 0) ? 1.0f : thickness; - draw_list->AddNgon({ x + half_sz, y + half_sz }, half_sz, col, ngon_sides, th, flags); x += sz + spacing; // N-gon + draw_list->AddNgon({ x + half_sz, y + half_sz }, half_sz, col, ngon_segments, th, flags); x += sz + spacing; // N-gon draw_list->AddCircle({ x + half_sz, y + half_sz }, half_sz, col, circle_segments, th, flags); x += sz + spacing; // Circle draw_list->AddEllipse({ x + half_sz, y + half_sz }, { sz * 0.5f, sz * 0.3f }, col, -0.3f, circle_segments, th, flags); x += sz + spacing; // Ellipse draw_list->AddRect({ x, y }, { x + sz, y + sz }, col, 0.0f, th, flags); x += sz + spacing; // Square diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 3812880d3..d86534984 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1181,6 +1181,27 @@ static ImU32 ImAlphaMultiply(ImU32 col, float alpha_mul) return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT); } +void ImDrawList::_SelectFringeTexture(float screen_thickness, ImVec4& tex_uvs, float& fringe) +{ + if (screen_thickness <= 2.f) + { + // Handle the thickness between [1..2]. The texture scaling in this range will cause visual pop near 1, so we generate super sampled textures in this range. + // There IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX+1 textures, where 0 maps to 1.0 and IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX maps 2.0. + constexpr float base_width = 1.f; + const int texture_idx = ImClamp((int)((screen_thickness - base_width) * IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX + 0.5f), 0, IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX); + const float tex_width = base_width + (float)texture_idx / IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX; + fringe = _FringeScale * (screen_thickness / tex_width); // Scale the fringe to cover the discrepancy between the texture and requested size. + tex_uvs = _Data->TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + texture_idx]; + } + else + { + // Bias the texture selection towards higher resolution to avoid visual pops when the texture change. + const int texture_idx = ImClamp((int)(screen_thickness + 0.95f), 1, IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1); + fringe = _FringeScale * (screen_thickness / (float)texture_idx); // Scale the fringe to cover the discrepancy between the texture and requested size. + tex_uvs = _Data->TexUvLines[texture_idx]; + } +} + extern bool g_LEGACY_STROKES; void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags) @@ -1238,10 +1259,9 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 sqr_lengths[points_count - 1] = 0.0f; } - // Bias the texture selection towards higher resolution to avoid visual pops when the texture change. - const int texture_idx = ImClamp((int)(screen_thickness + 0.95f), 1, IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1); - const float fringe = _FringeScale * (screen_thickness / (float)texture_idx); // Scale the fringe to cover the discrepancy between the texture and requested size. - const ImVec4 tex_uvs = _Data->TexUvLines[texture_idx]; + ImVec4 tex_uvs; + float fringe; + _SelectFringeTexture(screen_thickness, tex_uvs, fringe); _AddPolyline(points, normals, sqr_lengths, points_count, col, thickness, flags, tex_uvs, fringe); } @@ -2346,10 +2366,9 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, thickness = _FringeScale; } - // Bias the texture selection towards higher resolution to avoid visual pops when the texture change. - const int texture_idx = ImClamp((int)(screen_thickness + 0.95f), 1, IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1); - const float fringe = _FringeScale * (screen_thickness / (float)texture_idx); // Scale the fringe to cover the discrepancy between the texture and requested size. - const ImVec4 tex_uvs = _Data->TexUvLines[texture_idx]; + ImVec4 tex_uvs; + float fringe; + _SelectFringeTexture(screen_thickness, tex_uvs, fringe); const int arc_step = ImClamp(IM_DRAWLIST_ARCFAST_SAMPLE_MAX / this->_CalcCircleAutoSegmentCount(rounding), 1, IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4); const int arc_point_count = (IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4) / arc_step + 1; @@ -5095,54 +5114,114 @@ static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) bool add_and_draw = atlas->GetCustomRect(builder->PackIdLinesTexData, &r) == false; if (add_and_draw) { - ImVec2i pack_size = ImVec2i(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1); + ImVec2i pack_size = ImVec2i(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX + 1); builder->PackIdLinesTexData = atlas->AddCustomRect(pack_size.x, pack_size.y, &r); IM_ASSERT(builder->PackIdLinesTexData != ImFontAtlasRectId_Invalid); } - // Register texture region for thick lines - // The +2 here is to give space for the end caps, whilst height +1 is to accommodate the fact we have a zero-width row - // This generates a triangular shape in the texture, with the various line widths stacked on top of each other to allow interpolation between them - for (int n = 0; n < IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1; n++) // +1 because of the zero-width row { - // Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle - const int y = n; - const int line_width = n; - const int pad_left = (r.w - line_width) / 2; - const int pad_right = r.w - (pad_left + line_width); - IM_ASSERT(pad_left + line_width + pad_right == r.w && y < r.h); // Make sure we're inside the texture bounds before we start writing pixels - - // Write each slice - if (add_and_draw && tex->Format == ImTextureFormat_Alpha8) + // Register texture region for thick lines + // The +2 here is to give space for the end caps, whilst height +1 is to accommodate the fact we have a zero-width row + // This generates a triangular shape in the texture, with the various line widths stacked on top of each other to allow interpolation between them + for (int n = 0; n < IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1; n++) // +1 because of the zero-width row { - ImU8* write_ptr = (ImU8*)tex->GetPixelsAt(r.x, r.y + y); - for (int i = 0; i < pad_left; i++) - *(write_ptr + i) = 0x00; + // Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle + const int y = n; + const int line_width = n; + const int pad_left = (r.w - line_width) / 2; + const int pad_right = r.w - (pad_left + line_width); + IM_ASSERT(pad_left + line_width + pad_right == r.w && y < r.h); // Make sure we're inside the texture bounds before we start writing pixels - for (int i = 0; i < line_width; i++) - *(write_ptr + pad_left + i) = 0xFF; + // Write each slice + if (add_and_draw && tex->Format == ImTextureFormat_Alpha8) + { + ImU8* write_ptr = (ImU8*)tex->GetPixelsAt(r.x, r.y + y); + for (int i = 0; i < pad_left; i++) + *(write_ptr + i) = 0x00; - for (int i = 0; i < pad_right; i++) - *(write_ptr + pad_left + line_width + i) = 0x00; + for (int i = 0; i < line_width; i++) + *(write_ptr + pad_left + i) = 0xFF; + + for (int i = 0; i < pad_right; i++) + *(write_ptr + pad_left + line_width + i) = 0x00; + } + else if (add_and_draw && tex->Format == ImTextureFormat_RGBA32) + { + ImU32* write_ptr = (ImU32*)(void*)tex->GetPixelsAt(r.x, r.y + y); + for (int i = 0; i < pad_left; i++) + *(write_ptr + i) = IM_COL32(255, 255, 255, 0); + + for (int i = 0; i < line_width; i++) + *(write_ptr + pad_left + i) = IM_COL32_WHITE; + + for (int i = 0; i < pad_right; i++) + *(write_ptr + pad_left + line_width + i) = IM_COL32(255, 255, 255, 0); + } + + // Refresh UV coordinates + ImVec2 uv0 = ImVec2((float)(r.x + pad_left - 1), (float)(r.y + y)) * atlas->TexUvScale; + ImVec2 uv1 = ImVec2((float)(r.x + pad_left + line_width + 1), (float)(r.y + y + 1)) * atlas->TexUvScale; + float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts + atlas->TexUvLines[n] = ImVec4(uv0.x, half_v, uv1.x, half_v); } - else if (add_and_draw && tex->Format == ImTextureFormat_RGBA32) + } + + { + + ImU8 ramp[IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX]; + for (int n = 0; n < IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX; n++) + ramp[n] = (ImU8)(((float)n / IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX) * 255.f); + + for (int n = 0; n < IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX + 1; n++) { - ImU32* write_ptr = (ImU32*)(void*)tex->GetPixelsAt(r.x, r.y + y); - for (int i = 0; i < pad_left; i++) - *(write_ptr + i) = IM_COL32(255, 255, 255, 0); + // Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle + const int y = IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + n; + const int line_width = IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX + n; + IM_ASSERT(IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX + line_width + IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX <= r.w && y < r.h); // Make sure we're inside the texture bounds before we start writing pixels - for (int i = 0; i < line_width; i++) - *(write_ptr + pad_left + i) = IM_COL32_WHITE; + // Write each slice + if (add_and_draw && tex->Format == ImTextureFormat_Alpha8) + { + ImU8* write_ptr = (ImU8*)tex->GetPixelsAt(r.x, r.y + y); + int idx = 0; - for (int i = 0; i < pad_right; i++) - *(write_ptr + pad_left + line_width + i) = IM_COL32(255, 255, 255, 0); + for (int i = 0; i < IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX; i++) + write_ptr[idx++] = ramp[i]; + + for (int i = 0; i < line_width; i++) + write_ptr[idx++] = 0xFF; + + for (int i = IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX-1; i >= 0; i--) + write_ptr[idx++] = ramp[i]; + + while (idx < r.w) + write_ptr[idx++] = 0x0; + } + else if (add_and_draw && tex->Format == ImTextureFormat_RGBA32) + { + ImU32* write_ptr = (ImU32*)(void*)tex->GetPixelsAt(r.x, r.y + y); + + int idx = 0; + + for (int i = 0; i < IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX; i++) + write_ptr[idx++] = IM_COL32(255, 255, 255, ramp[i]); + + for (int i = 0; i < line_width; i++) + write_ptr[idx++] = IM_COL32(255, 255, 255, 255); + + for (int i = IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX - 1; i >= 0; i--) + write_ptr[idx++] = IM_COL32(255, 255, 255, ramp[i]); + + while (idx < r.w) + write_ptr[idx++] = IM_COL32(255, 255, 255, 0); + } + + // Refresh UV coordinates + ImVec2 uv0 = ImVec2((float)(r.x), (float)(r.y + y)) * atlas->TexUvScale; + ImVec2 uv1 = ImVec2((float)(r.x + IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX * 2 + line_width), (float)(r.y + y + 1)) * atlas->TexUvScale; + float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts + atlas->TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + n] = ImVec4(uv0.x, half_v, uv1.x, half_v); } - - // Refresh UV coordinates - ImVec2 uv0 = ImVec2((float)(r.x + pad_left - 1), (float)(r.y + y)) * atlas->TexUvScale; - ImVec2 uv1 = ImVec2((float)(r.x + pad_left + line_width + 1), (float)(r.y + y + 1)) * atlas->TexUvScale; - float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts - atlas->TexUvLines[n] = ImVec4(uv0.x, half_v, uv1.x, half_v); } } From 6f00e0b39f8f4376bf005ab412bed272b563c379 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 26 May 2026 16:25:55 +0200 Subject: [PATCH 039/120] Demo: fixed using Miter/Square/NoAAends checkboxes interfering with stroke mode. warnings. Demo: update "Lines" demo to help diagnose brightness change. --- imgui_demo.cpp | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index bd99567e3..0d7d1df64 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10397,15 +10397,18 @@ static void ShowExampleAppCustomRendering(bool* p_open) static int curve_segments_override_v = 8; static ImVec4 colf_stroke = ImVec4(1.00f, 1.00f, 0.40f, 1.0f); static ImVec4 colf_fill = ImVec4(0.70f, 0.18f, 0.62f, 1.0f); - const float thickness_anim_period = 5.f; - float thickness_wave = 1.0f - fabs(fmod((float)ImGui::GetTime(), thickness_anim_period) * (2.0f / thickness_anim_period) - 1.0f); - float thickness = animate_thickness ? thickness_wave * base_thickness : base_thickness; ImGui::DragFloat("Size", &sz, 0.2f, 0.2f, 100.0f, "%.0f"); - ImGui::DragFloat("Thickness", &base_thickness, 0.1f, 0.0f, 30.0f, "%.02f"); - ImGui::Checkbox("Animate Thickness", &animate_thickness); + + const float thickness_anim_period = 5.f; + const float thickness_wave = 1.0f - fabsf(fmodf((float)ImGui::GetTime(), thickness_anim_period) * (2.0f / thickness_anim_period) - 1.0f); + const float thickness = animate_thickness ? thickness_wave * base_thickness : base_thickness; + ImGui::DragFloat("Thickness", &base_thickness, 0.02f, 0.0f, 30.0f, "%.02f"); + ImGui::SameLine(); + ImGui::Checkbox("Animate", &animate_thickness); ImGui::SameLine(); ImGui::Text("%.2f", thickness); + ImGui::SliderInt("N-gon sides", &ngon_segments, 3, 12); ImGui::Checkbox("##circlesegmentoverride", &circle_segments_override); ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); @@ -10416,18 +10419,20 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::ColorEdit4("Stroke Color", &colf_stroke.x); ImGui::ColorEdit4("Fill Color", &colf_fill.x); - static ImDrawFlags flags = ImDrawFlags_None; + static ImDrawFlags stroke_flags = ImDrawFlags_None; + static ImDrawFlags other_flags = ImDrawFlags_None; ImGui::Text("Stroke Flags:"); - ImGui::RadioButton("Default", &flags, ImDrawFlags_None); ImGui::SameLine(); - ImGui::RadioButton("Inside", &flags, ImDrawFlags_StrokeInside); ImGui::SameLine(); - ImGui::RadioButton("Center", &flags, ImDrawFlags_StrokeCenter); ImGui::SameLine(); - ImGui::RadioButton("CenterAligned", &flags, ImDrawFlags_StrokeCenterAligned); ImGui::SameLine(); - ImGui::RadioButton("Outside", &flags, ImDrawFlags_StrokeOutside); ImGui::SameLine(); - ImGui::RadioButton("Legacy", &flags, ImDrawFlags_StrokeLegacy); - ImGui::CheckboxFlags("MiterOnly", &flags, ImDrawFlags_MiterOnly); ImGui::SameLine(); - ImGui::CheckboxFlags("SquareCap", &flags, ImDrawFlags_SquareCap); ImGui::SameLine(); - ImGui::CheckboxFlags("NoAAEnds", &flags, ImDrawFlags_NoAAEnds); + ImGui::RadioButton("Default", &stroke_flags, ImDrawFlags_None); ImGui::SameLine(); + ImGui::RadioButton("Inside", &stroke_flags, ImDrawFlags_StrokeInside); ImGui::SameLine(); + ImGui::RadioButton("Center", &stroke_flags, ImDrawFlags_StrokeCenter); ImGui::SameLine(); + ImGui::RadioButton("CenterAligned", &stroke_flags, ImDrawFlags_StrokeCenterAligned); ImGui::SameLine(); + ImGui::RadioButton("Outside", &stroke_flags, ImDrawFlags_StrokeOutside); ImGui::SameLine(); + ImGui::RadioButton("Legacy", &stroke_flags, ImDrawFlags_StrokeLegacy); + ImGui::CheckboxFlags("MiterOnly", &other_flags, ImDrawFlags_MiterOnly); ImGui::SameLine(); + ImGui::CheckboxFlags("SquareCap", &other_flags, ImDrawFlags_SquareCap); ImGui::SameLine(); + ImGui::CheckboxFlags("NoAAEnds", &other_flags, ImDrawFlags_NoAAEnds); ImGui::Spacing(); + const ImDrawFlags flags = stroke_flags | other_flags; ImVec2 start_pos = ImGui::GetCursorScreenPos(); const float pi = 3.141592f; From 9f5bfdf83b518af09efc34cd34517ced505ec2db Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 26 May 2026 16:26:14 +0200 Subject: [PATCH 040/120] DrawList: AddQuad() defaults to ImDrawFlags_StrokeInside. --- imgui_draw.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index d86534984..dd22954da 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2755,6 +2755,8 @@ void ImDrawList::AddQuad(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, c if ((col & IM_COL32_A_MASK) == 0) return; + ImDrawFlags stroke_pos = g_LEGACY_STROKES ? ImDrawFlags_StrokeLegacy : _GetStrokePos(flags, ImDrawFlags_StrokeInside); + flags = (flags & ~ImDrawFlags_StrokeMask_) | stroke_pos; PathLineTo(p1); PathLineTo(p2); PathLineTo(p3); From 90e45bdde028ef9677d88b5dc9ec7b8338a72e5c Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Tue, 26 May 2026 17:37:50 +0300 Subject: [PATCH 041/120] DrawList: extend the detailed stroke textures up stroke thickness 4. --- imgui.h | 12 +++++++++--- imgui_draw.cpp | 34 +++++++++++++++++----------------- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/imgui.h b/imgui.h index 5c2833141..527147137 100644 --- a/imgui.h +++ b/imgui.h @@ -3207,10 +3207,16 @@ struct ImGuiSelectionExternalStorage #define IM_DRAWLIST_TEX_LINES_WIDTH_MAX (32) #endif -#ifndef IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX -#define IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX (2) // How many samples for lines between [1..2) thickness. +#ifndef IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH +#define IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH (4) // Calculate detailed textures for width [1..IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH] #endif +#ifndef IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT +#define IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT (2) // How many samples per integer thickness level. +#endif + +#define IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX (IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT) + #ifndef IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX #define IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX (16) #endif @@ -3903,7 +3909,7 @@ struct ImFontAtlas ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel. May change as new texture gets created. ImVector Fonts; // Hold all the fonts returned by AddFont*. Fonts[0] is the default font upon calling ImGui::NewFrame(), use ImGui::PushFont()/PopFont() to change the current font. ImVector Sources; // Source/configuration data - ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX + 1]; // UVs for baked anti-aliased lines + ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines ImVec4 TexUvCorners[IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX * IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX]; // UVs for baked anti-aliased corners (0= fill, 1> stroke thickness) int TexNextUniqueID; // Next value to be stored in TexData->UniqueID int FontNextUniqueID; // Next value to be stored in ImFont->FontID diff --git a/imgui_draw.cpp b/imgui_draw.cpp index dd22954da..944bcf6a4 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1185,18 +1185,18 @@ void ImDrawList::_SelectFringeTexture(float screen_thickness, ImVec4& tex_uvs, f { if (screen_thickness <= 2.f) { - // Handle the thickness between [1..2]. The texture scaling in this range will cause visual pop near 1, so we generate super sampled textures in this range. - // There IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX+1 textures, where 0 maps to 1.0 and IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX maps 2.0. + // Handle the thickness between [1..IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH]. The texture scaling in this range will cause slight visual pops, so we generate super sampled textures in this range. + // There are IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX+1 textures, where 0 maps to 1.0 and IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX maps to IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH. constexpr float base_width = 1.f; - const int texture_idx = ImClamp((int)((screen_thickness - base_width) * IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX + 0.5f), 0, IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX); - const float tex_width = base_width + (float)texture_idx / IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX; + const int texture_idx = ImClamp((int)((screen_thickness - base_width) * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + 0.995f), 0, IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX); + const float tex_width = base_width + (float)texture_idx / IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT; fringe = _FringeScale * (screen_thickness / tex_width); // Scale the fringe to cover the discrepancy between the texture and requested size. tex_uvs = _Data->TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + texture_idx]; } else { // Bias the texture selection towards higher resolution to avoid visual pops when the texture change. - const int texture_idx = ImClamp((int)(screen_thickness + 0.95f), 1, IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1); + const int texture_idx = ImClamp((int)(screen_thickness + 0.995f), 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1); fringe = _FringeScale * (screen_thickness / (float)texture_idx); // Scale the fringe to cover the discrepancy between the texture and requested size. tex_uvs = _Data->TexUvLines[texture_idx]; } @@ -5116,7 +5116,7 @@ static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) bool add_and_draw = atlas->GetCustomRect(builder->PackIdLinesTexData, &r) == false; if (add_and_draw) { - ImVec2i pack_size = ImVec2i(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX + 1); + ImVec2i pack_size = ImVec2i(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX + 1); builder->PackIdLinesTexData = atlas->AddCustomRect(pack_size.x, pack_size.y, &r); IM_ASSERT(builder->PackIdLinesTexData != ImFontAtlasRectId_Invalid); } @@ -5170,16 +5170,16 @@ static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) { - ImU8 ramp[IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX]; - for (int n = 0; n < IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX; n++) - ramp[n] = (ImU8)(((float)n / IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX) * 255.f); + ImU8 ramp[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT]; + for (int n = 0; n < IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT; n++) + ramp[n] = (ImU8)(((float)n / IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT) * 255.f); - for (int n = 0; n < IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX + 1; n++) + for (int n = 0; n < IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX + 1; n++) { // Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle const int y = IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + n; - const int line_width = IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX + n; - IM_ASSERT(IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX + line_width + IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX <= r.w && y < r.h); // Make sure we're inside the texture bounds before we start writing pixels + const int line_width = IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + n; + IM_ASSERT(IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + line_width + IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT <= r.w && y < r.h); // Make sure we're inside the texture bounds before we start writing pixels // Write each slice if (add_and_draw && tex->Format == ImTextureFormat_Alpha8) @@ -5187,13 +5187,13 @@ static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) ImU8* write_ptr = (ImU8*)tex->GetPixelsAt(r.x, r.y + y); int idx = 0; - for (int i = 0; i < IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX; i++) + for (int i = 0; i < IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT; i++) write_ptr[idx++] = ramp[i]; for (int i = 0; i < line_width; i++) write_ptr[idx++] = 0xFF; - for (int i = IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX-1; i >= 0; i--) + for (int i = IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT - 1; i >= 0; i--) write_ptr[idx++] = ramp[i]; while (idx < r.w) @@ -5205,13 +5205,13 @@ static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) int idx = 0; - for (int i = 0; i < IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX; i++) + for (int i = 0; i < IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT; i++) write_ptr[idx++] = IM_COL32(255, 255, 255, ramp[i]); for (int i = 0; i < line_width; i++) write_ptr[idx++] = IM_COL32(255, 255, 255, 255); - for (int i = IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX - 1; i >= 0; i--) + for (int i = IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT - 1; i >= 0; i--) write_ptr[idx++] = IM_COL32(255, 255, 255, ramp[i]); while (idx < r.w) @@ -5220,7 +5220,7 @@ static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) // Refresh UV coordinates ImVec2 uv0 = ImVec2((float)(r.x), (float)(r.y + y)) * atlas->TexUvScale; - ImVec2 uv1 = ImVec2((float)(r.x + IM_DRAWLIST_TEX_LINES_SUPERSAMPLE_MAX * 2 + line_width), (float)(r.y + y + 1)) * atlas->TexUvScale; + ImVec2 uv1 = ImVec2((float)(r.x + IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT * 2 + line_width), (float)(r.y + y + 1)) * atlas->TexUvScale; float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts atlas->TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + n] = ImVec4(uv0.x, half_v, uv1.x, half_v); } From ed8ed8bb714c7dc9fbd3e7b4e8d0f0ff053ec299 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Tue, 26 May 2026 22:10:25 +0300 Subject: [PATCH 042/120] DrawList: fixed thin lines appearing too bright. - fixed how super sampled line textures are generated (and documented wrong assumption) - switched to more samples and smaller thickness range - tweaked super sampled texture selection to hide the texture changed better --- imgui.h | 4 ++-- imgui_draw.cpp | 19 ++++++++++++++----- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/imgui.h b/imgui.h index 527147137..bfad43218 100644 --- a/imgui.h +++ b/imgui.h @@ -3208,11 +3208,11 @@ struct ImGuiSelectionExternalStorage #endif #ifndef IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH -#define IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH (4) // Calculate detailed textures for width [1..IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH] +#define IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH (3) // Calculate detailed textures for width [1..IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH] #endif #ifndef IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT -#define IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT (2) // How many samples per integer thickness level. +#define IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT (4) // How many samples per integer thickness level. #endif #define IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX (IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 944bcf6a4..1e74198da 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1183,15 +1183,15 @@ static ImU32 ImAlphaMultiply(ImU32 col, float alpha_mul) void ImDrawList::_SelectFringeTexture(float screen_thickness, ImVec4& tex_uvs, float& fringe) { - if (screen_thickness <= 2.f) + if (screen_thickness <= IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH) { // Handle the thickness between [1..IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH]. The texture scaling in this range will cause slight visual pops, so we generate super sampled textures in this range. // There are IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX+1 textures, where 0 maps to 1.0 and IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX maps to IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH. constexpr float base_width = 1.f; - const int texture_idx = ImClamp((int)((screen_thickness - base_width) * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + 0.995f), 0, IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX); + const int texture_idx = ImClamp((int)((screen_thickness - base_width) * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + 0.1f), 0, IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX); const float tex_width = base_width + (float)texture_idx / IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT; fringe = _FringeScale * (screen_thickness / tex_width); // Scale the fringe to cover the discrepancy between the texture and requested size. - tex_uvs = _Data->TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + texture_idx]; + tex_uvs = _Data->TexUvLines[(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1) + texture_idx]; } else { @@ -5176,9 +5176,18 @@ static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) for (int n = 0; n < IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX + 1; n++) { - // Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle const int y = IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + n; - const int line_width = IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + n; + // For integer thickness lines the one pixel line texture looks like this: + // [ 0 | 1 | 0 ] + // :...........: + // Since the texture samples are at the center of the texel, we only use range above. + // The result after bilinear filtering is a triangle across the used uv range. + // + // To super 2x sample that signal, we end up with following texture: + // [ 0 | .5 | 1 | .5 | 0 ] + // :.......................: + // One might think that there should be 2 opaque pixels the the middle section, but no singe we're super sampling the triangle signal. + const int line_width = 1 + n; IM_ASSERT(IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + line_width + IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT <= r.w && y < r.h); // Make sure we're inside the texture bounds before we start writing pixels // Write each slice From d453a59b9c07d2d848802d854a246c2f128f7312 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 26 May 2026 21:05:00 +0200 Subject: [PATCH 043/120] DrawList: avoid using output references in _SelectFringeTexture() + misc tweaks, comments. Matching coding style and more consistent for matching with other languages. --- imgui.h | 14 +++++++------- imgui_demo.cpp | 3 ++- imgui_draw.cpp | 29 ++++++++++++++--------------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/imgui.h b/imgui.h index bfad43218..3d111504d 100644 --- a/imgui.h +++ b/imgui.h @@ -3343,8 +3343,8 @@ enum ImDrawFlags_ // Stroke position relative to the shape outline ImDrawFlags_StrokeInside = 1 << 16, // Draw stroke inside of the shape outline (default for closed shapes and AddLineH, AddLineV functions) - ImDrawFlags_StrokeCenter = 2 << 16, // Draw stroke at the center of the shape outline - ImDrawFlags_StrokeCenterAligned = 3 << 16, // Draw stroke at the center of the shape outline, so that half thickness (rounded down) will be outside, and rest inside the shape outline. + ImDrawFlags_StrokeCenter = 2 << 16, // Draw stroke at the center of the shape outline (default for paths, bezier, and AddLine functions) + ImDrawFlags_StrokeCenterAligned = 3 << 16, // Draw stroke at the center of the shape outline, so that half thickness (rounded down) will be outside, and rest inside the shape outline. Does not animate well! ImDrawFlags_StrokeOutside = 4 << 16, // Draw stroke outside of the shape outline ImDrawFlags_StrokeLegacy = 5 << 16, // Use legacy positioning + enable MiterOnly and NoAAEnds flags. ImDrawFlags_StrokeMask_ = 0x07 << 16, @@ -3362,7 +3362,7 @@ enum ImDrawListFlags_ ImDrawListFlags_AntiAliasedFill = 1 << 2, // Enable anti-aliased edge around filled shapes (rounded rectangles, circles). ImDrawListFlags_AllowVtxOffset = 1 << 3, // Can emit 'VtxOffset > 0' to allow large meshes. Set when 'ImGuiBackendFlags_RendererHasVtxOffset' is enabled. ImDrawListFlags_TextNoPixelSnap = 1 << 4, // Disable automatically snapping AddText() calls to pixel boundaries. - ImDrawListFlags_RoundCornersUseTex = 1 << 5, // Enable using textures instead of strokes to draw rounded corners/circles where possible. + ImDrawListFlags_RoundCornersUseTex = 1 << 5, // Enable using textures instead of strokes to draw rounded corners/circles where possible. Set when `ImFontAtlasFlags_NoBakedRoundCorners` is enabled in the font atlas. }; // Draw command list @@ -3543,11 +3543,11 @@ struct ImDrawList IMGUI_API int _CalcCircleAutoSegmentCount(float radius) const; IMGUI_API void _PathArcToFastEx(const ImVec2& center, float radius, int a_min_sample, int a_max_sample, int a_step); IMGUI_API void _PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments); - IMGUI_API void _AddRectFilledBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, ImVec4 tex_uvs, ImDrawFlags flags); - IMGUI_API void _AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, float t, ImVec4 tex_uvs, ImDrawFlags flags); - IMGUI_API void _AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, ImVec4 tex_uvs, float fringe); + IMGUI_API void _AddRectFilledBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, const ImVec4& tex_uvs, ImDrawFlags flags); + IMGUI_API void _AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, float t, const ImVec4& tex_uvs, ImDrawFlags flags); + IMGUI_API void _AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, const ImVec4& tex_uvs, float fringe); IMGUI_API void _AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags); - IMGUI_API void _SelectFringeTexture(float screen_thickness, ImVec4& tex_uvs, float& fringe); + IMGUI_API void _SelectFringeTexture(float screen_thickness, ImVec4* out_tex_uvs, float* out_fringe); }; // All draw data to render a Dear ImGui frame diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 0d7d1df64..89ce2a3be 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10533,7 +10533,8 @@ static void ShowExampleAppCustomRendering(bool* p_open) x += half_sz + spacing; if (!(draw_fill && draw_strokes)) //-V560 - draw_list->AddLine({ x, y }, { x + sz, y + sz }, col, th, flags); x += sz + spacing; // Diagonal line + draw_list->AddLine({ x, y }, { x + sz, y + sz }, col, th, flags); // Diagonal line + x += sz + spacing; // Rotating square for (int n = 0; n < 4; n++) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 1e74198da..41eb2abd8 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -856,7 +856,7 @@ static float CalculateCenterAlignedOffset(float thickness, float _FringeScale) #define IM_POLYLINE_MITER_ANGLE_LIMIT (-0.9999619f) // cos(179.5) #define IM_POLYLINE_MITER_LIMIT (4.0f) -void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, ImVec4 tex_uvs, float fringe) +void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, const ImVec4& tex_uvs, float fringe) { const ImU32 col_trans = col & ~IM_COL32_A_MASK; @@ -1181,7 +1181,7 @@ static ImU32 ImAlphaMultiply(ImU32 col, float alpha_mul) return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT); } -void ImDrawList::_SelectFringeTexture(float screen_thickness, ImVec4& tex_uvs, float& fringe) +void ImDrawList::_SelectFringeTexture(float screen_thickness, ImVec4* out_tex_uvs, float* out_fringe) { if (screen_thickness <= IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH) { @@ -1190,15 +1190,15 @@ void ImDrawList::_SelectFringeTexture(float screen_thickness, ImVec4& tex_uvs, f constexpr float base_width = 1.f; const int texture_idx = ImClamp((int)((screen_thickness - base_width) * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + 0.1f), 0, IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX); const float tex_width = base_width + (float)texture_idx / IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT; - fringe = _FringeScale * (screen_thickness / tex_width); // Scale the fringe to cover the discrepancy between the texture and requested size. - tex_uvs = _Data->TexUvLines[(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1) + texture_idx]; + *out_fringe = _FringeScale * (screen_thickness / tex_width); // Scale the fringe to cover the discrepancy between the texture and requested size. + *out_tex_uvs = _Data->TexUvLines[(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1) + texture_idx]; } else { // Bias the texture selection towards higher resolution to avoid visual pops when the texture change. const int texture_idx = ImClamp((int)(screen_thickness + 0.995f), 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1); - fringe = _FringeScale * (screen_thickness / (float)texture_idx); // Scale the fringe to cover the discrepancy between the texture and requested size. - tex_uvs = _Data->TexUvLines[texture_idx]; + *out_fringe = _FringeScale * (screen_thickness / (float)texture_idx); // Scale the fringe to cover the discrepancy between the texture and requested size. + *out_tex_uvs = _Data->TexUvLines[texture_idx]; } } @@ -1261,8 +1261,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 ImVec4 tex_uvs; float fringe; - _SelectFringeTexture(screen_thickness, tex_uvs, fringe); - + _SelectFringeTexture(screen_thickness, &tex_uvs, &fringe); _AddPolyline(points, normals, sqr_lengths, points_count, col, thickness, flags, tex_uvs, fringe); } @@ -2234,7 +2233,7 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th } } -void ImDrawList::_AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, float t, ImVec4 tex_uvs, ImDrawFlags flags) +void ImDrawList::_AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, float t, const ImVec4& tex_uvs, ImDrawFlags flags) { const ImVec2 uv_tl(tex_uvs.x, tex_uvs.y); const ImVec2 uv_tr(tex_uvs.z, tex_uvs.y); @@ -2368,7 +2367,7 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, ImVec4 tex_uvs; float fringe; - _SelectFringeTexture(screen_thickness, tex_uvs, fringe); + _SelectFringeTexture(screen_thickness, &tex_uvs, &fringe); const int arc_step = ImClamp(IM_DRAWLIST_ARCFAST_SAMPLE_MAX / this->_CalcCircleAutoSegmentCount(rounding), 1, IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4); const int arc_point_count = (IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4) / arc_step + 1; @@ -2383,7 +2382,8 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, static const ImVec2 corner_offset[4] = { {1,1}, {-1,1}, {-1,-1}, {1,-1} }; static const ImDrawFlags corner_flags[4] = { ImDrawFlags_RoundCornersTopLeft, ImDrawFlags_RoundCornersTopRight, ImDrawFlags_RoundCornersBottomRight, ImDrawFlags_RoundCornersBottomLeft }; const float half_fringe = fringe * 0.5f; - const ImVec2 corner_pos[4] = { + const ImVec2 corner_pos[4] = + { ImVec2(p_min.x - half_fringe, p_min.y - half_fringe), ImVec2(p_max.x + half_fringe, p_min.y - half_fringe), ImVec2(p_max.x + half_fringe, p_max.y + half_fringe), @@ -2450,7 +2450,7 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, { for (int i = 0; i < arc_point_count - 1; i++) IM_APPEND_TRI(stem_idx, arc_idx + i, arc_idx + i + 1); - } + } // (keep braces because of macro above) // Connect with previous IM_APPEND_TRI(base_idx + 0, arc_idx, stem_idx); @@ -2582,9 +2582,9 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl if ((Flags & ImDrawListFlags_RoundCornersUseTex) && s_thickness <= IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX && s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) { // Pixel aligned rect with round corners rendered using baked textures. - IM_ASSERT_PARANOID(!(_Data->Font->OwnerAtlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners)); const int size = ImMax(s_rounding, s_thickness); // This is matching the baking calculations. const int idx = (s_thickness * IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) + s_rounding - 1; + IM_ASSERT_PARANOID((_Data->Font->OwnerAtlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners) == 0); IM_ASSERT_PARANOID(idx >= 0 && idx < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX * IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX); const ImVec4 tex_uvs = _Data->TexUvCorners[idx]; _AddRectBaked(outer_min, outer_max, col, (float)size * _FringeScale, (float)thickness, tex_uvs, flags); @@ -2621,7 +2621,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | ImDrawFlags_StrokeInside); } -void ImDrawList::_AddRectFilledBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, ImVec4 tex_uvs, ImDrawFlags flags) +void ImDrawList::_AddRectFilledBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, const ImVec4& tex_uvs, ImDrawFlags flags) { // 9-slice with corner mirroring const ImVec2 uv_tl(tex_uvs.x, tex_uvs.y); @@ -2637,7 +2637,6 @@ void ImDrawList::_AddRectFilledBaked(const ImVec2& p_min, const ImVec2& p_max, I const float r_bl = (flags & ImDrawFlags_RoundCornersBottomLeft) ? r : 0.0f; ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; - IM_APPEND_VTX(p_min.x, p_min.y, uv_tl, col); IM_APPEND_VTX(p_min.x + r_tl, p_min.y, uv_tr, col); IM_APPEND_VTX(p_max.x - r_tr, p_min.y, uv_tr, col); From 68b5bf4466192a0f4a91b99ae18b3b824862336f Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 26 May 2026 21:15:55 +0200 Subject: [PATCH 044/120] DrawList: use straight floats when possible in _AddRectTinyRounding(). --- imgui_draw.cpp | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 41eb2abd8..3b23930b0 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2392,7 +2392,7 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, thickness += fringe; rounding += half_fringe; - // We need to stem offset to be atleast fringe scale away, to maintain the fringe size. + // We need to stem offset to be at least fringe scale away, to maintain the fringe size. const float stem_offset = thickness - ImMax(thickness - rounding, _FringeScale); const float half_texel = 0.5f * _Data->FontAtlas->TexUvScale.x; @@ -2411,39 +2411,37 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, IM_APPEND_VTX(0, 0, stem_uv, col); IM_APPEND_VTX(0, 0, outer_uv, col); - ImVec2 outer, stem, inner, center; - for (int corner = 0; corner < 4; corner++) { - outer = corner_pos[corner]; - stem.x = corner_pos[corner].x + corner_offset[corner].x * stem_offset; - stem.y = corner_pos[corner].y + corner_offset[corner].y * stem_offset; - inner.x = corner_pos[corner].x + corner_offset[corner].x * thickness; - inner.y = corner_pos[corner].y + corner_offset[corner].y * thickness; - - const bool is_rounded = flags & corner_flags[corner]; + const float outer_x = corner_pos[corner].x; + const float outer_y = corner_pos[corner].y; + const float stem_x = outer_x + corner_offset[corner].x * stem_offset; + const float stem_y = outer_y + corner_offset[corner].y * stem_offset; + const float inner_x = outer_x + corner_offset[corner].x * thickness; + const float inner_y = outer_y + corner_offset[corner].y * thickness; + const bool is_rounded = (flags & corner_flags[corner]); const int arc_idx = (int)_VtxCurrentIdx; if (is_rounded) { - center.x = outer.x + corner_offset[corner].x * rounding; - center.y = outer.y + corner_offset[corner].y * rounding; + const float center_x = outer_x + corner_offset[corner].x * rounding; + const float center_y = outer_y + corner_offset[corner].y * rounding; int a = (IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4) * corner; for (int i = 0; i < arc_point_count; i++) { - IM_APPEND_VTX(center.x - _Data->ArcFastVtx[a].x * rounding, center.y - _Data->ArcFastVtx[a].y * rounding, inner_uv, col); + IM_APPEND_VTX(center_x - _Data->ArcFastVtx[a].x * rounding, center_y - _Data->ArcFastVtx[a].y * rounding, inner_uv, col); a = (a + arc_step) % IM_DRAWLIST_ARCFAST_TABLE_SIZE; } } else { - IM_APPEND_VTX(outer.x, outer.y, inner_uv, col); + IM_APPEND_VTX(outer_x, outer_y, inner_uv, col); } const int stem_idx = (int)_VtxCurrentIdx; - IM_APPEND_VTX(stem.x, stem.y, stem_uv, col); + IM_APPEND_VTX(stem_x, stem_y, stem_uv, col); const int inner_idx = (int)_VtxCurrentIdx; - IM_APPEND_VTX(inner.x, inner.y, outer_uv, col); + IM_APPEND_VTX(inner_x, inner_y, outer_uv, col); // Arc if (is_rounded) From 20110d751d8cf5c9c4b6926a3d20aca545596fe9 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 19 May 2026 16:07:47 +0200 Subject: [PATCH 045/120] Docs: update changelog. --- docs/CHANGELOG.txt | 116 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 12c9de64c..bbc7e9c98 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -39,6 +39,122 @@ HOW TO UPDATE? VERSION 1.93.0 WIP (In Progress) ----------------------------------------------------------------------- +- In 1.93, the ImDrawList rendering 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, + and the coordinates passed to the API are now consistent. + 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. + 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. +- Most of the work courtesy of @memononen, with bits from @ocornut, @thedmd, @potocpav. +- Refer to our full guides: + - ImDrawList Vector Rendering Reference + https://github.com/ocornut/imgui/wiki/Draw-List + - 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 +- (Breaking) 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. But you might as well apply the offset manually! + - Generally better to use to newly introduced `AddLineH()`, `AddLineV()` functions. + 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 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, y1}, {x2, y2}, col, thickness, ImDrawFlags_StrokeLegacy); // Offset by +0.5f + disable AA ends. + == AddLine({x1 + 0.5f, y1 + 0.5f}, {x2+ 0.5f, y2 + 0.5f}, col, thickness, ImDrawFlags_NoAAEnds); // Same + This reapplies the old offset, and should get you exactly the same result you previously got for thickness=1.0f lines. + But said result was sometimes ambiguous and renderer dependent. (#3116, #3258, #2441) + - Old AddLine() did not have 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 could + have added fudge offsets here and there which are not necessary anymore or may be simplified. +- AddPolyline(), PathStroke(), AddTriangle(): the algorithm to render lines got overhauled. (#2183, #2964, #7972) + - 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 bevelled 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. + - Added anti-aliasing ends, most noticeable for thick lines. + - Added `ImDrawFlags_NoAAEnds` flag to disable anti-aliased line ends (slightly faster + and reduce vertex/index data). + - Added `ImDrawFlags_MiterOnly` flag to disable using beveled corner (closer to legacy + rendering, slightly faster, but corners sharpers than 90 degrees may expand far out). + Automatically used by AddRect(), AddCircle(), AddNgon(), AddEllipse() etc functions + since we know that the input path does not contains sharper corners. + - Added `ImDrawFlags_SquareCap` flag to use extend lines ends using square caps. +- 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_MiterOnly`, to facilitate matching + a stroke using the same flag. +- AddRect(), AddRectFilled(): various optimization for rounded rectangles. (#1962) + - Integer-aligned coordinates and thicknesses will automatically use baked textures, + saving on both CPU and vertex/index data. + - Added `ImFontAtlasFlags_NoBakedRoundCorners` to disable baking round corners in font atlas. + - Added `ImDrawListFlags_RoundCornersUseTex` to disable using round corners. +- AddRectFilled(): non-integer coordinates will now display anti-aliased edges. (#6971) +- Improved minor mismatches when overlapping strokes and filled shapes, e.g. when using inside strokes, + - `AddRect()` and `AddRectFilled()` with rounding now overlap better. (#3656) + - `AddCircle()` and `AddCircleFilled()` now overlap better. +- Added flags to specify stroke position in all ImDrawList stroking functions: + - `ImDrawFlags_StrokeInside` (default for closed primitives and AddLineH, AddLineV) + - `ImDrawFlags_StrokeCenter` (default for paths, bezier and AddLine) + - `ImDrawFlags_StrokeCenterAligned` + - `ImDrawFlags_StrokeOutside` + - `ImDrawFlags_StrokeLegacy` + - Legacy code was generally applying +0.50f offset 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 slightly 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) + - Recap: + ------------------------------------------------------------------------ + Legacy Inside Outside Center CenterAligned + -------------------------------------------------------------------------------- + Thickness=1.0f sharp sharp sharp blurry sharp + Thickness=2.0f blurry sharp sharp sharp sharp + Thickness=3.0f sharp sharp sharp blurry sharp + ------------------------------------------------------------------------ +- (Breaking) AddRect, AddCircle, AddNgon, AddEllipse: 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. +- Tweaked line rendering in various locations to avoid blurryness: + - Windows: title-bar and menu-bar border (when thickness>1.0f). + - Tables: borders (when thickness>1.0f). + - SeparatorText: border (when thickness>1.0f). + - Tree: hierarchy lines (when thickness>1.0f). + - TabBar: selected overline border ((when thickness>1.0f). + - Demo: Custom Rendering: canvas lines. +- (Breaking) Removed support for non-aliased shapes. + - Clearing `ImDrawListFlags_AntiAliasedLines` / `ImDrawListFlags_AntiAliasedFill` will have no effect. +- Demo: Custom Rendering: exposed new flags; Showcasing overlapping strokes and + filled shapes; Added an option to animation thickness; Added new shapes to + showcase new rendering features that previously hit limitations. + + Breaking Changes: - Style: obsoleted `style.CurveTessellationTol (default 1.25)` which was in Pixels² unit in From 5410a04db462219a10ae5ca6508c8e6a18a29e9d Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Thu, 28 May 2026 10:44:37 +0300 Subject: [PATCH 046/120] DrawList: fixed out of bounds access of baked corner based on thickness --- imgui_draw.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 3b23930b0..b1e167cb7 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2577,9 +2577,10 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl return; } - if ((Flags & ImDrawListFlags_RoundCornersUseTex) && s_thickness <= IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX && s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) + if ((Flags & ImDrawListFlags_RoundCornersUseTex) && s_thickness < IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX && s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) { // Pixel aligned rect with round corners rendered using baked textures. + IM_ASSERT_PARANOID(s_thickness > 0 && s_rounding > 0); const int size = ImMax(s_rounding, s_thickness); // This is matching the baking calculations. const int idx = (s_thickness * IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) + s_rounding - 1; IM_ASSERT_PARANOID((_Data->Font->OwnerAtlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners) == 0); From 3fca7ef414aec775be8f9269e92e3d8424e6817c Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 28 May 2026 15:26:32 +0200 Subject: [PATCH 047/120] DrawList: skip PathLineTo/PathStroke calls for common AddLine() functions. (#4091) Rough numbers Debug x64, 100k lines: 91.5->80 ms, 87->75.5 ms (NoAAEnds), 87->76 ms (Legacy) Release x64, 300 lines: 25.2->25 ms, 22.2->18.3 ms (NoAAEnds), 22.2->19.3 ms (Legacy) --- imgui_draw.cpp | 31 ++++++++++++------------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index b1e167cb7..bc331ad6d 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2143,7 +2143,7 @@ void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float th if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) { const ImVec2 points[2] = { ImVec2(min_x + 0.5f, y + 0.5f), ImVec2(max_x + 0.5f, y + 0.5f) }; // Same as AddLine() above. - AddPolyline(points, 2, col, thickness, stroke_pos); + AddPolyline(points, 2, col, thickness, ImDrawFlags_StrokeLegacy); return; } @@ -2177,9 +2177,8 @@ void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float th flags = (flags & ~ImDrawFlags_StrokeMask_) | stroke_pos; // For generic case use line, since it needs less triangles than AA rectangle. - PathLineTo(ImVec2(min_x, y)); - PathLineTo(ImVec2(max_x, y)); - PathStroke(col, thickness, flags); + const ImVec2 points[2] = { ImVec2(min_x, y), ImVec2(max_x, y) }; + AddPolyline(points, 2, col, thickness, flags); } } @@ -2191,8 +2190,8 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) { - const ImVec2 points[2] = { ImVec2(x + 0.5f, max_y + 0.5f), ImVec2(x + 0.5f, min_y + 0.5f) }; // Same as AddLine() above. - AddPolyline(points, 2, col, thickness, stroke_pos); + const ImVec2 points[2] = { ImVec2(x + 0.5f, min_y + 0.5f), ImVec2(x + 0.5f, max_y + 0.5f) }; // Same as AddLine() above. + AddPolyline(points, 2, col, thickness, ImDrawFlags_StrokeLegacy); return; } @@ -2227,9 +2226,8 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th // For generic case use line, since it needs less triangles than AA rectangle. // Drawing for bottom to top, so that the inside stroke pos expands to right. - PathLineTo(ImVec2(x, max_y)); - PathLineTo(ImVec2(x, min_y)); - PathStroke(col, thickness, flags); + const ImVec2 points[2] = { ImVec2(x, max_y), ImVec2(x, min_y) }; + AddPolyline(points, 2, col, thickness, flags); } } @@ -2494,9 +2492,9 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) { if (Flags & ImDrawListFlags_AntiAliasedLines) - PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.50f, 0.50f), rounding, flags); + PathRect(ImVec2(p_min.x + 0.50f, p_min.y + 0.50f), ImVec2(p_max.x - 0.50f, p_max.y - 0.50f), rounding, flags); else - PathRect(p_min + ImVec2(0.50f, 0.50f), p_max - ImVec2(0.49f, 0.49f), rounding, flags); // Better looking lower-right corner and rounded non-AA shapes. + PathRect(ImVec2(p_min.x + 0.50f, p_min.y + 0.50f), ImVec2(p_max.x - 0.49f, p_max.y - 0.49f), rounding, flags); // Better looking lower-right corner and rounded non-AA shapes. PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | ImDrawFlags_StrokeCenter); return; } @@ -2593,10 +2591,8 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl if (!has_rounding || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) { - PathLineTo(outer_min); - PathLineTo(ImVec2(outer_max.x, outer_min.y)); - PathLineTo(outer_max); - PathLineTo(ImVec2(outer_min.x, outer_max.y)); + const ImVec2 points[4] = { outer_min, ImVec2(outer_max.x, outer_min.y), outer_max, ImVec2(outer_min.x, outer_max.y) }; + AddPolyline(points, 4, col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | ImDrawFlags_StrokeInside); } else { @@ -2615,9 +2611,8 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl PathArcToFast(ImVec2(outer_max.x - rounding_tr, outer_min.y + rounding_tr), rounding_tr, 9, 12); PathArcToFast(ImVec2(outer_max.x - rounding_br, outer_max.y - rounding_br), rounding_br, 0, 3); PathArcToFast(ImVec2(outer_min.x + rounding_bl, outer_max.y - rounding_bl), rounding_bl, 3, 6); + PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | ImDrawFlags_StrokeInside); } - - PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | ImDrawFlags_StrokeInside); } void ImDrawList::_AddRectFilledBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, const ImVec4& tex_uvs, ImDrawFlags flags) @@ -2951,9 +2946,7 @@ void ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 co ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) - { stroke_pos = ImDrawFlags_StrokeCenter; - } ImVec2 rad = radius; rad.x = ImMax(0.01f, rad.x); From e9246e6ad3a8a0589becd14b44bff52b79769537 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Fri, 29 May 2026 16:08:49 +0300 Subject: [PATCH 048/120] DrawList: Fixed vertex count calculation in_AddRectTinyRounding() --- imgui_draw.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index bc331ad6d..6031a782e 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2370,7 +2370,7 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, const int arc_step = ImClamp(IM_DRAWLIST_ARCFAST_SAMPLE_MAX / this->_CalcCircleAutoSegmentCount(rounding), 1, IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4); const int arc_point_count = (IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4) / arc_step + 1; - int vtx_count = (4 + arc_point_count) * 3 + 3; + int vtx_count = (2 + arc_point_count) * 4 + 3; int idx_count = (4 + arc_point_count - 1) * 4 * 3; PrimReserve(idx_count, vtx_count); From c59367efcbf22ba04fc980315cbd9588bc9865b6 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 1 Jun 2026 17:20:17 +0200 Subject: [PATCH 049/120] DrawList: renamed ImDrawFlags_StrokeCenterAligned to ImDrawFlags_StrokeCenterBiased. --- imgui.h | 2 +- imgui_demo.cpp | 2 +- imgui_draw.cpp | 28 ++++++++++++++-------------- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/imgui.h b/imgui.h index 3d111504d..3cb10c746 100644 --- a/imgui.h +++ b/imgui.h @@ -3344,7 +3344,7 @@ enum ImDrawFlags_ // Stroke position relative to the shape outline ImDrawFlags_StrokeInside = 1 << 16, // Draw stroke inside of the shape outline (default for closed shapes and AddLineH, AddLineV functions) ImDrawFlags_StrokeCenter = 2 << 16, // Draw stroke at the center of the shape outline (default for paths, bezier, and AddLine functions) - ImDrawFlags_StrokeCenterAligned = 3 << 16, // Draw stroke at the center of the shape outline, so that half thickness (rounded down) will be outside, and rest inside the shape outline. Does not animate well! + ImDrawFlags_StrokeCenterBiased = 3 << 16, // Draw stroke at the center of the shape outline, so that half thickness (rounded down) will be outside, and rest inside the shape outline. Does not animate well! ImDrawFlags_StrokeOutside = 4 << 16, // Draw stroke outside of the shape outline ImDrawFlags_StrokeLegacy = 5 << 16, // Use legacy positioning + enable MiterOnly and NoAAEnds flags. ImDrawFlags_StrokeMask_ = 0x07 << 16, diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 89ce2a3be..c4b0939b0 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10425,7 +10425,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::RadioButton("Default", &stroke_flags, ImDrawFlags_None); ImGui::SameLine(); ImGui::RadioButton("Inside", &stroke_flags, ImDrawFlags_StrokeInside); ImGui::SameLine(); ImGui::RadioButton("Center", &stroke_flags, ImDrawFlags_StrokeCenter); ImGui::SameLine(); - ImGui::RadioButton("CenterAligned", &stroke_flags, ImDrawFlags_StrokeCenterAligned); ImGui::SameLine(); + ImGui::RadioButton("CenterBiased", &stroke_flags, ImDrawFlags_StrokeCenterBiased); ImGui::SameLine(); ImGui::RadioButton("Outside", &stroke_flags, ImDrawFlags_StrokeOutside); ImGui::SameLine(); ImGui::RadioButton("Legacy", &stroke_flags, ImDrawFlags_StrokeLegacy); ImGui::CheckboxFlags("MiterOnly", &other_flags, ImDrawFlags_MiterOnly); ImGui::SameLine(); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 6031a782e..32ebdb6f7 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -813,12 +813,12 @@ void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, c } -static float CalculateCenterAlignedOffset(float thickness, float _FringeScale) +static float CalculateCenterBiasedOffset(float thickness, float _FringeScale) { // Old jumpy one. // return IM_TRUNC(thickness * 0.5f / _FringeScale) * _FringeScale; - // Calculate outside offset for stroke position CenterAligned + // Calculate outside offset for stroke position ImDrawFlags_StrokeCenterBiased // so that one side the of the line is always at pixel boundary. // On integer thickness both sides of the line are on pixel boundary. const float screen_thickness = thickness / _FringeScale; @@ -871,8 +871,8 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ float thickness0 = (thickness + fringe) * 0.5f; // Center (or legacy) if (stroke_pos == ImDrawFlags_StrokeOutside) thickness0 = thickness + fringe * 0.5f; - else if (stroke_pos == ImDrawFlags_StrokeCenterAligned) - thickness0 = CalculateCenterAlignedOffset(thickness, _FringeScale) + fringe * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeCenterBiased) + thickness0 = CalculateCenterBiasedOffset(thickness, _FringeScale) + fringe * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeInside) thickness0 = fringe * 0.5f; float thickness1 = (thickness + fringe) - thickness0; @@ -2150,8 +2150,8 @@ void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float th float top_y = y; // Inside if (stroke_pos == ImDrawFlags_StrokeCenter) top_y -= thickness * 0.5f; - else if (stroke_pos == ImDrawFlags_StrokeCenterAligned) - top_y -= CalculateCenterAlignedOffset(thickness, _FringeScale); + else if (stroke_pos == ImDrawFlags_StrokeCenterBiased) + top_y -= CalculateCenterBiasedOffset(thickness, _FringeScale); else if (stroke_pos == ImDrawFlags_StrokeOutside) top_y -= thickness; @@ -2198,8 +2198,8 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th float left_x = x; // Inside if (stroke_pos == ImDrawFlags_StrokeCenter) left_x -= thickness * 0.5f; - else if (stroke_pos == ImDrawFlags_StrokeCenterAligned) - left_x -= CalculateCenterAlignedOffset(thickness, _FringeScale); + else if (stroke_pos == ImDrawFlags_StrokeCenterBiased) + left_x -= CalculateCenterBiasedOffset(thickness, _FringeScale); else if (stroke_pos == ImDrawFlags_StrokeOutside) left_x -= thickness; @@ -2509,8 +2509,8 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl float offset = 0.0f; // Inside if (stroke_pos == ImDrawFlags_StrokeCenter) offset = thickness * 0.5f; - else if (stroke_pos == ImDrawFlags_StrokeCenterAligned) - offset = CalculateCenterAlignedOffset(thickness, _FringeScale); + else if (stroke_pos == ImDrawFlags_StrokeCenterBiased) + offset = CalculateCenterBiasedOffset(thickness, _FringeScale); else if (stroke_pos == ImDrawFlags_StrokeOutside) offset = thickness; outer_min.x -= offset; @@ -2810,8 +2810,8 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu float outer_radius = radius; if (stroke_pos == ImDrawFlags_StrokeCenter) outer_radius = radius + thickness * 0.5f; - else if (stroke_pos == ImDrawFlags_StrokeCenterAligned) - outer_radius = radius + CalculateCenterAlignedOffset(thickness, _FringeScale); + else if (stroke_pos == ImDrawFlags_StrokeCenterBiased) + outer_radius = radius + CalculateCenterBiasedOffset(thickness, _FringeScale); else if (stroke_pos == ImDrawFlags_StrokeInside) outer_radius = radius; else if (stroke_pos == ImDrawFlags_StrokeOutside) @@ -2897,8 +2897,8 @@ void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_ float outer_radius = radius; if (stroke_pos == ImDrawFlags_StrokeCenter) outer_radius = radius + miter_thickness * 0.5f; - else if (stroke_pos == ImDrawFlags_StrokeCenterAligned) - outer_radius = radius + CalculateCenterAlignedOffset(miter_thickness, _FringeScale); + else if (stroke_pos == ImDrawFlags_StrokeCenterBiased) + outer_radius = radius + CalculateCenterBiasedOffset(miter_thickness, _FringeScale); else if (stroke_pos == ImDrawFlags_StrokeInside) outer_radius = radius; else if (stroke_pos == ImDrawFlags_StrokeOutside) From e8c8a10a2d9af65acc24f6260f9a1d83455d9bc1 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 1 Jun 2026 17:20:34 +0200 Subject: [PATCH 050/120] DrawList: amends/review legacy stroke paths + minor tweaks. --- imgui.cpp | 15 +++++---------- imgui_draw.cpp | 36 ++++++++++++++++++------------------ imgui_widgets.cpp | 25 ++++++++++++++----------- 3 files changed, 37 insertions(+), 39 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index b6cef8d0c..b9a700608 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -7312,16 +7312,11 @@ static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window) } if (g.Style.FrameBorderSize > 0 && !(window->Flags & ImGuiWindowFlags_NoTitleBar)) { - if (g_LEGACY_STROKES) - { float y = window->Pos.y + window->TitleBarHeight - 1; + if (g_LEGACY_STROKES) window->DrawList->AddLineH(window->Pos.x + border_size * 0.5f, window->Pos.x + window->Size.x - border_size * 0.5f, y, border_col, g.Style.FrameBorderSize); - } else - { - float y = window->Pos.y + window->TitleBarHeight - g.Style.FrameBorderSize; - window->DrawList->AddLineH(window->Pos.x + border_size, window->Pos.x + window->Size.x - border_size, y, border_col, g.Style.FrameBorderSize); - } + window->DrawList->AddLineH(window->Pos.x + border_size, window->Pos.x + window->Size.x - border_size, y, border_col, g.Style.FrameBorderSize, ImDrawFlags_StrokeCenterBiased); } } @@ -7393,7 +7388,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar if (g_LEGACY_STROKES) window->DrawList->AddLineH(menu_bar_rect.Min.x + window_border_size * 0.5f, menu_bar_rect.Max.x - window_border_size * 0.5f, menu_bar_rect.Max.y, GetColorU32(ImGuiCol_Border), style.FrameBorderSize); else - window->DrawList->AddLineH(menu_bar_rect.Min.x + window_border_size, menu_bar_rect.Max.x - window_border_size, menu_bar_rect.Max.y - style.FrameBorderSize, GetColorU32(ImGuiCol_Border), style.FrameBorderSize); + window->DrawList->AddLineH(menu_bar_rect.Min.x + window_border_size, menu_bar_rect.Max.x - window_border_size, menu_bar_rect.Max.y, GetColorU32(ImGuiCol_Border), style.FrameBorderSize, ImDrawFlags_StrokeCenterBiased); } } @@ -18340,12 +18335,12 @@ void ImGui::DebugDrawLineExtents(ImU32 col) float curr_x = window->DC.CursorPos.x; float line_y1 = (window->DC.IsSameLine ? window->DC.CursorPosPrevLine.y : window->DC.CursorPos.y); float line_y2 = line_y1 + (window->DC.IsSameLine ? window->DC.PrevLineSize.y : window->DC.CurrLineSize.y); - window->DrawList->AddLineH(curr_x - 5.0f, curr_x + 5.0f, line_y1, col, 1.0f); + window->DrawList->AddLineH(curr_x - 4.0f, curr_x + 5.0f, line_y1, col, 1.0f); if (g_LEGACY_STROKES) window->DrawList->AddLineV(curr_x - 0.5f, line_y1, line_y2, col, 1.0f); else window->DrawList->AddLineV(curr_x, line_y1, line_y2, col, 1.0f); - window->DrawList->AddLineH(curr_x - 5.0f, curr_x + 5.0f, line_y2, col, 1.0f); + window->DrawList->AddLineH(curr_x - 4.0f, curr_x + 5.0f, line_y2, col, 1.0f); } // Draw last item rect in ForegroundDrawList (so it is always visible) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 32ebdb6f7..a8696841d 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1187,7 +1187,7 @@ void ImDrawList::_SelectFringeTexture(float screen_thickness, ImVec4* out_tex_uv { // Handle the thickness between [1..IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH]. The texture scaling in this range will cause slight visual pops, so we generate super sampled textures in this range. // There are IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX+1 textures, where 0 maps to 1.0 and IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX maps to IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH. - constexpr float base_width = 1.f; + constexpr float base_width = 1.0f; const int texture_idx = ImClamp((int)((screen_thickness - base_width) * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + 0.1f), 0, IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX); const float tex_width = base_width + (float)texture_idx / IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT; *out_fringe = _FringeScale * (screen_thickness / tex_width); // Scale the fringe to cover the discrepancy between the texture and requested size. @@ -2158,12 +2158,12 @@ void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float th if (_FringeScaleIsInteger && ImIsTruncated4(min_x, max_x, top_y, thickness) && (flags & ImDrawFlags_SquareCap) == 0) { float screen_thickness = thickness / _FringeScale; - if (screen_thickness < 1.f / 255.f) + if (screen_thickness < 1.0f / 255.0f) return; - if (screen_thickness < 1.f) + if (screen_thickness < 1.0f) { col = ImAlphaMultiply(col, screen_thickness); - screen_thickness = 1.f; + screen_thickness = 1.0f; thickness = _FringeScale; } @@ -2206,12 +2206,12 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th if (_FringeScaleIsInteger && ImIsTruncated4(left_x, min_y, max_y, thickness) && (flags & ImDrawFlags_SquareCap) == 0) { float screen_thickness = thickness / _FringeScale; - if (screen_thickness < 1.f / 255.f) + if (screen_thickness < 1.0f / 255.0f) return; - if (screen_thickness < 1.f) + if (screen_thickness < 1.0f) { col = ImAlphaMultiply(col, screen_thickness); - screen_thickness = 1.f; + screen_thickness = 1.0f; thickness = _FringeScale; } @@ -2673,18 +2673,18 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c const float width = b_max.x - b_min.x; const float height = b_max.y - b_min.y; - const float inv_fringe_scale = 1.f / _FringeScale; + const float inv_fringe_scale = 1.0f / _FringeScale; const float screen_width = width * inv_fringe_scale; const float screen_height = height * inv_fringe_scale; - if (ImMin(screen_width, screen_height) < 1.f / 255.f) + if (ImMin(screen_width, screen_height) < 1.0f / 255.0f) return; - if (screen_width < 1.f) + if (screen_width < 1.0f) { col = ImAlphaMultiply(col, screen_width); b_max.x = b_min.x + _FringeScale; } - if (screen_height < 1.f) + if (screen_height < 1.0f) { col = ImAlphaMultiply(col, screen_height); b_max.y = b_min.y + _FringeScale; @@ -2970,18 +2970,18 @@ void ImDrawList::AddEllipseFilled(const ImVec2& center, const ImVec2& radius, Im ImVec2 rad = radius; - const float inv_fringe_scale = 1.f / _FringeScale; - float screen_diameter_x = rad.x* 2.f * inv_fringe_scale; - float screen_diameter_y = rad.y * 2.f * inv_fringe_scale; - if (screen_diameter_x < 1.f / 255.f || screen_diameter_y < 1.f / 255.f) + const float inv_fringe_scale = 1.0f / _FringeScale; + float screen_diameter_x = rad.x * 2.0f * inv_fringe_scale; + float screen_diameter_y = rad.y * 2.0f * inv_fringe_scale; + if (screen_diameter_x < 1.0f / 255.0f || screen_diameter_y < 1.0f / 255.0f) return; - if (screen_diameter_x < 1.f) + if (screen_diameter_x < 1.0f) { col = ImAlphaMultiply(col, screen_diameter_x); rad.x = _FringeScale * 0.5f; } - if (screen_diameter_y < 1.f) + if (screen_diameter_y < 1.0f) { col = ImAlphaMultiply(col, screen_diameter_y); rad.y = _FringeScale * 0.5f; @@ -5163,7 +5163,7 @@ static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) ImU8 ramp[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT]; for (int n = 0; n < IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT; n++) - ramp[n] = (ImU8)(((float)n / IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT) * 255.f); + ramp[n] = (ImU8)(((float)n / IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT) * 255.0f); for (int n = 0; n < IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX + 1; n++) { diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 8413344a8..d5d3facbd 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -1763,7 +1763,7 @@ void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end if (g_LEGACY_STROKES) seps_y = ImTrunc((bb.Min.y + bb.Max.y) * 0.5f + 0.999f); else - seps_y = ImTrunc((bb.Min.y + bb.Max.y) * 0.5f + (-separator_thickness * 0.5f + 1.0f) + 0.999f); // Align hline vertically, and snap to pixels. + seps_y = ImTrunc((bb.Min.y + bb.Max.y) * 0.5f) + 1.0f; const float label_avail_w = ImMax(0.0f, sep2_x2 - sep1_x1 - padding.x * 2.0f); const ImVec2 label_pos(pos.x + padding.x + ImMax(0.0f, (label_avail_w - label_size.x - extra_w) * style.SeparatorTextAlign.x), pos.y + text_baseline_y); // FIXME-ALIGN @@ -1777,9 +1777,9 @@ void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end const float sep1_x2 = label_pos.x - style.ItemSpacing.x; const float sep2_x1 = label_pos.x + label_size.x + extra_w + style.ItemSpacing.x; if (sep1_x2 > sep1_x1 && separator_thickness > 0.0f) - window->DrawList->AddLineH(sep1_x1, sep1_x2, seps_y, separator_col, separator_thickness); + window->DrawList->AddLineH(sep1_x1, sep1_x2, seps_y, separator_col, separator_thickness, ImDrawFlags_StrokeCenterBiased); if (sep2_x2 > sep2_x1 && separator_thickness > 0.0f) - window->DrawList->AddLineH(sep2_x1, sep2_x2, seps_y, separator_col, separator_thickness); + window->DrawList->AddLineH(sep2_x1, sep2_x2, seps_y, separator_col, separator_thickness, ImDrawFlags_StrokeCenterBiased); if (g.LogEnabled) LogSetNextTextDecoration("---", NULL); RenderTextEllipsis(window->DrawList, label_pos, ImVec2(bb.Max.x, bb.Max.y + style.ItemSpacing.y), bb.Max.x, label, label_end, &label_size); @@ -1789,7 +1789,7 @@ void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end if (g.LogEnabled) LogText("---"); if (separator_thickness > 0.0f) - window->DrawList->AddLineH(sep1_x1, sep2_x2, seps_y, separator_col, separator_thickness); + window->DrawList->AddLineH(sep1_x1, sep2_x2, seps_y, separator_col, separator_thickness, ImDrawFlags_StrokeCenterBiased); } } @@ -5771,7 +5771,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ else cursor_screen_rect = ImRect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 1.0f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.0f); if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) - draw_window->DrawList->AddLineV(cursor_screen_rect.Min.x, cursor_screen_rect.Min.y, cursor_screen_rect.Max.y, GetColorU32(ImGuiCol_InputTextCursor), style.InputTextCursorSize); + draw_window->DrawList->AddLineV(cursor_screen_rect.Min.x, cursor_screen_rect.Min.y, cursor_screen_rect.Max.y, GetColorU32(ImGuiCol_InputTextCursor), style.InputTextCursorSize, ImDrawFlags_StrokeInside); // Notify OS of text input position for advanced IME (-1 x offset so that Windows IME can cover our cursor. Bit of an extra nicety.) // This is required for some backends (SDL3) to start emitting character/text inputs. @@ -7318,23 +7318,25 @@ void ImGui::TreeNodeDrawLineToChildNode(const ImVec2& target_pos) if ((window->DC.TreeHasStackDataDepthMask & TREE_NODE_GET_DEPTH_MASK(window->DC.TreeDepth - 1)) == 0) return; + // FIXME: Could this be simplified using new stroke flags? ImGuiTreeNodeStackData* parent_data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1]; + const float half_size = g.Style.TreeLinesSize * 0.5f; float x1; if (g_LEGACY_STROKES) x1 = ImTrunc(parent_data->DrawLinesX1); else - x1 = ImTrunc(parent_data->DrawLinesX1 - g.Style.TreeLinesSize * 0.5f) + g.Style.TreeLinesSize * 0.5f; // Draw line centered at X1, but snap to pixels boundary. + x1 = ImTrunc(parent_data->DrawLinesX1 - half_size) + half_size; // Draw line centered at X1, but snap to pixels boundary. float x2 = ImTrunc(target_pos.x - g.Style.ItemInnerSpacing.x); float y; if (g_LEGACY_STROKES) y = ImTrunc(target_pos.y); else - y = ImTrunc(target_pos.y) + g.Style.TreeLinesSize * 0.5f; + y = ImTrunc(target_pos.y - half_size) + half_size; float rounding = (g.Style.TreeLinesRounding > 0.0f) ? ImMin(x2 - x1, g.Style.TreeLinesRounding) : 0.0f; parent_data->DrawLinesToNodesY2 = ImMax(parent_data->DrawLinesToNodesY2, y - rounding); if (x1 >= x2) return; - if (rounding > 0.0f) + if (rounding > 0.0f && rounding > half_size) { if (g_LEGACY_STROKES) x1 += 0.5f + rounding; @@ -7348,9 +7350,9 @@ void ImGui::TreeNodeDrawLineToChildNode(const ImVec2& target_pos) else { // We use AddLine() instead of AddLineH() as we provide coordinates for line center, - // in order to make the code use same coordinates for both rounded and non-rounded version. + // in order to make the code use same coordinates for both rounded and non-rounded version. (FIXME-OPT) if (!g_LEGACY_STROKES) - x1 += g.Style.TreeLinesSize * 0.5f; // Avoid overdraw + x1 += half_size; // Avoid overdraw window->DrawList->AddLine(ImVec2(x1, y), ImVec2(x2, y), GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); } } @@ -7381,7 +7383,8 @@ void ImGui::TreeNodeDrawLineToTreePop(const ImGuiTreeNodeStackData* data) x = ImTrunc(data->DrawLinesX1 - g.Style.TreeLinesSize * 0.5f) + g.Style.TreeLinesSize * 0.5f; // Draw line centered at X1, but snap to pixels boundary. if (data->DrawLinesTableColumn != -1) TablePushColumnChannel(data->DrawLinesTableColumn); - // We use AddLine() instead of AddLineV() as we provide coordinates for line center, + + // We use AddLine() instead of AddLineV() as we provide coordinates for line center (FIXME-OPT) window->DrawList->AddLine(ImVec2(x, y1), ImVec2(x, y2), GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); if (data->DrawLinesTableColumn != -1) TablePopColumnChannel(); From 7ec1bc17fd0b27cb0c5f1471fb7fef0fb02ed1fe Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Mon, 1 Jun 2026 20:35:16 +0300 Subject: [PATCH 051/120] DrawList: debug build optimizations. - changed truncated tests to use macro, and inline the computation in debug builds (~15% was spent on calling the functions) - inlined the common rectangle cases in AddRect() for debug build speed up --- imgui_demo.cpp | 2 +- imgui_draw.cpp | 55 +++++++++++++++++++++++++++++++++++++------------- 2 files changed, 42 insertions(+), 15 deletions(-) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index c4b0939b0..e7d8473a9 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10400,7 +10400,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::DragFloat("Size", &sz, 0.2f, 0.2f, 100.0f, "%.0f"); - const float thickness_anim_period = 5.f; + const float thickness_anim_period = 5.0f; const float thickness_wave = 1.0f - fabsf(fmodf((float)ImGui::GetTime(), thickness_anim_period) * (2.0f / thickness_anim_period) - 1.0f); const float thickness = animate_thickness ? thickness_wave * base_thickness : base_thickness; ImGui::DragFloat("Thickness", &base_thickness, 0.02f, 0.0f, 30.0f, "%.02f"); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index a8696841d..03e51358f 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -854,7 +854,16 @@ static float CalculateCenterBiasedOffset(float thickness, float _FringeScale) } (void)0 #define IM_POLYLINE_MITER_ANGLE_LIMIT (-0.9999619f) // cos(179.5) -#define IM_POLYLINE_MITER_LIMIT (4.0f) +#define IM_POLYLINE_MITER_LIMIT (4.0f) // ~29 deg + +// In debug builds the functions are likely not inlined, so inlined check is cheaper. +#if defined(DEBUG) || defined(_DEBUG) +#define IM_IS_TRUNCATED4(a, b, c, d) ((float)(int)(a) == (a)) && ((float)(int)(b) == (b)) && ((float)(int)(c) == (c)) && ((float)(int)(d) == (d)) +#define IM_IS_TRUNCATED(a) ((float)(int)(a) == (a)) +#else +#define IM_IS_TRUNCATED4(a, b, c, d) ImIsTruncated4(a, b, c, d) +#define IM_IS_TRUNCATED(a) ImIsTruncated(a) +#endif void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, const ImVec4& tex_uvs, float fringe) { @@ -2155,7 +2164,8 @@ void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float th else if (stroke_pos == ImDrawFlags_StrokeOutside) top_y -= thickness; - if (_FringeScaleIsInteger && ImIsTruncated4(min_x, max_x, top_y, thickness) && (flags & ImDrawFlags_SquareCap) == 0) + const bool is_truncated = IM_IS_TRUNCATED4(min_x, max_x, top_y, thickness); + if (_FringeScaleIsInteger && is_truncated && (flags & ImDrawFlags_SquareCap) == 0) { float screen_thickness = thickness / _FringeScale; if (screen_thickness < 1.0f / 255.0f) @@ -2203,7 +2213,8 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th else if (stroke_pos == ImDrawFlags_StrokeOutside) left_x -= thickness; - if (_FringeScaleIsInteger && ImIsTruncated4(left_x, min_y, max_y, thickness) && (flags & ImDrawFlags_SquareCap) == 0) + const bool is_truncated = IM_IS_TRUNCATED4(left_x, min_y, max_y, thickness); + if (_FringeScaleIsInteger && is_truncated && (flags & ImDrawFlags_SquareCap) == 0) { float screen_thickness = thickness / _FringeScale; if (screen_thickness < 1.0f / 255.0f) @@ -2537,7 +2548,8 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl outer_rounding = ImMin(outer_rounding, ImFabs(height) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f); } - if (_FringeScaleIsInteger && ImIsTruncated4(outer_min.x, outer_min.y, outer_max.x, outer_max.y) && ImIsTruncated4(outer_rounding, thickness, 0.0f, 0.0f)) + const bool is_truncated = IM_IS_TRUNCATED4(outer_min.x, outer_min.y, outer_max.x, outer_max.y) && IM_IS_TRUNCATED4(outer_rounding, thickness, 0.0f, 0.0f); + if (_FringeScaleIsInteger && is_truncated) { int s_rounding = (int)(outer_rounding / _FringeScale); int s_thickness = (int)(thickness / _FringeScale); @@ -2690,11 +2702,12 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c b_max.y = b_min.y + _FringeScale; } - if (ImIsTruncated4(p_min.x, p_min.y, p_max.x, p_max.y) && ImIsTruncated(rounding)) - { - if ((flags & ImDrawFlags_RoundCornersMask_) == 0) - flags |= ImDrawFlags_RoundCornersAll; + if ((flags & ImDrawFlags_RoundCornersMask_) == 0) + flags |= ImDrawFlags_RoundCornersAll; + const bool is_truncated = IM_IS_TRUNCATED4(p_min.x, p_min.y, p_max.x, p_max.y) && IM_IS_TRUNCATED(rounding); + if (_FringeScaleIsInteger && is_truncated) + { rounding = ImMin(rounding, ImFabs(width) * (((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f) - 1.0f); rounding = ImMin(rounding, ImFabs(height) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f); @@ -2702,7 +2715,18 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c if (s_rounding <= 0 || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) { PrimReserve(6, 4); - PrimRect(p_min, p_max, col); + + const ImVec2 opaque_uv = _Data->TexUvWhitePixel; + const ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; + IM_APPEND_VTX(p_min.x, p_min.y, opaque_uv, col); + IM_APPEND_VTX(p_max.x, p_min.y, opaque_uv, col); + IM_APPEND_VTX(p_max.x, p_max.y, opaque_uv, col); + IM_APPEND_VTX(p_min.x, p_max.y, opaque_uv, col); + + IM_APPEND_TRI(idx + 0, idx + 1, idx + 2); + IM_APPEND_TRI(idx + 0, idx + 2, idx + 3); + + return; } else if ((Flags & ImDrawListFlags_RoundCornersUseTex) && s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) { @@ -2712,12 +2736,15 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c IM_ASSERT_PARANOID(idx >= 0 && idx < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX); ImVec4 tex_uvs = _Data->TexUvCorners[idx]; _AddRectFilledBaked(p_min, p_max, col, (float)size * _FringeScale, tex_uvs, flags); + return; } - else - { - PathRect(p_min, p_max, (float)s_rounding, flags); - PathFillConvex(col); - } + } + + const bool has_rounding = (rounding >= _FringeScale * 0.5f); + if (!has_rounding || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) + { + const ImVec2 points[4] = { p_min, ImVec2(p_max.x, p_min.y), p_max, ImVec2(p_min.x, p_max.y) }; + AddConvexPolyFilled(points, 4, col, ImDrawFlags_MiterOnly); } else { From b484a908465ed0dcf29eeb8abb7006939ef87fb1 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Tue, 2 Jun 2026 09:52:26 +0300 Subject: [PATCH 052/120] DrawList: minor render optimizations. - Use inline array + AddPolyline/AddConvexPolyFilled for triangle and quad - do less computation for detecting small shape rendering - add IM_UNLIKELY for very unlikely branches --- imgui_draw.cpp | 128 ++++++++++++++++++++++++------------------------- 1 file changed, 62 insertions(+), 66 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 03e51358f..8837d14cf 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2500,7 +2500,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); - if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) + if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) IM_UNLIKELY { if (Flags & ImDrawListFlags_AntiAliasedLines) PathRect(ImVec2(p_min.x + 0.50f, p_min.y + 0.50f), ImVec2(p_max.x - 0.50f, p_max.y - 0.50f), rounding, flags); @@ -2532,7 +2532,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl const float width = outer_max.x - outer_min.x; const float height = outer_max.y - outer_min.y; - if (ImMin(width, height) < (thickness + _FringeScale) * 2.0f) + if (ImMin(width, height) < (thickness + _FringeScale) * 2.0f) IM_UNLIKELY { // Rectangle has collapsed into filled rectangle. AddRectFilled(outer_min, outer_max, col, outer_rounding, flags); @@ -2680,26 +2680,28 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c if ((col & IM_COL32_A_MASK) == 0) return; - ImVec2 b_min = p_min; - ImVec2 b_max = p_max; - const float width = b_max.x - b_min.x; - const float height = b_max.y - b_min.y; + float width = p_max.x - p_min.x; + float height = p_max.y - p_min.y; + const float min_dim = ImMin(width, height); - const float inv_fringe_scale = 1.0f / _FringeScale; - const float screen_width = width * inv_fringe_scale; - const float screen_height = height * inv_fringe_scale; - - if (ImMin(screen_width, screen_height) < 1.0f / 255.0f) + // The rect is smaller than pixel in one dimension. + if (min_dim < _FringeScale) IM_UNLIKELY + { + if (min_dim < (1.0f / 255.0f) * _FringeScale) + return; + if (width < _FringeScale) + { + col = ImAlphaMultiply(col, width / _FringeScale); + width = _FringeScale; + } + if (height < _FringeScale) + { + col = ImAlphaMultiply(col, height / _FringeScale); + height = _FringeScale; + } + const ImVec2 points[4] = { p_min, ImVec2(p_min.x + width, p_min.y), ImVec2(p_min.x + width, p_min.y + height), ImVec2(p_min.x, p_max.y + height) }; + AddConvexPolyFilled(points, 4, col, ImDrawFlags_MiterOnly); return; - if (screen_width < 1.0f) - { - col = ImAlphaMultiply(col, screen_width); - b_max.x = b_min.x + _FringeScale; - } - if (screen_height < 1.0f) - { - col = ImAlphaMultiply(col, screen_height); - b_max.y = b_min.y + _FringeScale; } if ((flags & ImDrawFlags_RoundCornersMask_) == 0) @@ -2777,11 +2779,8 @@ void ImDrawList::AddQuad(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, c ImDrawFlags stroke_pos = g_LEGACY_STROKES ? ImDrawFlags_StrokeLegacy : _GetStrokePos(flags, ImDrawFlags_StrokeInside); flags = (flags & ~ImDrawFlags_StrokeMask_) | stroke_pos; - PathLineTo(p1); - PathLineTo(p2); - PathLineTo(p3); - PathLineTo(p4); - PathStroke(col, thickness, flags | ImDrawFlags_Closed); + const ImVec2 points[4] = { p1, p2, p3, p4 }; + AddPolyline(points, 4, col, thickness, flags | ImDrawFlags_Closed); } void ImDrawList::AddQuadFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col) @@ -2789,11 +2788,8 @@ void ImDrawList::AddQuadFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& if ((col & IM_COL32_A_MASK) == 0) return; - PathLineTo(p1); - PathLineTo(p2); - PathLineTo(p3); - PathLineTo(p4); - PathFillConvex(col); + const ImVec2 points[4] = { p1, p2, p3, p4 }; + AddConvexPolyFilled(points, 4, col); } void ImDrawList::AddTriangle(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness, ImDrawFlags flags) @@ -2803,10 +2799,9 @@ void ImDrawList::AddTriangle(const ImVec2& p1, const ImVec2& p2, const ImVec2& p ImDrawFlags stroke_pos = g_LEGACY_STROKES ? ImDrawFlags_StrokeLegacy : _GetStrokePos(flags, ImDrawFlags_StrokeInside); flags = (flags & ~ImDrawFlags_StrokeMask_) | stroke_pos; - PathLineTo(p1); - PathLineTo(p2); - PathLineTo(p3); - PathStroke(col, thickness, flags | ImDrawFlags_Closed); + + const ImVec2 points[3] = { p1, p2, p3 }; + AddPolyline(points, 3, col, thickness, flags | ImDrawFlags_Closed); } void ImDrawList::AddTriangleFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col) @@ -2814,10 +2809,8 @@ void ImDrawList::AddTriangleFilled(const ImVec2& p1, const ImVec2& p2, const ImV if ((col & IM_COL32_A_MASK) == 0) return; - PathLineTo(p1); - PathLineTo(p2); - PathLineTo(p3); - PathFillConvex(col); + const ImVec2 points[3] = { p1, p2, p3 }; + AddConvexPolyFilled(points, 3, col); } void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness, ImDrawFlags flags) @@ -2826,7 +2819,7 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu return; ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); - if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) + if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) IM_UNLIKELY { radius -= 0.5f; stroke_pos = ImDrawFlags_StrokeCenter; @@ -2843,7 +2836,7 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu outer_radius = radius; else if (stroke_pos == ImDrawFlags_StrokeOutside) outer_radius = radius + thickness; - if (outer_radius < (thickness + _FringeScale)) + if (outer_radius < (thickness + _FringeScale)) IM_UNLIKELY { // The circle has collapsed into a filled circle. AddCircleFilled(center, outer_radius, col, num_segments); @@ -2875,12 +2868,12 @@ void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col, if ((col & IM_COL32_A_MASK) == 0) return; - float screen_diameter = radius * 2.0f / _FringeScale; - if (screen_diameter < 1.0f / 255.0f) - return; - if (screen_diameter < 1.0f) + const float diameter = radius * 2.0f; + if (diameter < _FringeScale) IM_UNLIKELY { - col = ImAlphaMultiply(col, screen_diameter); + if (diameter < (1.0f / 255.0f) * _FringeScale) + return; + col = ImAlphaMultiply(col, diameter / _FringeScale); radius = _FringeScale * 0.5f; } @@ -2910,7 +2903,7 @@ void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_ return; ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); - if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) + if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) IM_UNLIKELY { radius -= 0.5f; stroke_pos = ImDrawFlags_StrokeCenter; @@ -2930,7 +2923,7 @@ void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_ outer_radius = radius; else if (stroke_pos == ImDrawFlags_StrokeOutside) outer_radius = radius + miter_thickness; - if (outer_radius < (miter_thickness + _FringeScale)) + if (outer_radius < (miter_thickness + _FringeScale)) IM_UNLIKELY { // The polygon has collapsed into a filled polygon. AddNgonFilled(center, outer_radius, col, num_segments); @@ -2949,12 +2942,12 @@ void ImDrawList::AddNgonFilled(const ImVec2& center, float radius, ImU32 col, in if ((col & IM_COL32_A_MASK) == 0 || num_segments <= 2) return; - float screen_diameter = radius * 2.0f / _FringeScale; - if (screen_diameter < 1.0f / 255.0f) - return; - if (screen_diameter < 1.0f) + const float diameter = radius * 2.0f; + if (diameter < _FringeScale) IM_UNLIKELY { - col = ImAlphaMultiply(col, screen_diameter); + if (diameter < (1.0f / 255.0f) * _FringeScale) + return; + col = ImAlphaMultiply(col, diameter / _FringeScale); radius = _FringeScale * 0.5f; } @@ -2996,22 +2989,25 @@ void ImDrawList::AddEllipseFilled(const ImVec2& center, const ImVec2& radius, Im return; ImVec2 rad = radius; + float diameter_x = rad.x * 2.0f; + float diameter_y = rad.y * 2.0f; + const float min_dim = ImMin(diameter_x, diameter_y); - const float inv_fringe_scale = 1.0f / _FringeScale; - float screen_diameter_x = rad.x * 2.0f * inv_fringe_scale; - float screen_diameter_y = rad.y * 2.0f * inv_fringe_scale; - if (screen_diameter_x < 1.0f / 255.0f || screen_diameter_y < 1.0f / 255.0f) - return; - - if (screen_diameter_x < 1.0f) + // The ellipse is smaller than pixel in one dimension. + if (min_dim < _FringeScale) IM_UNLIKELY { - col = ImAlphaMultiply(col, screen_diameter_x); - rad.x = _FringeScale * 0.5f; - } - if (screen_diameter_y < 1.0f) - { - col = ImAlphaMultiply(col, screen_diameter_y); - rad.y = _FringeScale * 0.5f; + if (min_dim < (1.0f / 255.0f) * _FringeScale) + return; + if (diameter_x < _FringeScale) + { + col = ImAlphaMultiply(col, diameter_x / _FringeScale); + rad.x = _FringeScale * 0.5f; + } + if (diameter_y < _FringeScale) + { + col = ImAlphaMultiply(col, diameter_y / _FringeScale); + rad.y = _FringeScale * 0.5f; + } } if (num_segments <= 0) From 6856b3e06bb96b48261df3ee4f8f5846110ca261 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Tue, 2 Jun 2026 10:05:54 +0300 Subject: [PATCH 053/120] DrawList: added _InvFringeScale to use in cases of 1/_FringeScale. --- imgui.h | 4 +++- imgui_draw.cpp | 58 +++++++++++++++++++++++--------------------------- 2 files changed, 30 insertions(+), 32 deletions(-) diff --git a/imgui.h b/imgui.h index 3cb10c746..cf7f079b3 100644 --- a/imgui.h +++ b/imgui.h @@ -3393,7 +3393,7 @@ struct ImDrawList ImVector _ClipRectStack; // [Internal] ImVector _TextureStack; // [Internal] ImVector _CallbacksDataBuf; // [Internal] - float _FringeScale; // [Internal] anti-alias fringe is scaled by this value, this helps to keep things sharp while zooming at vertex buffer content. + float _FringeScale; // [Internal] anti-alias fringe is scaled by this value, this helps to keep things sharp while zooming at vertex buffer content float _InvFringeScale; // [internal] 1.0 / _FringeScale // FIXME: Consider renaming to _PixelDensity. bool _FringeScaleIsInteger; // [Internal] true if 1/_FringeScale is a whole number, used to select fast path for rendering const char* _OwnerName; // Pointer to owner window's name for debugging @@ -3548,6 +3548,8 @@ struct ImDrawList IMGUI_API void _AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, const ImVec4& tex_uvs, float fringe); IMGUI_API void _AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags); IMGUI_API void _SelectFringeTexture(float screen_thickness, ImVec4* out_tex_uvs, float* out_fringe); + IMGUI_API float _CalculateCenterBiasedOffset(float thickness); + }; // All draw data to render a Dear ImGui frame diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 8837d14cf..3d1dbf533 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -813,7 +813,7 @@ void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, c } -static float CalculateCenterBiasedOffset(float thickness, float _FringeScale) +float ImDrawList::_CalculateCenterBiasedOffset(float thickness) { // Old jumpy one. // return IM_TRUNC(thickness * 0.5f / _FringeScale) * _FringeScale; @@ -821,7 +821,7 @@ static float CalculateCenterBiasedOffset(float thickness, float _FringeScale) // Calculate outside offset for stroke position ImDrawFlags_StrokeCenterBiased // so that one side the of the line is always at pixel boundary. // On integer thickness both sides of the line are on pixel boundary. - const float screen_thickness = thickness / _FringeScale; + const float screen_thickness = thickness * _InvFringeScale; const int s_thickness = (int)screen_thickness; return ((float)(s_thickness / 2) + (s_thickness & 1) * (screen_thickness - s_thickness)) * _FringeScale; } @@ -881,7 +881,7 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ if (stroke_pos == ImDrawFlags_StrokeOutside) thickness0 = thickness + fringe * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeCenterBiased) - thickness0 = CalculateCenterBiasedOffset(thickness, _FringeScale) + fringe * 0.5f; + thickness0 = _CalculateCenterBiasedOffset(thickness) + fringe * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeInside) thickness0 = fringe * 0.5f; float thickness1 = (thickness + fringe) - thickness0; @@ -1224,7 +1224,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 if (points_count < 2 || (col & IM_COL32_A_MASK) == 0) return; - float screen_thickness = thickness / _FringeScale; + float screen_thickness = thickness * _InvFringeScale; if (screen_thickness < 1.0f / 255.0f) return; if (screen_thickness < 1.0f) @@ -2160,20 +2160,18 @@ void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float th if (stroke_pos == ImDrawFlags_StrokeCenter) top_y -= thickness * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeCenterBiased) - top_y -= CalculateCenterBiasedOffset(thickness, _FringeScale); + top_y -= _CalculateCenterBiasedOffset(thickness); else if (stroke_pos == ImDrawFlags_StrokeOutside) top_y -= thickness; const bool is_truncated = IM_IS_TRUNCATED4(min_x, max_x, top_y, thickness); if (_FringeScaleIsInteger && is_truncated && (flags & ImDrawFlags_SquareCap) == 0) { - float screen_thickness = thickness / _FringeScale; - if (screen_thickness < 1.0f / 255.0f) - return; - if (screen_thickness < 1.0f) + if (thickness < _FringeScale) IM_UNLIKELY { - col = ImAlphaMultiply(col, screen_thickness); - screen_thickness = 1.0f; + if (thickness < (1.0f / 255.0f) * _FringeScale) + return; + col = ImAlphaMultiply(col, thickness * _InvFringeScale); thickness = _FringeScale; } @@ -2209,20 +2207,18 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th if (stroke_pos == ImDrawFlags_StrokeCenter) left_x -= thickness * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeCenterBiased) - left_x -= CalculateCenterBiasedOffset(thickness, _FringeScale); + left_x -= _CalculateCenterBiasedOffset(thickness); else if (stroke_pos == ImDrawFlags_StrokeOutside) left_x -= thickness; const bool is_truncated = IM_IS_TRUNCATED4(left_x, min_y, max_y, thickness); if (_FringeScaleIsInteger && is_truncated && (flags & ImDrawFlags_SquareCap) == 0) { - float screen_thickness = thickness / _FringeScale; - if (screen_thickness < 1.0f / 255.0f) - return; - if (screen_thickness < 1.0f) + if (thickness < _FringeScale) IM_UNLIKELY { - col = ImAlphaMultiply(col, screen_thickness); - screen_thickness = 1.0f; + if (thickness < (1.0f / 255.0f) * _FringeScale) + return; + col = ImAlphaMultiply(col, thickness * _InvFringeScale); thickness = _FringeScale; } @@ -2364,7 +2360,7 @@ void ImDrawList::_AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 c // The stroke is positioned inside the rectangle. void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags) { - float screen_thickness = thickness / _FringeScale; + float screen_thickness = thickness * _InvFringeScale; if (screen_thickness < 1.0f / 255.0f) return; if (screen_thickness < 1.0f) @@ -2521,7 +2517,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl if (stroke_pos == ImDrawFlags_StrokeCenter) offset = thickness * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeCenterBiased) - offset = CalculateCenterBiasedOffset(thickness, _FringeScale); + offset = _CalculateCenterBiasedOffset(thickness); else if (stroke_pos == ImDrawFlags_StrokeOutside) offset = thickness; outer_min.x -= offset; @@ -2551,8 +2547,8 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl const bool is_truncated = IM_IS_TRUNCATED4(outer_min.x, outer_min.y, outer_max.x, outer_max.y) && IM_IS_TRUNCATED4(outer_rounding, thickness, 0.0f, 0.0f); if (_FringeScaleIsInteger && is_truncated) { - int s_rounding = (int)(outer_rounding / _FringeScale); - int s_thickness = (int)(thickness / _FringeScale); + int s_rounding = (int)(outer_rounding * _InvFringeScale); + int s_thickness = (int)(thickness * _InvFringeScale); if (s_thickness <= 0) return; @@ -2691,12 +2687,12 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c return; if (width < _FringeScale) { - col = ImAlphaMultiply(col, width / _FringeScale); + col = ImAlphaMultiply(col, width * _InvFringeScale); width = _FringeScale; } if (height < _FringeScale) { - col = ImAlphaMultiply(col, height / _FringeScale); + col = ImAlphaMultiply(col, height * _InvFringeScale); height = _FringeScale; } const ImVec2 points[4] = { p_min, ImVec2(p_min.x + width, p_min.y), ImVec2(p_min.x + width, p_min.y + height), ImVec2(p_min.x, p_max.y + height) }; @@ -2713,7 +2709,7 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c rounding = ImMin(rounding, ImFabs(width) * (((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f) - 1.0f); rounding = ImMin(rounding, ImFabs(height) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f); - const int s_rounding = (int)(rounding / _FringeScale); + const int s_rounding = (int)(rounding * _InvFringeScale); if (s_rounding <= 0 || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) { PrimReserve(6, 4); @@ -2831,7 +2827,7 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu if (stroke_pos == ImDrawFlags_StrokeCenter) outer_radius = radius + thickness * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeCenterBiased) - outer_radius = radius + CalculateCenterBiasedOffset(thickness, _FringeScale); + outer_radius = radius + _CalculateCenterBiasedOffset(thickness); else if (stroke_pos == ImDrawFlags_StrokeInside) outer_radius = radius; else if (stroke_pos == ImDrawFlags_StrokeOutside) @@ -2873,7 +2869,7 @@ void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col, { if (diameter < (1.0f / 255.0f) * _FringeScale) return; - col = ImAlphaMultiply(col, diameter / _FringeScale); + col = ImAlphaMultiply(col, diameter * _InvFringeScale); radius = _FringeScale * 0.5f; } @@ -2918,7 +2914,7 @@ void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_ if (stroke_pos == ImDrawFlags_StrokeCenter) outer_radius = radius + miter_thickness * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeCenterBiased) - outer_radius = radius + CalculateCenterBiasedOffset(miter_thickness, _FringeScale); + outer_radius = radius + _CalculateCenterBiasedOffset(miter_thickness); else if (stroke_pos == ImDrawFlags_StrokeInside) outer_radius = radius; else if (stroke_pos == ImDrawFlags_StrokeOutside) @@ -2947,7 +2943,7 @@ void ImDrawList::AddNgonFilled(const ImVec2& center, float radius, ImU32 col, in { if (diameter < (1.0f / 255.0f) * _FringeScale) return; - col = ImAlphaMultiply(col, diameter / _FringeScale); + col = ImAlphaMultiply(col, diameter * _InvFringeScale); radius = _FringeScale * 0.5f; } @@ -3000,12 +2996,12 @@ void ImDrawList::AddEllipseFilled(const ImVec2& center, const ImVec2& radius, Im return; if (diameter_x < _FringeScale) { - col = ImAlphaMultiply(col, diameter_x / _FringeScale); + col = ImAlphaMultiply(col, diameter_x * _InvFringeScale); rad.x = _FringeScale * 0.5f; } if (diameter_y < _FringeScale) { - col = ImAlphaMultiply(col, diameter_y / _FringeScale); + col = ImAlphaMultiply(col, diameter_y * _InvFringeScale); rad.y = _FringeScale * 0.5f; } } From 130be3be429ce3e985a5c264f226326696b432b1 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Tue, 2 Jun 2026 11:11:28 +0300 Subject: [PATCH 054/120] DrawList: Optimization: added specialized 2 point polyline _AddLine(). --- imgui.h | 1 + imgui_draw.cpp | 170 +++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 157 insertions(+), 14 deletions(-) diff --git a/imgui.h b/imgui.h index cf7f079b3..357915cd8 100644 --- a/imgui.h +++ b/imgui.h @@ -3546,6 +3546,7 @@ struct ImDrawList IMGUI_API void _AddRectFilledBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, const ImVec4& tex_uvs, ImDrawFlags flags); IMGUI_API void _AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, float t, const ImVec4& tex_uvs, ImDrawFlags flags); IMGUI_API void _AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, const ImVec4& tex_uvs, float fringe); + IMGUI_API void _AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness, ImDrawFlags flags); IMGUI_API void _AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags); IMGUI_API void _SelectFringeTexture(float screen_thickness, ImVec4* out_tex_uvs, float* out_fringe); IMGUI_API float _CalculateCenterBiasedOffset(float thickness); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 3d1dbf533..f4925568f 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1197,7 +1197,9 @@ void ImDrawList::_SelectFringeTexture(float screen_thickness, ImVec4* out_tex_uv // Handle the thickness between [1..IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH]. The texture scaling in this range will cause slight visual pops, so we generate super sampled textures in this range. // There are IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX+1 textures, where 0 maps to 1.0 and IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX maps to IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH. constexpr float base_width = 1.0f; - const int texture_idx = ImClamp((int)((screen_thickness - base_width) * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + 0.1f), 0, IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX); + int texture_idx = (int)((screen_thickness - base_width) * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + 0.1f); + texture_idx = ImMax(texture_idx, 0); + texture_idx = ImMin(texture_idx, IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX); const float tex_width = base_width + (float)texture_idx / IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT; *out_fringe = _FringeScale * (screen_thickness / tex_width); // Scale the fringe to cover the discrepancy between the texture and requested size. *out_tex_uvs = _Data->TexUvLines[(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1) + texture_idx]; @@ -1205,7 +1207,9 @@ void ImDrawList::_SelectFringeTexture(float screen_thickness, ImVec4* out_tex_uv else { // Bias the texture selection towards higher resolution to avoid visual pops when the texture change. - const int texture_idx = ImClamp((int)(screen_thickness + 0.995f), 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1); + int texture_idx = (int)(screen_thickness + 0.995f); + texture_idx = ImMax(texture_idx, 2); + texture_idx = ImMin(texture_idx, IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1); *out_fringe = _FringeScale * (screen_thickness / (float)texture_idx); // Scale the fringe to cover the discrepancy between the texture and requested size. *out_tex_uvs = _Data->TexUvLines[texture_idx]; } @@ -2126,6 +2130,150 @@ ImDrawFlags ImDrawList::_GetStrokePos(ImDrawFlags flags, ImDrawFlags default_str return _Data->OverrideStrokePos ? _Data->OverrideStrokePos : default_stroke_pos; } +void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness, ImDrawFlags flags) +{ + if (g_LEGACY_STROKES) IM_UNLIKELY + { + ImVec2 points[2] = { p1, p2 }; + AddPolylineLegacy(points, 2, col, thickness, flags); + return; + } + + if ((col & IM_COL32_A_MASK) == 0) IM_UNLIKELY + return; + + float screen_thickness = thickness * _InvFringeScale; + if (screen_thickness < 1.0f / 255.0f) IM_UNLIKELY + return; + if (screen_thickness < 1.0f) IM_UNLIKELY + { + col = ImAlphaMultiply(col, screen_thickness); + screen_thickness = 1.0f; + thickness = _FringeScale; + } + + ImVec4 tex_uvs; + float fringe; + _SelectFringeTexture(screen_thickness, &tex_uvs, &fringe); + + float dir_x = p2.x - p1.x; + float dir_y = p2.y - p1.y; + const float d2 = dir_x * dir_x + dir_y * dir_y; + const float inv_len = (d2 > 0.0f) ? ImRsqrtPrecise(d2) : 0.0f; + dir_x *= inv_len; + dir_y *= inv_len; + const float norm_x = -dir_y; + const float norm_y = dir_x; + + const ImU32 col_trans = col & ~IM_COL32_A_MASK; + + const ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeCenter); + if (stroke_pos == ImDrawFlags_StrokeLegacy) + flags |= ImDrawFlags_MiterOnly | ImDrawFlags_NoAAEnds; + + float thickness0 = (thickness + fringe) * 0.5f; // Center (or legacy) + if (stroke_pos == ImDrawFlags_StrokeOutside) + thickness0 = thickness + fringe * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeCenterBiased) + thickness0 = _CalculateCenterBiasedOffset(thickness) + fringe * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeInside) + thickness0 = fringe * 0.5f; + float thickness1 = (thickness + fringe) - thickness0; + + + const float half_texel = 0.5f * _Data->FontAtlas->TexUvScale.x; + const float ratio = thickness0 / (thickness0 + thickness1); // The points using uv2 are placed on the path, calculate the position from stroke offets. + ImVec2 uv0, uv1, uv2; + uv0.x = tex_uvs.x + half_texel; + uv0.y = tex_uvs.y; + uv1.x = tex_uvs.z - half_texel; + uv1.y = tex_uvs.y; + uv2.x = uv0.x + (uv1.x - uv0.x) * ratio; + uv2.y = tex_uvs.y; + + const float half_aa = _FringeScale * 0.5f; // Used for end caps, does not use "fringe" since end cap AA is not using the texture. + const float half_thickness = thickness * 0.5f; + + if (flags & ImDrawFlags_NoAAEnds) + PrimReserve(2*3, 4); + else + PrimReserve(6*3, 8); + + int base_idx = (int)_VtxCurrentIdx; + + float p1_x = p1.x; + float p1_y = p1.y; + float p2_x = p2.x; + float p2_y = p2.y; + + if (flags & ImDrawFlags_SquareCap) + { + p1_x -= dir_x * half_thickness; + p1_y -= dir_y * half_thickness; + p2_x += dir_x * half_thickness; + p2_y += dir_y * half_thickness; + } + + // Start cap + if (flags & ImDrawFlags_NoAAEnds) + { + base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(p1_x - norm_x * thickness0, p1_y - norm_y * thickness0, uv0, col); + IM_APPEND_VTX(p1_x + norm_x * thickness1, p1_y + norm_y * thickness1, uv1, col); + + IM_APPEND_VTX(p2_x - norm_x * thickness0, p2_y - norm_y * thickness0, uv0, col); + IM_APPEND_VTX(p2_x + norm_x * thickness1, p2_y + norm_y * thickness1, uv1, col); + + // Connect + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); + IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); + } + else + { + // Start + float pa_x = p1_x - dir_x * half_aa; + float pa_y = p1_y - dir_y * half_aa; + float pb_x = p1_x + dir_x * half_aa; + float pb_y = p1_y + dir_y * half_aa; + + base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(pa_x - norm_x * thickness0, pa_y - norm_y * thickness0, uv0, col_trans); + IM_APPEND_VTX(pa_x + norm_x * thickness1, pa_y + norm_y * thickness1, uv1, col_trans); + + int next_base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(pb_x - norm_x * thickness0, pb_y - norm_y * thickness0, uv0, col); + IM_APPEND_VTX(pb_x + norm_x * thickness1, pb_y + norm_y * thickness1, uv1, col); + + // AA cap + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); + IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); + base_idx = next_base_idx; + + // End + pa_x = p2_x - dir_x * half_aa; + pa_y = p2_y - dir_y * half_aa; + pb_x = p2_x + dir_x * half_aa; + pb_y = p2_y + dir_y * half_aa; + + next_base_idx = (int)_VtxCurrentIdx; + IM_APPEND_VTX(pa_x - norm_x * thickness0, pa_y - norm_y * thickness0, uv0, col); + IM_APPEND_VTX(pa_x + norm_x * thickness1, pa_y + norm_y * thickness1, uv1, col); + + IM_APPEND_VTX(pb_x - norm_x * thickness0, pb_y - norm_y * thickness0, uv0, col_trans); + IM_APPEND_VTX(pb_x + norm_x * thickness1, pb_y + norm_y * thickness1, uv1, col_trans); + + // Connect + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); + IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); + base_idx = next_base_idx; + + // AA cap + IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); + IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); + + } +} + void ImDrawList::AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) @@ -2134,13 +2282,11 @@ void ImDrawList::AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float th ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeCenter); if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) { - const ImVec2 points[2] = { ImVec2(p1.x + 0.5f, p1.y + 0.5f), ImVec2(p2.x + 0.5f, p2.y + 0.5f) }; - AddPolyline(points, 2, col, thickness, ImDrawFlags_StrokeLegacy); + _AddLine(ImVec2(p1.x + 0.5f, p1.y + 0.5f), ImVec2(p2.x + 0.5f, p2.y + 0.5f), col, thickness, flags); return; } - const ImVec2 points[2] = { p1, p2 }; - AddPolyline(points, 2, col, thickness, flags); + _AddLine(p1, p2, col, thickness, flags); } void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float thickness, ImDrawFlags flags) @@ -2151,8 +2297,7 @@ void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float th ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) { - const ImVec2 points[2] = { ImVec2(min_x + 0.5f, y + 0.5f), ImVec2(max_x + 0.5f, y + 0.5f) }; // Same as AddLine() above. - AddPolyline(points, 2, col, thickness, ImDrawFlags_StrokeLegacy); + _AddLine(ImVec2(min_x + 0.5f, y + 0.5f), ImVec2(max_x + 0.5f, y + 0.5f), col, thickness, flags); return; } @@ -2185,8 +2330,7 @@ void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float th flags = (flags & ~ImDrawFlags_StrokeMask_) | stroke_pos; // For generic case use line, since it needs less triangles than AA rectangle. - const ImVec2 points[2] = { ImVec2(min_x, y), ImVec2(max_x, y) }; - AddPolyline(points, 2, col, thickness, flags); + _AddLine(ImVec2(min_x, y), ImVec2(max_x, y), col, thickness, flags); } } @@ -2198,8 +2342,7 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) { - const ImVec2 points[2] = { ImVec2(x + 0.5f, min_y + 0.5f), ImVec2(x + 0.5f, max_y + 0.5f) }; // Same as AddLine() above. - AddPolyline(points, 2, col, thickness, ImDrawFlags_StrokeLegacy); + _AddLine(ImVec2(x + 0.5f, min_y + 0.5f), ImVec2(x + 0.5f, max_y + 0.5f), col, thickness, flags); return; } @@ -2233,8 +2376,7 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th // For generic case use line, since it needs less triangles than AA rectangle. // Drawing for bottom to top, so that the inside stroke pos expands to right. - const ImVec2 points[2] = { ImVec2(x, max_y), ImVec2(x, min_y) }; - AddPolyline(points, 2, col, thickness, flags); + _AddLine(ImVec2(x, max_y), ImVec2(x, min_y), col, thickness, flags); } } From fc8f5e66d48521d6b78ea0b3502247747f367bc0 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Tue, 2 Jun 2026 12:27:39 +0300 Subject: [PATCH 055/120] DrawList: fixed indices when AllowVtxOffset is on - fix shape closing indices for AddPolyline and _AddRectTinyRounding --- imgui_draw.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index f4925568f..c65bd7423 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1171,8 +1171,8 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ else { // Connect the path. - start_vtx_ptr[0] = VtxBuffer.Data[base_idx+0]; - start_vtx_ptr[1] = VtxBuffer.Data[base_idx+1]; + start_vtx_ptr[0] = VtxBuffer.Data[_CmdHeader.VtxOffset + base_idx+0]; + start_vtx_ptr[1] = VtxBuffer.Data[_CmdHeader.VtxOffset + base_idx+1]; } // Restore unused memory @@ -2494,6 +2494,7 @@ void ImDrawList::_AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 c const int idx_used = (int)(_IdxWritePtr - start_idx_ptr); const int vtx_used = (int)(_VtxWritePtr - start_vtx_ptr); + IM_ASSERT_PARANOID(idx_used <= idx_allocated && vtx_used <= vtx_allocated); if (idx_used < idx_allocated || vtx_used < vtx_allocated) PrimUnreserve(idx_allocated - idx_used, vtx_allocated - vtx_used); } @@ -2607,13 +2608,13 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, } // Close - start_vtx_ptr[0] = VtxBuffer.Data[base_idx + 0]; - start_vtx_ptr[1] = VtxBuffer.Data[base_idx + 1]; - start_vtx_ptr[2] = VtxBuffer.Data[base_idx + 2]; + start_vtx_ptr[0] = VtxBuffer.Data[_CmdHeader.VtxOffset + base_idx + 0]; + start_vtx_ptr[1] = VtxBuffer.Data[_CmdHeader.VtxOffset + base_idx + 1]; + start_vtx_ptr[2] = VtxBuffer.Data[_CmdHeader.VtxOffset + base_idx + 2]; const int idx_used = (int)(_IdxWritePtr - start_idx_ptr); const int vtx_used = (int)(_VtxWritePtr - start_vtx_ptr); - IM_ASSERT(idx_used <= idx_count && vtx_used <= vtx_count); + IM_ASSERT_PARANOID(idx_used <= idx_count && vtx_used <= vtx_count); if (idx_used < idx_count || vtx_used < vtx_count) PrimUnreserve(idx_count - idx_used, vtx_count - vtx_used); From a5a4bed3cafe74a330ff143b7374a17c9ee9e435 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Tue, 2 Jun 2026 14:41:58 +0300 Subject: [PATCH 056/120] DrawList: improved worst case vertex count for AddConvexPolyFilled, convex polys can have only 2 bevels verts. --- imgui_draw.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index c65bd7423..d6a2bc5c4 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -855,6 +855,7 @@ float ImDrawList::_CalculateCenterBiasedOffset(float thickness) #define IM_POLYLINE_MITER_ANGLE_LIMIT (-0.9999619f) // cos(179.5) #define IM_POLYLINE_MITER_LIMIT (4.0f) // ~29 deg +#define IM_POLYLINE_CONVEX_POLY_MAX_BEVELS (2) // (int)(360/floor(180-29)) // In debug builds the functions are likely not inlined, so inlined check is cheaper. #if defined(DEBUG) || defined(_DEBUG) @@ -1068,7 +1069,6 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ if (sin_theta < 0.0f) { IM_APPEND_VTX(p1.x - (bn_x + sd_x) * thickness0, p1.y - (bn_y + sd_y) * thickness0, uv0, col); // 2 - // TODO: calc uv2 correctly. IM_APPEND_VTX(p1.x, p1.y, uv2, col); // 3 const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; IM_APPEND_VTX(p1.x - (bn_x - sd_x) * thickness0, p1.y - (bn_y - sd_y) * thickness0, uv0, col); // 4 @@ -1086,7 +1086,6 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ else { IM_APPEND_VTX(p1.x + (bn_x + sd_x) * thickness1, p1.y + (bn_y + sd_y) * thickness1, uv1, col); // 2 - // TODO: calc uv2 correctly. IM_APPEND_VTX(p1.x, p1.y, uv2, col); // 3 const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; IM_APPEND_VTX(p1.x - miter_offset_x * thickness0, p1.y - miter_offset_y * thickness0, uv0, col); // 4 @@ -1559,8 +1558,8 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun const bool miters_only = (flags & ImDrawFlags_MiterOnly) != 0; const float miter_distance_limit_sqr = IM_POLYLINE_MITER_LIMIT * IM_POLYLINE_MITER_LIMIT; - const int idx_count = ((points_count - 2) + points_count * 3) * 3; - const int vtx_count = (points_count * 3); + const int idx_count = ((points_count - 2) + points_count * 2 + IM_POLYLINE_CONVEX_POLY_MAX_BEVELS) * 3; + const int vtx_count = points_count * 2 + IM_POLYLINE_CONVEX_POLY_MAX_BEVELS; PrimReserve(idx_count, vtx_count); ImDrawVert* start_vtx_ptr = _VtxWritePtr; ImDrawIdx* start_idx_ptr = _IdxWritePtr; @@ -1634,6 +1633,7 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun } else { + int bevel_count = 0; for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) { // Average normals @@ -1679,8 +1679,12 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun unsigned int outer_idx = _VtxCurrentIdx; // Outer - if (bevel) + if (bevel && bevel_count < IM_POLYLINE_CONVEX_POLY_MAX_BEVELS) IM_UNLIKELY { + // Because the polygon is convex, we know the maximum number of bevel corners we can hit (which is very small number). + // We keep track of them just in case the calculations disagree. + bevel_count++; + IM_APPEND_VTX(p1.x + n0.x * half_aa, p1.y + n0.y * half_aa, uv, col_trans); IM_APPEND_VTX(p1.x + n1.x * half_aa, p1.y + n1.y * half_aa, uv, col_trans); // Connect with previous From ea638a4a11395bbbe09cf18e1f2a92dde514d98c Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Tue, 2 Jun 2026 16:37:36 +0200 Subject: [PATCH 057/120] DrawList: added vertex offset 16-bit ImDrawIdx support for _AddPolyline --- imgui_draw.cpp | 80 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 67 insertions(+), 13 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index d6a2bc5c4..c63ceba6d 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -853,8 +853,8 @@ float ImDrawList::_CalculateCenterBiasedOffset(float thickness) _IdxWritePtr += 3; \ } (void)0 -#define IM_POLYLINE_MITER_ANGLE_LIMIT (-0.9999619f) // cos(179.5) -#define IM_POLYLINE_MITER_LIMIT (4.0f) // ~29 deg +#define IM_POLYLINE_MITER_ANGLE_LIMIT (-0.9999619f) // cos(179.5) +#define IM_POLYLINE_MITER_LIMIT (4.0f) // ~29 deg #define IM_POLYLINE_CONVEX_POLY_MAX_BEVELS (2) // (int)(360/floor(180-29)) // In debug builds the functions are likely not inlined, so inlined check is cheaper. @@ -866,6 +866,41 @@ float ImDrawList::_CalculateCenterBiasedOffset(float thickness) #define IM_IS_TRUNCATED(a) ImIsTruncated(a) #endif +// Called when the current vertex count for a polyline call would overflow the index range. +// Commit the vertices so far, reserve new buffers, reset index, and duplicate the base vertices so that they can be accessed by the new indices. +static void SplitPolyline(ImDrawList* draw_list, int& start_idx_offset, int& start_vtx_offset, int& base_idx) +{ + // Make copy of current base points. + ImDrawVert base0 = draw_list->VtxBuffer.Data[draw_list->_CmdHeader.VtxOffset + base_idx + 0]; + ImDrawVert base1 = draw_list->VtxBuffer.Data[draw_list->_CmdHeader.VtxOffset + base_idx + 1]; + + // Release unused verts and indices. + const int cur_idx_offset = (int)(draw_list->_IdxWritePtr - draw_list->IdxBuffer.Data); + const int cur_vtx_offset = (int)(draw_list->_VtxWritePtr - draw_list->VtxBuffer.Data); + const int remaining_idx_count = draw_list->IdxBuffer.Size - cur_idx_offset; + const int remaining_vtx_count = draw_list->VtxBuffer.Size - cur_vtx_offset; + + IM_ASSERT(remaining_vtx_count >= 0 && remaining_idx_count >= 0); + draw_list->PrimUnreserve(remaining_idx_count, remaining_vtx_count); + + // Reserve remaining verts and indices. + draw_list->PrimReserve(remaining_idx_count, remaining_vtx_count + 2); // +2 for the duplicated base points. + + start_idx_offset = cur_idx_offset; + start_vtx_offset = cur_vtx_offset; + base_idx = (int)draw_list->_VtxCurrentIdx; + + // Duplicate base points + draw_list->_VtxWritePtr[0] = base0; + draw_list->_VtxWritePtr[1] = base1; + draw_list->_VtxWritePtr += 2; + draw_list->_VtxCurrentIdx += 2; +} + +#define IM_ADD_POLYLINE_HANDLE_VTX_OFFSET_OVERFLOW(new_verts) \ + if (sizeof(ImDrawIdx) == 2 && (Flags & ImDrawListFlags_AllowVtxOffset) && _VtxCurrentIdx + (new_verts) > (1 << 16)) IM_UNLIKELY \ + SplitPolyline(this, start_idx_offset, start_vtx_offset, base_idx) + void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, const ImVec4& tex_uvs, float fringe) { const ImU32 col_trans = col & ~IM_COL32_A_MASK; @@ -918,9 +953,10 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ PrimReserve(idx_count, vtx_count); - ImDrawVert* start_vtx_ptr = _VtxWritePtr; - ImDrawIdx* start_idx_ptr = _IdxWritePtr; - + // These offsets change if we need to create new draw command mid shape. + const int first_vtx_offset = (int)(_VtxWritePtr - VtxBuffer.Data); + int start_vtx_offset = (int)(_VtxWritePtr - VtxBuffer.Data); + int start_idx_offset = (int)(_IdxWritePtr - IdxBuffer.Data); int base_idx = (int)_VtxCurrentIdx; ImVec2 pa, pb, dir; @@ -950,6 +986,8 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ if (flags & ImDrawFlags_NoAAEnds) { + IM_ADD_POLYLINE_HANDLE_VTX_OFFSET_OVERFLOW(2); + base_idx = (int)_VtxCurrentIdx; IM_APPEND_VTX(p1.x - n1.x * thickness0, p1.y - n1.y * thickness0, uv0, col); IM_APPEND_VTX(p1.x + n1.x * thickness1, p1.y + n1.y * thickness1, uv1, col); @@ -961,6 +999,8 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ pb.x = p1.x + dir.x * half_aa; pb.y = p1.y + dir.y * half_aa; + IM_ADD_POLYLINE_HANDLE_VTX_OFFSET_OVERFLOW(4); + base_idx = (int)_VtxCurrentIdx; IM_APPEND_VTX(pa.x - n1.x * thickness0, pa.y - n1.y * thickness0, uv0, col_trans); IM_APPEND_VTX(pa.x + n1.x * thickness1, pa.y + n1.y * thickness1, uv1, col_trans); @@ -983,6 +1023,8 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ len_sqr1 = sqr_lengths[points_count-1]; // This will be filled later, allocate space. + IM_ADD_POLYLINE_HANDLE_VTX_OFFSET_OVERFLOW(2); + base_idx = (int)_VtxCurrentIdx; IM_APPEND_VTX(0, 0, uv0, col); IM_APPEND_VTX(0, 0, uv1, col); @@ -1006,6 +1048,8 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ const float miter_offset_y = (n0.y + n1.y) * miter_scale_factor; // Miter + IM_ADD_POLYLINE_HANDLE_VTX_OFFSET_OVERFLOW(2); + const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; IM_APPEND_VTX(p1.x - miter_offset_x * thickness0, p1.y - miter_offset_y * thickness0, uv0, col); // 2 IM_APPEND_VTX(p1.x + miter_offset_x * thickness1, p1.y + miter_offset_y * thickness1, uv1, col); // 3 @@ -1068,6 +1112,8 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ // Bevel if (sin_theta < 0.0f) { + IM_ADD_POLYLINE_HANDLE_VTX_OFFSET_OVERFLOW(4); + IM_APPEND_VTX(p1.x - (bn_x + sd_x) * thickness0, p1.y - (bn_y + sd_y) * thickness0, uv0, col); // 2 IM_APPEND_VTX(p1.x, p1.y, uv2, col); // 3 const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; @@ -1085,6 +1131,8 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ } else { + IM_ADD_POLYLINE_HANDLE_VTX_OFFSET_OVERFLOW(4); + IM_APPEND_VTX(p1.x + (bn_x + sd_x) * thickness1, p1.y + (bn_y + sd_y) * thickness1, uv1, col); // 2 IM_APPEND_VTX(p1.x, p1.y, uv2, col); // 3 const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; @@ -1103,6 +1151,8 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ } else { + IM_ADD_POLYLINE_HANDLE_VTX_OFFSET_OVERFLOW(4); + // Miter const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; IM_APPEND_VTX(p1.x - miter_offset_x * thickness0, p1.y - miter_offset_y * thickness0, uv0, col); // 2 @@ -1136,6 +1186,8 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ if (flags & ImDrawFlags_NoAAEnds) { + IM_ADD_POLYLINE_HANDLE_VTX_OFFSET_OVERFLOW(2); + IM_APPEND_VTX(p1.x - n1.x * thickness0, p1.y - n1.y * thickness0, uv0, col); IM_APPEND_VTX(p1.x + n1.x * thickness1, p1.y + n1.y * thickness1, uv1, col); @@ -1150,6 +1202,8 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ pb.x = p1.x + dir.x * half_aa; pb.y = p1.y + dir.y * half_aa; + IM_ADD_POLYLINE_HANDLE_VTX_OFFSET_OVERFLOW(4); + int next_base_idx = (int)_VtxCurrentIdx; IM_APPEND_VTX(pa.x - n1.x * thickness0, pa.y - n1.y * thickness0, uv0, col); IM_APPEND_VTX(pa.x + n1.x * thickness1, pa.y + n1.y * thickness1, uv1, col); @@ -1170,15 +1224,17 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ else { // Connect the path. - start_vtx_ptr[0] = VtxBuffer.Data[_CmdHeader.VtxOffset + base_idx+0]; - start_vtx_ptr[1] = VtxBuffer.Data[_CmdHeader.VtxOffset + base_idx+1]; + VtxBuffer.Data[first_vtx_offset + 0] = VtxBuffer.Data[_CmdHeader.VtxOffset + base_idx+0]; + VtxBuffer.Data[first_vtx_offset + 1] = VtxBuffer.Data[_CmdHeader.VtxOffset + base_idx+1]; } // Restore unused memory - const int vtx_used = (int)(_VtxWritePtr - start_vtx_ptr); - const int idx_used = (int)(_IdxWritePtr - start_idx_ptr); - IM_ASSERT(vtx_used <= vtx_count && idx_used <= idx_count); - PrimUnreserve(idx_count - idx_used, vtx_count - vtx_used); + const int cur_idx_offset = (int)(_IdxWritePtr - IdxBuffer.Data); + const int cur_vtx_offset = (int)(_VtxWritePtr - VtxBuffer.Data); + const int remaining_idx_count = IdxBuffer.Size - cur_idx_offset; + const int remaining_vtx_count = VtxBuffer.Size - cur_vtx_offset; + IM_ASSERT_PARANOID(remaining_idx_count >= 0 && remaining_vtx_count >= 0); + PrimUnreserve(remaining_idx_count, remaining_vtx_count); } static ImU32 ImAlphaMultiply(ImU32 col, float alpha_mul) @@ -1237,8 +1293,6 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 thickness = _FringeScale; } - // TODO: support splitting very long lines to multiple draw calls. - // Allocate data for temp buffers _Data->TempBuffer.reserve_discard(points_count * 2); ImVec2* normals = _Data->TempBuffer.Data; From 16860367b34b52006724845ad9286133e4bb3293 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 2 Jun 2026 15:58:50 +0200 Subject: [PATCH 058/120] DrawList: Debug build optimizations: disable over zealous stack checks in PrimReserve, PrimUnreserve, PrimRect, PrimRectUV, PrimQuadUV, _CalculateCenterBiasedOffset, ImAlphaMultiply, _SelectFringeTexture + Undo inlined PrimRect() in AddRectFilled(). --- imgui_draw.cpp | 57 ++++++++++++++++++++++---------------------------- 1 file changed, 25 insertions(+), 32 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index c63ceba6d..ebf0682d6 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -728,6 +728,7 @@ void ImDrawList::_SetPixelDensity(float pixel_density) _FringeScaleIsInteger = ImIsTruncated(_InvFringeScale); } +IM_MSVC_RUNTIME_CHECKS_OFF // Reserve space for a number of vertices and indices. // You must finish filling your reserved data before calling PrimReserve() again, as it may reallocate or // submit the intermediate results. PrimUnreserve() can be used to release unused allocations. @@ -767,6 +768,26 @@ void ImDrawList::PrimUnreserve(int idx_count, int vtx_count) IdxBuffer.shrink(IdxBuffer.Size - idx_count); } +// We avoid using the ImVec2 math operators here to reduce cost to a minimum for debug/non-inlined builds. +// We avoid using the 'do { } while (false)` idiom in those macros as they typically have overhead in debug builds. +#define IM_APPEND_VTX(PX, PY, UV, COL) \ + { \ + _VtxWritePtr->pos.x = PX; \ + _VtxWritePtr->pos.y = PY; \ + _VtxWritePtr->uv = UV; \ + _VtxWritePtr->col = COL; \ + _VtxWritePtr++; \ + _VtxCurrentIdx++; \ + } (void)0 + +#define IM_APPEND_TRI(a, b, c) \ + { \ + _IdxWritePtr[0] = (ImDrawIdx)(a); \ + _IdxWritePtr[1] = (ImDrawIdx)(b); \ + _IdxWritePtr[2] = (ImDrawIdx)(c); \ + _IdxWritePtr += 3; \ + } (void)0 + // Fully unrolled with inline call to keep our debug builds decently fast. void ImDrawList::PrimRect(const ImVec2& a, const ImVec2& c, ImU32 col) { @@ -812,7 +833,6 @@ void ImDrawList::PrimQuadUV(const ImVec2& a, const ImVec2& b, const ImVec2& c, c _IdxWritePtr += 6; } - float ImDrawList::_CalculateCenterBiasedOffset(float thickness) { // Old jumpy one. @@ -825,6 +845,7 @@ float ImDrawList::_CalculateCenterBiasedOffset(float thickness) const int s_thickness = (int)screen_thickness; return ((float)(s_thickness / 2) + (s_thickness & 1) * (screen_thickness - s_thickness)) * _FringeScale; } +IM_MSVC_RUNTIME_CHECKS_RESTORE // On AddPolyline() and AddConvexPolyFilled() we intentionally avoid using ImVec2 and superfluous function calls to optimize debug/non-inlined builds. // - Those macros expects l-values and need to be used as their own statement. @@ -833,26 +854,6 @@ float ImDrawList::_CalculateCenterBiasedOffset(float thickness) #define IM_FIXNORMAL2F_MAX_INVLEN2 100.0f // 500.0f (see #4053, #3366) #define IM_FIXNORMAL2F(VX,VY) { float d2 = VX*VX + VY*VY; if (d2 > 0.000001f) { float inv_len2 = 1.0f / d2; if (inv_len2 > IM_FIXNORMAL2F_MAX_INVLEN2) inv_len2 = IM_FIXNORMAL2F_MAX_INVLEN2; VX *= inv_len2; VY *= inv_len2; } } (void)0 -// We avoid using the ImVec2 math operators here to reduce cost to a minimum for debug/non-inlined builds. -// We avoid using the 'do { } while (false)` idiom in those macros as they typically have overhead in debug builds. -#define IM_APPEND_VTX(PX, PY, UV, COL) \ - { \ - _VtxWritePtr->pos.x = PX; \ - _VtxWritePtr->pos.y = PY; \ - _VtxWritePtr->uv = UV; \ - _VtxWritePtr->col = COL; \ - _VtxWritePtr++; \ - _VtxCurrentIdx++; \ - } (void)0 - -#define IM_APPEND_TRI(a, b, c) \ - { \ - _IdxWritePtr[0] = (ImDrawIdx)(a); \ - _IdxWritePtr[1] = (ImDrawIdx)(b); \ - _IdxWritePtr[2] = (ImDrawIdx)(c); \ - _IdxWritePtr += 3; \ - } (void)0 - #define IM_POLYLINE_MITER_ANGLE_LIMIT (-0.9999619f) // cos(179.5) #define IM_POLYLINE_MITER_LIMIT (4.0f) // ~29 deg #define IM_POLYLINE_CONVEX_POLY_MAX_BEVELS (2) // (int)(360/floor(180-29)) @@ -1237,6 +1238,7 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ PrimUnreserve(remaining_idx_count, remaining_vtx_count); } +IM_MSVC_RUNTIME_CHECKS_OFF static ImU32 ImAlphaMultiply(ImU32 col, float alpha_mul) { IM_ASSERT_PARANOID(a >= 0.0f && a < 1.0f); // We don't clamp! @@ -1269,6 +1271,7 @@ void ImDrawList::_SelectFringeTexture(float screen_thickness, ImVec4* out_tex_uv *out_tex_uvs = _Data->TexUvLines[texture_idx]; } } +IM_MSVC_RUNTIME_CHECKS_RESTORE extern bool g_LEGACY_STROKES; @@ -2914,17 +2917,7 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c if (s_rounding <= 0 || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) { PrimReserve(6, 4); - - const ImVec2 opaque_uv = _Data->TexUvWhitePixel; - const ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; - IM_APPEND_VTX(p_min.x, p_min.y, opaque_uv, col); - IM_APPEND_VTX(p_max.x, p_min.y, opaque_uv, col); - IM_APPEND_VTX(p_max.x, p_max.y, opaque_uv, col); - IM_APPEND_VTX(p_min.x, p_max.y, opaque_uv, col); - - IM_APPEND_TRI(idx + 0, idx + 1, idx + 2); - IM_APPEND_TRI(idx + 0, idx + 2, idx + 3); - + PrimRect(p_min, p_max, col); return; } else if ((Flags & ImDrawListFlags_RoundCornersUseTex) && s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) From c6e5d8c85f64a3dfb451cb2dd0d458b6820704a4 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 2 Jun 2026 16:54:44 +0200 Subject: [PATCH 059/120] DrawList: Debug build optimizations: add non-template ImMin/ImMax/ImClamp for int/float to avoid missing out on IM_MSVC_RUNTIME_CHECKS_OFF. --- imgui_internal.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/imgui_internal.h b/imgui_internal.h index d632533f3..28251df79 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -512,11 +512,17 @@ inline float ImRsqrtPrecise(float x) { const float r = _mm_cvtss_f32(_mm_rsq inline float ImRsqrt(float x) { return 1.0f / sqrtf(x); } inline float ImRsqrtPrecise(float x) { return 1.0f / sqrtf(x); } #endif -inline double ImRsqrt(double x) { return 1.0 / sqrt(x); } +inline double ImRsqrt(double x) { return 1.0 / sqrt(x); } #endif // - ImMin/ImMax/ImClamp/ImLerp/ImSwap are used by widgets which support variety of types: signed/unsigned int/long long float/double -// (Exceptionally using templates here but we could also redefine them for those types) +// (Specialized version ensure that runtime-check pragmas/macros are applied with caller settings. Template version are less reliable in this regards) +inline int ImMin(int lhs, int rhs) { return lhs < rhs ? lhs : rhs; } +inline int ImMax(int lhs, int rhs) { return lhs >= rhs ? lhs : rhs; } +inline int ImClamp(int v, int mn, int mx) { return (v < mn) ? mn : (v > mx) ? mx : v; } +inline float ImMin(float lhs, float rhs) { return lhs < rhs ? lhs : rhs; } +inline float ImMax(float lhs, float rhs) { return lhs >= rhs ? lhs : rhs; } +inline float ImClamp(float v, float mn, float mx) { return (v < mn) ? mn : (v > mx) ? mx : v; } template T ImMin(T lhs, T rhs) { return lhs < rhs ? lhs : rhs; } template T ImMax(T lhs, T rhs) { return lhs >= rhs ? lhs : rhs; } template T ImClamp(T v, T mn, T mx) { return (v < mn) ? mn : (v > mx) ? mx : v; } From f2055fe160e75263cdfc0e70a972e1eb9cc878b6 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Wed, 3 Jun 2026 00:28:23 +0300 Subject: [PATCH 060/120] DrawList: fixed reserve counts in _AddPolyline(). --- imgui_draw.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index ebf0682d6..2e474e8c2 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -934,8 +934,8 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ } else { - vtx_count = /*body*/(points_count - 2) * max_verts_per_point + /*caps*/(6 * 2); - idx_count = (/*body*/(points_count - 2) * max_tris_per_point + /*last seg*/4 + /*caps*/(4 * 2)) * 3; + vtx_count = /*body*/(points_count - 2) * max_verts_per_point + /*caps*/(4 * 2); + idx_count = (/*body*/(points_count - 2) * max_tris_per_point + /*last seg*/2 + /*caps*/(2 * 2)) * 3; } From ec510539c7e1f934fa0aa6497154be5f3c2a2da6 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Thu, 4 Jun 2026 00:43:36 +0300 Subject: [PATCH 061/120] DrawList: increase detailed line textures from 3px to 4px to better hide the texture change. + amend: align #defines. --- imgui.h | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/imgui.h b/imgui.h index 357915cd8..a0c51cdda 100644 --- a/imgui.h +++ b/imgui.h @@ -3202,27 +3202,22 @@ struct ImGuiSelectionExternalStorage // Hold a series of drawing commands. The user provides a renderer for ImDrawData which essentially contains an array of ImDrawList. //----------------------------------------------------------------------------- -// The maximum line width to bake anti-aliased textures for. Build atlas with ImFontAtlasFlags_NoBakedLines to disable baking. #ifndef IM_DRAWLIST_TEX_LINES_WIDTH_MAX -#define IM_DRAWLIST_TEX_LINES_WIDTH_MAX (32) +#define IM_DRAWLIST_TEX_LINES_WIDTH_MAX (32) // The maximum line width to bake anti-aliased textures for. Build atlas with ImFontAtlasFlags_NoBakedLines to disable baking. #endif - #ifndef IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH -#define IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH (3) // Calculate detailed textures for width [1..IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH] +#define IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH (4) // Calculate detailed textures for width [1..IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH] #endif - #ifndef IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT -#define IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT (4) // How many samples per integer thickness level. +#define IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT (4) // How many samples per integer thickness level. #endif - -#define IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX (IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT) - #ifndef IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX -#define IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX (16) +#define IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX (16) #endif #ifndef IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX -#define IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX (4) // 0: fill, 1-3: strokes thickness +#define IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX (4) // 0: fill, 1-3: strokes thickness #endif +#define IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX (IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT) // ImDrawIdx: vertex index. [Compile-time configurable type] // - To use 16-bit indices + allow large meshes: backend need to set 'io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset' and handle ImDrawCmd::VtxOffset (recommended). From ed50e4790b8c0782b8f841040dae634339629f52 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Thu, 4 Jun 2026 00:44:19 +0300 Subject: [PATCH 062/120] Demo: added rounding slider+animate to allow better test range of rounding values. --- imgui_demo.cpp | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index e7d8473a9..b27736666 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10388,8 +10388,10 @@ static void ShowExampleAppCustomRendering(bool* p_open) // Draw a bunch of primitives ImGui::Text("All primitives"); static float sz = 42.0f; - static bool animate_thickness = false; + static float base_rounding = 8.0f; static float base_thickness = 3.0f; + static bool animate_rounding = false; + static bool animate_thickness = false; static int ngon_segments = 6; static bool circle_segments_override = false; static int circle_segments_override_v = 12; @@ -10400,12 +10402,21 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::DragFloat("Size", &sz, 0.2f, 0.2f, 100.0f, "%.0f"); + const float rounding_anim_period = 8.0f; + const float rounding_wave = 1.0f - fabsf(fmodf((float)ImGui::GetTime(), rounding_anim_period) * (2.0f / rounding_anim_period) - 1.0f); + const float rounding = animate_rounding ? (float)((int)(rounding_wave * base_rounding * 10.0f)) / 10.0f : base_rounding; + ImGui::DragFloat("Rounding", &base_rounding, 0.02f, 0.0f, 30.0f, "%.02f"); + ImGui::SameLine(); + ImGui::Checkbox("Animate##rounding", &animate_rounding); + ImGui::SameLine(); + ImGui::Text("%.2f", rounding); + const float thickness_anim_period = 5.0f; const float thickness_wave = 1.0f - fabsf(fmodf((float)ImGui::GetTime(), thickness_anim_period) * (2.0f / thickness_anim_period) - 1.0f); const float thickness = animate_thickness ? thickness_wave * base_thickness : base_thickness; ImGui::DragFloat("Thickness", &base_thickness, 0.02f, 0.0f, 30.0f, "%.02f"); ImGui::SameLine(); - ImGui::Checkbox("Animate", &animate_thickness); + ImGui::Checkbox("Animate##thickness", &animate_thickness); ImGui::SameLine(); ImGui::Text("%.2f", thickness); @@ -10438,7 +10449,6 @@ static void ShowExampleAppCustomRendering(bool* p_open) const float pi = 3.141592f; const float spacing = 10.0f; const ImDrawFlags corners_tl_br = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersBottomRight; - const float rounding = sz / 5.0f; const float half_sz = sz * 0.5f; const float half_szi = (float)(int)(sz * 0.5f); const int circle_segments = circle_segments_override ? circle_segments_override_v : 0; From 002e2a5f0085bc1e8e70d340e7f5597605a0dd37 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Thu, 4 Jun 2026 00:57:20 +0300 Subject: [PATCH 063/120] DrawList: added macro to have consistent check for rounding - use macro to test if rounding value is worthy - changed threshold to be a bit smaller (for smoother handling of small radius), and relative to fringe --- imgui_draw.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 2e474e8c2..30cff95bb 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -847,6 +847,9 @@ float ImDrawList::_CalculateCenterBiasedOffset(float thickness) } IM_MSVC_RUNTIME_CHECKS_RESTORE +// Check if R is considered to be worthy rounding. Can be only used within ImDrawList due to use of _FringeScale. +#define IM_HAS_ROUNDING(RAD) ((RAD) >= (0.25f * _FringeScale)) + // On AddPolyline() and AddConvexPolyFilled() we intentionally avoid using ImVec2 and superfluous function calls to optimize debug/non-inlined builds. // - Those macros expects l-values and need to be used as their own statement. // - Those macros are intentionally not surrounded by the 'do {} while (0)' idiom because even that translates to runtime with debug compilers. @@ -1866,7 +1869,7 @@ void ImDrawList::AddConvexPolyFilledLegacy(const ImVec2* points, const int point void ImDrawList::_PathArcToFastEx(const ImVec2& center, float radius, int a_min_sample, int a_max_sample, int a_step) { - if (radius < 0.5f) + if (!IM_HAS_ROUNDING(radius)) { _Path.push_back(center); return; @@ -1958,7 +1961,7 @@ void ImDrawList::_PathArcToFastEx(const ImVec2& center, float radius, int a_min_ void ImDrawList::_PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments) { - if (radius < 0.5f) + if (!IM_HAS_ROUNDING(radius)) { _Path.push_back(center); return; @@ -1977,7 +1980,7 @@ void ImDrawList::_PathArcToN(const ImVec2& center, float radius, float a_min, fl // 0: East, 3: South, 6: West, 9: North, 12: East void ImDrawList::PathArcToFast(const ImVec2& center, float radius, int a_min_of_12, int a_max_of_12) { - if (radius < 0.5f) + if (!IM_HAS_ROUNDING(radius)) { _Path.push_back(center); return; @@ -1987,7 +1990,7 @@ void ImDrawList::PathArcToFast(const ImVec2& center, float radius, int a_min_of_ void ImDrawList::PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments) { - if (radius < 0.5f) + if (!IM_HAS_ROUNDING(radius)) { _Path.push_back(center); return; @@ -2154,7 +2157,8 @@ void ImDrawList::PathBezierQuadraticCurveTo(const ImVec2& p2, const ImVec2& p3, void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDrawFlags flags) { - if (rounding >= 0.5f) + const bool has_rounding = IM_HAS_ROUNDING(rounding); + if (has_rounding) { if ((flags & ImDrawFlags_RoundCornersMask_) == 0) flags |= ImDrawFlags_RoundCornersAll; @@ -2162,7 +2166,7 @@ void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDr rounding = ImMin(rounding, ImFabs(b.x - a.x) * (((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f) - 1.0f); rounding = ImMin(rounding, ImFabs(b.y - a.y) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f); } - if (rounding < 0.5f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) + if (!has_rounding || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) { PathLineTo(a); PathLineTo(ImVec2(b.x, a.y)); @@ -2710,7 +2714,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl return; } - const bool has_rounding = (rounding >= _FringeScale * 0.5f); + const bool has_rounding = IM_HAS_ROUNDING(rounding); // Calculate the outer boundary of the rectangle based on the stroke position. ImVec2 outer_min = p_min; From 2edac09d9fefa631517fb12e9c6020e3b001939b Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Thu, 4 Jun 2026 01:11:37 +0300 Subject: [PATCH 064/120] DrawList: fixed baked corners to better match polyline corners - added CalcArcStepAndCount() to calculate the step size consistently - _AddRectTinyRounding: fixed stem placement with thin lines (clamping was needed for an older version of the function) - _AddRectTinyRounding: fixed texture coordinates (uses if inner/outer were reversed) - use polygon instead of circle when calculating baked corner rounds (the circle was more accurate but looked different size comparet to polyline) --- imgui_draw.cpp | 157 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 122 insertions(+), 35 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 30cff95bb..4d4bff835 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1866,6 +1866,16 @@ void ImDrawList::AddConvexPolyFilledLegacy(const ImVec2* points, const int point } } +// Calculates arc step and step count for 90 degree arc. The calculated arc step will complete a 90 degree arc when stepped arc_step_count times. +IM_MSVC_RUNTIME_CHECKS_OFF +static void CalcArcStepAndCount(int segment_count, int& arc_step, int& arc_step_count) +{ + arc_step = ImClamp(IM_DRAWLIST_ARCFAST_SAMPLE_MAX / segment_count, 1, IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4); + arc_step_count = (IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4) / arc_step; + if ((arc_step * arc_step_count) != (IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4)) // Make sure that the arc step can iterate 90 degree arc. + arc_step = (IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4) / arc_step_count; +} +IM_MSVC_RUNTIME_CHECKS_RESTORE void ImDrawList::_PathArcToFastEx(const ImVec2& center, float radius, int a_min_sample, int a_max_sample, int a_step) { @@ -1877,10 +1887,17 @@ void ImDrawList::_PathArcToFastEx(const ImVec2& center, float radius, int a_min_ // Calculate arc auto segment step size if (a_step <= 0) - a_step = IM_DRAWLIST_ARCFAST_SAMPLE_MAX / _CalcCircleAutoSegmentCount(radius); - - // Make sure we never do steps larger than one quarter of the circle - a_step = ImClamp(a_step, 1, IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4); + { + // Calculate step size using the common function. No need to clamp, the step is already in range. + const int auto_seg_count = _CalcCircleAutoSegmentCount(radius); + int arc_step_count; + CalcArcStepAndCount(auto_seg_count, a_step, arc_step_count); + } + else + { + // Make sure we never do steps larger than one quarter of the circle + a_step = ImClamp(a_step, 1, IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4); + } const int sample_range = ImAbs(a_max_sample - a_min_sample); const int a_next_step = a_step; @@ -2582,8 +2599,10 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, float fringe; _SelectFringeTexture(screen_thickness, &tex_uvs, &fringe); - const int arc_step = ImClamp(IM_DRAWLIST_ARCFAST_SAMPLE_MAX / this->_CalcCircleAutoSegmentCount(rounding), 1, IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4); - const int arc_point_count = (IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4) / arc_step + 1; + const int auto_seg_count = _CalcCircleAutoSegmentCount(rounding); + int arc_step, arc_step_count; + CalcArcStepAndCount(auto_seg_count, arc_step, arc_step_count); + const int arc_point_count = arc_step_count + 1; int vtx_count = (2 + arc_point_count) * 4 + 3; int idx_count = (4 + arc_point_count - 1) * 4 * 3; @@ -2605,17 +2624,15 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, thickness += fringe; rounding += half_fringe; - // We need to stem offset to be at least fringe scale away, to maintain the fringe size. - const float stem_offset = thickness - ImMax(thickness - rounding, _FringeScale); - + const float stem_offset = thickness - (thickness - rounding); const float half_texel = 0.5f * _Data->FontAtlas->TexUvScale.x; const float ratio = stem_offset / thickness; ImVec2 inner_uv, outer_uv, stem_uv; - inner_uv.x = tex_uvs.x + half_texel; - inner_uv.y = tex_uvs.y; - outer_uv.x = tex_uvs.z - half_texel; + outer_uv.x = tex_uvs.x + half_texel; outer_uv.y = tex_uvs.y; - stem_uv.x = inner_uv.x + (outer_uv.x - inner_uv.x) * ratio; + inner_uv.x = tex_uvs.z - half_texel; + inner_uv.y = tex_uvs.y; + stem_uv.x = outer_uv.x + (inner_uv.x - outer_uv.x) * ratio; stem_uv.y = tex_uvs.y; // Previous vertices, copied from the last vertices. @@ -2642,19 +2659,19 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, int a = (IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4) * corner; for (int i = 0; i < arc_point_count; i++) { - IM_APPEND_VTX(center_x - _Data->ArcFastVtx[a].x * rounding, center_y - _Data->ArcFastVtx[a].y * rounding, inner_uv, col); + IM_APPEND_VTX(center_x - _Data->ArcFastVtx[a].x * rounding, center_y - _Data->ArcFastVtx[a].y * rounding, outer_uv, col); a = (a + arc_step) % IM_DRAWLIST_ARCFAST_TABLE_SIZE; } } else { - IM_APPEND_VTX(outer_x, outer_y, inner_uv, col); + IM_APPEND_VTX(outer_x, outer_y, outer_uv, col); } const int stem_idx = (int)_VtxCurrentIdx; IM_APPEND_VTX(stem_x, stem_y, stem_uv, col); const int inner_idx = (int)_VtxCurrentIdx; - IM_APPEND_VTX(inner_x, inner_y, outer_uv, col); + IM_APPEND_VTX(inner_x, inner_y, inner_uv, col); // Arc if (is_rounded) @@ -5140,23 +5157,84 @@ static void ImFontAtlasBuildUpdateTexDataBasic(ImFontAtlas* atlas) atlas->TexUvWhitePixel = ImVec2((r.x + 0.5f) * atlas->TexUvScale.x, (r.y + 0.5f) * atlas->TexUvScale.y); } -static ImU8 SampleCorner(float x, float y, float cx, float cy, float r) + +#define IM_DRAWLIST_CORNER_POINTS_MAX (IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4 + 3) // Max number of points in corner profiles + +static int InitCornerGeometry(float cx, float cy, float rounding, float circle_segment_max_error, ImVec2* line_normals, float* line_distances) { - const float dx = ImMin(x, cx) - cx; - const float dy = ImMin(y, cy) - cy; - const float d = ImSqrt(dx*dx + dy*dy); - const float aa_width = 1.0f; - return (ImU8)(ImClamp(1.0f - (d - r + aa_width * 0.5f) / aa_width, 0.0f, 1.0f) * 255.0f); + // This tries to quite faithfully replication how we render the corners using tessellation. + // Older code sampled a circle SDF directly, but it had clearly visible discrepancy between the baked corners + // and tessellated corners due to circle_segment_max_error. + + const int auto_seg_count = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(rounding, circle_segment_max_error); + int arc_step, arc_step_count; + CalcArcStepAndCount(auto_seg_count, arc_step, arc_step_count); + const int arc_point_count = arc_step_count + 1; + + // Build arc of points, with straight segments at each end. + // . [n-2]..[n-1] + // .` + // . + // [1] x (cx,cy) + // : + // [0] + ImVec2 points[IM_DRAWLIST_CORNER_POINTS_MAX]; + int num_points = 0; + + points[num_points++] = ImVec2(cx - rounding, cy + 1.0f); + + float da = ((float)arc_step / (float)IM_DRAWLIST_ARCFAST_TABLE_SIZE) * IM_PI * 2.0f; + float a = IM_PI; + for (int i = 0; i < arc_point_count; i++) + { + float dx = cosf(a); + float dy = sinf(a); + points[num_points++] = ImVec2(cx + dx * rounding, cy + dy * rounding); + a += da; + } + + points[num_points++] = ImVec2(cx + 1.0f, cy - rounding); + + // Turn segments to half spaces. + for (int i = 0; i < num_points - 1; i++) + { + const ImVec2 diff = points[i + 1] - points[i]; + const float d2 = diff.x * diff.x + diff.y * diff.y; + const float inv_d = ImRsqrtPrecise(d2); + line_normals[i].x = diff.y * inv_d; // Normal points outwards + line_normals[i].y = -diff.x * inv_d; + line_distances[i] = -ImDot(points[i], line_normals[i]); + } + + return num_points - 1; } -static ImU8 SampleCornerStroke(float x, float y, float cx, float cy, float outer_r, float inner_r) +// Returns distance to convex shape limited by lines. +static float SampleConvex(float x, float y, const ImVec2* line_normals, const float* line_distances, int num_lines) { - const float dx = ImMin(x, cx) - cx; - const float dy = ImMin(y, cy) - cy; - const float d = ImSqrt(dx*dx + dy*dy); + float result = -FLT_MAX; + for (int i = 0; i < num_lines; i++) + { + const float d = line_normals[i].x * x + line_normals[i].y * y + line_distances[i]; + if (d > result) + result = d; + } + return result; +} + +static ImU8 SampleCorner(float x, float y, const ImVec2* line_normals, const float* line_distances, int num_lines) +{ + const float d = SampleConvex(x, y, line_normals, line_distances, num_lines); const float aa_width = 1.0f; - const float outer = 1.0f - (d - outer_r + aa_width * 0.5f) / aa_width; - const float inner = (d - inner_r + aa_width * 0.5f) / aa_width; + return (ImU8)(ImClamp(1.0f - (d + aa_width * 0.5f) / aa_width, 0.0f, 1.0f) * 255.0f); +} + +static ImU8 SampleCornerStroke(float x, float y, float thickness, const ImVec2* line_normals, const float* line_distances, int num_lines) +{ + const float d = SampleConvex(x, y, line_normals, line_distances, num_lines); + const float aa_width = 1.0f; + const float outer = 1.0f - (d + aa_width * 0.5f) / aa_width; + const float inner = (d + thickness + aa_width * 0.5f) / aa_width; return (ImU8)(ImClamp(ImMin(inner, outer), 0.0f, 1.0f) * 255.0f); } @@ -5169,6 +5247,11 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) ImTextureData* tex = atlas->TexData; ImFontAtlasBuilder* builder = atlas->Builder; + // FIXME-TEXCORNERS: Ideally we should pick up the max error from the styles, but it does not seem be to set by the time we build the data the first time. + float circle_segment_max_error = 0.30f; // Default matching ImGuiStyle::CircleTessellationMaxError. + // if (atlas->OwnerContext) + // circle_segment_max_error = atlas->OwnerContext->DrawListSharedData.CircleSegmentMaxError; + ImFontAtlasRect r; bool add_and_draw = atlas->GetCustomRect(builder->PackIdCornersTexData, &r) == false; if (add_and_draw) @@ -5207,6 +5290,9 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) int y = r.y; int row_height = 0; + ImVec2 line_normals[IM_DRAWLIST_CORNER_POINTS_MAX]; + float line_distances[IM_DRAWLIST_CORNER_POINTS_MAX]; + // Filled for (int n = 0; n < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX; n++) { @@ -5218,9 +5304,10 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) // Corner circle const float cx = 1.0f + (float)(n+1); const float cy = 1.0f + (float)(n+1); - const float rad = (float)rounding; IM_ASSERT((x + w) <= (r.x + r.w) && (y + h) <= (r.y + r.h)); // Make sure we're inside the texture bounds before we start writing pixels + const int num_lines = InitCornerGeometry(cx, cy, (float)rounding, circle_segment_max_error, line_normals, line_distances); + // Write each slice if (add_and_draw && tex->Format == ImTextureFormat_Alpha8) { @@ -5229,7 +5316,7 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) for (int ly = 0; ly < h; ly++) { for (int lx = 0; lx < h; lx++) - write_ptr[lx] = SampleCorner((float)lx + 0.5f, (float)ly + 0.5f, cx, cy, rad); + write_ptr[lx] = SampleCorner((float)lx + 0.5f, (float)ly + 0.5f, line_normals, line_distances, num_lines); write_ptr += pitch; } } @@ -5240,7 +5327,7 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) for (int ly = 0; ly < h; ly++) { for (int lx = 0; lx < h; lx++) - write_ptr[lx] = IM_COL32(255, 255, 255, SampleCorner((float)lx + 0.5f, (float)ly + 0.5f, cx, cy, rad)); + write_ptr[lx] = IM_COL32(255, 255, 255, SampleCorner((float)lx + 0.5f, (float)ly + 0.5f, line_normals, line_distances, num_lines)); write_ptr += pitch; } } @@ -5270,11 +5357,11 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) // Corner circle const float cx = 1.0f + (float)(n + 1); const float cy = 1.0f + (float)(n + 1); - const float outer_rad = (float)rounding; - const float inner_rad = (float)(rounding - thickness); IM_ASSERT((x + w) <= (r.x + r.w) && (y + h) <= (r.y + r.h)); // Make sure we're inside the texture bounds before we start writing pixels + const int num_lines = InitCornerGeometry(cx, cy, (float)rounding, circle_segment_max_error, line_normals, line_distances); + // Write each slice if (add_and_draw && tex->Format == ImTextureFormat_Alpha8) { @@ -5283,7 +5370,7 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) for (int ly = 0; ly < h; ly++) { for (int lx = 0; lx < h; lx++) - write_ptr[lx] = SampleCornerStroke((float)lx + 0.5f, (float)ly + 0.5f, cx, cy, outer_rad, inner_rad); + write_ptr[lx] = SampleCornerStroke((float)lx + 0.5f, (float)ly + 0.5f, (float)thickness, line_normals, line_distances, num_lines); write_ptr += pitch; } } @@ -5294,7 +5381,7 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) for (int ly = 0; ly < h; ly++) { for (int lx = 0; lx < h; lx++) - write_ptr[lx] = IM_COL32(255, 255, 255, SampleCornerStroke((float)lx + 0.5f, (float)ly + 0.5f, cx, cy, outer_rad, inner_rad)); + write_ptr[lx] = IM_COL32(255, 255, 255, SampleCornerStroke((float)lx + 0.5f, (float)ly + 0.5f, (float)thickness, line_normals, line_distances, num_lines)); write_ptr += pitch; } } From a5d8450a0f7d38d66a34c3890bff40925d33a080 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Thu, 4 Jun 2026 10:28:18 +0300 Subject: [PATCH 065/120] DrawList: fixed overdraw of transparent rounded rectangles - transition earlier to _AddRectTinyRounding() to avoid overdraw issues --- imgui_draw.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 4d4bff835..04262db88 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2624,7 +2624,7 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, thickness += fringe; rounding += half_fringe; - const float stem_offset = thickness - (thickness - rounding); + const float stem_offset = ImMin(rounding, thickness); // The function might get called with rounding slightly bigger than thickness, which is ok, but we should keep the stem inside the shape. const float half_texel = 0.5f * _Data->FontAtlas->TexUvScale.x; const float ratio = stem_offset / thickness; ImVec2 inner_uv, outer_uv, stem_uv; @@ -2829,7 +2829,8 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl } else { - if (thickness > outer_rounding) + // Switch to specialized rendering a bit early to avoid overdraw visible in transparent shapes. + if (thickness + (0.75f * _FringeScale) > outer_rounding) { // Special case rendering to avoid rendering artifacts at the corners. // If rendered using the regular polyline, the stroke will fold and leave artifact on the corner if rendered with transparency. From 478813c359cec417064aa9e0b0fd10d0b4abab63 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Thu, 4 Jun 2026 10:30:04 +0300 Subject: [PATCH 066/120] DrawList: restored non-AA rendering mode. - the ImDrawListFlags_AntiAliasedFill and ImDrawListFlags_AntiAliasedLines flags work again - added non-AA rendering for polyline and convex poly filled - do not use corner textures when AA is not used - Add missing no-AA for single line case --- docs/CHANGELOG.txt | 2 - imgui_demo.cpp | 12 ++ imgui_draw.cpp | 355 ++++++++++++++++++++++++++------------------- 3 files changed, 214 insertions(+), 155 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index bbc7e9c98..8720d1179 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -148,8 +148,6 @@ HOW TO UPDATE? - Tree: hierarchy lines (when thickness>1.0f). - TabBar: selected overline border ((when thickness>1.0f). - Demo: Custom Rendering: canvas lines. -- (Breaking) Removed support for non-aliased shapes. - - Clearing `ImDrawListFlags_AntiAliasedLines` / `ImDrawListFlags_AntiAliasedFill` will have no effect. - Demo: Custom Rendering: exposed new flags; Showcasing overlapping strokes and filled shapes; Added an option to animation thickness; Added new shapes to showcase new rendering features that previously hit limitations. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index b27736666..7e52eaf34 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10445,6 +10445,16 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::Spacing(); const ImDrawFlags flags = stroke_flags | other_flags; + // FIXME: Clarify how this is exposed, consider changing Demo code to simply alter Style.s + static ImDrawListFlags draw_list_flags = ImDrawListFlags_AntiAliasedLines | ImDrawListFlags_AntiAliasedFill | ImDrawListFlags_RoundCornersUseTex; + ImGui::Text("DrawList Flags:"); + ImGui::CheckboxFlags("AntiAliasedLines", &draw_list_flags, ImDrawListFlags_AntiAliasedLines); ImGui::SameLine(); + ImGui::CheckboxFlags("AntiAliasedFill", &draw_list_flags, ImDrawListFlags_AntiAliasedFill); ImGui::SameLine(); + ImGui::CheckboxFlags("RoundCornersUseTex", &draw_list_flags, ImDrawListFlags_RoundCornersUseTex); + ImDrawListFlags backup_draw_list_flags = draw_list->Flags; + draw_list->Flags &= ~(ImDrawListFlags_AntiAliasedLines | ImDrawListFlags_AntiAliasedFill | ImDrawListFlags_RoundCornersUseTex); + draw_list->Flags |= draw_list_flags; + ImVec2 start_pos = ImGui::GetCursorScreenPos(); const float pi = 3.141592f; const float spacing = 10.0f; @@ -10577,6 +10587,8 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::PopItemFlag(); ImGui::PopItemWidth(); ImGui::EndTabItem(); + + draw_list->Flags = backup_draw_list_flags; } if (ImGui::BeginTabItem("Canvas")) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 04262db88..fcd253b74 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1333,7 +1333,18 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 ImVec4 tex_uvs; float fringe; - _SelectFringeTexture(screen_thickness, &tex_uvs, &fringe); + if (Flags & ImDrawListFlags_AntiAliasedLines) + { + _SelectFringeTexture(screen_thickness, &tex_uvs, &fringe); + } + else + { + tex_uvs.x = _Data->TexUvWhitePixel.x; + tex_uvs.y = _Data->TexUvWhitePixel.y; + tex_uvs.z = _Data->TexUvWhitePixel.x; + tex_uvs.w = _Data->TexUvWhitePixel.y; + fringe = 0.0f; + } _AddPolyline(points, normals, sqr_lengths, points_count, col, thickness, flags, tex_uvs, fringe); } @@ -1613,175 +1624,187 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun }*/ const ImVec2 uv = _Data->TexUvWhitePixel; - const float half_aa = _FringeScale * 0.5f; - ImU32 col_trans = col & ~IM_COL32_A_MASK; - const bool miters_only = (flags & ImDrawFlags_MiterOnly) != 0; - const float miter_distance_limit_sqr = IM_POLYLINE_MITER_LIMIT * IM_POLYLINE_MITER_LIMIT; - - const int idx_count = ((points_count - 2) + points_count * 2 + IM_POLYLINE_CONVEX_POLY_MAX_BEVELS) * 3; - const int vtx_count = points_count * 2 + IM_POLYLINE_CONVEX_POLY_MAX_BEVELS; - PrimReserve(idx_count, vtx_count); - ImDrawVert* start_vtx_ptr = _VtxWritePtr; - ImDrawIdx* start_idx_ptr = _IdxWritePtr; - - // Compute normals and segment lengths - _Data->TempBuffer.reserve_discard(points_count * 2); - ImVec2* temp_normals = _Data->TempBuffer.Data; - float* temp_sqr_lengths = (float*)(_Data->TempBuffer.Data + points_count); - for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) + if (Flags & ImDrawListFlags_AntiAliasedFill) { - const ImVec2& p0 = points[i0]; - const ImVec2& p1 = points[i1]; - float dx = p1.x - p0.x; - float dy = p1.y - p0.y; - float d2 = dx*dx + dy*dy; - if (d2 > 0.0f) - { - const float inv_len = ImRsqrtPrecise(d2); - dx *= inv_len; - dy *= inv_len; - } - temp_normals[i0].x = dy; - temp_normals[i0].y = -dx; - temp_sqr_lengths[i0] = d2; - } + const float half_aa = _FringeScale * 0.5f; + ImU32 col_trans = col & ~IM_COL32_A_MASK; + const bool miters_only = (flags & ImDrawFlags_MiterOnly) != 0; + const float miter_distance_limit_sqr = IM_POLYLINE_MITER_LIMIT * IM_POLYLINE_MITER_LIMIT; + const int idx_count = ((points_count - 2) + points_count * 2 + IM_POLYLINE_CONVEX_POLY_MAX_BEVELS) * 3; + const int vtx_count = points_count * 2 + IM_POLYLINE_CONVEX_POLY_MAX_BEVELS; + PrimReserve(idx_count, vtx_count); + ImDrawVert* start_vtx_ptr = _VtxWritePtr; + ImDrawIdx* start_idx_ptr = _IdxWritePtr; - unsigned int vtx_inner_idx = _VtxCurrentIdx; - ImDrawVert* inner_vtx_ptr = _VtxWritePtr; - _VtxWritePtr += points_count; - _VtxCurrentIdx += points_count; - - unsigned int prev_outer_idx = 0; // We dont know outer vert could yet, will need to patch once we're done. - - - if (miters_only) - { + // Compute normals and segment lengths + _Data->TempBuffer.reserve_discard(points_count * 2); + ImVec2* temp_normals = _Data->TempBuffer.Data; + float* temp_sqr_lengths = (float*)(_Data->TempBuffer.Data + points_count); for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) { - // Average normals - const ImVec2 p1 = points[i1]; - const ImVec2 n0 = temp_normals[i0]; - const ImVec2 n1 = temp_normals[i1]; - - // theta is the angle between two segments - const float cos_theta = n0.x * n1.x + n0.y * n1.y; - // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm - const float cos_theta_clamped = ImMax(IM_POLYLINE_MITER_ANGLE_LIMIT, cos_theta); // Avoid div by 0. - const float miter_scale_factor = ImMin(1000.0f, 1.0f / (1.0f + cos_theta_clamped)); - const float miter_offset_x = (n0.x + n1.x) * miter_scale_factor * half_aa; - const float miter_offset_y = (n0.y + n1.y) * miter_scale_factor * half_aa; - - // Inner - inner_vtx_ptr->pos.x = p1.x - miter_offset_x; - inner_vtx_ptr->pos.y = p1.y - miter_offset_y; - inner_vtx_ptr->uv = uv; inner_vtx_ptr->col = col; - inner_vtx_ptr++; - - const unsigned int prev_inner_idx = vtx_inner_idx + i0; - const unsigned int inner_idx = vtx_inner_idx + i1; - const unsigned int outer_idx = _VtxCurrentIdx; - - // Outer - IM_APPEND_VTX(p1.x + miter_offset_x, p1.y + miter_offset_y, uv, col_trans); - // Connect with previous - IM_APPEND_TRI(prev_outer_idx, outer_idx, inner_idx); - IM_APPEND_TRI(prev_outer_idx, inner_idx, prev_inner_idx); - - prev_outer_idx = outer_idx; + const ImVec2& p0 = points[i0]; + const ImVec2& p1 = points[i1]; + float dx = p1.x - p0.x; + float dy = p1.y - p0.y; + float d2 = dx * dx + dy * dy; + if (d2 > 0.0f) + { + const float inv_len = ImRsqrtPrecise(d2); + dx *= inv_len; + dy *= inv_len; + } + temp_normals[i0].x = dy; + temp_normals[i0].y = -dx; + temp_sqr_lengths[i0] = d2; } - } - else - { - int bevel_count = 0; - for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) + + unsigned int vtx_inner_idx = _VtxCurrentIdx; + ImDrawVert* inner_vtx_ptr = _VtxWritePtr; + _VtxWritePtr += points_count; + _VtxCurrentIdx += points_count; + + unsigned int prev_outer_idx = 0; // We don't know outer vert could yet, will need to patch once we're done. + if (miters_only) { - // Average normals - const ImVec2 p1 = points[i1]; - const ImVec2 n0 = temp_normals[i0]; - const ImVec2 n1 = temp_normals[i1]; - - // theta is the angle between two segments - const float cos_theta = n0.x * n1.x + n0.y * n1.y; - // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm - const float cos_theta_clamped = ImMax(IM_POLYLINE_MITER_ANGLE_LIMIT, cos_theta); // Avoid div by 0. - const float miter_scale_factor = ImMin(1000.0f, 1.0f / (1.0f + cos_theta_clamped)); - float miter_offset_x = (n0.x + n1.x) * miter_scale_factor; - float miter_offset_y = (n0.y + n1.y) * miter_scale_factor; - - const float miter_distance_sqr = miter_offset_x * miter_offset_x + miter_offset_y * miter_offset_y; - bool bevel = miter_distance_sqr > miter_distance_limit_sqr; - if (bevel) + for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) { - // Limit inner bevel so that it is does not shoot out outside the polygon. - const float ref_thickness_sqr = half_aa * half_aa; - const float limit_sqr = ImMax(temp_sqr_lengths[i0], temp_sqr_lengths[i1]); - const float ref_miter_dist_sqr = miter_distance_sqr * ref_thickness_sqr; - if (ref_miter_dist_sqr > limit_sqr) - { - const float scale = ImSqrt(limit_sqr / ref_miter_dist_sqr); - miter_offset_x *= scale; - miter_offset_y *= scale; - } - } + // Average normals + const ImVec2 p1 = points[i1]; + const ImVec2 n0 = temp_normals[i0]; + const ImVec2 n1 = temp_normals[i1]; - miter_offset_x *= half_aa; - miter_offset_y *= half_aa; + // theta is the angle between two segments + const float cos_theta = n0.x * n1.x + n0.y * n1.y; + // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm + const float cos_theta_clamped = ImMax(IM_POLYLINE_MITER_ANGLE_LIMIT, cos_theta); // Avoid div by 0. + const float miter_scale_factor = ImMin(1000.f, 1.0f / (1.0f + cos_theta_clamped)); + const float miter_offset_x = (n0.x + n1.x) * miter_scale_factor * half_aa; + const float miter_offset_y = (n0.y + n1.y) * miter_scale_factor * half_aa; - // Inner - inner_vtx_ptr->pos.x = p1.x - miter_offset_x; - inner_vtx_ptr->pos.y = p1.y - miter_offset_y; - inner_vtx_ptr->uv = uv; inner_vtx_ptr->col = col; - inner_vtx_ptr++; + // Inner + inner_vtx_ptr->pos.x = p1.x - miter_offset_x; + inner_vtx_ptr->pos.y = p1.y - miter_offset_y; + inner_vtx_ptr->uv = uv; inner_vtx_ptr->col = col; + inner_vtx_ptr++; - const unsigned int prev_inner_idx = vtx_inner_idx + i0; - const unsigned int inner_idx = vtx_inner_idx + i1; - unsigned int outer_idx = _VtxCurrentIdx; + const unsigned int prev_inner_idx = vtx_inner_idx + i0; + const unsigned int inner_idx = vtx_inner_idx + i1; + const unsigned int outer_idx = _VtxCurrentIdx; - // Outer - if (bevel && bevel_count < IM_POLYLINE_CONVEX_POLY_MAX_BEVELS) IM_UNLIKELY - { - // Because the polygon is convex, we know the maximum number of bevel corners we can hit (which is very small number). - // We keep track of them just in case the calculations disagree. - bevel_count++; - - IM_APPEND_VTX(p1.x + n0.x * half_aa, p1.y + n0.y * half_aa, uv, col_trans); - IM_APPEND_VTX(p1.x + n1.x * half_aa, p1.y + n1.y * half_aa, uv, col_trans); - // Connect with previous - IM_APPEND_TRI(prev_outer_idx, outer_idx, inner_idx); - IM_APPEND_TRI(prev_outer_idx, inner_idx, prev_inner_idx); - // Fill bevel - IM_APPEND_TRI(outer_idx, outer_idx + 1, inner_idx); - outer_idx++; - } - else - { + // Outer IM_APPEND_VTX(p1.x + miter_offset_x, p1.y + miter_offset_y, uv, col_trans); // Connect with previous IM_APPEND_TRI(prev_outer_idx, outer_idx, inner_idx); IM_APPEND_TRI(prev_outer_idx, inner_idx, prev_inner_idx); + + prev_outer_idx = outer_idx; } - - prev_outer_idx = outer_idx; } + else + { + int bevel_count = 0; + for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) + { + // Average normals + const ImVec2 p1 = points[i1]; + const ImVec2 n0 = temp_normals[i0]; + const ImVec2 n1 = temp_normals[i1]; + + // theta is the angle between two segments + const float cos_theta = n0.x * n1.x + n0.y * n1.y; + // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm + const float cos_theta_clamped = ImMax(IM_POLYLINE_MITER_ANGLE_LIMIT, cos_theta); // Avoid div by 0. + const float miter_scale_factor = ImMin(1000.f, 1.0f / (1.0f + cos_theta_clamped)); + float miter_offset_x = (n0.x + n1.x) * miter_scale_factor; + float miter_offset_y = (n0.y + n1.y) * miter_scale_factor; + + const float miter_distance_sqr = miter_offset_x * miter_offset_x + miter_offset_y * miter_offset_y; + bool bevel = miter_distance_sqr > miter_distance_limit_sqr; + if (bevel) + { + // Limit inner bevel so that it is does not shoot out outside the polygon. + const float ref_thickness_sqr = half_aa * half_aa; + const float limit_sqr = ImMax(temp_sqr_lengths[i0], temp_sqr_lengths[i1]); + const float ref_miter_dist_sqr = miter_distance_sqr * ref_thickness_sqr; + if (ref_miter_dist_sqr > limit_sqr) + { + const float scale = ImSqrt(limit_sqr / ref_miter_dist_sqr); + miter_offset_x *= scale; + miter_offset_y *= scale; + } + } + + miter_offset_x *= half_aa; + miter_offset_y *= half_aa; + + // Inner + inner_vtx_ptr->pos.x = p1.x - miter_offset_x; + inner_vtx_ptr->pos.y = p1.y - miter_offset_y; + inner_vtx_ptr->uv = uv; inner_vtx_ptr->col = col; + inner_vtx_ptr++; + + const unsigned int prev_inner_idx = vtx_inner_idx + i0; + const unsigned int inner_idx = vtx_inner_idx + i1; + unsigned int outer_idx = _VtxCurrentIdx; + + // Outer + if (bevel && bevel_count < IM_POLYLINE_CONVEX_POLY_MAX_BEVELS) IM_UNLIKELY + { + // Because the polygon is convex, we know the maximum number of bevel corners we can hit (which is very small number). + // We keep track of them just in case the calculations disagree. + bevel_count++; + + IM_APPEND_VTX(p1.x + n0.x * half_aa, p1.y + n0.y * half_aa, uv, col_trans); + IM_APPEND_VTX(p1.x + n1.x * half_aa, p1.y + n1.y * half_aa, uv, col_trans); + // Connect with previous + IM_APPEND_TRI(prev_outer_idx, outer_idx, inner_idx); + IM_APPEND_TRI(prev_outer_idx, inner_idx, prev_inner_idx); + // Fill bevel + IM_APPEND_TRI(outer_idx, outer_idx + 1, inner_idx); + outer_idx++; + } + else + { + IM_APPEND_VTX(p1.x + miter_offset_x, p1.y + miter_offset_y, uv, col_trans); + // Connect with previous + IM_APPEND_TRI(prev_outer_idx, outer_idx, inner_idx); + IM_APPEND_TRI(prev_outer_idx, inner_idx, prev_inner_idx); + } + + prev_outer_idx = outer_idx; + } + } + + // Patch first segment to wrap around + start_idx_ptr[0] = (ImDrawIdx)prev_outer_idx; + start_idx_ptr[3] = (ImDrawIdx)prev_outer_idx; + + // Add indices for fill + for (int i = 2; i < points_count; i++) + { + IM_APPEND_TRI(vtx_inner_idx, vtx_inner_idx + i - 1, vtx_inner_idx + i); + } + + const int idx_used = (int)(_IdxWritePtr - start_idx_ptr); + const int vtx_used = (int)(_VtxWritePtr - start_vtx_ptr); + IM_ASSERT(idx_used <= idx_count && vtx_used <= vtx_count); + + if (idx_used < idx_count || vtx_used < vtx_count) + PrimUnreserve(idx_count - idx_used, vtx_count - vtx_used); } - - // Patch first segment to wrap around - start_idx_ptr[0] = (ImDrawIdx)prev_outer_idx; - start_idx_ptr[3] = (ImDrawIdx)prev_outer_idx; - - // Add indices for fill - for (int i = 2; i < points_count; i++) + else { - IM_APPEND_TRI(vtx_inner_idx, vtx_inner_idx + i - 1, vtx_inner_idx + i); + // Non Anti-aliased Fill + const int idx_count = (points_count - 2) * 3; + const int vtx_count = points_count; + PrimReserve(idx_count, vtx_count); + int base_idx = _VtxCurrentIdx; + for (int i = 0; i < vtx_count; i++) + IM_APPEND_VTX(points[i].x, points[i].y, uv, col); + for (int i = 2; i < points_count; i++) + IM_APPEND_TRI(base_idx, base_idx + i - 1, base_idx + i); } - - const int idx_used = (int)(_IdxWritePtr - start_idx_ptr); - const int vtx_used = (int)(_VtxWritePtr - start_vtx_ptr); - IM_ASSERT(idx_used <= idx_count && vtx_used <= vtx_count); - - if (idx_used < idx_count || vtx_used < vtx_count) - PrimUnreserve(idx_count - idx_used, vtx_count - vtx_used); } void ImDrawList::AddConvexPolyFilledLegacy(const ImVec2* points, const int points_count, ImU32 col) @@ -2236,7 +2259,18 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t ImVec4 tex_uvs; float fringe; - _SelectFringeTexture(screen_thickness, &tex_uvs, &fringe); + if (Flags & ImDrawListFlags_AntiAliasedLines) + { + _SelectFringeTexture(screen_thickness, &tex_uvs, &fringe); + } + else + { + tex_uvs.x = _Data->TexUvWhitePixel.x; + tex_uvs.y = _Data->TexUvWhitePixel.y; + tex_uvs.z = _Data->TexUvWhitePixel.x; + tex_uvs.w = _Data->TexUvWhitePixel.y; + fringe = 0.0f; + } float dir_x = p2.x - p1.x; float dir_y = p2.y - p1.y; @@ -2597,7 +2631,18 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, ImVec4 tex_uvs; float fringe; - _SelectFringeTexture(screen_thickness, &tex_uvs, &fringe); + if (Flags & ImDrawListFlags_AntiAliasedLines) + { + _SelectFringeTexture(screen_thickness, &tex_uvs, &fringe); + } + else + { + tex_uvs.x = _Data->TexUvWhitePixel.x; + tex_uvs.y = _Data->TexUvWhitePixel.y; + tex_uvs.z = _Data->TexUvWhitePixel.x; + tex_uvs.w = _Data->TexUvWhitePixel.y; + fringe = 0.0f; + } const int auto_seg_count = _CalcCircleAutoSegmentCount(rounding); int arc_step, arc_step_count; @@ -2808,7 +2853,9 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl return; } - if ((Flags & ImDrawListFlags_RoundCornersUseTex) && s_thickness < IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX && s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) + // Textured corners are baked with AA, do not use them if no-AA is requested. + const bool allow_tex_corners = (Flags & (ImDrawListFlags_RoundCornersUseTex | ImDrawListFlags_AntiAliasedLines)) == (ImDrawListFlags_RoundCornersUseTex | ImDrawListFlags_AntiAliasedLines); + if (allow_tex_corners && s_thickness < IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX && s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) { // Pixel aligned rect with round corners rendered using baked textures. IM_ASSERT_PARANOID(s_thickness > 0 && s_rounding > 0); @@ -2942,7 +2989,9 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c PrimRect(p_min, p_max, col); return; } - else if ((Flags & ImDrawListFlags_RoundCornersUseTex) && s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) + // Textured corners are baked with AA, do not use them if no-AA is requested. + const bool allow_tex_corners = (Flags & (ImDrawListFlags_RoundCornersUseTex | ImDrawListFlags_AntiAliasedFill)) == (ImDrawListFlags_RoundCornersUseTex | ImDrawListFlags_AntiAliasedFill); + if (allow_tex_corners && s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) { IM_ASSERT_PARANOID(!(_Data->Font->OwnerAtlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners)); const int size = ImMax(2, s_rounding); // This is matching the baking calculations. From 9be9c998d7488ac705cbfd4921db010dbe61a964 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Thu, 4 Jun 2026 11:13:40 +0300 Subject: [PATCH 067/120] DrawList: removed vertex offset overflow (for 16-bit ImDrawIdx support) handling. This seems unnecessary for now. --- imgui_draw.cpp | 56 -------------------------------------------------- 1 file changed, 56 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index fcd253b74..0564ebd91 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -870,41 +870,6 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE #define IM_IS_TRUNCATED(a) ImIsTruncated(a) #endif -// Called when the current vertex count for a polyline call would overflow the index range. -// Commit the vertices so far, reserve new buffers, reset index, and duplicate the base vertices so that they can be accessed by the new indices. -static void SplitPolyline(ImDrawList* draw_list, int& start_idx_offset, int& start_vtx_offset, int& base_idx) -{ - // Make copy of current base points. - ImDrawVert base0 = draw_list->VtxBuffer.Data[draw_list->_CmdHeader.VtxOffset + base_idx + 0]; - ImDrawVert base1 = draw_list->VtxBuffer.Data[draw_list->_CmdHeader.VtxOffset + base_idx + 1]; - - // Release unused verts and indices. - const int cur_idx_offset = (int)(draw_list->_IdxWritePtr - draw_list->IdxBuffer.Data); - const int cur_vtx_offset = (int)(draw_list->_VtxWritePtr - draw_list->VtxBuffer.Data); - const int remaining_idx_count = draw_list->IdxBuffer.Size - cur_idx_offset; - const int remaining_vtx_count = draw_list->VtxBuffer.Size - cur_vtx_offset; - - IM_ASSERT(remaining_vtx_count >= 0 && remaining_idx_count >= 0); - draw_list->PrimUnreserve(remaining_idx_count, remaining_vtx_count); - - // Reserve remaining verts and indices. - draw_list->PrimReserve(remaining_idx_count, remaining_vtx_count + 2); // +2 for the duplicated base points. - - start_idx_offset = cur_idx_offset; - start_vtx_offset = cur_vtx_offset; - base_idx = (int)draw_list->_VtxCurrentIdx; - - // Duplicate base points - draw_list->_VtxWritePtr[0] = base0; - draw_list->_VtxWritePtr[1] = base1; - draw_list->_VtxWritePtr += 2; - draw_list->_VtxCurrentIdx += 2; -} - -#define IM_ADD_POLYLINE_HANDLE_VTX_OFFSET_OVERFLOW(new_verts) \ - if (sizeof(ImDrawIdx) == 2 && (Flags & ImDrawListFlags_AllowVtxOffset) && _VtxCurrentIdx + (new_verts) > (1 << 16)) IM_UNLIKELY \ - SplitPolyline(this, start_idx_offset, start_vtx_offset, base_idx) - void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, const ImVec4& tex_uvs, float fringe) { const ImU32 col_trans = col & ~IM_COL32_A_MASK; @@ -957,10 +922,7 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ PrimReserve(idx_count, vtx_count); - // These offsets change if we need to create new draw command mid shape. const int first_vtx_offset = (int)(_VtxWritePtr - VtxBuffer.Data); - int start_vtx_offset = (int)(_VtxWritePtr - VtxBuffer.Data); - int start_idx_offset = (int)(_IdxWritePtr - IdxBuffer.Data); int base_idx = (int)_VtxCurrentIdx; ImVec2 pa, pb, dir; @@ -990,8 +952,6 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ if (flags & ImDrawFlags_NoAAEnds) { - IM_ADD_POLYLINE_HANDLE_VTX_OFFSET_OVERFLOW(2); - base_idx = (int)_VtxCurrentIdx; IM_APPEND_VTX(p1.x - n1.x * thickness0, p1.y - n1.y * thickness0, uv0, col); IM_APPEND_VTX(p1.x + n1.x * thickness1, p1.y + n1.y * thickness1, uv1, col); @@ -1003,8 +963,6 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ pb.x = p1.x + dir.x * half_aa; pb.y = p1.y + dir.y * half_aa; - IM_ADD_POLYLINE_HANDLE_VTX_OFFSET_OVERFLOW(4); - base_idx = (int)_VtxCurrentIdx; IM_APPEND_VTX(pa.x - n1.x * thickness0, pa.y - n1.y * thickness0, uv0, col_trans); IM_APPEND_VTX(pa.x + n1.x * thickness1, pa.y + n1.y * thickness1, uv1, col_trans); @@ -1027,8 +985,6 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ len_sqr1 = sqr_lengths[points_count-1]; // This will be filled later, allocate space. - IM_ADD_POLYLINE_HANDLE_VTX_OFFSET_OVERFLOW(2); - base_idx = (int)_VtxCurrentIdx; IM_APPEND_VTX(0, 0, uv0, col); IM_APPEND_VTX(0, 0, uv1, col); @@ -1052,8 +1008,6 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ const float miter_offset_y = (n0.y + n1.y) * miter_scale_factor; // Miter - IM_ADD_POLYLINE_HANDLE_VTX_OFFSET_OVERFLOW(2); - const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; IM_APPEND_VTX(p1.x - miter_offset_x * thickness0, p1.y - miter_offset_y * thickness0, uv0, col); // 2 IM_APPEND_VTX(p1.x + miter_offset_x * thickness1, p1.y + miter_offset_y * thickness1, uv1, col); // 3 @@ -1116,8 +1070,6 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ // Bevel if (sin_theta < 0.0f) { - IM_ADD_POLYLINE_HANDLE_VTX_OFFSET_OVERFLOW(4); - IM_APPEND_VTX(p1.x - (bn_x + sd_x) * thickness0, p1.y - (bn_y + sd_y) * thickness0, uv0, col); // 2 IM_APPEND_VTX(p1.x, p1.y, uv2, col); // 3 const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; @@ -1135,8 +1087,6 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ } else { - IM_ADD_POLYLINE_HANDLE_VTX_OFFSET_OVERFLOW(4); - IM_APPEND_VTX(p1.x + (bn_x + sd_x) * thickness1, p1.y + (bn_y + sd_y) * thickness1, uv1, col); // 2 IM_APPEND_VTX(p1.x, p1.y, uv2, col); // 3 const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; @@ -1155,8 +1105,6 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ } else { - IM_ADD_POLYLINE_HANDLE_VTX_OFFSET_OVERFLOW(4); - // Miter const int next_base_idx = (ImDrawIdx)_VtxCurrentIdx; IM_APPEND_VTX(p1.x - miter_offset_x * thickness0, p1.y - miter_offset_y * thickness0, uv0, col); // 2 @@ -1190,8 +1138,6 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ if (flags & ImDrawFlags_NoAAEnds) { - IM_ADD_POLYLINE_HANDLE_VTX_OFFSET_OVERFLOW(2); - IM_APPEND_VTX(p1.x - n1.x * thickness0, p1.y - n1.y * thickness0, uv0, col); IM_APPEND_VTX(p1.x + n1.x * thickness1, p1.y + n1.y * thickness1, uv1, col); @@ -1206,8 +1152,6 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ pb.x = p1.x + dir.x * half_aa; pb.y = p1.y + dir.y * half_aa; - IM_ADD_POLYLINE_HANDLE_VTX_OFFSET_OVERFLOW(4); - int next_base_idx = (int)_VtxCurrentIdx; IM_APPEND_VTX(pa.x - n1.x * thickness0, pa.y - n1.y * thickness0, uv0, col); IM_APPEND_VTX(pa.x + n1.x * thickness1, pa.y + n1.y * thickness1, uv1, col); From f272141a4d9ee4548b4e1898c1c0c3dc4acd51ab Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Thu, 4 Jun 2026 11:17:16 +0300 Subject: [PATCH 068/120] DrawList: merged _AddPolyline back into AddPolyline. - AddPolyline used to call different versions of _AddPolyline, the separation is not needed anymore, one less function call in debug mode --- imgui.h | 1 - imgui_draw.cpp | 242 ++++++++++++++++++++++++------------------------- 2 files changed, 118 insertions(+), 125 deletions(-) diff --git a/imgui.h b/imgui.h index a0c51cdda..d38f58e48 100644 --- a/imgui.h +++ b/imgui.h @@ -3540,7 +3540,6 @@ struct ImDrawList IMGUI_API void _PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments); IMGUI_API void _AddRectFilledBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, const ImVec4& tex_uvs, ImDrawFlags flags); IMGUI_API void _AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, float t, const ImVec4& tex_uvs, ImDrawFlags flags); - IMGUI_API void _AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, const ImVec4& tex_uvs, float fringe); IMGUI_API void _AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness, ImDrawFlags flags); IMGUI_API void _AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags); IMGUI_API void _SelectFringeTexture(float screen_thickness, ImVec4* out_tex_uvs, float* out_fringe); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 0564ebd91..e31a08874 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -870,8 +870,111 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE #define IM_IS_TRUNCATED(a) ImIsTruncated(a) #endif -void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_lengths, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, const ImVec4& tex_uvs, float fringe) +IM_MSVC_RUNTIME_CHECKS_OFF +static ImU32 ImAlphaMultiply(ImU32 col, float alpha_mul) { + IM_ASSERT_PARANOID(a >= 0.0f && a < 1.0f); // We don't clamp! + ImU32 a = (col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT; + a = (ImU32)(a * alpha_mul); // We don't need to clamp 0..255 because alpha is in 0..1 range. + return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT); +} + +void ImDrawList::_SelectFringeTexture(float screen_thickness, ImVec4* out_tex_uvs, float* out_fringe) +{ + if (screen_thickness <= IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH) + { + // Handle the thickness between [1..IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH]. The texture scaling in this range will cause slight visual pops, so we generate super sampled textures in this range. + // There are IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX+1 textures, where 0 maps to 1.0 and IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX maps to IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH. + constexpr float base_width = 1.0f; + int texture_idx = (int)((screen_thickness - base_width) * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + 0.1f); + texture_idx = ImMax(texture_idx, 0); + texture_idx = ImMin(texture_idx, IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX); + const float tex_width = base_width + (float)texture_idx / IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT; + *out_fringe = _FringeScale * (screen_thickness / tex_width); // Scale the fringe to cover the discrepancy between the texture and requested size. + *out_tex_uvs = _Data->TexUvLines[(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1) + texture_idx]; + } + else + { + // Bias the texture selection towards higher resolution to avoid visual pops when the texture change. + int texture_idx = (int)(screen_thickness + 0.995f); + texture_idx = ImMax(texture_idx, 2); + texture_idx = ImMin(texture_idx, IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1); + *out_fringe = _FringeScale * (screen_thickness / (float)texture_idx); // Scale the fringe to cover the discrepancy between the texture and requested size. + *out_tex_uvs = _Data->TexUvLines[texture_idx]; + } +} +IM_MSVC_RUNTIME_CHECKS_RESTORE + +extern bool g_LEGACY_STROKES; + +void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags) +{ + if (g_LEGACY_STROKES) + { + AddPolylineLegacy(points, points_count, col, thickness, flags); + return; + } + + if (points_count < 2 || (col & IM_COL32_A_MASK) == 0) + return; + + float screen_thickness = thickness * _InvFringeScale; + if (screen_thickness < 1.0f / 255.0f) + return; + if (screen_thickness < 1.0f) + { + col = ImAlphaMultiply(col, screen_thickness); + screen_thickness = 1.0f; + thickness = _FringeScale; + } + + // Allocate data for temp buffers + _Data->TempBuffer.reserve_discard(points_count * 2); + ImVec2* normals = _Data->TempBuffer.Data; + float* sqr_lengths = (float*)(normals + points_count); + + // Calculate normals for each line segment + for (int i = 0; i < points_count - 1; i++) + { + float dx = points[i + 1].x - points[i].x; + float dy = points[i + 1].y - points[i].y; + const float d2 = dx * dx + dy * dy; + const float inv_len = (d2 > 0.0f) ? ImRsqrtPrecise(d2) : 0.0f; + normals[i].x = -dy * inv_len; + normals[i].y = dx * inv_len; + sqr_lengths[i] = d2; + } + if ((flags & ImDrawFlags_Closed) != 0) + { + const float dx = points[0].x - points[points_count - 1].x; + const float dy = points[0].y - points[points_count - 1].y; + const float d2 = dx * dx + dy * dy; + const float inv_len = (d2 > 0.0f) ? ImRsqrtPrecise(d2) : 0.0f; + normals[points_count - 1].x = -dy * inv_len; + normals[points_count - 1].y = dx * inv_len; + sqr_lengths[points_count - 1] = d2; + } + else + { + normals[points_count - 1] = normals[points_count - 2]; + sqr_lengths[points_count - 1] = 0.0f; + } + + ImVec4 tex_uvs; + float fringe; + if (Flags & ImDrawListFlags_AntiAliasedLines) + { + _SelectFringeTexture(screen_thickness, &tex_uvs, &fringe); + } + else + { + tex_uvs.x = _Data->TexUvWhitePixel.x; + tex_uvs.y = _Data->TexUvWhitePixel.y; + tex_uvs.z = _Data->TexUvWhitePixel.x; + tex_uvs.w = _Data->TexUvWhitePixel.y; + fringe = 0.0f; + } + const ImU32 col_trans = col & ~IM_COL32_A_MASK; const ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeCenter); @@ -883,12 +986,12 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ const float miter_distance_limit_sqr = IM_POLYLINE_MITER_LIMIT * IM_POLYLINE_MITER_LIMIT; float thickness0 = (thickness + fringe) * 0.5f; // Center (or legacy) - if (stroke_pos == ImDrawFlags_StrokeOutside) - thickness0 = thickness + fringe * 0.5f; + if (stroke_pos == ImDrawFlags_StrokeOutside) + thickness0 = thickness + fringe * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeCenterBiased) thickness0 = _CalculateCenterBiasedOffset(thickness) + fringe * 0.5f; - else if (stroke_pos == ImDrawFlags_StrokeInside) - thickness0 = fringe * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeInside) + thickness0 = fringe * 0.5f; float thickness1 = (thickness + fringe) - thickness0; int idx_count = 0; @@ -905,7 +1008,7 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ vtx_count = /*body*/(points_count - 2) * max_verts_per_point + /*caps*/(4 * 2); idx_count = (/*body*/(points_count - 2) * max_tris_per_point + /*last seg*/2 + /*caps*/(2 * 2)) * 3; } - + PrimReserve(idx_count, vtx_count); const float half_texel = 0.5f * _Data->FontAtlas->TexUvScale.x; const float ratio = thickness0 / (thickness0 + thickness1); // The points using uv2 are placed on the path, calculate the position from stroke offets. @@ -920,8 +1023,6 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ const float half_aa = _FringeScale * 0.5f; // Used for end caps, does not use "fringe" since end cap AA is not using the texture. const float half_thickness = thickness * 0.5f; - PrimReserve(idx_count, vtx_count); - const int first_vtx_offset = (int)(_VtxWritePtr - VtxBuffer.Data); int base_idx = (int)_VtxCurrentIdx; @@ -980,9 +1081,9 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ else { // Wrap around segment - p1 = points[points_count-1]; - n1 = normals[points_count-1]; - len_sqr1 = sqr_lengths[points_count-1]; + p1 = points[points_count - 1]; + n1 = normals[points_count - 1]; + len_sqr1 = sqr_lengths[points_count - 1]; // This will be filled later, allocate space. base_idx = (int)_VtxCurrentIdx; @@ -1124,8 +1225,8 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ // End cap if (!closed) { - p1 = points[points_count-1]; - n1 = normals[points_count-1]; + p1 = points[points_count - 1]; + n1 = normals[points_count - 1]; // End cap dir.x = n1.y; @@ -1172,8 +1273,8 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ else { // Connect the path. - VtxBuffer.Data[first_vtx_offset + 0] = VtxBuffer.Data[_CmdHeader.VtxOffset + base_idx+0]; - VtxBuffer.Data[first_vtx_offset + 1] = VtxBuffer.Data[_CmdHeader.VtxOffset + base_idx+1]; + VtxBuffer.Data[first_vtx_offset + 0] = VtxBuffer.Data[_CmdHeader.VtxOffset + base_idx + 0]; + VtxBuffer.Data[first_vtx_offset + 1] = VtxBuffer.Data[_CmdHeader.VtxOffset + base_idx + 1]; } // Restore unused memory @@ -1185,113 +1286,6 @@ void ImDrawList::_AddPolyline(const ImVec2* points, ImVec2* normals, float* sqr_ PrimUnreserve(remaining_idx_count, remaining_vtx_count); } -IM_MSVC_RUNTIME_CHECKS_OFF -static ImU32 ImAlphaMultiply(ImU32 col, float alpha_mul) -{ - IM_ASSERT_PARANOID(a >= 0.0f && a < 1.0f); // We don't clamp! - ImU32 a = (col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT; - a = (ImU32)(a * alpha_mul); // We don't need to clamp 0..255 because alpha is in 0..1 range. - return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT); -} - -void ImDrawList::_SelectFringeTexture(float screen_thickness, ImVec4* out_tex_uvs, float* out_fringe) -{ - if (screen_thickness <= IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH) - { - // Handle the thickness between [1..IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH]. The texture scaling in this range will cause slight visual pops, so we generate super sampled textures in this range. - // There are IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX+1 textures, where 0 maps to 1.0 and IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX maps to IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH. - constexpr float base_width = 1.0f; - int texture_idx = (int)((screen_thickness - base_width) * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + 0.1f); - texture_idx = ImMax(texture_idx, 0); - texture_idx = ImMin(texture_idx, IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX); - const float tex_width = base_width + (float)texture_idx / IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT; - *out_fringe = _FringeScale * (screen_thickness / tex_width); // Scale the fringe to cover the discrepancy between the texture and requested size. - *out_tex_uvs = _Data->TexUvLines[(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1) + texture_idx]; - } - else - { - // Bias the texture selection towards higher resolution to avoid visual pops when the texture change. - int texture_idx = (int)(screen_thickness + 0.995f); - texture_idx = ImMax(texture_idx, 2); - texture_idx = ImMin(texture_idx, IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1); - *out_fringe = _FringeScale * (screen_thickness / (float)texture_idx); // Scale the fringe to cover the discrepancy between the texture and requested size. - *out_tex_uvs = _Data->TexUvLines[texture_idx]; - } -} -IM_MSVC_RUNTIME_CHECKS_RESTORE - -extern bool g_LEGACY_STROKES; - -void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags) -{ - if (g_LEGACY_STROKES) - { - AddPolylineLegacy(points, points_count, col, thickness, flags); - return; - } - - if (points_count < 2 || (col & IM_COL32_A_MASK) == 0) - return; - - float screen_thickness = thickness * _InvFringeScale; - if (screen_thickness < 1.0f / 255.0f) - return; - if (screen_thickness < 1.0f) - { - col = ImAlphaMultiply(col, screen_thickness); - screen_thickness = 1.0f; - thickness = _FringeScale; - } - - // Allocate data for temp buffers - _Data->TempBuffer.reserve_discard(points_count * 2); - ImVec2* normals = _Data->TempBuffer.Data; - float* sqr_lengths = (float*)(normals + points_count); - - // Calculate normals for each line segment - for (int i = 0; i < points_count - 1; i++) - { - float dx = points[i + 1].x - points[i].x; - float dy = points[i + 1].y - points[i].y; - const float d2 = dx * dx + dy * dy; - const float inv_len = (d2 > 0.0f) ? ImRsqrtPrecise(d2) : 0.0f; - normals[i].x = -dy * inv_len; - normals[i].y = dx * inv_len; - sqr_lengths[i] = d2; - } - if ((flags & ImDrawFlags_Closed) != 0) - { - const float dx = points[0].x - points[points_count - 1].x; - const float dy = points[0].y - points[points_count - 1].y; - const float d2 = dx * dx + dy * dy; - const float inv_len = (d2 > 0.0f) ? ImRsqrtPrecise(d2) : 0.0f; - normals[points_count - 1].x = -dy * inv_len; - normals[points_count - 1].y = dx * inv_len; - sqr_lengths[points_count - 1] = d2; - } - else - { - normals[points_count - 1] = normals[points_count - 2]; - sqr_lengths[points_count - 1] = 0.0f; - } - - ImVec4 tex_uvs; - float fringe; - if (Flags & ImDrawListFlags_AntiAliasedLines) - { - _SelectFringeTexture(screen_thickness, &tex_uvs, &fringe); - } - else - { - tex_uvs.x = _Data->TexUvWhitePixel.x; - tex_uvs.y = _Data->TexUvWhitePixel.y; - tex_uvs.z = _Data->TexUvWhitePixel.x; - tex_uvs.w = _Data->TexUvWhitePixel.y; - fringe = 0.0f; - } - _AddPolyline(points, normals, sqr_lengths, points_count, col, thickness, flags, tex_uvs, fringe); -} - void ImDrawList::AddPolylineLegacy(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags) { if (points_count < 2 || (col & IM_COL32_A_MASK) == 0) @@ -1622,7 +1616,7 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun const float cos_theta = n0.x * n1.x + n0.y * n1.y; // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm const float cos_theta_clamped = ImMax(IM_POLYLINE_MITER_ANGLE_LIMIT, cos_theta); // Avoid div by 0. - const float miter_scale_factor = ImMin(1000.f, 1.0f / (1.0f + cos_theta_clamped)); + const float miter_scale_factor = ImMin(1000.0f, 1.0f / (1.0f + cos_theta_clamped)); const float miter_offset_x = (n0.x + n1.x) * miter_scale_factor * half_aa; const float miter_offset_y = (n0.y + n1.y) * miter_scale_factor * half_aa; @@ -1659,7 +1653,7 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun const float cos_theta = n0.x * n1.x + n0.y * n1.y; // miter offset formula is derived here: https://www.angusj.com/clipper2/Docs/Trigonometry.htm const float cos_theta_clamped = ImMax(IM_POLYLINE_MITER_ANGLE_LIMIT, cos_theta); // Avoid div by 0. - const float miter_scale_factor = ImMin(1000.f, 1.0f / (1.0f + cos_theta_clamped)); + const float miter_scale_factor = ImMin(1000.0f, 1.0f / (1.0f + cos_theta_clamped)); float miter_offset_x = (n0.x + n1.x) * miter_scale_factor; float miter_offset_y = (n0.y + n1.y) * miter_scale_factor; From d24d23fb408fc04c03558246a6e337bcd24ba72a Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Thu, 4 Jun 2026 15:57:43 +0300 Subject: [PATCH 069/120] DrawList: No AA ends when line AA is disabled --- imgui_draw.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index e31a08874..d8402b10a 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -973,6 +973,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 tex_uvs.z = _Data->TexUvWhitePixel.x; tex_uvs.w = _Data->TexUvWhitePixel.y; fringe = 0.0f; + flags |= ImDrawFlags_NoAAEnds; } const ImU32 col_trans = col & ~IM_COL32_A_MASK; @@ -2208,6 +2209,7 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t tex_uvs.z = _Data->TexUvWhitePixel.x; tex_uvs.w = _Data->TexUvWhitePixel.y; fringe = 0.0f; + flags |= ImDrawFlags_NoAAEnds; } float dir_x = p2.x - p1.x; From b181739cead7d251dd53a5554ad5eb6b67a941c4 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Fri, 5 Jun 2026 09:19:33 +0300 Subject: [PATCH 070/120] DrawList: Revert texture selection to simple round() in _SelectFringeTexture - revert texture selection to use just (inlined) round() - the values were tuned to hide texture selection pops on small thickness - since we're using super sampling for the problematic range, the tweaks are not necessayr anymore --- imgui_draw.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index d8402b10a..65fc3eb3f 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -883,10 +883,11 @@ void ImDrawList::_SelectFringeTexture(float screen_thickness, ImVec4* out_tex_uv { if (screen_thickness <= IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH) { - // Handle the thickness between [1..IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH]. The texture scaling in this range will cause slight visual pops, so we generate super sampled textures in this range. - // There are IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX+1 textures, where 0 maps to 1.0 and IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX maps to IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH. + // Handle the thickness between [1..IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH]. + // We use super sampled textures in this range to make texture changes less noticeable. + // There are IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX+1 textures, where 0 maps to 1.0 and IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX maps to IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH (4.0). constexpr float base_width = 1.0f; - int texture_idx = (int)((screen_thickness - base_width) * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + 0.1f); + int texture_idx = (int)((screen_thickness - base_width) * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + 0.5f); texture_idx = ImMax(texture_idx, 0); texture_idx = ImMin(texture_idx, IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX); const float tex_width = base_width + (float)texture_idx / IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT; @@ -895,8 +896,7 @@ void ImDrawList::_SelectFringeTexture(float screen_thickness, ImVec4* out_tex_uv } else { - // Bias the texture selection towards higher resolution to avoid visual pops when the texture change. - int texture_idx = (int)(screen_thickness + 0.995f); + int texture_idx = (int)(screen_thickness + 0.5f); texture_idx = ImMax(texture_idx, 2); texture_idx = ImMin(texture_idx, IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1); *out_fringe = _FringeScale * (screen_thickness / (float)texture_idx); // Scale the fringe to cover the discrepancy between the texture and requested size. From efb5c0e1da14ae8c062f33017ab5ffc292b0f1a2 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Fri, 5 Jun 2026 09:58:47 +0300 Subject: [PATCH 071/120] DrawList: Added some comments and amended existing ones --- imgui_draw.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 65fc3eb3f..c8a4c4328 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -857,9 +857,9 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE #define IM_FIXNORMAL2F_MAX_INVLEN2 100.0f // 500.0f (see #4053, #3366) #define IM_FIXNORMAL2F(VX,VY) { float d2 = VX*VX + VY*VY; if (d2 > 0.000001f) { float inv_len2 = 1.0f / d2; if (inv_len2 > IM_FIXNORMAL2F_MAX_INVLEN2) inv_len2 = IM_FIXNORMAL2F_MAX_INVLEN2; VX *= inv_len2; VY *= inv_len2; } } (void)0 -#define IM_POLYLINE_MITER_ANGLE_LIMIT (-0.9999619f) // cos(179.5) -#define IM_POLYLINE_MITER_LIMIT (4.0f) // ~29 deg -#define IM_POLYLINE_CONVEX_POLY_MAX_BEVELS (2) // (int)(360/floor(180-29)) +#define IM_POLYLINE_MITER_ANGLE_LIMIT (-0.9999619f) // Safeguard for miter corner calculation to avoid div by zero. cos(179.5) +#define IM_POLYLINE_MITER_LIMIT (4.0f) // How much the miter can be scaled before we turn it into a bevel instead. (equals ~29 deg corner) +#define IM_POLYLINE_CONVEX_POLY_MAX_BEVELS (2) // How many bevels there can be in a convex polygon. (int)(360/floor(180-29)) // In debug builds the functions are likely not inlined, so inlined check is cheaper. #if defined(DEBUG) || defined(_DEBUG) @@ -968,6 +968,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 } else { + // For no anti-alias we use fully opaque texture, and no fringe expansion. tex_uvs.x = _Data->TexUvWhitePixel.x; tex_uvs.y = _Data->TexUvWhitePixel.y; tex_uvs.z = _Data->TexUvWhitePixel.x; @@ -1012,7 +1013,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 PrimReserve(idx_count, vtx_count); const float half_texel = 0.5f * _Data->FontAtlas->TexUvScale.x; - const float ratio = thickness0 / (thickness0 + thickness1); // The points using uv2 are placed on the path, calculate the position from stroke offets. + const float ratio = thickness0 / (thickness0 + thickness1); // The points that use uv2 are placed directly on the path (offset = 0), calculate the texture position from stroke offsets. ImVec2 uv0, uv1, uv2; uv0.x = tex_uvs.x + half_texel; uv0.y = tex_uvs.y; @@ -1603,12 +1604,11 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun _VtxWritePtr += points_count; _VtxCurrentIdx += points_count; - unsigned int prev_outer_idx = 0; // We don't know outer vert could yet, will need to patch once we're done. + unsigned int prev_outer_idx = 0; // This should be the index of the last vertex, but we don't know it yet. Will need to patch once we're done. if (miters_only) { for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) { - // Average normals const ImVec2 p1 = points[i1]; const ImVec2 n0 = temp_normals[i0]; const ImVec2 n1 = temp_normals[i1]; @@ -1645,7 +1645,6 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun int bevel_count = 0; for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) { - // Average normals const ImVec2 p1 = points[i1]; const ImVec2 n0 = temp_normals[i0]; const ImVec2 n1 = temp_normals[i1]; @@ -2204,6 +2203,7 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t } else { + // For no anti-alias we use fully opaque texture, and no fringe expansion. tex_uvs.x = _Data->TexUvWhitePixel.x; tex_uvs.y = _Data->TexUvWhitePixel.y; tex_uvs.z = _Data->TexUvWhitePixel.x; @@ -2555,7 +2555,7 @@ void ImDrawList::_AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 c PrimUnreserve(idx_allocated - idx_used, vtx_allocated - vtx_used); } -// Draws rounded rectangle where thickness/2 > rounding. If rendered using the regular polyline, the stroke will fold and leave artefact on the corner if rendered with transparency. +// Draws rounded rectangle where thickness > rounding. If rendered using the regular polyline, the stroke will fold and leave artifacts on the corners if rendered with transparency. // The stroke is positioned inside the rectangle. void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags) { @@ -2577,6 +2577,7 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, } else { + // For no anti-alias we use fully opaque texture, and no fringe expansion. tex_uvs.x = _Data->TexUvWhitePixel.x; tex_uvs.y = _Data->TexUvWhitePixel.y; tex_uvs.z = _Data->TexUvWhitePixel.x; @@ -2820,7 +2821,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl if (thickness + (0.75f * _FringeScale) > outer_rounding) { // Special case rendering to avoid rendering artifacts at the corners. - // If rendered using the regular polyline, the stroke will fold and leave artifact on the corner if rendered with transparency. + // If rendered using the regular polyline, the stroke will fold and leave artifacts on the corners if rendered with transparency. _AddRectTinyRounding(outer_min, outer_max, col, outer_rounding, thickness, flags); return; } From 230249ad58def4695aa94166cff62a730d8b5c12 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 5 Jun 2026 16:40:11 +0200 Subject: [PATCH 072/120] ImFontAtlas: moved TexUv buffers and configurable defines to internals. --- imgui.h | 21 +-------------------- imgui_draw.cpp | 12 ++++++------ imgui_internal.h | 31 ++++++++++++++++++++++++++----- 3 files changed, 33 insertions(+), 31 deletions(-) diff --git a/imgui.h b/imgui.h index d38f58e48..2e372f373 100644 --- a/imgui.h +++ b/imgui.h @@ -3202,23 +3202,6 @@ struct ImGuiSelectionExternalStorage // Hold a series of drawing commands. The user provides a renderer for ImDrawData which essentially contains an array of ImDrawList. //----------------------------------------------------------------------------- -#ifndef IM_DRAWLIST_TEX_LINES_WIDTH_MAX -#define IM_DRAWLIST_TEX_LINES_WIDTH_MAX (32) // The maximum line width to bake anti-aliased textures for. Build atlas with ImFontAtlasFlags_NoBakedLines to disable baking. -#endif -#ifndef IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH -#define IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH (4) // Calculate detailed textures for width [1..IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH] -#endif -#ifndef IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT -#define IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT (4) // How many samples per integer thickness level. -#endif -#ifndef IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX -#define IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX (16) -#endif -#ifndef IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX -#define IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX (4) // 0: fill, 1-3: strokes thickness -#endif -#define IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX (IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT) - // ImDrawIdx: vertex index. [Compile-time configurable type] // - To use 16-bit indices + allow large meshes: backend need to set 'io.BackendFlags |= ImGuiBackendFlags_RendererHasVtxOffset' and handle ImDrawCmd::VtxOffset (recommended). // - To use 32-bit indices: override with '#define ImDrawIdx unsigned int' in your imconfig.h file. @@ -3339,7 +3322,7 @@ enum ImDrawFlags_ // Stroke position relative to the shape outline ImDrawFlags_StrokeInside = 1 << 16, // Draw stroke inside of the shape outline (default for closed shapes and AddLineH, AddLineV functions) ImDrawFlags_StrokeCenter = 2 << 16, // Draw stroke at the center of the shape outline (default for paths, bezier, and AddLine functions) - ImDrawFlags_StrokeCenterBiased = 3 << 16, // Draw stroke at the center of the shape outline, so that half thickness (rounded down) will be outside, and rest inside the shape outline. Does not animate well! + ImDrawFlags_StrokeCenterBiased = 3 << 16, // Draw stroke at the center of the shape outline, so that half thickness rounded down will be outside, and rest inside the shape outline. Useful for axis-aligned shapes: AddLineH, AddLineV, AddRect. Does not animate well! ImDrawFlags_StrokeOutside = 4 << 16, // Draw stroke outside of the shape outline ImDrawFlags_StrokeLegacy = 5 << 16, // Use legacy positioning + enable MiterOnly and NoAAEnds flags. ImDrawFlags_StrokeMask_ = 0x07 << 16, @@ -3906,8 +3889,6 @@ struct ImFontAtlas ImVec2 TexUvWhitePixel; // Texture coordinates to a white pixel. May change as new texture gets created. ImVector Fonts; // Hold all the fonts returned by AddFont*. Fonts[0] is the default font upon calling ImGui::NewFrame(), use ImGui::PushFont()/PopFont() to change the current font. ImVector Sources; // Source/configuration data - ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines - ImVec4 TexUvCorners[IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX * IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX]; // UVs for baked anti-aliased corners (0= fill, 1> stroke thickness) int TexNextUniqueID; // Next value to be stored in TexData->UniqueID int FontNextUniqueID; // Next value to be stored in ImFont->FontID ImVector DrawListSharedDatas; // List of users for this atlas. Typically one per Dear ImGui context. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index c8a4c4328..582d2f727 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -5326,7 +5326,7 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) // Refresh UV coordinates ImVec2 uv0 = ImVec2((float)(x + 1), (float)(y + 1)) * atlas->TexUvScale; ImVec2 uv1 = ImVec2((float)(x + 1 + s), (float)(y + 1 + s)) * atlas->TexUvScale; - atlas->TexUvCorners[n] = ImVec4(uv0.x, uv0.y, uv1.x, uv1.y); + builder->TexUvCorners[n] = ImVec4(uv0.x, uv0.y, uv1.x, uv1.y); x += w; row_height = ImMax(row_height, h); @@ -5380,7 +5380,7 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) // Refresh UV coordinates ImVec2 uv0 = ImVec2((float)(x + 1), (float)(y + 1)) * atlas->TexUvScale; ImVec2 uv1 = ImVec2((float)(x + 1 + s), (float)(y + 1 + s)) * atlas->TexUvScale; - atlas->TexUvCorners[thickness * IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX + n] = ImVec4(uv0.x, uv0.y, uv1.x, uv1.y); + builder->TexUvCorners[thickness * IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX + n] = ImVec4(uv0.x, uv0.y, uv1.x, uv1.y); x += w; row_height = ImMax(row_height, h); @@ -5450,7 +5450,7 @@ static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) ImVec2 uv0 = ImVec2((float)(r.x + pad_left - 1), (float)(r.y + y)) * atlas->TexUvScale; ImVec2 uv1 = ImVec2((float)(r.x + pad_left + line_width + 1), (float)(r.y + y + 1)) * atlas->TexUvScale; float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts - atlas->TexUvLines[n] = ImVec4(uv0.x, half_v, uv1.x, half_v); + builder->TexUvLines[n] = ImVec4(uv0.x, half_v, uv1.x, half_v); } } @@ -5517,7 +5517,7 @@ static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) ImVec2 uv0 = ImVec2((float)(r.x), (float)(r.y + y)) * atlas->TexUvScale; ImVec2 uv1 = ImVec2((float)(r.x + IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT * 2 + line_width), (float)(r.y + y + 1)) * atlas->TexUvScale; float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts - atlas->TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + n] = ImVec4(uv0.x, half_v, uv1.x, half_v); + builder->TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + n] = ImVec4(uv0.x, half_v, uv1.x, half_v); } } } @@ -5892,8 +5892,8 @@ void ImFontAtlasUpdateDrawListsSharedData(ImFontAtlas* atlas) if (shared_data->FontAtlas == atlas) { shared_data->TexUvWhitePixel = atlas->TexUvWhitePixel; - shared_data->TexUvLines = atlas->TexUvLines; - shared_data->TexUvCorners = atlas->TexUvCorners; + shared_data->TexUvLines = atlas->Builder->TexUvLines; + shared_data->TexUvCorners = atlas->Builder->TexUvCorners; } } diff --git a/imgui_internal.h b/imgui_internal.h index 28251df79..38e475087 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -917,11 +917,28 @@ IMGUI_API ImGuiStoragePair* ImLowerBound(ImGuiStoragePair* in_begin, ImGuiStorag #define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_R(_N,_MAXERROR) ((_MAXERROR) / (1 - ImCos(IM_PI / ImMax((float)(_N), IM_PI)))) #define IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC_ERROR(_N,_RAD) ((1 - ImCos(IM_PI / ImMax((float)(_N), IM_PI))) / (_RAD)) -// ImDrawList: Lookup table size for adaptive arc drawing, cover full circle. +// ImDrawList: Lookup tables #ifndef IM_DRAWLIST_ARCFAST_TABLE_SIZE -#define IM_DRAWLIST_ARCFAST_TABLE_SIZE 48 // Number of samples in lookup table. +#define IM_DRAWLIST_ARCFAST_TABLE_SIZE (48) // Number of samples in adaptive arc drawing lookup table. #endif -#define IM_DRAWLIST_ARCFAST_SAMPLE_MAX IM_DRAWLIST_ARCFAST_TABLE_SIZE // Sample index _PathArcToFastEx() for 360 angle. +#define IM_DRAWLIST_ARCFAST_SAMPLE_MAX IM_DRAWLIST_ARCFAST_TABLE_SIZE // Sample index _PathArcToFastEx() for 360 angle. + +#ifndef IM_DRAWLIST_TEX_LINES_WIDTH_MAX +#define IM_DRAWLIST_TEX_LINES_WIDTH_MAX (32) // The maximum line width to bake anti-aliased textures for. Build atlas with ImFontAtlasFlags_NoBakedLines to disable baking. +#endif +#ifndef IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH +#define IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH (4) // Calculate detailed textures for width [1..IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH] +#endif +#ifndef IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT +#define IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT (4) // How many samples per integer thickness level. +#endif +#ifndef IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX +#define IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX (16) +#endif +#ifndef IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX +#define IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX (4) // 0: fill, 1-3: strokes thickness +#endif +#define IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX (IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT) // Data shared between all ImDrawList instances // Conceptually this could have been called e.g. ImDrawListSharedContext @@ -930,8 +947,8 @@ IMGUI_API ImGuiStoragePair* ImLowerBound(ImGuiStoragePair* in_begin, ImGuiStorag struct IMGUI_API ImDrawListSharedData { ImVec2 TexUvWhitePixel; // UV of white pixel in the atlas (== FontAtlas->TexUvWhitePixel) - const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas (== FontAtlas->TexUvLines) - const ImVec4* TexUvCorners; // UV of rounded corner (== FontAtlas->TexUvCorners) + const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas (== FontAtlas->Builder->TexUvLines) + const ImVec4* TexUvCorners; // UV of rounded corner (== FontAtlas->Builder->TexUvCorners) ImFontAtlas* FontAtlas; // Current font atlas ImFont* Font; // Current font (used for simplified AddText overload) float FontSize; // Current font size (used for simplified AddText overload) @@ -4070,6 +4087,10 @@ struct ImFontAtlasBuilder ImFontAtlasRectId PackIdLineFractTexData; ImFontAtlasRectId PackIdCornersTexData; + // Cached UV coordinates + ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines + ImVec4 TexUvCorners[IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX * IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX]; // UVs for baked anti-aliased corners (0= fill, 1> stroke thickness) + ImFontAtlasBuilder() { memset((void*)this, 0, sizeof(*this)); FrameCount = -1; RectsIndexFreeListStart = -1; PackIdMouseCursors = PackIdLinesTexData = PackIdLineFractTexData = PackIdCornersTexData = -1; } }; From d9378b71c55eb30190d08b9f2a0e38d7180106cf Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 5 Jun 2026 18:37:52 +0200 Subject: [PATCH 073/120] DrawList: surround _GetStrokePos() in IM_MSVC_RUNTIME_CHECKS_OFF macro. --- imgui_draw.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 582d2f727..ee7bc62c9 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2166,12 +2166,14 @@ void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDr // We intently don't turn g_LEGACY_STROKES into ImDrawFlags_StrokeLegacy here. // The earlier should use verbatim legacy code but will be removed before release once we confirm that both matches. +IM_MSVC_RUNTIME_CHECKS_OFF ImDrawFlags ImDrawList::_GetStrokePos(ImDrawFlags flags, ImDrawFlags default_stroke_pos) { if (flags & ImDrawFlags_StrokeMask_) return (flags & ImDrawFlags_StrokeMask_); return _Data->OverrideStrokePos ? _Data->OverrideStrokePos : default_stroke_pos; } +IM_MSVC_RUNTIME_CHECKS_RESTORE void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness, ImDrawFlags flags) { From c5c76dcda5ae201630ee1f84f8996dbf58123108 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Mon, 15 Jun 2026 21:00:20 +0300 Subject: [PATCH 074/120] DrawList: Optimize _SelectFringeTexture() - store inverse line thickness in the spare texture uv component (same v was stord twice) to avoid division in _SelectFringeTexture --- imgui_draw.cpp | 89 ++++++++++++++++++++++++++------------------------ 1 file changed, 47 insertions(+), 42 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index ee7bc62c9..d9246462f 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -879,29 +879,30 @@ static ImU32 ImAlphaMultiply(ImU32 col, float alpha_mul) return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT); } +// Select texture and adjust fringe size for specified line thickness. void ImDrawList::_SelectFringeTexture(float screen_thickness, ImVec4* out_tex_uvs, float* out_fringe) { - if (screen_thickness <= IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH) + // We assume that the screen_thickness has been clamped to 1.0 by the caller. + IM_ASSERT_PARANOID(screen_thickness >= 1.0f); + + ImVec4 tex_uvs; + if (screen_thickness <= IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH) IM_LIKELY { - // Handle the thickness between [1..IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH]. + // Handle the thickness between 1.0 - IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH]. // We use super sampled textures in this range to make texture changes less noticeable. // There are IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX+1 textures, where 0 maps to 1.0 and IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX maps to IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH (4.0). - constexpr float base_width = 1.0f; - int texture_idx = (int)((screen_thickness - base_width) * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + 0.5f); - texture_idx = ImMax(texture_idx, 0); - texture_idx = ImMin(texture_idx, IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX); - const float tex_width = base_width + (float)texture_idx / IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT; - *out_fringe = _FringeScale * (screen_thickness / tex_width); // Scale the fringe to cover the discrepancy between the texture and requested size. - *out_tex_uvs = _Data->TexUvLines[(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1) + texture_idx]; + int texture_idx = (int)((screen_thickness - 1.0f) * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + 0.5f); + tex_uvs = _Data->TexUvLines[(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1) + texture_idx]; } else { - int texture_idx = (int)(screen_thickness + 0.5f); - texture_idx = ImMax(texture_idx, 2); - texture_idx = ImMin(texture_idx, IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1); - *out_fringe = _FringeScale * (screen_thickness / (float)texture_idx); // Scale the fringe to cover the discrepancy between the texture and requested size. - *out_tex_uvs = _Data->TexUvLines[texture_idx]; + const int texture_idx = ImMin((int)(screen_thickness + 0.5f), IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1); + tex_uvs = _Data->TexUvLines[texture_idx]; } + + *out_tex_uvs = tex_uvs; + // Scale the fringe so that the texture matches the line thickness. + *out_fringe = _FringeScale * screen_thickness * tex_uvs.w; // tex_uvs.w = 1 / thickness } IM_MSVC_RUNTIME_CHECKS_RESTORE @@ -969,10 +970,10 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 else { // For no anti-alias we use fully opaque texture, and no fringe expansion. - tex_uvs.x = _Data->TexUvWhitePixel.x; - tex_uvs.y = _Data->TexUvWhitePixel.y; - tex_uvs.z = _Data->TexUvWhitePixel.x; - tex_uvs.w = _Data->TexUvWhitePixel.y; + tex_uvs.x = _Data->TexUvWhitePixel.x; // u0 + tex_uvs.y = _Data->TexUvWhitePixel.x; // u1 + tex_uvs.z = _Data->TexUvWhitePixel.y; // v + tex_uvs.w = 0.0f; // 1/thickness, unused. fringe = 0.0f; flags |= ImDrawFlags_NoAAEnds; } @@ -1016,11 +1017,11 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 const float ratio = thickness0 / (thickness0 + thickness1); // The points that use uv2 are placed directly on the path (offset = 0), calculate the texture position from stroke offsets. ImVec2 uv0, uv1, uv2; uv0.x = tex_uvs.x + half_texel; - uv0.y = tex_uvs.y; - uv1.x = tex_uvs.z - half_texel; - uv1.y = tex_uvs.y; + uv0.y = tex_uvs.z; + uv1.x = tex_uvs.y - half_texel; + uv1.y = tex_uvs.z; uv2.x = uv0.x + (uv1.x - uv0.x) * ratio; - uv2.y = tex_uvs.y; + uv2.y = tex_uvs.z; const float half_aa = _FringeScale * 0.5f; // Used for end caps, does not use "fringe" since end cap AA is not using the texture. const float half_thickness = thickness * 0.5f; @@ -1424,8 +1425,8 @@ void ImDrawList::AddPolylineLegacy(const ImVec2* points, const int points_count, tex_uvs.z = tex_uvs.z + (tex_uvs_1.z - tex_uvs.z) * fractional_thickness; tex_uvs.w = tex_uvs.w + (tex_uvs_1.w - tex_uvs.w) * fractional_thickness; }*/ - ImVec2 tex_uv0(tex_uvs.x, tex_uvs.y); - ImVec2 tex_uv1(tex_uvs.z, tex_uvs.w); + ImVec2 tex_uv0(tex_uvs.x, tex_uvs.z); + ImVec2 tex_uv1(tex_uvs.y, tex_uvs.z); for (int i = 0; i < points_count; i++) { _VtxWritePtr[0].pos = temp_points[i * 2 + 0]; _VtxWritePtr[0].uv = tex_uv0; _VtxWritePtr[0].col = col; // Left-side outer edge @@ -2206,10 +2207,10 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t else { // For no anti-alias we use fully opaque texture, and no fringe expansion. - tex_uvs.x = _Data->TexUvWhitePixel.x; - tex_uvs.y = _Data->TexUvWhitePixel.y; - tex_uvs.z = _Data->TexUvWhitePixel.x; - tex_uvs.w = _Data->TexUvWhitePixel.y; + tex_uvs.x = _Data->TexUvWhitePixel.x; // u0 + tex_uvs.y = _Data->TexUvWhitePixel.x; // u1 + tex_uvs.z = _Data->TexUvWhitePixel.y; // v + tex_uvs.w = 0.0f; // 1/thickness, unused. fringe = 0.0f; flags |= ImDrawFlags_NoAAEnds; } @@ -2243,11 +2244,11 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t const float ratio = thickness0 / (thickness0 + thickness1); // The points using uv2 are placed on the path, calculate the position from stroke offets. ImVec2 uv0, uv1, uv2; uv0.x = tex_uvs.x + half_texel; - uv0.y = tex_uvs.y; - uv1.x = tex_uvs.z - half_texel; - uv1.y = tex_uvs.y; + uv0.y = tex_uvs.z; + uv1.x = tex_uvs.y - half_texel; + uv1.y = tex_uvs.z; uv2.x = uv0.x + (uv1.x - uv0.x) * ratio; - uv2.y = tex_uvs.y; + uv2.y = tex_uvs.z; const float half_aa = _FringeScale * 0.5f; // Used for end caps, does not use "fringe" since end cap AA is not using the texture. const float half_thickness = thickness * 0.5f; @@ -2580,10 +2581,10 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, else { // For no anti-alias we use fully opaque texture, and no fringe expansion. - tex_uvs.x = _Data->TexUvWhitePixel.x; - tex_uvs.y = _Data->TexUvWhitePixel.y; - tex_uvs.z = _Data->TexUvWhitePixel.x; - tex_uvs.w = _Data->TexUvWhitePixel.y; + tex_uvs.x = _Data->TexUvWhitePixel.x; // u0 + tex_uvs.y = _Data->TexUvWhitePixel.x; // u1 + tex_uvs.z = _Data->TexUvWhitePixel.y; // v + tex_uvs.w = 0.0f; // 1/thickness, unused. fringe = 0.0f; } @@ -2617,11 +2618,11 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, const float ratio = stem_offset / thickness; ImVec2 inner_uv, outer_uv, stem_uv; outer_uv.x = tex_uvs.x + half_texel; - outer_uv.y = tex_uvs.y; - inner_uv.x = tex_uvs.z - half_texel; - inner_uv.y = tex_uvs.y; + outer_uv.y = tex_uvs.z; + inner_uv.x = tex_uvs.y - half_texel; + inner_uv.y = tex_uvs.z; stem_uv.x = outer_uv.x + (inner_uv.x - outer_uv.x) * ratio; - stem_uv.y = tex_uvs.y; + stem_uv.y = tex_uvs.z; // Previous vertices, copied from the last vertices. int base_idx = (int)_VtxCurrentIdx; @@ -5452,7 +5453,9 @@ static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) ImVec2 uv0 = ImVec2((float)(r.x + pad_left - 1), (float)(r.y + y)) * atlas->TexUvScale; ImVec2 uv1 = ImVec2((float)(r.x + pad_left + line_width + 1), (float)(r.y + y + 1)) * atlas->TexUvScale; float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts - builder->TexUvLines[n] = ImVec4(uv0.x, half_v, uv1.x, half_v); + // Store inverse of the line-width (thickness) to the w component so that we can avoid division when selecting texture. + float inv_line_width = line_width > 0.f ? 1.0f / line_width : 0.f; + builder->TexUvLines[n] = ImVec4(uv0.x, uv1.x, half_v, inv_line_width); } } @@ -5519,7 +5522,9 @@ static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) ImVec2 uv0 = ImVec2((float)(r.x), (float)(r.y + y)) * atlas->TexUvScale; ImVec2 uv1 = ImVec2((float)(r.x + IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT * 2 + line_width), (float)(r.y + y + 1)) * atlas->TexUvScale; float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts - builder->TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + n] = ImVec4(uv0.x, half_v, uv1.x, half_v); + // Store inverse of the line-width (thickness) to the w component so that we can avoid division when selecting texture. + float inv_line_width = 1.0f / (1.0f + (float)n / (float)IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT); + builder->TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + n] = ImVec4(uv0.x, uv1.x, half_v, inv_line_width); } } } From 145b838818b2282213ba20312d9ca0c08c536137 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Mon, 15 Jun 2026 21:07:08 +0300 Subject: [PATCH 075/120] DrawList: Optimize AddRectFilled() - detect and draw non-rounded non-AA rect with less branches and calculations - improves new AddRectFilled() overhead from 30% to 5-10% --- imgui_draw.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index d9246462f..af60ae5bb 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2917,17 +2917,27 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c return; } - if ((flags & ImDrawFlags_RoundCornersMask_) == 0) - flags |= ImDrawFlags_RoundCornersAll; - + const bool has_rounding = (flags & ImDrawFlags_RoundCornersMask_) != ImDrawFlags_RoundCornersNone && IM_HAS_ROUNDING(rounding); const bool is_truncated = IM_IS_TRUNCATED4(p_min.x, p_min.y, p_max.x, p_max.y) && IM_IS_TRUNCATED(rounding); + if (_FringeScaleIsInteger && is_truncated) { + // Fast path for the non-AA non-rounded rect. + if (!has_rounding) + { + PrimReserve(6, 4); + PrimRect(p_min, p_max, col); + return; + } + + if ((flags & ImDrawFlags_RoundCornersMask_) == 0) + flags |= ImDrawFlags_RoundCornersAll; + rounding = ImMin(rounding, ImFabs(width) * (((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f) - 1.0f); rounding = ImMin(rounding, ImFabs(height) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f); const int s_rounding = (int)(rounding * _InvFringeScale); - if (s_rounding <= 0 || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) + if (s_rounding <= 0) { PrimReserve(6, 4); PrimRect(p_min, p_max, col); @@ -2947,8 +2957,7 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c } } - const bool has_rounding = (rounding >= _FringeScale * 0.5f); - if (!has_rounding || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) + if (!has_rounding) { const ImVec2 points[4] = { p_min, ImVec2(p_max.x, p_min.y), p_max, ImVec2(p_min.x, p_max.y) }; AddConvexPolyFilled(points, 4, col, ImDrawFlags_MiterOnly); From 10aa5d92e6ce4f317ff6659b685260fe64db2715 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Mon, 15 Jun 2026 22:23:38 +0300 Subject: [PATCH 076/120] DrawList: Optimize AddNgon() - test overestimate before testing more expensive details when testing if stroked ngon is actualy filled - Note: not a common function, but the test harness is using this to draw a triangle, which makes it show up in profiling --- imgui_draw.cpp | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index af60ae5bb..ab0292ed2 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -3125,23 +3125,27 @@ void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_ radius = ImMax(0.01f, radius); - const float unit_apothem = ImCos(IM_PI / (float)num_segments); - const float miter_thickness = thickness / unit_apothem; - - float outer_radius = radius; - if (stroke_pos == ImDrawFlags_StrokeCenter) - outer_radius = radius + miter_thickness * 0.5f; - else if (stroke_pos == ImDrawFlags_StrokeCenterBiased) - outer_radius = radius + _CalculateCenterBiasedOffset(miter_thickness); - else if (stroke_pos == ImDrawFlags_StrokeInside) - outer_radius = radius; - else if (stroke_pos == ImDrawFlags_StrokeOutside) - outer_radius = radius + miter_thickness; - if (outer_radius < (miter_thickness + _FringeScale)) IM_UNLIKELY + // Check overestimate first before testing details. + if (radius < (thickness * 2.f + 1.f)) { - // The polygon has collapsed into a filled polygon. - AddNgonFilled(center, outer_radius, col, num_segments); - return; + const float unit_apothem = ImCos(IM_PI / (float)num_segments); + const float miter_thickness = thickness / unit_apothem; + + float outer_radius = radius; + if (stroke_pos == ImDrawFlags_StrokeCenter) + outer_radius = radius + miter_thickness * 0.5f; + else if (stroke_pos == ImDrawFlags_StrokeCenterBiased) + outer_radius = radius + _CalculateCenterBiasedOffset(miter_thickness); + else if (stroke_pos == ImDrawFlags_StrokeInside) + outer_radius = radius; + else if (stroke_pos == ImDrawFlags_StrokeOutside) + outer_radius = radius + miter_thickness; + if (outer_radius < (miter_thickness + _FringeScale)) IM_UNLIKELY + { + // The polygon has collapsed into a filled polygon. + AddNgonFilled(center, outer_radius, col, num_segments); + return; + } } // Because we are filling a closed shape we remove 1 from the count of segments/points From 9b608ba0798c10dffbdc540e77affc07081725af Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 27 Jul 2026 19:35:30 +0200 Subject: [PATCH 077/120] DrawList: fixed warnings, added macro to facilitate visualizing rounded corner texture usage. --- imgui_draw.cpp | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index ab0292ed2..5c56995c5 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2758,7 +2758,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl outer_rounding = ImMin(outer_rounding, ImFabs(height) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f); } - const bool is_truncated = IM_IS_TRUNCATED4(outer_min.x, outer_min.y, outer_max.x, outer_max.y) && IM_IS_TRUNCATED4(outer_rounding, thickness, 0.0f, 0.0f); + const bool is_truncated = IM_IS_TRUNCATED4(outer_min.x, outer_min.y, outer_max.x, outer_max.y) && IM_IS_TRUNCATED4(outer_rounding, thickness, 0.0f, 0.0f); //-V501 if (_FringeScaleIsInteger && is_truncated) { int s_rounding = (int)(outer_rounding * _InvFringeScale); @@ -3126,7 +3126,7 @@ void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_ radius = ImMax(0.01f, radius); // Check overestimate first before testing details. - if (radius < (thickness * 2.f + 1.f)) + if (radius < (thickness * 2.0f + 1.0f)) { const float unit_apothem = ImCos(IM_PI / (float)num_segments); const float miter_thickness = thickness / unit_apothem; @@ -5245,6 +5245,14 @@ static ImU8 SampleCornerStroke(float x, float y, float thickness, const ImVec2* return (ImU8)(ImClamp(ImMin(inner, outer), 0.0f, 1.0f) * 255.0f); } +#if 0 // Set to 1 to visualize coverage of textured corners +#define DEBUG_TEX_CORNER_U8(_LX, _H) ((_LX & 1) ? 255 : 0) +#define DEBUG_TEX_CORNER_U32(_LX, _H) IM_COL32((_LX & 1) ? 255: 0, 0, 0, (_LX & 1) ? 200 : 0) +#else +#define DEBUG_TEX_CORNER_U8(_LX, _H) 0xFF +#define DEBUG_TEX_CORNER_U32(_LX, _H) 0xFFFFFFFF +#endif + static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) { if (atlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners) @@ -5323,7 +5331,7 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) for (int ly = 0; ly < h; ly++) { for (int lx = 0; lx < h; lx++) - write_ptr[lx] = SampleCorner((float)lx + 0.5f, (float)ly + 0.5f, line_normals, line_distances, num_lines); + write_ptr[lx] = SampleCorner((float)lx + 0.5f, (float)ly + 0.5f, line_normals, line_distances, num_lines) & DEBUG_TEX_CORNER_U8(lx, h); write_ptr += pitch; } } @@ -5334,7 +5342,7 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) for (int ly = 0; ly < h; ly++) { for (int lx = 0; lx < h; lx++) - write_ptr[lx] = IM_COL32(255, 255, 255, SampleCorner((float)lx + 0.5f, (float)ly + 0.5f, line_normals, line_distances, num_lines)); + write_ptr[lx] = IM_COL32(255, 255, 255, SampleCorner((float)lx + 0.5f, (float)ly + 0.5f, line_normals, line_distances, num_lines)) & DEBUG_TEX_CORNER_U32(lx, h); write_ptr += pitch; } } @@ -5377,7 +5385,7 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) for (int ly = 0; ly < h; ly++) { for (int lx = 0; lx < h; lx++) - write_ptr[lx] = SampleCornerStroke((float)lx + 0.5f, (float)ly + 0.5f, (float)thickness, line_normals, line_distances, num_lines); + write_ptr[lx] = SampleCornerStroke((float)lx + 0.5f, (float)ly + 0.5f, (float)thickness, line_normals, line_distances, num_lines) & DEBUG_TEX_CORNER_U8(lx, h); write_ptr += pitch; } } @@ -5388,7 +5396,7 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) for (int ly = 0; ly < h; ly++) { for (int lx = 0; lx < h; lx++) - write_ptr[lx] = IM_COL32(255, 255, 255, SampleCornerStroke((float)lx + 0.5f, (float)ly + 0.5f, (float)thickness, line_normals, line_distances, num_lines)); + write_ptr[lx] = IM_COL32(255, 255, 255, SampleCornerStroke((float)lx + 0.5f, (float)ly + 0.5f, (float)thickness, line_normals, line_distances, num_lines)) & DEBUG_TEX_CORNER_U32(lx, h); write_ptr += pitch; } } @@ -5467,7 +5475,7 @@ static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) ImVec2 uv1 = ImVec2((float)(r.x + pad_left + line_width + 1), (float)(r.y + y + 1)) * atlas->TexUvScale; float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts // Store inverse of the line-width (thickness) to the w component so that we can avoid division when selecting texture. - float inv_line_width = line_width > 0.f ? 1.0f / line_width : 0.f; + float inv_line_width = (line_width > 0) ? (1.0f / (float)line_width) : 0.0f; builder->TexUvLines[n] = ImVec4(uv0.x, uv1.x, half_v, inv_line_width); } } From eb75c8056edec6cb3deb0c66286e5ef63126abf0 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 27 Jul 2026 19:57:03 +0200 Subject: [PATCH 078/120] DrawList: inverted ImDrawFlags_NoAAEnds into ImDrawFlags_AAEnds. --- docs/CHANGELOG.txt | 6 +++--- imgui.h | 4 ++-- imgui_demo.cpp | 4 ++-- imgui_draw.cpp | 16 ++++++++-------- 4 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 8720d1179..f7eae62f1 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -77,7 +77,7 @@ HOW TO UPDATE? 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, y1}, {x2, y2}, col, thickness, ImDrawFlags_StrokeLegacy); // Offset by +0.5f + disable AA ends. - == AddLine({x1 + 0.5f, y1 + 0.5f}, {x2+ 0.5f, y2 + 0.5f}, col, thickness, ImDrawFlags_NoAAEnds); // Same + == AddLine({x1 + 0.5f, y1 + 0.5f}, {x2+ 0.5f, y2 + 0.5f}, col, thickness); // Same This reapplies the old offset, and should get you exactly the same result you previously got for thickness=1.0f lines. But said result was sometimes ambiguous and renderer dependent. (#3116, #3258, #2441) - Old AddLine() did not have anti-aliased ends, which could create gaps when rendering shapes out @@ -93,8 +93,8 @@ HOW TO UPDATE? but the artifacts should be more localized. The new implementation attempted to favor simple implementation and robustness over the corner case accuracy. - Added anti-aliasing ends, most noticeable for thick lines. - - Added `ImDrawFlags_NoAAEnds` flag to disable anti-aliased line ends (slightly faster - and reduce vertex/index data). + - Added `ImDrawFlags_AAEnds` flag to enable anti-aliased line ends. Enabling AA Ends by + default would be more "correct", but it is slower and increase vertex/index data. - Added `ImDrawFlags_MiterOnly` flag to disable using beveled corner (closer to legacy rendering, slightly faster, but corners sharpers than 90 degrees may expand far out). Automatically used by AddRect(), AddCircle(), AddNgon(), AddEllipse() etc functions diff --git a/imgui.h b/imgui.h index 2e372f373..83768b8fe 100644 --- a/imgui.h +++ b/imgui.h @@ -3317,14 +3317,14 @@ enum ImDrawFlags_ ImDrawFlags_Closed = 1 << 9, // PathStroke(), AddPolyline(): specify that shape should be closed. ImDrawFlags_MiterOnly = 1 << 10, // PathStroke(), AddPolyline(): use miter corners only. This assumes that the input polyline does not have corners sharper than 90 degrees. Slightly faster. ImDrawFlags_SquareCap = 1 << 11, // PathStroke(), AddPolyline(): use square cap line ends. - ImDrawFlags_NoAAEnds = 1 << 12, // PathStroke(), AddPolyline(): do not generate anti-aliased line ends. This can speed up single line rendering. + ImDrawFlags_AAEnds = 1 << 12, // PathStroke(), AddPolyline(): generate anti-aliased line ends. // Stroke position relative to the shape outline ImDrawFlags_StrokeInside = 1 << 16, // Draw stroke inside of the shape outline (default for closed shapes and AddLineH, AddLineV functions) ImDrawFlags_StrokeCenter = 2 << 16, // Draw stroke at the center of the shape outline (default for paths, bezier, and AddLine functions) ImDrawFlags_StrokeCenterBiased = 3 << 16, // Draw stroke at the center of the shape outline, so that half thickness rounded down will be outside, and rest inside the shape outline. Useful for axis-aligned shapes: AddLineH, AddLineV, AddRect. Does not animate well! ImDrawFlags_StrokeOutside = 4 << 16, // Draw stroke outside of the shape outline - ImDrawFlags_StrokeLegacy = 5 << 16, // Use legacy positioning + enable MiterOnly and NoAAEnds flags. + ImDrawFlags_StrokeLegacy = 5 << 16, // Use legacy positioning + enable MiterOnly, disable AAEnds flags. ImDrawFlags_StrokeMask_ = 0x07 << 16, ImDrawFlags_InvalidMask_ = ~0x7FFFFFF0, // == 0x8000000F, diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 7e52eaf34..f698984a6 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10430,7 +10430,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::ColorEdit4("Stroke Color", &colf_stroke.x); ImGui::ColorEdit4("Fill Color", &colf_fill.x); - static ImDrawFlags stroke_flags = ImDrawFlags_None; + static ImDrawFlags stroke_flags = ImDrawFlags_AAEnds; static ImDrawFlags other_flags = ImDrawFlags_None; ImGui::Text("Stroke Flags:"); ImGui::RadioButton("Default", &stroke_flags, ImDrawFlags_None); ImGui::SameLine(); @@ -10441,7 +10441,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::RadioButton("Legacy", &stroke_flags, ImDrawFlags_StrokeLegacy); ImGui::CheckboxFlags("MiterOnly", &other_flags, ImDrawFlags_MiterOnly); ImGui::SameLine(); ImGui::CheckboxFlags("SquareCap", &other_flags, ImDrawFlags_SquareCap); ImGui::SameLine(); - ImGui::CheckboxFlags("NoAAEnds", &other_flags, ImDrawFlags_NoAAEnds); + ImGui::CheckboxFlags("AAEnds", &other_flags, ImDrawFlags_AAEnds); ImGui::Spacing(); const ImDrawFlags flags = stroke_flags | other_flags; diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 5c56995c5..72cd27d54 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -975,14 +975,14 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 tex_uvs.z = _Data->TexUvWhitePixel.y; // v tex_uvs.w = 0.0f; // 1/thickness, unused. fringe = 0.0f; - flags |= ImDrawFlags_NoAAEnds; + flags &= ~ImDrawFlags_AAEnds; } const ImU32 col_trans = col & ~IM_COL32_A_MASK; const ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeCenter); if (stroke_pos == ImDrawFlags_StrokeLegacy) - flags |= ImDrawFlags_MiterOnly | ImDrawFlags_NoAAEnds; + flags = (flags | ImDrawFlags_MiterOnly) & ~ImDrawFlags_AAEnds; const bool closed = (flags & ImDrawFlags_Closed) != 0; const bool miters_only = (flags & ImDrawFlags_MiterOnly) != 0; @@ -1054,7 +1054,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 p1.y -= dir.y * half_thickness; } - if (flags & ImDrawFlags_NoAAEnds) + if ((flags & ImDrawFlags_AAEnds) == 0) { base_idx = (int)_VtxCurrentIdx; IM_APPEND_VTX(p1.x - n1.x * thickness0, p1.y - n1.y * thickness0, uv0, col); @@ -1240,7 +1240,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 p1.y += dir.y * half_thickness; } - if (flags & ImDrawFlags_NoAAEnds) + if ((flags & ImDrawFlags_AAEnds) == 0) { IM_APPEND_VTX(p1.x - n1.x * thickness0, p1.y - n1.y * thickness0, uv0, col); IM_APPEND_VTX(p1.x + n1.x * thickness1, p1.y + n1.y * thickness1, uv1, col); @@ -2212,7 +2212,7 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t tex_uvs.z = _Data->TexUvWhitePixel.y; // v tex_uvs.w = 0.0f; // 1/thickness, unused. fringe = 0.0f; - flags |= ImDrawFlags_NoAAEnds; + flags &= ~ImDrawFlags_AAEnds; } float dir_x = p2.x - p1.x; @@ -2228,7 +2228,7 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t const ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeCenter); if (stroke_pos == ImDrawFlags_StrokeLegacy) - flags |= ImDrawFlags_MiterOnly | ImDrawFlags_NoAAEnds; + flags = (flags | ImDrawFlags_MiterOnly) & ~ImDrawFlags_AAEnds; float thickness0 = (thickness + fringe) * 0.5f; // Center (or legacy) if (stroke_pos == ImDrawFlags_StrokeOutside) @@ -2253,7 +2253,7 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t const float half_aa = _FringeScale * 0.5f; // Used for end caps, does not use "fringe" since end cap AA is not using the texture. const float half_thickness = thickness * 0.5f; - if (flags & ImDrawFlags_NoAAEnds) + if ((flags & ImDrawFlags_AAEnds) == 0) PrimReserve(2*3, 4); else PrimReserve(6*3, 8); @@ -2274,7 +2274,7 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t } // Start cap - if (flags & ImDrawFlags_NoAAEnds) + if ((flags & ImDrawFlags_AAEnds) == 0) { base_idx = (int)_VtxCurrentIdx; IM_APPEND_VTX(p1_x - norm_x * thickness0, p1_y - norm_y * thickness0, uv0, col); From cf85b48e55f7d1d8a705779f069a0a8de3161e18 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 5 Jun 2026 19:09:14 +0200 Subject: [PATCH 079/120] DrawList: new ImDrawFlags API (yet unused in this commit) --- imgui.h | 86 +++++++++++++++++++++++++++++++++----------------- imgui_demo.cpp | 4 +-- imgui_draw.cpp | 16 +++++----- 3 files changed, 67 insertions(+), 39 deletions(-) diff --git a/imgui.h b/imgui.h index 83768b8fe..bbd576900 100644 --- a/imgui.h +++ b/imgui.h @@ -3295,39 +3295,67 @@ struct ImDrawListSplitter // Flags for ImDrawList functions enum ImDrawFlags_ { - ImDrawFlags_None = 0, - //ImDrawFlags_Closed = 1, // -> FLAG MOVED BELOW. Prior to 1.92.8 (May 2026): ImDrawFlags_Closed was guaranteed to be == (1<<0) == 1, for legacy compatibility reason. Hardcoded use of 1 or true should be replaced with 'ImDrawFlags_Closed'. + ImDrawFlags_None = 0, + //ImDrawFlags_Closed = 1, // -> FLAG MOVED BELOW. Prior to 1.92.8 (May 2026): ImDrawFlags_Closed was guaranteed to be == (1<<0) == 1, for legacy compatibility reason. Hardcoded use of 1 or true should be replaced with 'ImDrawFlags_Closed'. - // Rounding for AddRect(), AddRectFilled(), PathRect() - // - When not specified, we defaults to ImDrawFlags_RoundCornersAll! So you only need to use those flags if you want another configuration. - ImDrawFlags_RoundCornersTopLeft = 1 << 4, // Round top-left corner only (when rounding > 0.0f, we default to all corners). - ImDrawFlags_RoundCornersTopRight = 1 << 5, // Round top-right corner only (when rounding > 0.0f, we default to all corners). - ImDrawFlags_RoundCornersBottomLeft = 1 << 6, // Round bottom-left corner only (when rounding > 0.0f, we default to all corners). - ImDrawFlags_RoundCornersBottomRight = 1 << 7, // Round bottom-right corner only (when rounding > 0.0f, we default to all corners). - ImDrawFlags_RoundCornersNone = 1 << 8, // Disable rounding even if `float rounding > 0.0f`. This is NOT zero, NOT an implicit flag! - ImDrawFlags_RoundCornersAll = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight | ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight, // (Default!!) - ImDrawFlags_RoundCornersDefault_ = ImDrawFlags_RoundCornersAll, // Default to ALL corners if none of the _RoundCornersXX flags are specified! - ImDrawFlags_RoundCornersTop = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight, - ImDrawFlags_RoundCornersBottom = ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight, - ImDrawFlags_RoundCornersLeft = ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersTopLeft, - ImDrawFlags_RoundCornersRight = ImDrawFlags_RoundCornersBottomRight | ImDrawFlags_RoundCornersTopRight, - ImDrawFlags_RoundCornersMask_ = ImDrawFlags_RoundCornersAll | ImDrawFlags_RoundCornersNone, + // About usage of flags: + // - "Prim" column: 'OK' = flag can be used to configure an individual AddXXX() call. + // - "Scope" column: 'OK' = initialized by ImGui based on Style options (e.g. whether anti-aliased is enabled). + // OK(0)/OK(1) indicates whether this flag is set in the default ImGui Style settings. + // (flag will be possible to alter in a scope using PushDrawFlags()). - // Stroke options - ImDrawFlags_Closed = 1 << 9, // PathStroke(), AddPolyline(): specify that shape should be closed. - ImDrawFlags_MiterOnly = 1 << 10, // PathStroke(), AddPolyline(): use miter corners only. This assumes that the input polyline does not have corners sharper than 90 degrees. Slightly faster. - ImDrawFlags_SquareCap = 1 << 11, // PathStroke(), AddPolyline(): use square cap line ends. - ImDrawFlags_AAEnds = 1 << 12, // PathStroke(), AddPolyline(): generate anti-aliased line ends. + // - Rounding default to ImDrawFlags_RoundCornersAll when 'rounding > 0'. + // - So you only need to use the _RoundCorners flags if you want a special configuration (e.g. a rectangle with one rounded corner). + // Rounding for AddRectXXX(), PathRect() ------ // Prim/Scope? + ImDrawFlags_RoundCornersTopLeft = 1 << 4, // OK -- // Round top-left corner only (when 'rounding > 0.0f', we default to all corners). + ImDrawFlags_RoundCornersTopRight = 1 << 5, // OK -- // Round top-right corner only (when 'rounding > 0.0f', we default to all corners). + ImDrawFlags_RoundCornersBottomLeft = 1 << 6, // OK -- // Round bottom-left corner only (when 'rounding > 0.0f', we default to all corners). + ImDrawFlags_RoundCornersBottomRight = 1 << 7, // OK -- // Round bottom-right corner only (when 'rounding > 0.0f', we default to all corners). + ImDrawFlags_RoundCornersNone = 1 << 8, // OK -- // Disable rounding even when 'rounding > 0.0f'. This value is NOT zero, it is NOT an implicit flag! + ImDrawFlags_RoundCornersAll = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight | ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight, // (Default!!) + ImDrawFlags_RoundCornersTop = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight, + ImDrawFlags_RoundCornersBottom = ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight, + ImDrawFlags_RoundCornersLeft = ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersTopLeft, + ImDrawFlags_RoundCornersRight = ImDrawFlags_RoundCornersBottomRight | ImDrawFlags_RoundCornersTopRight, - // Stroke position relative to the shape outline - ImDrawFlags_StrokeInside = 1 << 16, // Draw stroke inside of the shape outline (default for closed shapes and AddLineH, AddLineV functions) - ImDrawFlags_StrokeCenter = 2 << 16, // Draw stroke at the center of the shape outline (default for paths, bezier, and AddLine functions) - ImDrawFlags_StrokeCenterBiased = 3 << 16, // Draw stroke at the center of the shape outline, so that half thickness rounded down will be outside, and rest inside the shape outline. Useful for axis-aligned shapes: AddLineH, AddLineV, AddRect. Does not animate well! - ImDrawFlags_StrokeOutside = 4 << 16, // Draw stroke outside of the shape outline - ImDrawFlags_StrokeLegacy = 5 << 16, // Use legacy positioning + enable MiterOnly, disable AAEnds flags. - ImDrawFlags_StrokeMask_ = 0x07 << 16, + // Stroke Options ----------------------------- // Prim/Scope? + ImDrawFlags_Closed = 1 << 9, // OK -- // PathStroke(), AddPolyline(): specify that shape should be closed. + ImDrawFlags_MiterOnly = 1 << 10, // OK -- // PathStroke(), AddPolyline(): use miter corners only. This assumes that the input polyline does not have corners sharper than 90 degrees. Slightly faster. + ImDrawFlags_SquareCap = 1 << 11, // OK -- // PathStroke(), AddPolyline(): use square cap line ends. - ImDrawFlags_InvalidMask_ = ~0x7FFFFFF0, // == 0x8000000F, + // About Stroke Ends: + // - Rendering is optimized for fast pixel-perfect UI, so line ends are not anti-aliased by default. It cheaper (we can emit less vertices). + // - For more free-form drawings, light graphs and markers, or when using thick strokes: you can turn them on using the ImDrawFlags_AALineEnds flag. + // - See 'Demo->Examples->Custom Rendering' to interactively toy with those flags. + // - 'OK*' indicates using this at the AddXXX() call site is unlikely: it would only makes sense if (1) the option is disabled in style/scope and (2) you want to forcefully enable it for a single primitives. Possible but unlikely! Only supported for functions taking flags inputs. + // Stroke/Fill Anti-aliasing ------------------ // Prim/Scope? + ImDrawFlags_AAFill = 1 << 12, // OK* OK(1) // Enable anti-aliasing for Filled shapes. + ImDrawFlags_AALines = 1 << 13, // OK* OK(1) // Enable anti-aliasing for Strokes (lines, borders). + ImDrawFlags_AALineEnds = 1 << 14, // OK OK(0) // Enable anti-aliasing for Strokes Ends. Useful on thick strokes or for precise continuity of multiple lines. A little more costly. + //ImDrawFlags_NoAA = 1 << 15, // OK -- // Disable anti-aliasing for a given primitive. + //ImDrawFlags_NoAALineEnds = 1 << 16, // OK -- // Disable anti-aliasing ends for a given primitive. + + // About Stroke Position: + // - Read https://github.com/ocornut/imgui/wiki/Draw-List + // - Read https://github.com/ocornut/imgui/wiki/Pixel-Perfect-Rendering + // Stroke Position relative to shape outline -- // Prim/Scope? + ImDrawFlags_StrokeInside = 1 << 17, // OK -- // Draw stroke inside of the shape outline (default for closed shapes and AddLineH, AddLineV functions) + ImDrawFlags_StrokeCenter = 2 << 17, // OK -- // Draw stroke at the center of the shape outline (default for paths, bezier, and AddLine functions) + ImDrawFlags_StrokeCenterBiased = 3 << 17, // OK -- // Draw stroke at the center of the shape outline, so that half thickness rounded down will be outside, and rest inside the shape outline. Useful for axis-aligned shapes: AddLineH, AddLineV, AddRect. Does not animate well! + ImDrawFlags_StrokeOutside = 4 << 17, // OK -- // Draw stroke outside of the shape outline + ImDrawFlags_StrokeLegacy = 7 << 17, // OK OK(0) // Use legacy positioning + enable MiterOnly + disable AALineEnds. Must be all bits set. + + // Other Options ------------------------------ // Prim/Scope? + ImDrawFlags_TextNoPixelSnap = 1 << 20, // OK OK(0) // Disable automatically snapping AddText() calls to pixel boundaries. + ImDrawFlags_UseTexForRoundCorners = 1 << 21, // -- OK(1) // Enable using textures instead of strokes to draw rounded corners/circles where possible (faster). Used by default unless 'ImFontAtlasFlags_NoBakedRoundCorners' is enabled in the font atlas. + ImDrawFlags_UseTexForStrokeLegacy = 1 << 22, // -- OK(1) // Enable anti-aliased lines/borders using textures when using legacy strokes. Require backend to render with bilinear filtering (NOT point/nearest filtering). Used by default unless 'ImFontAtlasFlags_NoBakedLines' is set in the font atlas. + ImDrawFlags_UseVtxOffset = 1 << 23, // -- OK(1) // Can emit 'VtxOffset > 0' to allow large meshes with 16-bit ImDrawIdx. Used by default when 'ImGuiBackendFlags_RendererHasVtxOffset' is enabled by the backend. + + // [Internal] + ImDrawFlags_AllowedInScope_ = ImDrawFlags_AAFill | ImDrawFlags_AALines | ImDrawFlags_AALineEnds | ImDrawFlags_StrokeLegacy | ImDrawFlags_TextNoPixelSnap | ImDrawFlags_UseTexForRoundCorners | ImDrawFlags_UseTexForStrokeLegacy | ImDrawFlags_UseVtxOffset, // [Internal] Values allowed in scope e.g. incoming PushDrawFlags() stack. + ImDrawFlags_RoundCornersMask_ = ImDrawFlags_RoundCornersAll | ImDrawFlags_RoundCornersNone, // [Internal] + ImDrawFlags_StrokeMask_ = 0x07 << 17, // [Internal] + ImDrawFlags_InvalidMask_ = ~0x7FFFFFF0, // [Internal] == 0x8000000F. Reserved to detect misuses. }; // Flags for ImDrawList instance. Those are set automatically by ImGui:: functions from ImGuiIO settings, and generally not manipulated directly. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index f698984a6..e7a326463 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10430,7 +10430,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::ColorEdit4("Stroke Color", &colf_stroke.x); ImGui::ColorEdit4("Fill Color", &colf_fill.x); - static ImDrawFlags stroke_flags = ImDrawFlags_AAEnds; + static ImDrawFlags stroke_flags = ImDrawFlags_AALineEnds; static ImDrawFlags other_flags = ImDrawFlags_None; ImGui::Text("Stroke Flags:"); ImGui::RadioButton("Default", &stroke_flags, ImDrawFlags_None); ImGui::SameLine(); @@ -10441,7 +10441,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::RadioButton("Legacy", &stroke_flags, ImDrawFlags_StrokeLegacy); ImGui::CheckboxFlags("MiterOnly", &other_flags, ImDrawFlags_MiterOnly); ImGui::SameLine(); ImGui::CheckboxFlags("SquareCap", &other_flags, ImDrawFlags_SquareCap); ImGui::SameLine(); - ImGui::CheckboxFlags("AAEnds", &other_flags, ImDrawFlags_AAEnds); + ImGui::CheckboxFlags("AALineEnds", &other_flags, ImDrawFlags_AALineEnds); ImGui::Spacing(); const ImDrawFlags flags = stroke_flags | other_flags; diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 72cd27d54..811bdd3d2 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -975,14 +975,14 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 tex_uvs.z = _Data->TexUvWhitePixel.y; // v tex_uvs.w = 0.0f; // 1/thickness, unused. fringe = 0.0f; - flags &= ~ImDrawFlags_AAEnds; + flags &= ~ImDrawFlags_AALineEnds; } const ImU32 col_trans = col & ~IM_COL32_A_MASK; const ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeCenter); if (stroke_pos == ImDrawFlags_StrokeLegacy) - flags = (flags | ImDrawFlags_MiterOnly) & ~ImDrawFlags_AAEnds; + flags = (flags | ImDrawFlags_MiterOnly) & ~ImDrawFlags_AALineEnds; const bool closed = (flags & ImDrawFlags_Closed) != 0; const bool miters_only = (flags & ImDrawFlags_MiterOnly) != 0; @@ -1054,7 +1054,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 p1.y -= dir.y * half_thickness; } - if ((flags & ImDrawFlags_AAEnds) == 0) + if ((flags & ImDrawFlags_AALineEnds) == 0) { base_idx = (int)_VtxCurrentIdx; IM_APPEND_VTX(p1.x - n1.x * thickness0, p1.y - n1.y * thickness0, uv0, col); @@ -1240,7 +1240,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 p1.y += dir.y * half_thickness; } - if ((flags & ImDrawFlags_AAEnds) == 0) + if ((flags & ImDrawFlags_AALineEnds) == 0) { IM_APPEND_VTX(p1.x - n1.x * thickness0, p1.y - n1.y * thickness0, uv0, col); IM_APPEND_VTX(p1.x + n1.x * thickness1, p1.y + n1.y * thickness1, uv1, col); @@ -2212,7 +2212,7 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t tex_uvs.z = _Data->TexUvWhitePixel.y; // v tex_uvs.w = 0.0f; // 1/thickness, unused. fringe = 0.0f; - flags &= ~ImDrawFlags_AAEnds; + flags &= ~ImDrawFlags_AALineEnds; } float dir_x = p2.x - p1.x; @@ -2228,7 +2228,7 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t const ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeCenter); if (stroke_pos == ImDrawFlags_StrokeLegacy) - flags = (flags | ImDrawFlags_MiterOnly) & ~ImDrawFlags_AAEnds; + flags = (flags | ImDrawFlags_MiterOnly) & ~ImDrawFlags_AALineEnds; float thickness0 = (thickness + fringe) * 0.5f; // Center (or legacy) if (stroke_pos == ImDrawFlags_StrokeOutside) @@ -2253,7 +2253,7 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t const float half_aa = _FringeScale * 0.5f; // Used for end caps, does not use "fringe" since end cap AA is not using the texture. const float half_thickness = thickness * 0.5f; - if ((flags & ImDrawFlags_AAEnds) == 0) + if ((flags & ImDrawFlags_AALineEnds) == 0) PrimReserve(2*3, 4); else PrimReserve(6*3, 8); @@ -2274,7 +2274,7 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t } // Start cap - if ((flags & ImDrawFlags_AAEnds) == 0) + if ((flags & ImDrawFlags_AALineEnds) == 0) { base_idx = (int)_VtxCurrentIdx; IM_APPEND_VTX(p1_x - norm_x * thickness0, p1_y - norm_y * thickness0, uv0, col); From 02adb46a9f357f04dba6aa965dc0768b92b35c18 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 8 Jun 2026 12:48:52 +0200 Subject: [PATCH 080/120] (Breaking) DrawList: removed ImDrawListFlags_in favor of using ImDrawFlags. # Conflicts: # imgui.cpp --- docs/CHANGELOG.txt | 13 ++++++++++--- imgui.cpp | 33 ++++++++++++++++++++------------- imgui.h | 33 ++++++++++++++++----------------- imgui_demo.cpp | 20 +++++++++++--------- imgui_draw.cpp | 37 +++++++++++++++++++------------------ imgui_internal.h | 2 +- 6 files changed, 77 insertions(+), 61 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index f7eae62f1..fb7af7591 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -93,8 +93,8 @@ HOW TO UPDATE? but the artifacts should be more localized. The new implementation attempted to favor simple implementation and robustness over the corner case accuracy. - Added anti-aliasing ends, most noticeable for thick lines. - - Added `ImDrawFlags_AAEnds` flag to enable anti-aliased line ends. Enabling AA Ends by - default would be more "correct", but it is slower and increase vertex/index data. + - Added `ImDrawFlags_AALineEnds` flag to enable anti-aliased line ends. Enabling AA Ends + by default would be more "correct", but it is slower and increase vertex/index data. - Added `ImDrawFlags_MiterOnly` flag to disable using beveled corner (closer to legacy rendering, slightly faster, but corners sharpers than 90 degrees may expand far out). Automatically used by AddRect(), AddCircle(), AddNgon(), AddEllipse() etc functions @@ -141,6 +141,14 @@ HOW TO UPDATE? - 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. +- (Breaking) Merged `ImDrawListFlags` into `ImDrawFlags`. + `ImDrawListFlags` was very rarely used directly. Keep inline redirection (will obsolete). + - `ImDrawListFlags_AntiAliasedLines` -> `ImDrawFlags_AALines` + - `ImDrawListFlags_AntiAliasedFill` -> `ImDrawFlags_AAFill` + - `ImDrawListFlags_AntiAliasedLinesUseTex` -> `ImDrawFlags_UseTexForStrokeLegacy` + - `ImDrawListFlags_AllowVtxOffset` -> `ImDrawFlags_UseVtxOffset` + - `ImDrawListFlags_TextNoPixelSnap` -> `ImDrawFlags_TextNoPixelSnap` + Unifying them allows easily using them for both per-primitives and scope alterations. - Tweaked line rendering in various locations to avoid blurryness: - Windows: title-bar and menu-bar border (when thickness>1.0f). - Tables: borders (when thickness>1.0f). @@ -152,7 +160,6 @@ HOW TO UPDATE? filled shapes; Added an option to animation thickness; Added new shapes to showcase new rendering features that previously hit limitations. - Breaking Changes: - Style: obsoleted `style.CurveTessellationTol (default 1.25)` which was in Pixels² unit in diff --git a/imgui.cpp b/imgui.cpp index b9a700608..15bde3e93 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -395,6 +395,13 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: When you are not sure about an old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files. You can read releases logs https://github.com/ocornut/imgui/releases for more details. + 2026/10/07 (1.93.0) - merged ImDrawListFlags into ImDrawFlags. obsoleted ImDrawListFlags (which were rarely used directly): + - ImDrawListFlags_AntiAliasedLines -> ImDrawFlags_AALines, + - ImDrawListFlags_AntiAliasedFill -> ImDrawFlags_AAFill, + - ImDrawListFlags_AntiAliasedLinesUseTex -> ImDrawFlags_UseTexForStrokeLegacy + - ImDrawListFlags_AllowVtxOffset -> ImDrawFlags_UseVtxOffset, + - ImDrawListFlags_TextNoPixelSnap -> ImDrawFlags_TextNoPixelSnap, + unifying them allows easily using them for both per-primitives alterations and scope alterations. - 2026/09/30 (1.93.0) - ImFont: renamed `AddRemapChar()` to `AddRemapCodepoint()` (rarely used, marked internal). (#609, #5748) - 2026/09/18 (1.93.0) - ImGuiTextFilter: removed `float width` parameter of `Draw(const char* filter, float width)`: prefer using `SetNextItemWidth(float)` which is standard. Kept inline redirection function. - 2026/08/03 (1.93.0) - Style: obsoleted `style.CurveTessellationTol (default 1.25)` which was in Pixels² unit in favor of `style.CurveTessellationMaxError` (default 1.12)` which is in Pixels unit. @@ -5619,17 +5626,17 @@ static void SetupDrawListSharedData() g.DrawListSharedData.ClipRectFullscreen = virtual_space.ToVec4(); g.DrawListSharedData.CurveTessellationMaxError = g.Style.CurveTessellationMaxError; g.DrawListSharedData.SetCircleTessellationMaxError(g.Style.CircleTessellationMaxError); - g.DrawListSharedData.InitialFlags = ImDrawListFlags_None; - if (g.Style.AntiAliasedLines) - g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLines; - if (g.Style.AntiAliasedLinesUseTex && !(g.IO.Fonts->Flags & ImFontAtlasFlags_NoBakedLines)) - g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLinesUseTex; - if (!(g.IO.Fonts->Flags & ImFontAtlasFlags_NoBakedRoundCorners)) - g.DrawListSharedData.InitialFlags |= ImDrawListFlags_RoundCornersUseTex; + g.DrawListSharedData.InitialDrawFlags = ImDrawFlags_None; if (g.Style.AntiAliasedFill) - g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedFill; + g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_AAFill; + if (g.Style.AntiAliasedLines) + g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_AALines; + if (g.Style.AntiAliasedLinesUseTex && !(g.IO.Fonts->Flags & ImFontAtlasFlags_NoBakedLines)) + g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_UseTexForStrokeLegacy; + if (!(g.IO.Fonts->Flags & ImFontAtlasFlags_NoBakedRoundCorners)) + g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_UseTexForRoundCorners; if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) - g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AllowVtxOffset; + g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_UseVtxOffset; } void ImGui::NewFrame() @@ -17680,8 +17687,8 @@ void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, con Selectable(buf, false); if (fg_draw_list && IsItemHovered()) { - ImDrawListFlags backup_flags = fg_draw_list->Flags; - fg_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines is more readable for very large and thin triangles. + ImDrawFlags backup_flags = fg_draw_list->Flags; + fg_draw_list->Flags &= ~ImDrawFlags_AALines; // Disable AA on triangle outlines is more readable for very large and thin triangles. fg_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), 1.0f, ImDrawFlags_Closed); fg_draw_list->Flags = backup_flags; } @@ -17699,8 +17706,8 @@ void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, co // Draw wire-frame version of all triangles ImRect clip_rect = draw_cmd->ClipRect; ImRect vtxs_rect(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX); - ImDrawListFlags backup_flags = out_draw_list->Flags; - out_draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; // Disable AA on triangle outlines is more readable for very large and thin triangles. + ImDrawFlags backup_flags = out_draw_list->Flags; + out_draw_list->Flags &= ~ImDrawFlags_AALines; // Disable AA on triangle outlines is more readable for very large and thin triangles. for (unsigned int idx_n = draw_cmd->IdxOffset, idx_end = draw_cmd->IdxOffset + draw_cmd->ElemCount; idx_n < idx_end; ) { ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL; // We don't hold on those pointers past iterations as ->AddPolyline() may invalidate them if out_draw_list==draw_list diff --git a/imgui.h b/imgui.h index bbd576900..09f8b5cc7 100644 --- a/imgui.h +++ b/imgui.h @@ -49,7 +49,7 @@ Index of this file: // [SECTION] Misc data structures (ImGuiInputTextCallbackData, ImGuiSizeCallbackData, ImGuiPayload) // [SECTION] Helpers (ImGuiOnceUponAFrame, ImGuiTextFilter, ImGuiTextBuffer, ImGuiStorage, ImGuiListClipper, Math Operators, ImColor) // [SECTION] Multi-Select API flags and structures (ImGuiMultiSelectFlags, ImGuiMultiSelectIO, ImGuiSelectionRequest, ImGuiSelectionBasicStorage, ImGuiSelectionExternalStorage) -// [SECTION] Drawing API (ImDrawCallback, ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawFlags, ImDrawListFlags, ImDrawList, ImDrawData) +// [SECTION] Drawing API (ImDrawCallback, ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawFlags, ImDrawList, ImDrawData) // [SECTION] Texture API (ImTextureFormat, ImTextureStatus, ImTextureRect, ImTextureData) // [SECTION] Font API (ImFontConfig, ImFontGlyph, ImFontGlyphRangesBuilder, ImFontAtlasFlags, ImFontAtlas, ImFontBaked, ImFont) // [SECTION] Viewports (ImGuiViewportFlags, ImGuiViewport) @@ -246,7 +246,6 @@ typedef int ImGuiTableBgTarget; // -> enum ImGuiTableBgTarget_ // Enum: A // - In Visual Studio w/ Visual Assist installed: Alt+G ("VAssistX.GoToImplementation") can also follow symbols inside comments. // - In VS Code, CLion, etc.: Ctrl+Click can follow symbols inside comments. typedef int ImDrawFlags; // -> enum ImDrawFlags_ // Flags: for ImDrawList functions -typedef int ImDrawListFlags; // -> enum ImDrawListFlags_ // Flags: for ImDrawList instance typedef int ImDrawTextFlags; // -> enum ImDrawTextFlags_ // Internal, do not use! typedef int ImFontFlags; // -> enum ImFontFlags_ // Flags: for ImFont typedef int ImFontAtlasFlags; // -> enum ImFontAtlasFlags_ // Flags: for ImFontAtlas @@ -3198,7 +3197,7 @@ struct ImGuiSelectionExternalStorage }; //----------------------------------------------------------------------------- -// [SECTION] Drawing API (ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawListFlags, ImDrawList, ImDrawData) +// [SECTION] Drawing API (ImDrawCmd, ImDrawIdx, ImDrawVert, ImDrawChannel, ImDrawListSplitter, ImDrawFlags, ImDrawList, ImDrawData) // Hold a series of drawing commands. The user provides a renderer for ImDrawData which essentially contains an array of ImDrawList. //----------------------------------------------------------------------------- @@ -3358,19 +3357,6 @@ enum ImDrawFlags_ ImDrawFlags_InvalidMask_ = ~0x7FFFFFF0, // [Internal] == 0x8000000F. Reserved to detect misuses. }; -// Flags for ImDrawList instance. Those are set automatically by ImGui:: functions from ImGuiIO settings, and generally not manipulated directly. -// It is however possible to temporarily alter flags between calls to ImDrawList:: functions. -enum ImDrawListFlags_ -{ - ImDrawListFlags_None = 0, - ImDrawListFlags_AntiAliasedLines = 1 << 0, // Enable anti-aliased lines/borders (*2 the number of triangles for 1.0f wide line or lines thin enough to be drawn using textures, otherwise *3 the number of triangles) - ImDrawListFlags_AntiAliasedLinesUseTex = 1 << 1, // Enable anti-aliased lines/borders using textures when possible. Require backend to render with bilinear filtering (NOT point/nearest filtering). - ImDrawListFlags_AntiAliasedFill = 1 << 2, // Enable anti-aliased edge around filled shapes (rounded rectangles, circles). - ImDrawListFlags_AllowVtxOffset = 1 << 3, // Can emit 'VtxOffset > 0' to allow large meshes. Set when 'ImGuiBackendFlags_RendererHasVtxOffset' is enabled. - ImDrawListFlags_TextNoPixelSnap = 1 << 4, // Disable automatically snapping AddText() calls to pixel boundaries. - ImDrawListFlags_RoundCornersUseTex = 1 << 5, // Enable using textures instead of strokes to draw rounded corners/circles where possible. Set when `ImFontAtlasFlags_NoBakedRoundCorners` is enabled in the font atlas. -}; - // Draw command list // This is the low-level list of polygons that ImGui:: functions are filling. At the end of the frame, // all command lists are passed to your ImGuiIO::RenderDrawListFn function for rendering. @@ -3386,7 +3372,7 @@ struct ImDrawList ImVector CmdBuffer; // Draw commands. Typically 1 command = 1 GPU draw call, unless the command is a callback. ImVector IdxBuffer; // Index buffer. Each command consume ImDrawCmd::ElemCount of those ImVector VtxBuffer; // Vertex buffer. - ImDrawListFlags Flags; // Flags, you may poke into these to adjust anti-aliasing settings per-primitive. + ImDrawFlags Flags; // Flags, you may poke into these to adjust anti-aliasing settings per-primitive. // [Internal, used while building lists] unsigned int _VtxCurrentIdx; // [Internal] generally == VtxBuffer.Size unless we are past 64K vertices, in which case this gets reset to 0. @@ -4294,6 +4280,19 @@ namespace ImGui #define ImDrawCallback_ResetRenderState (ImDrawCallback)(-8) // OBSOLETED in 1.92.8: Use ImGui::GetPlatformIO().DrawCallback_ResetRenderState + +// -- RENAMED in 1.93.0: Merged ImDrawListFlags into ImDrawFlags. +typedef ImDrawFlags ImDrawListFlags; +enum ImDrawListFlags_ +{ + ImDrawListFlags_None = ImDrawFlags_None, + ImDrawListFlags_AntiAliasedFill = ImDrawFlags_AAFill, + ImDrawListFlags_AntiAliasedLines = ImDrawFlags_AALines, + ImDrawListFlags_AntiAliasedLinesUseTex = ImDrawFlags_UseTexForStrokeLegacy,// Only apply to StrokeLegacy mode! + ImDrawListFlags_AllowVtxOffset = ImDrawFlags_UseVtxOffset, + ImDrawListFlags_TextNoPixelSnap = ImDrawFlags_TextNoPixelSnap, +}; + //-- OBSOLETED in 1.92.0: ImFontAtlasCustomRect becomes ImTextureRect // - ImFontAtlasCustomRect::X,Y --> ImTextureRect::x,y // - ImFontAtlasCustomRect::Width,Height --> ImTextureRect::w,h diff --git a/imgui_demo.cpp b/imgui_demo.cpp index e7a326463..d1188a348 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10443,17 +10443,19 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::CheckboxFlags("SquareCap", &other_flags, ImDrawFlags_SquareCap); ImGui::SameLine(); ImGui::CheckboxFlags("AALineEnds", &other_flags, ImDrawFlags_AALineEnds); ImGui::Spacing(); - const ImDrawFlags flags = stroke_flags | other_flags; // FIXME: Clarify how this is exposed, consider changing Demo code to simply alter Style.s - static ImDrawListFlags draw_list_flags = ImDrawListFlags_AntiAliasedLines | ImDrawListFlags_AntiAliasedFill | ImDrawListFlags_RoundCornersUseTex; - ImGui::Text("DrawList Flags:"); - ImGui::CheckboxFlags("AntiAliasedLines", &draw_list_flags, ImDrawListFlags_AntiAliasedLines); ImGui::SameLine(); - ImGui::CheckboxFlags("AntiAliasedFill", &draw_list_flags, ImDrawListFlags_AntiAliasedFill); ImGui::SameLine(); - ImGui::CheckboxFlags("RoundCornersUseTex", &draw_list_flags, ImDrawListFlags_RoundCornersUseTex); - ImDrawListFlags backup_draw_list_flags = draw_list->Flags; - draw_list->Flags &= ~(ImDrawListFlags_AntiAliasedLines | ImDrawListFlags_AntiAliasedFill | ImDrawListFlags_RoundCornersUseTex); - draw_list->Flags |= draw_list_flags; + const ImDrawFlags scope_flags_mask = ImDrawFlags_AllowedInScope_; + static ImDrawFlags scope_flags = scope_flags_mask; // All + ImGui::Text("AA Flags:"); + ImGui::CheckboxFlags("AAFill", &scope_flags, ImDrawFlags_AAFill); ImGui::SameLine(); + ImGui::CheckboxFlags("AALines", &scope_flags, ImDrawFlags_AALines); ImGui::SameLine(); + ImGui::CheckboxFlags("AALineEnds", &scope_flags, ImDrawFlags_AALineEnds); + ImGui::CheckboxFlags("UseTexForRoundCorners", &scope_flags, ImDrawFlags_UseTexForRoundCorners); + ImGui::CheckboxFlags("UseTexForStrokeLegacy", &scope_flags, ImDrawFlags_UseTexForStrokeLegacy); ImGui::SameLine(); + ImDrawFlags backup_draw_list_flags = draw_list->Flags; + draw_list->Flags = (draw_list->Flags & ~scope_flags_mask) | scope_flags; + const ImDrawFlags flags = stroke_flags | other_flags; ImVec2 start_pos = ImGui::GetCursorScreenPos(); const float pi = 3.141592f; diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 811bdd3d2..1f16f5a2d 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -461,7 +461,8 @@ void ImDrawList::_ResetForNewFrame() CmdBuffer.resize(0); IdxBuffer.resize(0); VtxBuffer.resize(0); - Flags = _Data->InitialFlags; + Flags = _Data->InitialDrawFlags; + IM_ASSERT((Flags & ~ImDrawFlags_AllowedInScope_) == 0); memset(&_CmdHeader, 0, sizeof(_CmdHeader)); _VtxCurrentIdx = 0; _VtxWritePtr = NULL; @@ -480,7 +481,7 @@ void ImDrawList::_ClearFreeMemory() CmdBuffer.clear(); IdxBuffer.clear(); VtxBuffer.clear(); - Flags = ImDrawListFlags_None; + Flags = ImDrawFlags_None; _VtxCurrentIdx = 0; _VtxWritePtr = NULL; _IdxWritePtr = NULL; @@ -736,7 +737,7 @@ void ImDrawList::PrimReserve(int idx_count, int vtx_count) { // Large mesh support (when enabled) IM_ASSERT_PARANOID(idx_count >= 0 && vtx_count >= 0); - if (sizeof(ImDrawIdx) == 2 && (_VtxCurrentIdx + vtx_count >= (1 << 16)) && (Flags & ImDrawListFlags_AllowVtxOffset)) + if (sizeof(ImDrawIdx) == 2 && (_VtxCurrentIdx + vtx_count >= (1 << 16)) && (Flags & ImDrawFlags_UseVtxOffset)) { // FIXME: In theory we should be testing that vtx_count <64k here. // In practice, RenderText() relies on reserving ahead for a worst case scenario so it is currently useful for us @@ -963,7 +964,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 ImVec4 tex_uvs; float fringe; - if (Flags & ImDrawListFlags_AntiAliasedLines) + if (Flags & ImDrawFlags_AALines) { _SelectFringeTexture(screen_thickness, &tex_uvs, &fringe); } @@ -1305,7 +1306,7 @@ void ImDrawList::AddPolylineLegacy(const ImVec2* points, const int points_count, // Read more details near AddRect() + see "API BREAKING CHANGES" section for 1.82, 1.90 and 1.92.8. IM_ASSERT_USER_ERROR_RET((flags & ImDrawFlags_InvalidMask_) == 0, "Incorrect parameter. Did you swap 'thickness' and 'flags'?"); - if (Flags & ImDrawListFlags_AntiAliasedLines) + if (Flags & ImDrawFlags_AALines) { // Anti-aliased stroke const float AA_SIZE = _FringeScale; @@ -1319,9 +1320,9 @@ void ImDrawList::AddPolylineLegacy(const ImVec2* points, const int points_count, // Do we want to draw this line using a texture? // - For now, only draw integer-width lines using textures to avoid issues with the way scaling occurs, could be improved. // - If AA_SIZE is not 1.0f we cannot use the texture path. - const bool use_texture = (Flags & ImDrawListFlags_AntiAliasedLinesUseTex) && (integer_thickness < IM_DRAWLIST_TEX_LINES_WIDTH_MAX) && (fractional_thickness <= 0.00001f) && (AA_SIZE == 1.0f); + const bool use_texture = (Flags & ImDrawFlags_UseTexForStrokeLegacy) && (integer_thickness < IM_DRAWLIST_TEX_LINES_WIDTH_MAX) && (fractional_thickness <= 0.00001f) && (AA_SIZE == 1.0f); - // We should never hit this, because NewFrame() doesn't set ImDrawListFlags_AntiAliasedLinesUseTex unless ImFontAtlasFlags_NoBakedLines is off + // We should never hit this, because NewFrame() doesn't set ImDrawFlags_UseTexForStrokeLegacy unless ImFontAtlasFlags_NoBakedLines is off IM_ASSERT_PARANOID(!use_texture || !(_Data->Font->OwnerAtlas->Flags & ImFontAtlasFlags_NoBakedLines)); const int idx_count = use_texture ? (count * 6) : (thick_line ? count * 18 : count * 12); @@ -1565,7 +1566,7 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun }*/ const ImVec2 uv = _Data->TexUvWhitePixel; - if (Flags & ImDrawListFlags_AntiAliasedFill) + if (Flags & ImDrawFlags_AAFill) { const float half_aa = _FringeScale * 0.5f; ImU32 col_trans = col & ~IM_COL32_A_MASK; @@ -1753,7 +1754,7 @@ void ImDrawList::AddConvexPolyFilledLegacy(const ImVec2* points, const int point const ImVec2 uv = _Data->TexUvWhitePixel; - if (Flags & ImDrawListFlags_AntiAliasedFill) + if (Flags & ImDrawFlags_AAFill) { // Anti-aliased Fill const float AA_SIZE = _FringeScale; @@ -2200,7 +2201,7 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t ImVec4 tex_uvs; float fringe; - if (Flags & ImDrawListFlags_AntiAliasedLines) + if (Flags & ImDrawFlags_AALines) { _SelectFringeTexture(screen_thickness, &tex_uvs, &fringe); } @@ -2574,7 +2575,7 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, ImVec4 tex_uvs; float fringe; - if (Flags & ImDrawListFlags_AntiAliasedLines) + if (Flags & ImDrawFlags_AALines) { _SelectFringeTexture(screen_thickness, &tex_uvs, &fringe); } @@ -2712,7 +2713,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) IM_UNLIKELY { - if (Flags & ImDrawListFlags_AntiAliasedLines) + if (Flags & ImDrawFlags_AALines) PathRect(ImVec2(p_min.x + 0.50f, p_min.y + 0.50f), ImVec2(p_max.x - 0.50f, p_max.y - 0.50f), rounding, flags); else PathRect(ImVec2(p_min.x + 0.50f, p_min.y + 0.50f), ImVec2(p_max.x - 0.49f, p_max.y - 0.49f), rounding, flags); // Better looking lower-right corner and rounded non-AA shapes. @@ -2798,7 +2799,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl return; } // Textured corners are baked with AA, do not use them if no-AA is requested. - const bool allow_tex_corners = (Flags & (ImDrawListFlags_RoundCornersUseTex | ImDrawListFlags_AntiAliasedLines)) == (ImDrawListFlags_RoundCornersUseTex | ImDrawListFlags_AntiAliasedLines); + const bool allow_tex_corners = (Flags & (ImDrawFlags_AALines | ImDrawFlags_UseTexForRoundCorners)) == (ImDrawFlags_AALines | ImDrawFlags_UseTexForRoundCorners); if (allow_tex_corners && s_thickness < IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX && s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) { // Pixel aligned rect with round corners rendered using baked textures. @@ -2944,7 +2945,7 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c return; } // Textured corners are baked with AA, do not use them if no-AA is requested. - const bool allow_tex_corners = (Flags & (ImDrawListFlags_RoundCornersUseTex | ImDrawListFlags_AntiAliasedFill)) == (ImDrawListFlags_RoundCornersUseTex | ImDrawListFlags_AntiAliasedFill); + const bool allow_tex_corners = (Flags & (ImDrawFlags_AAFill | ImDrawFlags_UseTexForRoundCorners)) == (ImDrawFlags_AAFill | ImDrawFlags_UseTexForRoundCorners); if (allow_tex_corners && s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) { IM_ASSERT_PARANOID(!(_Data->Font->OwnerAtlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners)); @@ -3581,7 +3582,7 @@ void ImDrawList::AddConcavePolyFilled(const ImVec2* points, const int points_cou const ImVec2 uv = _Data->TexUvWhitePixel; ImTriangulator triangulator; unsigned int triangle[3]; - if (Flags & ImDrawListFlags_AntiAliasedFill) + if (Flags & ImDrawFlags_AAFill) { // Anti-aliased Fill const float AA_SIZE = _FringeScale; @@ -3838,7 +3839,7 @@ void ImGui::AddDrawListToDrawDataEx(ImDrawData* draw_data, ImVector // May trigger for you if you are using PrimXXX functions incorrectly. IM_ASSERT(draw_list->VtxBuffer.Size == 0 || draw_list->_VtxWritePtr == draw_list->VtxBuffer.Data + draw_list->VtxBuffer.Size); IM_ASSERT(draw_list->IdxBuffer.Size == 0 || draw_list->_IdxWritePtr == draw_list->IdxBuffer.Data + draw_list->IdxBuffer.Size); - if (!(draw_list->Flags & ImDrawListFlags_AllowVtxOffset)) + if (!(draw_list->Flags & ImDrawFlags_UseVtxOffset)) IM_ASSERT((int)draw_list->_VtxCurrentIdx == draw_list->VtxBuffer.Size); // Check that draw_list doesn't use more vertices than indexable (default ImDrawIdx = unsigned short = 2 bytes = 64K vertices per ImDrawList = per window) @@ -7681,7 +7682,7 @@ void ImFont::RenderChar(ImDrawList* draw_list, float size, const ImVec2& pos, Im float scale = (size >= 0.0f) ? (size / baked->Size) : 1.0f; float x = pos.x; float y = pos.y; - if ((draw_list->Flags & ImDrawListFlags_TextNoPixelSnap) == 0) + if ((draw_list->Flags & ImDrawFlags_TextNoPixelSnap) == 0) { x = IM_TRUNC(x); y = IM_TRUNC(y); @@ -7723,7 +7724,7 @@ begin: float y = pos.y; if (y > clip_rect.w) return; - if ((draw_list->Flags & ImDrawListFlags_TextNoPixelSnap) == 0) + if ((draw_list->Flags & ImDrawFlags_TextNoPixelSnap) == 0) { x = IM_TRUNC(x); y = IM_TRUNC(y); diff --git a/imgui_internal.h b/imgui_internal.h index 38e475087..8466c2030 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -956,7 +956,7 @@ struct IMGUI_API ImDrawListSharedData ImDrawFlags OverrideStrokePos; // Override default StrokePos when not specified by an individual primitive. == 0 or ImDrawFlags_StrokeLegacy only. float CurveTessellationMaxError; // Tessellation tolerance when using PathBezierCurveTo() float CircleTessellationMaxError; // Number of circle segments to use per pixel of radius for AddCircle() etc - ImDrawListFlags InitialFlags; // Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards) + ImDrawFlags InitialDrawFlags; // Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards) ImVec4 ClipRectFullscreen; // Value for PushClipRectFullscreen() ImVector TempBuffer; // Temporary write buffer ImVector DrawLists; // All draw lists associated to this ImDrawListSharedData From 7ccc8348bc202a9ed619dab92b72fa48f0615e7d Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 27 Jul 2026 21:41:18 +0200 Subject: [PATCH 081/120] (Breaking) Obsoleted style.AntiAliasedLinesUseTex which now only makes sense for legacy strokes. Added style.AntiAliasedLineEnds. # Conflicts: # imgui.cpp --- imgui.cpp | 12 +++++++++--- imgui.h | 9 +++++---- imgui_demo.cpp | 6 ++++++ 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 15bde3e93..aa636181b 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -402,6 +402,7 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: - ImDrawListFlags_AllowVtxOffset -> ImDrawFlags_UseVtxOffset, - ImDrawListFlags_TextNoPixelSnap -> ImDrawFlags_TextNoPixelSnap, unifying them allows easily using them for both per-primitives alterations and scope alterations. + - obsoleted style.AntiAliasedLinesUseTex which now only makes sense for legacy strokes. - 2026/09/30 (1.93.0) - ImFont: renamed `AddRemapChar()` to `AddRemapCodepoint()` (rarely used, marked internal). (#609, #5748) - 2026/09/18 (1.93.0) - ImGuiTextFilter: removed `float width` parameter of `Draw(const char* filter, float width)`: prefer using `SetNextItemWidth(float)` which is standard. Kept inline redirection function. - 2026/08/03 (1.93.0) - Style: obsoleted `style.CurveTessellationTol (default 1.25)` which was in Pixels² unit in favor of `style.CurveTessellationMaxError` (default 1.12)` which is in Pixels unit. @@ -1585,9 +1586,9 @@ ImGuiStyle::ImGuiStyle() MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. // Rendering & Tessellation - AntiAliasedLines = true; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. - AntiAliasedLinesUseTex = true; // Enable anti-aliased lines/borders using textures where possible. Require backend to render with bilinear filtering (NOT point/nearest filtering). - AntiAliasedFill = true; // Enable anti-aliased filled shapes (rounded rectangles, circles, etc.). + AntiAliasedLines = true; // Enable anti-aliased lines/borders. Used at the beginning of the frame to set ImDrawFlags_AALines in all draw-lists. + AntiAliasedLineEnds = false; // Enable anti-aliased lines/borders ends. Nicer for thick lines but more expensive. Used at the beginning of the frame to set ImDrawFlags_AALineEnds in all draw-lists. + AntiAliasedFill = true; // Enable anti-aliased edges around filled shapes (rounded rectangles, circles, etc.). Used at the beginning of the frame to set ImDrawFlags_AAFill in all draw-lists. CurveTessellationMaxError = 1.12f; // Maximum error (in pixels) when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. CircleTessellationMaxError = 0.30f; // Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner rectangles with no explicit segment count specified. Decrease for higher quality but more geometry. @@ -1603,6 +1604,7 @@ ImGuiStyle::ImGuiStyle() _NextFrameFontSizeBase = 0.0f; #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS CurveTessellationTol = 0.0f; // Old CurveTessellationTol = CurveTessellationMaxError*CurveTessellationMaxError. + AntiAliasedLinesUseTex = true; // Enable anti-aliased lines/borders using textures for legacy strokes. Require backend to render with bilinear filtering (NOT point/nearest filtering). #endif // Default theme @@ -5631,8 +5633,12 @@ static void SetupDrawListSharedData() g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_AAFill; if (g.Style.AntiAliasedLines) g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_AALines; + if (g.Style.AntiAliasedLineEnds) + g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_AALineEnds; +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS if (g.Style.AntiAliasedLinesUseTex && !(g.IO.Fonts->Flags & ImFontAtlasFlags_NoBakedLines)) g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_UseTexForStrokeLegacy; +#endif if (!(g.IO.Fonts->Flags & ImFontAtlasFlags_NoBakedRoundCorners)) g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_UseTexForRoundCorners; if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) diff --git a/imgui.h b/imgui.h index 09f8b5cc7..27fc13213 100644 --- a/imgui.h +++ b/imgui.h @@ -2395,9 +2395,9 @@ struct ImGuiStyle float MouseCursorScale; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). We apply per-monitor DPI scaling over this scale. May be removed later. // Rendering & Tessellation - bool AntiAliasedLines; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. Latched at the beginning of the frame (copied to ImDrawList). - bool AntiAliasedLinesUseTex; // Enable anti-aliased lines/borders using textures where possible. Require backend to render with bilinear filtering (NOT point/nearest filtering). Latched at the beginning of the frame (copied to ImDrawList). - bool AntiAliasedFill; // Enable anti-aliased edges around filled shapes (rounded rectangles, circles, etc.). Disable if you are really tight on CPU/GPU. Latched at the beginning of the frame (copied to ImDrawList). + bool AntiAliasedLines; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. Latched at the beginning of the frame (copied to ImDrawList). bool AntiAliasedLines; // Enable anti-aliased lines/borders. Used at the beginning of the frame to set ImDrawFlags_AALines in all draw-lists. + bool AntiAliasedLineEnds; // Enable anti-aliased lines/borders ends. Nicer for thick lines but more expensive. Used at the beginning of the frame to set ImDrawFlags_AALineEnds in all draw-lists. + bool AntiAliasedFill; // Enable anti-aliased edges around filled shapes (rounded rectangles, circles, etc.). Used at the beginning of the frame to set ImDrawFlags_AAFill in all draw-lists. float CurveTessellationMaxError; // Maximum error (in pixels) when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. float CircleTessellationMaxError; // Maximum error (in pixels) allowed when using AddCircle()/AddCircleFilled() or drawing rounded corner rectangles with no explicit segment count specified. Decrease for higher quality but more geometry. @@ -2422,6 +2422,7 @@ struct ImGuiStyle // Obsolete names #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + bool AntiAliasedLinesUseTex; // [OBSOLETE] Enable anti-aliased lines/borders using textures for legacy strokes. Require backend to render with bilinear filtering (NOT point/nearest filtering). Latched at the beginning of the frame (copied to ImDrawList). float CurveTessellationTol; // [OBSOLETE] Old CurveTessellationTol = New CurveTessellationMaxError*CurveTessellationMaxError. // Changed in 1.93.0 // TabMinWidthForCloseButton = TabCloseButtonMinWidthUnselected // Renamed in 1.91.9. #endif @@ -3876,7 +3877,7 @@ struct ImFontAtlas // Input ImFontAtlasFlags Flags; // Build flags (see ImFontAtlasFlags_) ImTextureFormat TexDesiredFormat; // Desired texture format (default to ImTextureFormat_RGBA32 but may be changed to ImTextureFormat_Alpha8). - int TexGlyphPadding; // FIXME: Should be called "TexPackPadding". Padding between glyphs within texture in pixels. Defaults to 1. If your rendering method doesn't rely on bilinear filtering you may set this to 0 (will also need to set AntiAliasedLinesUseTex = false). + int TexGlyphPadding; // FIXME: Should be called "TexPackPadding". Padding between glyphs within texture in pixels. Defaults to 1. If your rendering method never relies on bilinear filtering you may set this to 0. int TexMinWidth; // Minimum desired texture width. Must be a power of two. Default to 512. int TexMinHeight; // Minimum desired texture height. Must be a power of two. Default to 128. int TexMaxWidth; // Maximum desired texture width. Must be a power of two. Default to 8192. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index d1188a348..9aaeccf41 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -8912,9 +8912,15 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) SameLine(); HelpMarker("When disabling anti-aliasing lines, you'll probably want to disable borders in your style as well."); + Checkbox("Anti-aliased line ends", &style.AntiAliasedLineEnds); + +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + BeginDisabled(); Checkbox("Anti-aliased lines use texture", &style.AntiAliasedLinesUseTex); SameLine(); HelpMarker("Faster lines using texture data. Require backend to render with bilinear filtering (not point/nearest filtering)."); + EndDisabled(); +#endif Checkbox("Anti-aliased fill", &style.AntiAliasedFill); PushItemWidth(GetFontSize() * 8); From 008e0990b3b21ae012d068712712b524e60a0b15 Mon Sep 17 00:00:00 2001 From: ocornut Date: Mon, 27 Jul 2026 21:26:10 +0200 Subject: [PATCH 082/120] DrawList: comment about ImFontAtlasFlags_NoBakedLines. Demo: amend test bed to exercices more flags. --- docs/CHANGELOG.txt | 2 ++ imgui.h | 2 +- imgui_demo.cpp | 57 ++++++++++++++++++++++++++-------------------- 3 files changed, 35 insertions(+), 26 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index fb7af7591..a4b76821e 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -149,6 +149,8 @@ HOW TO UPDATE? - `ImDrawListFlags_AllowVtxOffset` -> `ImDrawFlags_UseVtxOffset` - `ImDrawListFlags_TextNoPixelSnap` -> `ImDrawFlags_TextNoPixelSnap` Unifying them allows easily using them for both per-primitives and scope alterations. +- (Breaking) Enabling ImFontAtlasFlags_NoBakedLines in the font atlas will disable + support for anti-aliased lines. - Tweaked line rendering in various locations to avoid blurryness: - Windows: title-bar and menu-bar border (when thickness>1.0f). - Tables: borders (when thickness>1.0f). diff --git a/imgui.h b/imgui.h index 27fc13213..fd38c3074 100644 --- a/imgui.h +++ b/imgui.h @@ -3759,7 +3759,7 @@ enum ImFontAtlasFlags_ ImFontAtlasFlags_None = 0, ImFontAtlasFlags_NoPowerOfTwoHeight = 1 << 0, // Don't round the height to next power of two ImFontAtlasFlags_NoMouseCursors = 1 << 1, // Don't build software mouse cursors into the atlas (save a little texture memory) - ImFontAtlasFlags_NoBakedLines = 1 << 2, // Don't build thick line textures into the atlas (save a little texture memory, allow support for point/nearest filtering). The AntiAliasedLinesUseTex features uses them, otherwise they will be rendered using polygons (more expensive for CPU/GPU). + ImFontAtlasFlags_NoBakedLines = 1 << 2, // [OBSOLETE] Don't build line textures into the atlas (save a little texture memory). SINCE 1.93.0 THIS PREVENT ANTI-ALIASED STROKES FROM WORKING. ImFontAtlasFlags_NoBakedRoundCorners= 1 << 3, // Don't build round corners into the atlas. }; diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 9aaeccf41..77c689461 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10436,32 +10436,39 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::ColorEdit4("Stroke Color", &colf_stroke.x); ImGui::ColorEdit4("Fill Color", &colf_fill.x); - static ImDrawFlags stroke_flags = ImDrawFlags_AALineEnds; - static ImDrawFlags other_flags = ImDrawFlags_None; - ImGui::Text("Stroke Flags:"); - ImGui::RadioButton("Default", &stroke_flags, ImDrawFlags_None); ImGui::SameLine(); - ImGui::RadioButton("Inside", &stroke_flags, ImDrawFlags_StrokeInside); ImGui::SameLine(); - ImGui::RadioButton("Center", &stroke_flags, ImDrawFlags_StrokeCenter); ImGui::SameLine(); - ImGui::RadioButton("CenterBiased", &stroke_flags, ImDrawFlags_StrokeCenterBiased); ImGui::SameLine(); - ImGui::RadioButton("Outside", &stroke_flags, ImDrawFlags_StrokeOutside); ImGui::SameLine(); - ImGui::RadioButton("Legacy", &stroke_flags, ImDrawFlags_StrokeLegacy); - ImGui::CheckboxFlags("MiterOnly", &other_flags, ImDrawFlags_MiterOnly); ImGui::SameLine(); - ImGui::CheckboxFlags("SquareCap", &other_flags, ImDrawFlags_SquareCap); ImGui::SameLine(); - ImGui::CheckboxFlags("AALineEnds", &other_flags, ImDrawFlags_AALineEnds); - ImGui::Spacing(); + static ImDrawFlags prim_stroke_flags = ImDrawFlags_None; + static ImDrawFlags prim_other_flags = ImDrawFlags_AALineEnds; + ImGui::SeparatorText("Per primitive flags (AddXXX functions)"); + ImGui::RadioButton("Default", &prim_stroke_flags, ImDrawFlags_None); ImGui::SameLine(); + ImGui::RadioButton("Inside", &prim_stroke_flags, ImDrawFlags_StrokeInside); ImGui::SameLine(); + ImGui::RadioButton("Center", &prim_stroke_flags, ImDrawFlags_StrokeCenter); ImGui::SameLine(); + ImGui::RadioButton("CenterBiased", &prim_stroke_flags, ImDrawFlags_StrokeCenterBiased); ImGui::SameLine(); + ImGui::RadioButton("Outside", &prim_stroke_flags, ImDrawFlags_StrokeOutside); ImGui::SameLine(); + ImGui::RadioButton("Legacy", &prim_stroke_flags, ImDrawFlags_StrokeLegacy); + ImGui::CheckboxFlags("AAFill", &prim_other_flags, ImDrawFlags_AAFill); ImGui::SameLine(); // Note: Only for functions taking flags. + ImGui::CheckboxFlags("AALines", &prim_other_flags, ImDrawFlags_AALines); ImGui::SameLine(); // Note: Only for functions taking flags. + ImGui::CheckboxFlags("AALineEnds", &prim_other_flags, ImDrawFlags_AALineEnds); ImGui::SameLine(); + ImGui::CheckboxFlags("MiterOnly", &prim_other_flags, ImDrawFlags_MiterOnly); ImGui::SameLine(); + ImGui::CheckboxFlags("SquareCap", &prim_other_flags, ImDrawFlags_SquareCap); + //ImGui::CheckboxFlags("UseTexForRoundCorners", &prim_other_flags, ImDrawFlags_UseTexForRoundCorners); ImGui::SameLine(); + //ImGui::CheckboxFlags("UseTexForStrokeLegacy", &prim_other_flags, ImDrawFlags_UseTexForStrokeLegacy); - // FIXME: Clarify how this is exposed, consider changing Demo code to simply alter Style.s - const ImDrawFlags scope_flags_mask = ImDrawFlags_AllowedInScope_; - static ImDrawFlags scope_flags = scope_flags_mask; // All - ImGui::Text("AA Flags:"); + static ImDrawFlags scope_flags = draw_list->Flags; // First init copy current state. + ImGui::SeparatorText("Current ImDrawList flags (e.g. PushDrawFlag function)"); + ImGui::PushID("ScopeFlags"); ImGui::CheckboxFlags("AAFill", &scope_flags, ImDrawFlags_AAFill); ImGui::SameLine(); ImGui::CheckboxFlags("AALines", &scope_flags, ImDrawFlags_AALines); ImGui::SameLine(); - ImGui::CheckboxFlags("AALineEnds", &scope_flags, ImDrawFlags_AALineEnds); - ImGui::CheckboxFlags("UseTexForRoundCorners", &scope_flags, ImDrawFlags_UseTexForRoundCorners); - ImGui::CheckboxFlags("UseTexForStrokeLegacy", &scope_flags, ImDrawFlags_UseTexForStrokeLegacy); ImGui::SameLine(); + ImGui::CheckboxFlags("AALineEnds", &scope_flags, ImDrawFlags_AALineEnds); ImGui::SameLine(); + ImGui::CheckboxFlags("StrokeLegacy", &scope_flags, ImDrawFlags_StrokeLegacy); + //ImGui::CheckboxFlags("UseTexForRoundCorners", &scope_flags, ImDrawFlags_UseTexForRoundCorners); ImGui::SameLine(); + //ImGui::CheckboxFlags("UseTexForStrokeLegacy", &scope_flags, ImDrawFlags_UseTexForStrokeLegacy); + ImGui::PopID(); + ImGui::Spacing(); + ImDrawFlags backup_draw_list_flags = draw_list->Flags; - draw_list->Flags = (draw_list->Flags & ~scope_flags_mask) | scope_flags; - const ImDrawFlags flags = stroke_flags | other_flags; + draw_list->Flags = (draw_list->Flags & ~ImDrawFlags_AllowedInScope_) | scope_flags; // Equivalent to an hypothetical PushDrawFlags() API + const ImDrawFlags flags = prim_stroke_flags | prim_other_flags; + const ImDrawFlags fill_flags = flags & (ImDrawFlags_AAFill | ImDrawFlags_UseTexForRoundCorners); ImVec2 start_pos = ImGui::GetCursorScreenPos(); const float pi = 3.141592f; @@ -10494,9 +10501,9 @@ static void ShowExampleAppCustomRendering(bool* p_open) draw_list->AddNgonFilled({ x + half_sz, y + half_sz }, half_sz, col, ngon_segments); x += sz + spacing; // N-gon draw_list->AddCircleFilled({ x + half_sz, y + half_sz }, half_sz, col, circle_segments); x += sz + spacing; // Circle draw_list->AddEllipseFilled({ x + half_sz, y + half_sz }, { sz * 0.5f, sz * 0.3f }, col, -0.3f, circle_segments); x += sz + spacing;// Ellipse - draw_list->AddRectFilled({ x, y }, { x + sz, y + sz }, col); x += sz + spacing; // Square - draw_list->AddRectFilled({ x, y }, { x + sz, y + sz }, col, rounding); x += sz + spacing; // Square with all rounded corners - draw_list->AddRectFilled({ x, y }, { x + sz, y + sz }, col, rounding, corners_tl_br); x += sz + spacing; // Square with two rounded corners + draw_list->AddRectFilled({ x, y }, { x + sz, y + sz }, col, 0.0f, fill_flags); x += sz + spacing; // Square + draw_list->AddRectFilled({ x, y }, { x + sz, y + sz }, col, rounding, fill_flags); x += sz + spacing; // Square with all rounded corners + draw_list->AddRectFilled({ x, y }, { x + sz, y + sz }, col, rounding, corners_tl_br | fill_flags); x += sz + spacing; // Square with two rounded corners draw_list->AddTriangleFilled({ x + sz * 0.5f, y }, { x + sz, y + sz }, { x, y + sz }, col); x += sz + spacing; // Triangle draw_list->AddTriangleFilled({ x + sz * 0.2f, y }, { x + sz * 0.4f, y + sz }, { x, y + sz }, col); x += (float)(int)(sz * 0.4f) + spacing; // Thin triangle PathConcaveShape(draw_list, x, y, sz); draw_list->PathFillConcave(col); x += sz + spacing; // Concave shape From 7e87cb984dab5f695b47cba5ec0236af5bdb46ff Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 28 Jul 2026 11:24:48 +0200 Subject: [PATCH 083/120] DrawList: consistently support scope flags (ImDrawFlags::Flags) + primitive flags. ImDrawFlags_AAEnds and ImDrawFlags_StrokeLegacy ok. (the most important ones). ImDrawFlags_UseTexForRoundCorners ok. ImDrawFlags_AAFill, ImDrawFlags_AAStrokes: for primitives: only for selected ones taking flags params. Haven't added the flag to all functions because it feels like a fringe use case. ImDrawFlags_TextNoPixelSnap supported in RenderText(), per-prim (9417) --- docs/CHANGELOG.txt | 1 + imgui.h | 2 +- imgui_draw.cpp | 33 +++++++++++++++++++++++---------- imgui_internal.h | 1 - 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index a4b76821e..aef92b48f 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -151,6 +151,7 @@ HOW TO UPDATE? Unifying them allows easily using them for both per-primitives and scope alterations. - (Breaking) Enabling ImFontAtlasFlags_NoBakedLines in the font atlas will disable support for anti-aliased lines. +- `ImDrawFlags_TextNoPixelSnap` may be passed to low-level RenderText() primitive. (#9417) - Tweaked line rendering in various locations to avoid blurryness: - Windows: title-bar and menu-bar border (when thickness>1.0f). - Tables: borders (when thickness>1.0f). diff --git a/imgui.h b/imgui.h index fd38c3074..1548b6926 100644 --- a/imgui.h +++ b/imgui.h @@ -3373,7 +3373,7 @@ struct ImDrawList ImVector CmdBuffer; // Draw commands. Typically 1 command = 1 GPU draw call, unless the command is a callback. ImVector IdxBuffer; // Index buffer. Each command consume ImDrawCmd::ElemCount of those ImVector VtxBuffer; // Vertex buffer. - ImDrawFlags Flags; // Flags, you may poke into these to adjust anti-aliasing settings per-primitive. + ImDrawFlags Flags; // Current flags for drawing primitives. You may poke into these to adjust anti-aliasing settings per-primitive. Will be exposed as PushDrawFlags(). // [Internal, used while building lists] unsigned int _VtxCurrentIdx; // [Internal] generally == VtxBuffer.Size unless we are past 64K vertices, in which case this gets reset to 0. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 1f16f5a2d..05db80d74 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -964,7 +964,8 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 ImVec4 tex_uvs; float fringe; - if (Flags & ImDrawFlags_AALines) + flags |= Flags; + if (flags & ImDrawFlags_AALines) { _SelectFringeTexture(screen_thickness, &tex_uvs, &fringe); } @@ -1306,7 +1307,8 @@ void ImDrawList::AddPolylineLegacy(const ImVec2* points, const int points_count, // Read more details near AddRect() + see "API BREAKING CHANGES" section for 1.82, 1.90 and 1.92.8. IM_ASSERT_USER_ERROR_RET((flags & ImDrawFlags_InvalidMask_) == 0, "Incorrect parameter. Did you swap 'thickness' and 'flags'?"); - if (Flags & ImDrawFlags_AALines) + flags |= Flags; + if (flags & ImDrawFlags_AALines) { // Anti-aliased stroke const float AA_SIZE = _FringeScale; @@ -1320,7 +1322,7 @@ void ImDrawList::AddPolylineLegacy(const ImVec2* points, const int points_count, // Do we want to draw this line using a texture? // - For now, only draw integer-width lines using textures to avoid issues with the way scaling occurs, could be improved. // - If AA_SIZE is not 1.0f we cannot use the texture path. - const bool use_texture = (Flags & ImDrawFlags_UseTexForStrokeLegacy) && (integer_thickness < IM_DRAWLIST_TEX_LINES_WIDTH_MAX) && (fractional_thickness <= 0.00001f) && (AA_SIZE == 1.0f); + const bool use_texture = (flags & ImDrawFlags_UseTexForStrokeLegacy) && (integer_thickness < IM_DRAWLIST_TEX_LINES_WIDTH_MAX) && (fractional_thickness <= 0.00001f) && (AA_SIZE == 1.0f); // We should never hit this, because NewFrame() doesn't set ImDrawFlags_UseTexForStrokeLegacy unless ImFontAtlasFlags_NoBakedLines is off IM_ASSERT_PARANOID(!use_texture || !(_Data->Font->OwnerAtlas->Flags & ImFontAtlasFlags_NoBakedLines)); @@ -1566,7 +1568,8 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun }*/ const ImVec2 uv = _Data->TexUvWhitePixel; - if (Flags & ImDrawFlags_AAFill) + flags |= Flags; + if (flags & ImDrawFlags_AAFill) { const float half_aa = _FringeScale * 0.5f; ImU32 col_trans = col & ~IM_COL32_A_MASK; @@ -2173,7 +2176,7 @@ ImDrawFlags ImDrawList::_GetStrokePos(ImDrawFlags flags, ImDrawFlags default_str { if (flags & ImDrawFlags_StrokeMask_) return (flags & ImDrawFlags_StrokeMask_); - return _Data->OverrideStrokePos ? _Data->OverrideStrokePos : default_stroke_pos; + return default_stroke_pos; } IM_MSVC_RUNTIME_CHECKS_RESTORE @@ -2201,7 +2204,8 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t ImVec4 tex_uvs; float fringe; - if (Flags & ImDrawFlags_AALines) + flags |= Flags; + if (flags & ImDrawFlags_AALines) { _SelectFringeTexture(screen_thickness, &tex_uvs, &fringe); } @@ -2339,6 +2343,7 @@ void ImDrawList::AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float th if ((col & IM_COL32_A_MASK) == 0) return; + flags |= Flags; ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeCenter); if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) { @@ -2354,6 +2359,7 @@ void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float th if ((col & IM_COL32_A_MASK) == 0) return; + flags |= Flags; ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) { @@ -2399,6 +2405,7 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th if ((col & IM_COL32_A_MASK) == 0) return; + flags |= Flags; ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) { @@ -2575,7 +2582,8 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, ImVec4 tex_uvs; float fringe; - if (Flags & ImDrawFlags_AALines) + flags |= Flags; + if (flags & ImDrawFlags_AALines) { _SelectFringeTexture(screen_thickness, &tex_uvs, &fringe); } @@ -2709,6 +2717,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl if ((col & IM_COL32_A_MASK) == 0) return; + flags |= Flags; ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) IM_UNLIKELY @@ -2799,7 +2808,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl return; } // Textured corners are baked with AA, do not use them if no-AA is requested. - const bool allow_tex_corners = (Flags & (ImDrawFlags_AALines | ImDrawFlags_UseTexForRoundCorners)) == (ImDrawFlags_AALines | ImDrawFlags_UseTexForRoundCorners); + const bool allow_tex_corners = (flags & (ImDrawFlags_AALines | ImDrawFlags_UseTexForRoundCorners)) == (ImDrawFlags_AALines | ImDrawFlags_UseTexForRoundCorners); if (allow_tex_corners && s_thickness < IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX && s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) { // Pixel aligned rect with round corners rendered using baked textures. @@ -2897,6 +2906,7 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c float width = p_max.x - p_min.x; float height = p_max.y - p_min.y; const float min_dim = ImMin(width, height); + flags |= Flags; // The rect is smaller than pixel in one dimension. if (min_dim < _FringeScale) IM_UNLIKELY @@ -2945,7 +2955,7 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c return; } // Textured corners are baked with AA, do not use them if no-AA is requested. - const bool allow_tex_corners = (Flags & (ImDrawFlags_AAFill | ImDrawFlags_UseTexForRoundCorners)) == (ImDrawFlags_AAFill | ImDrawFlags_UseTexForRoundCorners); + const bool allow_tex_corners = (flags & (ImDrawFlags_AAFill | ImDrawFlags_UseTexForRoundCorners)) == (ImDrawFlags_AAFill | ImDrawFlags_UseTexForRoundCorners); if (allow_tex_corners && s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) { IM_ASSERT_PARANOID(!(_Data->Font->OwnerAtlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners)); @@ -3033,6 +3043,7 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu if ((col & IM_COL32_A_MASK) == 0) return; + flags |= Flags; ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) IM_UNLIKELY { @@ -3117,6 +3128,7 @@ void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_ if ((col & IM_COL32_A_MASK) == 0 || num_segments <= 2) return; + flags |= Flags; ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) IM_UNLIKELY { @@ -7724,7 +7736,8 @@ begin: float y = pos.y; if (y > clip_rect.w) return; - if ((draw_list->Flags & ImDrawFlags_TextNoPixelSnap) == 0) + flags |= draw_list->Flags; + if ((flags & ImDrawFlags_TextNoPixelSnap) == 0) { x = IM_TRUNC(x); y = IM_TRUNC(y); diff --git a/imgui_internal.h b/imgui_internal.h index 8466c2030..2a246cd7f 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -953,7 +953,6 @@ struct IMGUI_API ImDrawListSharedData ImFont* Font; // Current font (used for simplified AddText overload) float FontSize; // Current font size (used for simplified AddText overload) float FontScale; // Current font scale (== FontSize / Font->FontSize) - ImDrawFlags OverrideStrokePos; // Override default StrokePos when not specified by an individual primitive. == 0 or ImDrawFlags_StrokeLegacy only. float CurveTessellationMaxError; // Tessellation tolerance when using PathBezierCurveTo() float CircleTessellationMaxError; // Number of circle segments to use per pixel of radius for AddCircle() etc ImDrawFlags InitialDrawFlags; // Initial flags at the beginning of the frame (it is possible to alter flags on a per-drawlist basis afterwards) From f2cd901be646e689364596eda22fbe0f7e1cb94c Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 28 Jul 2026 15:48:40 +0200 Subject: [PATCH 084/120] DrawList: added PushDrawFlag(), PopDrawFlag(), ImSmallStack_<>. (#9504) --- docs/CHANGELOG.txt | 1 + imgui.cpp | 10 ++++------ imgui.h | 24 +++++++++++++++++++----- imgui_demo.cpp | 9 +++++---- imgui_draw.cpp | 26 +++++++++++++++++++++++--- 5 files changed, 52 insertions(+), 18 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index aef92b48f..a271b716b 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -137,6 +137,7 @@ HOW TO UPDATE? Thickness=2.0f blurry sharp sharp sharp sharp Thickness=3.0f sharp sharp sharp blurry sharp ------------------------------------------------------------------------ +- Added ImDrawList::PushDrawFlag()/PopDrawFlag() to alter certain flags for a scope. - (Breaking) AddRect, AddCircle, AddNgon, AddEllipse: 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 diff --git a/imgui.cpp b/imgui.cpp index aa636181b..c4c24af0a 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -17693,10 +17693,9 @@ void ImGui::DebugNodeDrawList(ImGuiWindow* window, ImGuiViewportP* viewport, con Selectable(buf, false); if (fg_draw_list && IsItemHovered()) { - ImDrawFlags backup_flags = fg_draw_list->Flags; - fg_draw_list->Flags &= ~ImDrawFlags_AALines; // Disable AA on triangle outlines is more readable for very large and thin triangles. + fg_draw_list->PushDrawFlag(ImDrawFlags_AALines, false); // Disable AA on triangle outlines is more readable for very large and thin triangles. fg_draw_list->AddPolyline(triangle, 3, IM_COL32(255, 255, 0, 255), 1.0f, ImDrawFlags_Closed); - fg_draw_list->Flags = backup_flags; + fg_draw_list->PopDrawFlag(); } } TreePop(); @@ -17712,8 +17711,7 @@ void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, co // Draw wire-frame version of all triangles ImRect clip_rect = draw_cmd->ClipRect; ImRect vtxs_rect(FLT_MAX, FLT_MAX, -FLT_MAX, -FLT_MAX); - ImDrawFlags backup_flags = out_draw_list->Flags; - out_draw_list->Flags &= ~ImDrawFlags_AALines; // Disable AA on triangle outlines is more readable for very large and thin triangles. + out_draw_list->PushDrawFlag(ImDrawFlags_AALines, false); // Disable AA on triangle outlines is more readable for very large and thin triangles. for (unsigned int idx_n = draw_cmd->IdxOffset, idx_end = draw_cmd->IdxOffset + draw_cmd->ElemCount; idx_n < idx_end; ) { ImDrawIdx* idx_buffer = (draw_list->IdxBuffer.Size > 0) ? draw_list->IdxBuffer.Data : NULL; // We don't hold on those pointers past iterations as ->AddPolyline() may invalidate them if out_draw_list==draw_list @@ -17731,7 +17729,7 @@ void ImGui::DebugNodeDrawCmdShowMeshAndBoundingBox(ImDrawList* out_draw_list, co out_draw_list->AddRect(ImTrunc(clip_rect.Min), ImTrunc(clip_rect.Max), IM_COL32(255, 0, 255, 255)); // In pink: clipping rectangle submitted to GPU out_draw_list->AddRect(ImTrunc(vtxs_rect.Min), ImTrunc(vtxs_rect.Max), IM_COL32(0, 255, 255, 255)); // In cyan: bounding box of triangles } - out_draw_list->Flags = backup_flags; + out_draw_list->PopDrawFlag(); } // [DEBUG] Compute mask of inputs with the same codepoint. diff --git a/imgui.h b/imgui.h index 1548b6926..43944f023 100644 --- a/imgui.h +++ b/imgui.h @@ -2885,6 +2885,19 @@ struct ImGuiStorage #endif }; +// [Internal] Used for small stacks. Do not use. +template +struct ImSmallStack_ +{ + T LocalData[Capacity]; // FIXME: should evolve into using heap, e.g union {} with T* HeapData + add SZ_T Capacity. + SZ_T Size = 0; + + inline void clear() { Size = 0; } + inline T& back() { IM_ASSERT(Size > 0); return LocalData[Size - 1]; } + inline void push_back(const T& v) { IM_ASSERT(Size < Capacity); memcpy(&LocalData[Size], &v, sizeof(v)); Size++; } + inline void pop_back() { IM_ASSERT(Size > 0); Size--; } +}; + // Flags for ImGuiListClipper (currently not fully exposed in function calls: a future refactor will likely add this to ImGuiListClipper::Begin function equivalent) enum ImGuiListClipperFlags_ { @@ -3300,9 +3313,8 @@ enum ImDrawFlags_ // About usage of flags: // - "Prim" column: 'OK' = flag can be used to configure an individual AddXXX() call. - // - "Scope" column: 'OK' = initialized by ImGui based on Style options (e.g. whether anti-aliased is enabled). + // - "Scope" column: 'OK' = initialized by ImGui based on Style options (e.g. whether anti-aliased is enabled) + may be modified using ImDrawList::PushDrawFlag(). // OK(0)/OK(1) indicates whether this flag is set in the default ImGui Style settings. - // (flag will be possible to alter in a scope using PushDrawFlags()). // - Rounding default to ImDrawFlags_RoundCornersAll when 'rounding > 0'. // - So you only need to use the _RoundCorners flags if you want a special configuration (e.g. a rectangle with one rounded corner). @@ -3352,7 +3364,7 @@ enum ImDrawFlags_ ImDrawFlags_UseVtxOffset = 1 << 23, // -- OK(1) // Can emit 'VtxOffset > 0' to allow large meshes with 16-bit ImDrawIdx. Used by default when 'ImGuiBackendFlags_RendererHasVtxOffset' is enabled by the backend. // [Internal] - ImDrawFlags_AllowedInScope_ = ImDrawFlags_AAFill | ImDrawFlags_AALines | ImDrawFlags_AALineEnds | ImDrawFlags_StrokeLegacy | ImDrawFlags_TextNoPixelSnap | ImDrawFlags_UseTexForRoundCorners | ImDrawFlags_UseTexForStrokeLegacy | ImDrawFlags_UseVtxOffset, // [Internal] Values allowed in scope e.g. incoming PushDrawFlags() stack. + ImDrawFlags_AllowedInScope_ = ImDrawFlags_AAFill | ImDrawFlags_AALines | ImDrawFlags_AALineEnds | ImDrawFlags_StrokeLegacy | ImDrawFlags_TextNoPixelSnap | ImDrawFlags_UseTexForRoundCorners | ImDrawFlags_UseTexForStrokeLegacy | ImDrawFlags_UseVtxOffset, // [Internal] Values allowed in PushDrawFlag() scope. ImDrawFlags_RoundCornersMask_ = ImDrawFlags_RoundCornersAll | ImDrawFlags_RoundCornersNone, // [Internal] ImDrawFlags_StrokeMask_ = 0x07 << 17, // [Internal] ImDrawFlags_InvalidMask_ = ~0x7FFFFFF0, // [Internal] == 0x8000000F. Reserved to detect misuses. @@ -3373,7 +3385,7 @@ struct ImDrawList ImVector CmdBuffer; // Draw commands. Typically 1 command = 1 GPU draw call, unless the command is a callback. ImVector IdxBuffer; // Index buffer. Each command consume ImDrawCmd::ElemCount of those ImVector VtxBuffer; // Vertex buffer. - ImDrawFlags Flags; // Current flags for drawing primitives. You may poke into these to adjust anti-aliasing settings per-primitive. Will be exposed as PushDrawFlags(). + ImDrawFlags Flags; // Current flags for drawing primitives. You may poke into these to adjust anti-aliasing settings per-primitive. Alter with PushDrawFlag(). // [Internal, used while building lists] unsigned int _VtxCurrentIdx; // [Internal] generally == VtxBuffer.Size unless we are past 64K vertices, in which case this gets reset to 0. @@ -3386,6 +3398,7 @@ struct ImDrawList ImVector _ClipRectStack; // [Internal] ImVector _TextureStack; // [Internal] ImVector _CallbacksDataBuf; // [Internal] + ImSmallStack_ _DrawFlagsStack;// [Internal] float _FringeScale; // [Internal] anti-alias fringe is scaled by this value, this helps to keep things sharp while zooming at vertex buffer content float _InvFringeScale; // [internal] 1.0 / _FringeScale // FIXME: Consider renaming to _PixelDensity. bool _FringeScaleIsInteger; // [Internal] true if 1/_FringeScale is a whole number, used to select fast path for rendering @@ -3401,6 +3414,8 @@ struct ImDrawList IMGUI_API void PopClipRect(); IMGUI_API void PushTexture(ImTextureRef tex_ref); IMGUI_API void PopTexture(); + IMGUI_API void PushDrawFlag(ImDrawFlags flags, bool enabled); // [BETA] Please notify me if you are using this. + IMGUI_API void PopDrawFlag(); inline ImVec2 GetClipRectMin() const { const ImVec4& cr = _ClipRectStack.back(); return ImVec2(cr.x, cr.y); } inline ImVec2 GetClipRectMax() const { const ImVec4& cr = _ClipRectStack.back(); return ImVec2(cr.z, cr.w); } @@ -3542,7 +3557,6 @@ struct ImDrawList IMGUI_API void _AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags); IMGUI_API void _SelectFringeTexture(float screen_thickness, ImVec4* out_tex_uvs, float* out_fringe); IMGUI_API float _CalculateCenterBiasedOffset(float thickness); - }; // All draw data to render a Dear ImGui frame diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 77c689461..5a5c4892b 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10465,8 +10465,8 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::PopID(); ImGui::Spacing(); - ImDrawFlags backup_draw_list_flags = draw_list->Flags; - draw_list->Flags = (draw_list->Flags & ~ImDrawFlags_AllowedInScope_) | scope_flags; // Equivalent to an hypothetical PushDrawFlags() API + draw_list->PushDrawFlag(ImDrawFlags_AllowedInScope_, false); // Reset all existing (very unusual: done for this demo) + draw_list->PushDrawFlag(scope_flags, true); const ImDrawFlags flags = prim_stroke_flags | prim_other_flags; const ImDrawFlags fill_flags = flags & (ImDrawFlags_AAFill | ImDrawFlags_UseTexForRoundCorners); @@ -10599,11 +10599,12 @@ static void ShowExampleAppCustomRendering(bool* p_open) } ImGui::Dummy(ImVec2((sz + spacing) * 15.0f, (sz + spacing) * 4.0f)); + draw_list->PopDrawFlag(); + draw_list->PopDrawFlag(); + ImGui::PopItemFlag(); ImGui::PopItemWidth(); ImGui::EndTabItem(); - - draw_list->Flags = backup_draw_list_flags; } if (ImGui::BeginTabItem("Canvas")) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 05db80d74..5d9851972 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -470,6 +470,7 @@ void ImDrawList::_ResetForNewFrame() _ClipRectStack.resize(0); _TextureStack.resize(0); _CallbacksDataBuf.resize(0); + _DrawFlagsStack.clear(); _Path.resize(0); _Splitter.Clear(); CmdBuffer.push_back(ImDrawCmd()); @@ -488,6 +489,7 @@ void ImDrawList::_ClearFreeMemory() _ClipRectStack.clear(); _TextureStack.clear(); _CallbacksDataBuf.clear(); + _DrawFlagsStack.clear(); _Path.clear(); _Splitter.ClearFreeMemory(); } @@ -711,6 +713,24 @@ void ImDrawList::PopTexture() _OnChangedTexture(); } +// IMPORTANT: currently limited to 3 deep +void ImDrawList::PushDrawFlag(ImDrawFlags flags, bool enabled) +{ + IM_ASSERT((flags & ~ImDrawFlags_AllowedInScope_) == 0); + _DrawFlagsStack.push_back(Flags); + if (enabled) + Flags |= flags; + else + Flags &= ~flags; +} + +void ImDrawList::PopDrawFlag() +{ + IM_ASSERT_USER_ERROR_RET(_DrawFlagsStack.Size > 0, "Calling PopDrawFlags() too many times!"); + Flags = _DrawFlagsStack.back(); + _DrawFlagsStack.pop_back(); +} + // This is used by ImGui::PushFont()/PopFont(). It works because we never use _TextureIdStack[] elsewhere than in PushTexture()/PopTexture(). void ImDrawList::_SetTexture(ImTextureRef tex_ref) { @@ -3138,7 +3158,7 @@ void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_ radius = ImMax(0.01f, radius); - // Check overestimate first before testing details. + // Check overestimate first before testing details (+1.0f is intended, not FringeScale) if (radius < (thickness * 2.0f + 1.0f)) { const float unit_apothem = ImCos(IM_PI / (float)num_segments); @@ -5343,7 +5363,7 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) const int pitch = tex->Width; for (int ly = 0; ly < h; ly++) { - for (int lx = 0; lx < h; lx++) + for (int lx = 0; lx < w; lx++) write_ptr[lx] = SampleCorner((float)lx + 0.5f, (float)ly + 0.5f, line_normals, line_distances, num_lines) & DEBUG_TEX_CORNER_U8(lx, h); write_ptr += pitch; } @@ -5354,7 +5374,7 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) const int pitch = tex->Width; for (int ly = 0; ly < h; ly++) { - for (int lx = 0; lx < h; lx++) + for (int lx = 0; lx < w; lx++) write_ptr[lx] = IM_COL32(255, 255, 255, SampleCorner((float)lx + 0.5f, (float)ly + 0.5f, line_normals, line_distances, num_lines)) & DEBUG_TEX_CORNER_U32(lx, h); write_ptr += pitch; } From 2fdcf895c8b06622a0dbca8ee8f8edad58d1165b Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 28 Jul 2026 16:35:56 +0200 Subject: [PATCH 085/120] DrawList: moved _GetStrokePos() to a local inline function. --- imgui.h | 1 - imgui_draw.cpp | 21 ++++++++++----------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/imgui.h b/imgui.h index 43944f023..37ab1cba3 100644 --- a/imgui.h +++ b/imgui.h @@ -3547,7 +3547,6 @@ struct ImDrawList IMGUI_API void _OnChangedTexture(); IMGUI_API void _OnChangedVtxOffset(); IMGUI_API void _SetTexture(ImTextureRef tex_ref); - IMGUI_API ImDrawFlags _GetStrokePos(ImDrawFlags flags, ImDrawFlags default_stroke_pos); IMGUI_API int _CalcCircleAutoSegmentCount(float radius) const; IMGUI_API void _PathArcToFastEx(const ImVec2& center, float radius, int a_min_sample, int a_max_sample, int a_step); IMGUI_API void _PathArcToN(const ImVec2& center, float radius, float a_min, float a_max, int num_segments); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 5d9851972..7fbe9b3ac 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -925,6 +925,16 @@ void ImDrawList::_SelectFringeTexture(float screen_thickness, ImVec4* out_tex_uv // Scale the fringe so that the texture matches the line thickness. *out_fringe = _FringeScale * screen_thickness * tex_uvs.w; // tex_uvs.w = 1 / thickness } + +// We intently don't turn g_LEGACY_STROKES into ImDrawFlags_StrokeLegacy here. +// The earlier should use verbatim legacy code but will be removed before release once we confirm that both matches. +static inline ImDrawFlags _GetStrokePos(ImDrawFlags flags, ImDrawFlags default_stroke_pos) +{ + if (flags & ImDrawFlags_StrokeMask_) + return (flags & ImDrawFlags_StrokeMask_); + return default_stroke_pos; +} + IM_MSVC_RUNTIME_CHECKS_RESTORE extern bool g_LEGACY_STROKES; @@ -2189,17 +2199,6 @@ void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDr } } -// We intently don't turn g_LEGACY_STROKES into ImDrawFlags_StrokeLegacy here. -// The earlier should use verbatim legacy code but will be removed before release once we confirm that both matches. -IM_MSVC_RUNTIME_CHECKS_OFF -ImDrawFlags ImDrawList::_GetStrokePos(ImDrawFlags flags, ImDrawFlags default_stroke_pos) -{ - if (flags & ImDrawFlags_StrokeMask_) - return (flags & ImDrawFlags_StrokeMask_); - return default_stroke_pos; -} -IM_MSVC_RUNTIME_CHECKS_RESTORE - void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness, ImDrawFlags flags) { if (g_LEGACY_STROKES) IM_UNLIKELY From bd0f638d8b06e87271449e64765bf95b58b5d19e Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 28 Jul 2026 16:16:54 +0200 Subject: [PATCH 086/120] Docs: update changelog. --- docs/CHANGELOG.txt | 104 +++++++++++++++++++++++++++------------------ 1 file changed, 63 insertions(+), 41 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index a271b716b..9c435e85b 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -51,12 +51,12 @@ HOW TO UPDATE? textures instead of tessellated geometry to draw round corners. - Most of the work courtesy of @memononen, with bits from @ocornut, @thedmd, @potocpav. - Refer to our full guides: - - ImDrawList Vector Rendering Reference - https://github.com/ocornut/imgui/wiki/Draw-List - - 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 + - ImDrawList Vector Rendering Reference: + https://github.com/ocornut/imgui/wiki/Draw-List + - 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 - (Breaking) 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. @@ -64,37 +64,44 @@ HOW TO UPDATE? - Use `ImDrawFlags_StrokeLegacy` to use old offset if required. But you might as well apply the offset manually! - Generally better to use to newly introduced `AddLineH()`, `AddLineV()` functions. 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 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, y1}, {x2, y2}, col, thickness, ImDrawFlags_StrokeLegacy); // Offset by +0.5f + disable AA ends. - == AddLine({x1 + 0.5f, y1 + 0.5f}, {x2+ 0.5f, y2 + 0.5f}, col, thickness); // Same - This reapplies the old offset, and should get you exactly the same result you previously got for thickness=1.0f lines. - But said result was sometimes ambiguous and renderer dependent. (#3116, #3258, #2441) - - Old AddLine() did not have 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 could - have added fudge offsets here and there which are not necessary anymore or may be simplified. -- AddPolyline(), PathStroke(), AddTriangle(): the algorithm to render lines got overhauled. (#2183, #2964, #7972) + - 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, y1}, {x2, y2}, col, thickness, ImDrawFlags_StrokeLegacy); // Offset by +0.5f + disable AA ends. + == AddLine({x1 + 0.5f, y1 + 0.5f}, {x2+ 0.5f, y2 + 0.5f}, col, thickness); // Same + This reapplies the old offset, and should get you exactly the same result you previously got for thickness=1.0f lines. + But said result was sometimes ambiguous and renderer dependent. (#3116, #3258, #2441) + - Old `AddLine()` did not have 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 could + have added fudge offsets here and there which are not necessary anymore or may be simplified. +- AddPolyline(), PathStroke(), AddTriangle(): the algorithm to render lines got overhauled. + (#2183, #2964, #7972) - 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 bevelled 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. + - 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 bevelled 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. - - Added anti-aliasing ends, most noticeable for thick lines. - - Added `ImDrawFlags_AALineEnds` flag to enable anti-aliased line ends. Enabling AA Ends - by default would be more "correct", but it is slower and increase vertex/index data. + - New rendering code fixes (subtle) inconsistencies between graphics API (e.g. DirectX vs OpenGL) + using different diamond exits rules. New code should work precisely the same regardless + of graphics API. (#3116, #3258, #2441) + - Added `ImDrawFlags_AALineEnds` flag to enable anti-aliased line ends. + - Most noticeable for thick lines. + - Helps fills minor gaps if combining multiple individual lines. + - Enabling AA Ends by default would be more "correct", but it is slower and increase vertex/index data.s - Added `ImDrawFlags_MiterOnly` flag to disable using beveled corner (closer to legacy rendering, slightly faster, but corners sharpers than 90 degrees may expand far out). Automatically used by AddRect(), AddCircle(), AddNgon(), AddEllipse() etc functions @@ -111,17 +118,18 @@ HOW TO UPDATE? - Integer-aligned coordinates and thicknesses will automatically use baked textures, saving on both CPU and vertex/index data. - Added `ImFontAtlasFlags_NoBakedRoundCorners` to disable baking round corners in font atlas. - - Added `ImDrawListFlags_RoundCornersUseTex` to disable using round corners. + - Added `ImDrawFlags_UseTexForRoundCorners` to enable using round corners. - AddRectFilled(): non-integer coordinates will now display anti-aliased edges. (#6971) -- Improved minor mismatches when overlapping strokes and filled shapes, e.g. when using inside strokes, +- Improved minor mismatches when overlapping strokes and filled shapes, + e.g. when using inside strokes, - `AddRect()` and `AddRectFilled()` with rounding now overlap better. (#3656) - `AddCircle()` and `AddCircleFilled()` now overlap better. -- Added flags to specify stroke position in all ImDrawList stroking functions: +- Added flags to specify stroke position in all `ImDrawList` stroking functions: - `ImDrawFlags_StrokeInside` (default for closed primitives and AddLineH, AddLineV) - `ImDrawFlags_StrokeCenter` (default for paths, bezier and AddLine) - `ImDrawFlags_StrokeCenterAligned` - `ImDrawFlags_StrokeOutside` - - `ImDrawFlags_StrokeLegacy` + - `ImDrawFlags_StrokeLegacy` (may be set as default for a given scope using PushItemFlag()). - Legacy code was generally applying +0.50f offset 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 slightly initial offset. @@ -129,7 +137,7 @@ HOW TO UPDATE? - 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) - - Recap: + - Read our Wiki guides! Recap: ------------------------------------------------------------------------ Legacy Inside Outside Center CenterAligned -------------------------------------------------------------------------------- @@ -137,7 +145,8 @@ HOW TO UPDATE? Thickness=2.0f blurry sharp sharp sharp sharp Thickness=3.0f sharp sharp sharp blurry sharp ------------------------------------------------------------------------ -- Added ImDrawList::PushDrawFlag()/PopDrawFlag() to alter certain flags for a scope. +- Added ImDrawList::PushDrawFlag()/PopDrawFlag() to alter certain flags for a scope, + e.g. `PushDrawFlag(ImDrawFlags_StrokeLegacy, true);` enforce legacy tweaks. - (Breaking) AddRect, AddCircle, AddNgon, AddEllipse: 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 @@ -150,9 +159,22 @@ HOW TO UPDATE? - `ImDrawListFlags_AllowVtxOffset` -> `ImDrawFlags_UseVtxOffset` - `ImDrawListFlags_TextNoPixelSnap` -> `ImDrawFlags_TextNoPixelSnap` Unifying them allows easily using them for both per-primitives and scope alterations. -- (Breaking) Enabling ImFontAtlasFlags_NoBakedLines in the font atlas will disable +- (Breaking) Enabling `ImFontAtlasFlags_NoBakedLines` in the font atlas will disable support for anti-aliased lines. -- `ImDrawFlags_TextNoPixelSnap` may be passed to low-level RenderText() primitive. (#9417) +- About `ImDrawFlags_StrokeLegacy`: + - This is designed to emulate old coordinates: + - Most shapes are "StrokeCenter". + - AddLine() adds a +0.5f,+0.5f offset. + - Closed shapes user miter angles. + - But there are known difference between legacy code and ImDrawFlags_StrokeLegacy: + - Thick shapes with acute angles will now preserve thickness. + - Thick shapes with acute angles will protrude a little more. +- About disabling anti-aliasing: + - Disabling anti-aliasing is now emulated by using different UV coordinates. + It does not result in a performance increase any more (however the tendency + is that the new code behave better than old code). + - 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) - Tweaked line rendering in various locations to avoid blurryness: - Windows: title-bar and menu-bar border (when thickness>1.0f). - Tables: borders (when thickness>1.0f). From f511ce18f9edd6ce551836b5ca7c2d72356d20a2 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 28 Jul 2026 18:51:22 +0200 Subject: [PATCH 087/120] DrawList: tweak _SelectFringeTexture(). move more-unlikely screen_thickness tests within already-unlikely block. re-add removed asserts. --- imgui_draw.cpp | 51 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 7fbe9b3ac..c975beab1 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -906,23 +906,18 @@ void ImDrawList::_SelectFringeTexture(float screen_thickness, ImVec4* out_tex_uv // We assume that the screen_thickness has been clamped to 1.0 by the caller. IM_ASSERT_PARANOID(screen_thickness >= 1.0f); - ImVec4 tex_uvs; + // Handle the thickness between 1.0 - IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH]. + // We use super sampled textures in this range to make texture changes less noticeable. + // There are IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX+1 textures, where 0 maps to 1.0 and IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX maps to IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH (4.0). + int texture_idx; if (screen_thickness <= IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH) IM_LIKELY - { - // Handle the thickness between 1.0 - IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH]. - // We use super sampled textures in this range to make texture changes less noticeable. - // There are IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX+1 textures, where 0 maps to 1.0 and IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX maps to IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH (4.0). - int texture_idx = (int)((screen_thickness - 1.0f) * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + 0.5f); - tex_uvs = _Data->TexUvLines[(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1) + texture_idx]; - } + texture_idx = (int)((screen_thickness - 1.0f) * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + 0.5f) + (IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1); else - { - const int texture_idx = ImMin((int)(screen_thickness + 0.5f), IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1); - tex_uvs = _Data->TexUvLines[texture_idx]; - } + texture_idx = ImMin((int)(screen_thickness + 0.5f), IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1); - *out_tex_uvs = tex_uvs; // Scale the fringe so that the texture matches the line thickness. + ImVec4 tex_uvs = _Data->TexUvLines[texture_idx]; + *out_tex_uvs = tex_uvs; *out_fringe = _FringeScale * screen_thickness * tex_uvs.w; // tex_uvs.w = 1 / thickness } @@ -951,15 +946,21 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 return; float screen_thickness = thickness * _InvFringeScale; - if (screen_thickness < 1.0f / 255.0f) - return; - if (screen_thickness < 1.0f) + if (screen_thickness < 1.0f) IM_UNLIKELY { + if (screen_thickness < 1.0f / 255.0f) IM_UNLIKELY + return; col = ImAlphaMultiply(col, screen_thickness); screen_thickness = 1.0f; thickness = _FringeScale; } + // If this assert triggers on legacy code: + // - 1.92.8 (2025/05): swapped two last parameters order: flags, thickness --> thickness, flags. This should normally be caught by compile-time type-checking. + // - 1.92.8 (2025/05): changed value of ImDrawList_Closed which was previously guaranteed to be == 1. Hardcoded use of 1 or true should be replaced. + // Read more details near AddRect() + see "API BREAKING CHANGES" section for 1.82, 1.90 and 1.92.8. + IM_ASSERT_USER_ERROR_RET((flags & ImDrawFlags_InvalidMask_) == 0, "Incorrect parameter. Did you swap 'thickness' and 'flags'?"); + // Allocate data for temp buffers _Data->TempBuffer.reserve_discard(points_count * 2); ImVec2* normals = _Data->TempBuffer.Data; @@ -2212,10 +2213,10 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t return; float screen_thickness = thickness * _InvFringeScale; - if (screen_thickness < 1.0f / 255.0f) IM_UNLIKELY - return; if (screen_thickness < 1.0f) IM_UNLIKELY { + if (screen_thickness < 1.0f / 255.0f) IM_UNLIKELY + return; col = ImAlphaMultiply(col, screen_thickness); screen_thickness = 1.0f; thickness = _FringeScale; @@ -2590,10 +2591,10 @@ void ImDrawList::_AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 c void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags) { float screen_thickness = thickness * _InvFringeScale; - if (screen_thickness < 1.0f / 255.0f) - return; - if (screen_thickness < 1.0f) + if (screen_thickness < 1.0f) IM_UNLIKELY { + if (screen_thickness < 1.0f / 255.0f) IM_UNLIKELY + return; col = ImAlphaMultiply(col, screen_thickness); screen_thickness = 1.0f; thickness = _FringeScale; @@ -2930,7 +2931,7 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c // The rect is smaller than pixel in one dimension. if (min_dim < _FringeScale) IM_UNLIKELY { - if (min_dim < (1.0f / 255.0f) * _FringeScale) + if (min_dim < (1.0f / 255.0f) * _FringeScale) IM_UNLIKELY return; if (width < _FringeScale) { @@ -3069,7 +3070,6 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu radius -= 0.5f; stroke_pos = ImDrawFlags_StrokeCenter; } - radius = ImMax(0.01f, radius); float outer_radius = radius; @@ -3154,7 +3154,6 @@ void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_ radius -= 0.5f; stroke_pos = ImDrawFlags_StrokeCenter; } - radius = ImMax(0.01f, radius); // Check overestimate first before testing details (+1.0f is intended, not FringeScale) @@ -3195,7 +3194,7 @@ void ImDrawList::AddNgonFilled(const ImVec2& center, float radius, ImU32 col, in const float diameter = radius * 2.0f; if (diameter < _FringeScale) IM_UNLIKELY { - if (diameter < (1.0f / 255.0f) * _FringeScale) + if (diameter < (1.0f / 255.0f) * _FringeScale) IM_UNLIKELY return; col = ImAlphaMultiply(col, diameter * _InvFringeScale); radius = _FringeScale * 0.5f; @@ -3246,7 +3245,7 @@ void ImDrawList::AddEllipseFilled(const ImVec2& center, const ImVec2& radius, Im // The ellipse is smaller than pixel in one dimension. if (min_dim < _FringeScale) IM_UNLIKELY { - if (min_dim < (1.0f / 255.0f) * _FringeScale) + if (min_dim < (1.0f / 255.0f) * _FringeScale) IM_UNLIKELY return; if (diameter_x < _FringeScale) { From 67bcd9d5451b1ef019598f0340964cf41cee0856 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 28 Jul 2026 19:01:43 +0200 Subject: [PATCH 088/120] DrawList: moved no-AA handling into _SelectFringeTexture(). --- imgui.h | 2 +- imgui_draw.cpp | 96 ++++++++++++++++---------------------------------- 2 files changed, 31 insertions(+), 67 deletions(-) diff --git a/imgui.h b/imgui.h index 37ab1cba3..0e2566a84 100644 --- a/imgui.h +++ b/imgui.h @@ -3554,7 +3554,7 @@ struct ImDrawList IMGUI_API void _AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, float t, const ImVec4& tex_uvs, ImDrawFlags flags); IMGUI_API void _AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness, ImDrawFlags flags); IMGUI_API void _AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags); - IMGUI_API void _SelectFringeTexture(float screen_thickness, ImVec4* out_tex_uvs, float* out_fringe); + IMGUI_API void _SelectFringeTexture(float screen_thickness, ImVec4* out_tex_uvs, float* out_fringe, ImDrawFlags flags); IMGUI_API float _CalculateCenterBiasedOffset(float thickness); }; diff --git a/imgui_draw.cpp b/imgui_draw.cpp index c975beab1..8e23dfd95 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -901,24 +901,33 @@ static ImU32 ImAlphaMultiply(ImU32 col, float alpha_mul) } // Select texture and adjust fringe size for specified line thickness. -void ImDrawList::_SelectFringeTexture(float screen_thickness, ImVec4* out_tex_uvs, float* out_fringe) +void ImDrawList::_SelectFringeTexture(float screen_thickness, ImVec4* out_tex_uvs, float* out_fringe, ImDrawFlags flags) { // We assume that the screen_thickness has been clamped to 1.0 by the caller. IM_ASSERT_PARANOID(screen_thickness >= 1.0f); - // Handle the thickness between 1.0 - IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH]. - // We use super sampled textures in this range to make texture changes less noticeable. - // There are IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX+1 textures, where 0 maps to 1.0 and IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX maps to IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH (4.0). - int texture_idx; - if (screen_thickness <= IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH) IM_LIKELY - texture_idx = (int)((screen_thickness - 1.0f) * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + 0.5f) + (IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1); - else - texture_idx = ImMin((int)(screen_thickness + 0.5f), IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1); + if (flags & ImDrawFlags_AALines) + { + // Handle the thickness between 1.0 - IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH]. + // We use super sampled textures in this range to make texture changes less noticeable. + // There are IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX+1 textures, where 0 maps to 1.0 and IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX maps to IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH (4.0). + int texture_idx; + if (screen_thickness <= IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH) IM_LIKELY + texture_idx = (int)((screen_thickness - 1.0f) * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + 0.5f) + (IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1); + else + texture_idx = ImMin((int)(screen_thickness + 0.5f), IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1); - // Scale the fringe so that the texture matches the line thickness. - ImVec4 tex_uvs = _Data->TexUvLines[texture_idx]; - *out_tex_uvs = tex_uvs; - *out_fringe = _FringeScale * screen_thickness * tex_uvs.w; // tex_uvs.w = 1 / thickness + // Scale the fringe so that the texture matches the line thickness. + ImVec4 tex_uvs = _Data->TexUvLines[texture_idx]; + *out_tex_uvs = tex_uvs; + *out_fringe = _FringeScale * screen_thickness * tex_uvs.w; // tex_uvs.w = 1 / thickness + } + else + { + // For no anti-alias we use fully opaque texture, and no fringe expansion. + *out_tex_uvs = ImVec4(_Data->TexUvWhitePixel.x, _Data->TexUvWhitePixel.x, _Data->TexUvWhitePixel.y, 0.0f); // u0, u1, v, 1/thickness (unused) + *out_fringe = 0.0f; + } } // We intently don't turn g_LEGACY_STROKES into ImDrawFlags_StrokeLegacy here. @@ -993,23 +1002,10 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 sqr_lengths[points_count - 1] = 0.0f; } + flags |= Flags; ImVec4 tex_uvs; float fringe; - flags |= Flags; - if (flags & ImDrawFlags_AALines) - { - _SelectFringeTexture(screen_thickness, &tex_uvs, &fringe); - } - else - { - // For no anti-alias we use fully opaque texture, and no fringe expansion. - tex_uvs.x = _Data->TexUvWhitePixel.x; // u0 - tex_uvs.y = _Data->TexUvWhitePixel.x; // u1 - tex_uvs.z = _Data->TexUvWhitePixel.y; // v - tex_uvs.w = 0.0f; // 1/thickness, unused. - fringe = 0.0f; - flags &= ~ImDrawFlags_AALineEnds; - } + _SelectFringeTexture(screen_thickness, &tex_uvs, &fringe, flags); const ImU32 col_trans = col & ~IM_COL32_A_MASK; @@ -1080,7 +1076,6 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 dir.x = n1.y; dir.y = -n1.x; - if (flags & ImDrawFlags_SquareCap) { p1.x -= dir.x * half_thickness; @@ -1694,7 +1689,7 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun float miter_offset_y = (n0.y + n1.y) * miter_scale_factor; const float miter_distance_sqr = miter_offset_x * miter_offset_x + miter_offset_y * miter_offset_y; - bool bevel = miter_distance_sqr > miter_distance_limit_sqr; + const bool bevel = miter_distance_sqr > miter_distance_limit_sqr; if (bevel) { // Limit inner bevel so that it is does not shoot out outside the polygon. @@ -1756,14 +1751,11 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun // Add indices for fill for (int i = 2; i < points_count; i++) - { IM_APPEND_TRI(vtx_inner_idx, vtx_inner_idx + i - 1, vtx_inner_idx + i); - } const int idx_used = (int)(_IdxWritePtr - start_idx_ptr); const int vtx_used = (int)(_VtxWritePtr - start_vtx_ptr); IM_ASSERT(idx_used <= idx_count && vtx_used <= vtx_count); - if (idx_used < idx_count || vtx_used < vtx_count) PrimUnreserve(idx_count - idx_used, vtx_count - vtx_used); } @@ -2222,23 +2214,10 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t thickness = _FringeScale; } + flags |= Flags; ImVec4 tex_uvs; float fringe; - flags |= Flags; - if (flags & ImDrawFlags_AALines) - { - _SelectFringeTexture(screen_thickness, &tex_uvs, &fringe); - } - else - { - // For no anti-alias we use fully opaque texture, and no fringe expansion. - tex_uvs.x = _Data->TexUvWhitePixel.x; // u0 - tex_uvs.y = _Data->TexUvWhitePixel.x; // u1 - tex_uvs.z = _Data->TexUvWhitePixel.y; // v - tex_uvs.w = 0.0f; // 1/thickness, unused. - fringe = 0.0f; - flags &= ~ImDrawFlags_AALineEnds; - } + _SelectFringeTexture(screen_thickness, &tex_uvs, &fringe, flags); float dir_x = p2.x - p1.x; float dir_y = p2.y - p1.y; @@ -2289,7 +2268,6 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t float p1_y = p1.y; float p2_x = p2.x; float p2_y = p2.y; - if (flags & ImDrawFlags_SquareCap) { p1_x -= dir_x * half_thickness; @@ -2600,22 +2578,10 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, thickness = _FringeScale; } + flags |= Flags; ImVec4 tex_uvs; float fringe; - flags |= Flags; - if (flags & ImDrawFlags_AALines) - { - _SelectFringeTexture(screen_thickness, &tex_uvs, &fringe); - } - else - { - // For no anti-alias we use fully opaque texture, and no fringe expansion. - tex_uvs.x = _Data->TexUvWhitePixel.x; // u0 - tex_uvs.y = _Data->TexUvWhitePixel.x; // u1 - tex_uvs.z = _Data->TexUvWhitePixel.y; // v - tex_uvs.w = 0.0f; // 1/thickness, unused. - fringe = 0.0f; - } + _SelectFringeTexture(screen_thickness, &tex_uvs, &fringe, flags); const int auto_seg_count = _CalcCircleAutoSegmentCount(rounding); int arc_step, arc_step_count; @@ -2693,10 +2659,8 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, // Arc if (is_rounded) - { for (int i = 0; i < arc_point_count - 1; i++) IM_APPEND_TRI(stem_idx, arc_idx + i, arc_idx + i + 1); - } // (keep braces because of macro above) // Connect with previous IM_APPEND_TRI(base_idx + 0, arc_idx, stem_idx); @@ -2715,7 +2679,6 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, const int idx_used = (int)(_IdxWritePtr - start_idx_ptr); const int vtx_used = (int)(_VtxWritePtr - start_vtx_ptr); IM_ASSERT_PARANOID(idx_used <= idx_count && vtx_used <= vtx_count); - if (idx_used < idx_count || vtx_used < vtx_count) PrimUnreserve(idx_count - idx_used, vtx_count - vtx_used); } @@ -2948,6 +2911,7 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c return; } + // When rounding non-integer and under threshold, has_rounding may be false but still affect is_truncated. Likely not worth catering for. const bool has_rounding = (flags & ImDrawFlags_RoundCornersMask_) != ImDrawFlags_RoundCornersNone && IM_HAS_ROUNDING(rounding); const bool is_truncated = IM_IS_TRUNCATED4(p_min.x, p_min.y, p_max.x, p_max.y) && IM_IS_TRUNCATED(rounding); From 5181e95a465a30a51795a225a0ed017a8723d2e6 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 28 Jul 2026 18:27:36 +0200 Subject: [PATCH 089/120] DrawList: removed g_LEGACY_STROKES. --- imgui.cpp | 17 -------------- imgui_draw.cpp | 38 ++++++++---------------------- imgui_tables.cpp | 18 ++------------- imgui_widgets.cpp | 59 ++++++++--------------------------------------- 4 files changed, 22 insertions(+), 110 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index c4c24af0a..23ef0bd6b 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1382,9 +1382,6 @@ static const ImVec2 TOOLTIP_DEFAULT_PIVOT_TOUCH = ImVec2(0.5f, 1.0f); // Multi // [SECTION] FORWARD DECLARATIONS //------------------------------------------------------------------------- -extern bool g_LEGACY_STROKES; -bool g_LEGACY_STROKES = false; - static void SetCurrentWindow(ImGuiWindow* window); static ImGuiWindow* CreateNewWindow(const char* name, ImGuiWindowFlags flags); static ImVec2 CalcNextScrollFromScrollTargetAndClamp(ImGuiWindow* window); @@ -5939,9 +5936,6 @@ void ImGui::NewFrame() g.DebugBeginReturnValueCullDepth = -1; #endif - g_LEGACY_STROKES = g.IO.KeyShift; - ImGui::Checkbox("g_LEGACY_STROKES", &g_LEGACY_STROKES); - CallContextHooks(&g, ImGuiContextHookType_NewFramePost); } @@ -7326,9 +7320,6 @@ static void ImGui::RenderWindowOuterBorders(ImGuiWindow* window) if (g.Style.FrameBorderSize > 0 && !(window->Flags & ImGuiWindowFlags_NoTitleBar)) { float y = window->Pos.y + window->TitleBarHeight - 1; - if (g_LEGACY_STROKES) - window->DrawList->AddLineH(window->Pos.x + border_size * 0.5f, window->Pos.x + window->Size.x - border_size * 0.5f, y, border_col, g.Style.FrameBorderSize); - else window->DrawList->AddLineH(window->Pos.x + border_size, window->Pos.x + window->Size.x - border_size, y, border_col, g.Style.FrameBorderSize, ImDrawFlags_StrokeCenterBiased); } } @@ -7397,12 +7388,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar menu_bar_rect.ClipWith(window->Rect()); // Soft clipping, in particular child window don't have minimum size covering the menu bar so this is useful for them. window->DrawList->AddRectFilled(menu_bar_rect.Min, menu_bar_rect.Max, GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawFlags_RoundCornersTop); if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y) - { - if (g_LEGACY_STROKES) - window->DrawList->AddLineH(menu_bar_rect.Min.x + window_border_size * 0.5f, menu_bar_rect.Max.x - window_border_size * 0.5f, menu_bar_rect.Max.y, GetColorU32(ImGuiCol_Border), style.FrameBorderSize); - else window->DrawList->AddLineH(menu_bar_rect.Min.x + window_border_size, menu_bar_rect.Max.x - window_border_size, menu_bar_rect.Max.y, GetColorU32(ImGuiCol_Border), style.FrameBorderSize, ImDrawFlags_StrokeCenterBiased); - } } // Scrollbars @@ -18347,9 +18333,6 @@ void ImGui::DebugDrawLineExtents(ImU32 col) float line_y1 = (window->DC.IsSameLine ? window->DC.CursorPosPrevLine.y : window->DC.CursorPos.y); float line_y2 = line_y1 + (window->DC.IsSameLine ? window->DC.PrevLineSize.y : window->DC.CurrLineSize.y); window->DrawList->AddLineH(curr_x - 4.0f, curr_x + 5.0f, line_y1, col, 1.0f); - if (g_LEGACY_STROKES) - window->DrawList->AddLineV(curr_x - 0.5f, line_y1, line_y2, col, 1.0f); - else window->DrawList->AddLineV(curr_x, line_y1, line_y2, col, 1.0f); window->DrawList->AddLineH(curr_x - 4.0f, curr_x + 5.0f, line_y2, col, 1.0f); } diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 8e23dfd95..30283c8fb 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -930,8 +930,6 @@ void ImDrawList::_SelectFringeTexture(float screen_thickness, ImVec4* out_tex_uv } } -// We intently don't turn g_LEGACY_STROKES into ImDrawFlags_StrokeLegacy here. -// The earlier should use verbatim legacy code but will be removed before release once we confirm that both matches. static inline ImDrawFlags _GetStrokePos(ImDrawFlags flags, ImDrawFlags default_stroke_pos) { if (flags & ImDrawFlags_StrokeMask_) @@ -941,16 +939,8 @@ static inline ImDrawFlags _GetStrokePos(ImDrawFlags flags, ImDrawFlags default_s IM_MSVC_RUNTIME_CHECKS_RESTORE -extern bool g_LEGACY_STROKES; - void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags) { - if (g_LEGACY_STROKES) - { - AddPolylineLegacy(points, points_count, col, thickness, flags); - return; - } - if (points_count < 2 || (col & IM_COL32_A_MASK) == 0) return; @@ -2194,13 +2184,6 @@ void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDr void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness, ImDrawFlags flags) { - if (g_LEGACY_STROKES) IM_UNLIKELY - { - ImVec2 points[2] = { p1, p2 }; - AddPolylineLegacy(points, 2, col, thickness, flags); - return; - } - if ((col & IM_COL32_A_MASK) == 0) IM_UNLIKELY return; @@ -2343,7 +2326,7 @@ void ImDrawList::AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float th flags |= Flags; ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeCenter); - if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) + if (stroke_pos == ImDrawFlags_StrokeLegacy) { _AddLine(ImVec2(p1.x + 0.5f, p1.y + 0.5f), ImVec2(p2.x + 0.5f, p2.y + 0.5f), col, thickness, flags); return; @@ -2359,7 +2342,7 @@ void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float th flags |= Flags; ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); - if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) + if (stroke_pos == ImDrawFlags_StrokeLegacy) { _AddLine(ImVec2(min_x + 0.5f, y + 0.5f), ImVec2(max_x + 0.5f, y + 0.5f), col, thickness, flags); return; @@ -2405,7 +2388,7 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th flags |= Flags; ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); - if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) + if (stroke_pos == ImDrawFlags_StrokeLegacy) { _AddLine(ImVec2(x + 0.5f, min_y + 0.5f), ImVec2(x + 0.5f, max_y + 0.5f), col, thickness, flags); return; @@ -2702,8 +2685,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl flags |= Flags; ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); - - if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) IM_UNLIKELY + if (stroke_pos == ImDrawFlags_StrokeLegacy) IM_UNLIKELY { if (Flags & ImDrawFlags_AALines) PathRect(ImVec2(p_min.x + 0.50f, p_min.y + 0.50f), ImVec2(p_max.x - 0.50f, p_max.y - 0.50f), rounding, flags); @@ -2986,8 +2968,9 @@ void ImDrawList::AddQuad(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, c if ((col & IM_COL32_A_MASK) == 0) return; - ImDrawFlags stroke_pos = g_LEGACY_STROKES ? ImDrawFlags_StrokeLegacy : _GetStrokePos(flags, ImDrawFlags_StrokeInside); + ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); flags = (flags & ~ImDrawFlags_StrokeMask_) | stroke_pos; + const ImVec2 points[4] = { p1, p2, p3, p4 }; AddPolyline(points, 4, col, thickness, flags | ImDrawFlags_Closed); } @@ -3006,7 +2989,7 @@ void ImDrawList::AddTriangle(const ImVec2& p1, const ImVec2& p2, const ImVec2& p if ((col & IM_COL32_A_MASK) == 0) return; - ImDrawFlags stroke_pos = g_LEGACY_STROKES ? ImDrawFlags_StrokeLegacy : _GetStrokePos(flags, ImDrawFlags_StrokeInside); + ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); flags = (flags & ~ImDrawFlags_StrokeMask_) | stroke_pos; const ImVec2 points[3] = { p1, p2, p3 }; @@ -3029,7 +3012,7 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu flags |= Flags; ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); - if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) IM_UNLIKELY + if (stroke_pos == ImDrawFlags_StrokeLegacy) IM_UNLIKELY { radius -= 0.5f; stroke_pos = ImDrawFlags_StrokeCenter; @@ -3113,7 +3096,7 @@ void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_ flags |= Flags; ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); - if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) IM_UNLIKELY + if (stroke_pos == ImDrawFlags_StrokeLegacy) IM_UNLIKELY { radius -= 0.5f; stroke_pos = ImDrawFlags_StrokeCenter; @@ -3177,8 +3160,7 @@ void ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 co return; ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); - - if (g_LEGACY_STROKES || stroke_pos == ImDrawFlags_StrokeLegacy) + if (stroke_pos == ImDrawFlags_StrokeLegacy) stroke_pos = ImDrawFlags_StrokeCenter; ImVec2 rad = radius; diff --git a/imgui_tables.cpp b/imgui_tables.cpp index 469311bcf..0391bf735 100644 --- a/imgui_tables.cpp +++ b/imgui_tables.cpp @@ -2947,8 +2947,6 @@ static ImU32 TableGetColumnBorderCol(ImGuiTable* table, int order_n, int column_ return table->BorderColorLight; } -extern bool g_LEGACY_STROKES; - // FIXME-TABLE: This is a mess, need to redesign how we render borders (as some are also done in TableEndRow) void ImGui::TableDrawBorders(ImGuiTable* table) { @@ -2963,11 +2961,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) // Draw inner border and resizing feedback ImGuiTableInstanceData* table_instance = TableGetInstanceData(table, table->InstanceCurrent); const float border_size = TABLE_BORDER_SIZE; - float draw_y1; - if (g_LEGACY_STROKES) - draw_y1 = ImMax(table->InnerRect.Min.y, (table->FreezeRowsCount >= 1 ? table->InnerRect.Min.y : table->WorkRect.Min.y) + table->AngledHeadersHeight) + ((table->Flags & ImGuiTableFlags_BordersOuterH) ? 1.0f : 0.0f); - else - draw_y1 = ImMax(table->InnerRect.Min.y, (table->FreezeRowsCount >= 1 ? table->InnerRect.Min.y : table->WorkRect.Min.y) + table->AngledHeadersHeight) + ((table->Flags & ImGuiTableFlags_BordersOuterH) ? border_size : 0.0f); + const float draw_y1 = ImMax(table->InnerRect.Min.y, (table->FreezeRowsCount >= 1 ? table->InnerRect.Min.y : table->WorkRect.Min.y) + table->AngledHeadersHeight) + ((table->Flags & ImGuiTableFlags_BordersOuterH) ? border_size : 0.0f); const float draw_y2_body = table->InnerRect.Max.y; const float draw_y2_head = table->IsUsingHeaders ? ImMin(table->InnerRect.Max.y, (table->FreezeRowsCount >= 1 ? table->InnerRect.Min.y : table->WorkRect.Min.y) + table_instance->LastTopHeadersRowHeight) : draw_y1; if (table->Flags & ImGuiTableFlags_BordersInnerV) @@ -3038,12 +3032,7 @@ void ImGui::TableDrawBorders(ImGuiTable* table) const float h_inset = (table->Flags & ImGuiTableFlags_BordersOuterV) ? border_size : 0.0f; const float border_y = table->RowPosY2; if (border_y >= table->BgClipRect.Min.y && border_y < table->BgClipRect.Max.y) - { - if (g_LEGACY_STROKES) - inner_drawlist->AddLineH(table->BorderX1, table->BorderX2, border_y, table->BorderColorLight, border_size); - else inner_drawlist->AddLineH(table->BorderX1 + h_inset, table->BorderX2 - h_inset, border_y, table->BorderColorLight, border_size); - } } inner_drawlist->PopClipRect(); @@ -3637,10 +3626,7 @@ void ImGui::TableAngledHeadersRowEx(ImGuiID row_id, float angle, float max_label { // Draw border const float border_size = TABLE_BORDER_SIZE; - if (g_LEGACY_STROKES) - draw_list->AddLine(bg_shape[0] - ImVec2(border_size * 0.5f, 0.0f), bg_shape[3] - ImVec2(border_size * 0.5f, 0.0f), TableGetColumnBorderCol(table, order_n, column_n), border_size); - else - draw_list->AddLine(bg_shape[0] + ImVec2(border_size * 0.5f, 0.0f), bg_shape[3] + ImVec2(border_size * 0.5f, 0.0f), TableGetColumnBorderCol(table, order_n, column_n), border_size); + draw_list->AddLine(bg_shape[0] + ImVec2(border_size * 0.5f, 0.0f), bg_shape[3] + ImVec2(border_size * 0.5f, 0.0f), TableGetColumnBorderCol(table, order_n, column_n), border_size); } } PopClipRect(); diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index d5d3facbd..81262b9d2 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -903,8 +903,6 @@ bool ImGui::ArrowButton(const char* str_id, ImGuiDir dir) return ArrowButtonEx(str_id, dir, ImVec2(sz, sz), ImGuiButtonFlags_None); } -extern bool g_LEGACY_STROKES; - // Button to close a window bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos) { @@ -934,7 +932,7 @@ bool ImGui::CloseButton(ImGuiID id, const ImVec2& pos) window->DrawList->AddRectFilled(bb.Min, bb.Max, bg_col); RenderNavCursor(bb, id, ImGuiNavRenderCursorFlags_Compact); const ImU32 cross_col = GetColorU32(ImGuiCol_Text); - const ImVec2 cross_center = g_LEGACY_STROKES ? (bb.GetCenter() - ImVec2(0.5f, 0.5f)) : bb.GetCenter(); + const ImVec2 cross_center = bb.GetCenter(); const float cross_extent = g.FontSize * 0.5f * 0.7071f - 1.0f; const float cross_thickness = 1.0f * (float)(int)g.Style._MainScale; // FIXME-DPI window->DrawList->AddLine(cross_center + ImVec2(+cross_extent, +cross_extent), cross_center + ImVec2(-cross_extent, -cross_extent), cross_col, cross_thickness); @@ -1759,11 +1757,7 @@ void ImGui::SeparatorTextEx(ImGuiID id, const char* label, const char* label_end const float sep1_x1 = pos.x; const float sep2_x2 = bb.Max.x; - float seps_y; - if (g_LEGACY_STROKES) - seps_y = ImTrunc((bb.Min.y + bb.Max.y) * 0.5f + 0.999f); - else - seps_y = ImTrunc((bb.Min.y + bb.Max.y) * 0.5f) + 1.0f; + float seps_y = ImTrunc((bb.Min.y + bb.Max.y) * 0.5f) + 1.0f; const float label_avail_w = ImMax(0.0f, sep2_x2 - sep1_x1 - padding.x * 2.0f); const ImVec2 label_pos(pos.x + padding.x + ImMax(0.0f, (label_avail_w - label_size.x - extra_w) * style.SeparatorTextAlign.x), pos.y + text_baseline_y); // FIXME-ALIGN @@ -5765,11 +5759,7 @@ bool ImGui::InputTextEx(const char* label, const char* hint, char* buf, int buf_ state->CursorAnim += io.DeltaTime; bool cursor_is_visible = (!g.IO.ConfigInputTextCursorBlink) || (state->CursorAnim <= 0.0f) || ImFmod(state->CursorAnim, 1.20f) <= 0.80f; ImVec2 cursor_screen_pos = ImTrunc(draw_pos + cursor_offset - draw_scroll); - ImRect cursor_screen_rect; - if (g_LEGACY_STROKES) - cursor_screen_rect = ImRect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 0.5f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.5f); - else - cursor_screen_rect = ImRect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 1.0f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.0f); + ImRect cursor_screen_rect = ImRect(cursor_screen_pos.x, cursor_screen_pos.y - g.FontSize + 1.0f, cursor_screen_pos.x + 1.0f, cursor_screen_pos.y - 1.0f); if (cursor_is_visible && cursor_screen_rect.Overlaps(clip_rect)) draw_window->DrawList->AddLineV(cursor_screen_rect.Min.x, cursor_screen_rect.Min.y, cursor_screen_rect.Max.y, GetColorU32(ImGuiCol_InputTextCursor), style.InputTextCursorSize, ImDrawFlags_StrokeInside); @@ -7321,27 +7311,16 @@ void ImGui::TreeNodeDrawLineToChildNode(const ImVec2& target_pos) // FIXME: Could this be simplified using new stroke flags? ImGuiTreeNodeStackData* parent_data = &g.TreeNodeStack.Data[g.TreeNodeStack.Size - 1]; const float half_size = g.Style.TreeLinesSize * 0.5f; - float x1; - if (g_LEGACY_STROKES) - x1 = ImTrunc(parent_data->DrawLinesX1); - else - x1 = ImTrunc(parent_data->DrawLinesX1 - half_size) + half_size; // Draw line centered at X1, but snap to pixels boundary. + float x1 = ImTrunc(parent_data->DrawLinesX1 - half_size) + half_size; // Draw line centered at X1, but snap to pixels boundary. float x2 = ImTrunc(target_pos.x - g.Style.ItemInnerSpacing.x); - float y; - if (g_LEGACY_STROKES) - y = ImTrunc(target_pos.y); - else - y = ImTrunc(target_pos.y - half_size) + half_size; + float y = ImTrunc(target_pos.y - half_size) + half_size; float rounding = (g.Style.TreeLinesRounding > 0.0f) ? ImMin(x2 - x1, g.Style.TreeLinesRounding) : 0.0f; parent_data->DrawLinesToNodesY2 = ImMax(parent_data->DrawLinesToNodesY2, y - rounding); if (x1 >= x2) return; if (rounding > 0.0f && rounding > half_size) { - if (g_LEGACY_STROKES) - x1 += 0.5f + rounding; - else - x1 += rounding; + x1 += rounding; window->DrawList->PathArcToFast(ImVec2(x1, y - rounding), rounding, 6, 3); if (x1 < x2) window->DrawList->PathLineTo(ImVec2(x2, y)); @@ -7351,8 +7330,7 @@ void ImGui::TreeNodeDrawLineToChildNode(const ImVec2& target_pos) { // We use AddLine() instead of AddLineH() as we provide coordinates for line center, // in order to make the code use same coordinates for both rounded and non-rounded version. (FIXME-OPT) - if (!g_LEGACY_STROKES) - x1 += half_size; // Avoid overdraw + x1 += half_size; // Avoid overdraw window->DrawList->AddLine(ImVec2(x1, y), ImVec2(x2, y), GetColorU32(ImGuiCol_TreeLines), g.Style.TreeLinesSize); } } @@ -7376,11 +7354,7 @@ void ImGui::TreeNodeDrawLineToTreePop(const ImGuiTreeNodeStackData* data) y2 = ImMin(y2, window->ClipRect.Max.y); if (y2 <= y1) return; - float x; - if (g_LEGACY_STROKES) - x = ImTrunc(data->DrawLinesX1); - else - x = ImTrunc(data->DrawLinesX1 - g.Style.TreeLinesSize * 0.5f) + g.Style.TreeLinesSize * 0.5f; // Draw line centered at X1, but snap to pixels boundary. + float x = ImTrunc(data->DrawLinesX1 - g.Style.TreeLinesSize * 0.5f) + g.Style.TreeLinesSize * 0.5f; // Draw line centered at X1, but snap to pixels boundary. if (data->DrawLinesTableColumn != -1) TablePushColumnChannel(data->DrawLinesTableColumn); @@ -10977,18 +10951,8 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, if (tab_contents_visible && (tab_bar->Flags & ImGuiTabBarFlags_DrawSelectedOverline) && style.TabBarOverlineSize > 0.0f) { // Might be moved to TabItemBackground() ? - ImVec2 tl; - ImVec2 tr; - if (g_LEGACY_STROKES) - { - tl = bb.GetTL() + ImVec2(0, 1.0f * g.CurrentDpiScale); - tr = bb.GetTR() + ImVec2(0, 1.0f * g.CurrentDpiScale); - } - else - { - tl = bb.GetTL() + ImVec2(0, style.TabBarOverlineSize * 0.5f); - tr = bb.GetTR() + ImVec2(0, style.TabBarOverlineSize * 0.5f); - } + ImVec2 tl = bb.GetTL() + ImVec2(0, style.TabBarOverlineSize * 0.5f); + ImVec2 tr = bb.GetTR() + ImVec2(0, style.TabBarOverlineSize * 0.5f); ImU32 overline_col = GetColorU32(tab_bar_focused ? ImGuiCol_TabSelectedOverline : ImGuiCol_TabDimmedSelectedOverline); if (style.TabRounding > 0.0f) { @@ -10999,9 +10963,6 @@ bool ImGui::TabItemEx(ImGuiTabBar* tab_bar, const char* label, bool* p_open, } else { - if (g_LEGACY_STROKES) - display_draw_list->AddLine(tl - ImVec2(0.5f, 0.5f), tr - ImVec2(0.5f, 0.5f), overline_col, style.TabBarOverlineSize); - else display_draw_list->AddLine(tl, tr, overline_col, style.TabBarOverlineSize); } } From 46e4c2609551d20efeaac7dd608da8418916466b Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 28 Jul 2026 18:29:52 +0200 Subject: [PATCH 090/120] DrawList: removed AddPolylineLegacy(), AddConvexPolyFilledLegacy(). --- imgui.h | 2 - imgui_draw.cpp | 351 ------------------------------------------------- 2 files changed, 353 deletions(-) diff --git a/imgui.h b/imgui.h index 0e2566a84..bb1f9452f 100644 --- a/imgui.h +++ b/imgui.h @@ -3451,9 +3451,7 @@ struct ImDrawList // - Only simple polygons are supported by filling functions (no self-intersections, no holes). // - Concave polygon fill is more expensive than convex one: it has O(N^2) complexity. Provided as a convenience for the user but not used by the main library. IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, float thickness, ImDrawFlags flags = 0); - IMGUI_API void AddPolylineLegacy(const ImVec2* points, int num_points, ImU32 col, float thickness, ImDrawFlags flags = 0); IMGUI_API void AddConvexPolyFilled(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags = 0); - IMGUI_API void AddConvexPolyFilledLegacy(const ImVec2* points, int num_points, ImU32 col); IMGUI_API void AddConcavePolyFilled(const ImVec2* points, int num_points, ImU32 col); // Image primitives diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 30283c8fb..2d09bf270 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1307,269 +1307,6 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 PrimUnreserve(remaining_idx_count, remaining_vtx_count); } -void ImDrawList::AddPolylineLegacy(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags) -{ - if (points_count < 2 || (col & IM_COL32_A_MASK) == 0) - return; - - const bool closed = (flags & ImDrawFlags_Closed) != 0; - const ImVec2 opaque_uv = _Data->TexUvWhitePixel; - const int count = closed ? points_count : points_count - 1; // The number of line segments we need to draw - const bool thick_line = (thickness > _FringeScale); - - // If this assert triggers on legacy code: - // - 1.92.8 (2025/05): swapped two last parameters order: flags, thickness --> thickness, flags. This should normally be caught by compile-time type-checking. - // - 1.92.8 (2025/05): changed value of ImDrawList_Closed which was previously guaranteed to be == 1. Hardcoded use of 1 or true should be replaced. - // Read more details near AddRect() + see "API BREAKING CHANGES" section for 1.82, 1.90 and 1.92.8. - IM_ASSERT_USER_ERROR_RET((flags & ImDrawFlags_InvalidMask_) == 0, "Incorrect parameter. Did you swap 'thickness' and 'flags'?"); - - flags |= Flags; - if (flags & ImDrawFlags_AALines) - { - // Anti-aliased stroke - const float AA_SIZE = _FringeScale; - const ImU32 col_trans = col & ~IM_COL32_A_MASK; - - // Thicknesses <1.0 should behave like thickness 1.0 - thickness = ImMax(thickness, 1.0f); - const int integer_thickness = (int)thickness; - const float fractional_thickness = thickness - integer_thickness; - - // Do we want to draw this line using a texture? - // - For now, only draw integer-width lines using textures to avoid issues with the way scaling occurs, could be improved. - // - If AA_SIZE is not 1.0f we cannot use the texture path. - const bool use_texture = (flags & ImDrawFlags_UseTexForStrokeLegacy) && (integer_thickness < IM_DRAWLIST_TEX_LINES_WIDTH_MAX) && (fractional_thickness <= 0.00001f) && (AA_SIZE == 1.0f); - - // We should never hit this, because NewFrame() doesn't set ImDrawFlags_UseTexForStrokeLegacy unless ImFontAtlasFlags_NoBakedLines is off - IM_ASSERT_PARANOID(!use_texture || !(_Data->Font->OwnerAtlas->Flags & ImFontAtlasFlags_NoBakedLines)); - - const int idx_count = use_texture ? (count * 6) : (thick_line ? count * 18 : count * 12); - const int vtx_count = use_texture ? (points_count * 2) : (thick_line ? points_count * 4 : points_count * 3); - PrimReserve(idx_count, vtx_count); - - // Temporary buffer - // The first items are normals at each line point, then after that there are either 2 or 4 temp points for each line point - _Data->TempBuffer.reserve_discard(points_count * ((use_texture || !thick_line) ? 3 : 5)); - ImVec2* temp_normals = _Data->TempBuffer.Data; - ImVec2* temp_points = temp_normals + points_count; - - // Calculate normals (tangents) for each line segment - for (int i1 = 0; i1 < count; i1++) - { - const int i2 = (i1 + 1) == points_count ? 0 : i1 + 1; - float dx = points[i2].x - points[i1].x; - float dy = points[i2].y - points[i1].y; - IM_NORMALIZE2F_OVER_ZERO(dx, dy); - temp_normals[i1].x = dy; - temp_normals[i1].y = -dx; - } - if (!closed) - temp_normals[points_count - 1] = temp_normals[points_count - 2]; - - // If we are drawing a one-pixel-wide line without a texture, or a textured line of any width, we only need 2 or 3 vertices per point - if (use_texture || !thick_line) - { - // [PATH 1] Texture-based lines (thick or non-thick) - // [PATH 2] Non texture-based lines (non-thick) - - // The width of the geometry we need to draw - this is essentially pixels for the line itself, plus "one pixel" for AA. - // - In the texture-based path, we don't use AA_SIZE here because the +1 is tied to the generated texture - // (see ImFontAtlasBuildRenderLinesTexData() function), and so alternate values won't work without changes to that code. - // - In the non texture-based paths, we would allow AA_SIZE to potentially be != 1.0f with a patch (e.g. fringe_scale patch to - // allow scaling geometry while preserving one-screen-pixel AA fringe). - const float half_draw_size = use_texture ? ((thickness * 0.5f) + 1) : AA_SIZE; - - // If line is not closed, the first and last points need to be generated differently as there are no normals to blend - if (!closed) - { - temp_points[0] = points[0] + temp_normals[0] * half_draw_size; - temp_points[1] = points[0] - temp_normals[0] * half_draw_size; - temp_points[(points_count-1)*2+0] = points[points_count-1] + temp_normals[points_count-1] * half_draw_size; - temp_points[(points_count-1)*2+1] = points[points_count-1] - temp_normals[points_count-1] * half_draw_size; - } - - // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges - // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps) - // FIXME-OPT: Merge the different loops, possibly remove the temporary buffer. - unsigned int idx1 = _VtxCurrentIdx; // Vertex index for start of line segment - for (int i1 = 0; i1 < count; i1++) // i1 is the first point of the line segment - { - const int i2 = (i1 + 1) == points_count ? 0 : i1 + 1; // i2 is the second point of the line segment - const unsigned int idx2 = ((i1 + 1) == points_count) ? _VtxCurrentIdx : (idx1 + (use_texture ? 2 : 3)); // Vertex index for end of segment - - // Average normals - float dm_x = (temp_normals[i1].x + temp_normals[i2].x) * 0.5f; - float dm_y = (temp_normals[i1].y + temp_normals[i2].y) * 0.5f; - IM_FIXNORMAL2F(dm_x, dm_y); - dm_x *= half_draw_size; // dm_x, dm_y are offset to the outer edge of the AA area - dm_y *= half_draw_size; - - // Add temporary vertices for the outer edges - ImVec2* out_vtx = &temp_points[i2 * 2]; - out_vtx[0].x = points[i2].x + dm_x; - out_vtx[0].y = points[i2].y + dm_y; - out_vtx[1].x = points[i2].x - dm_x; - out_vtx[1].y = points[i2].y - dm_y; - - if (use_texture) - { - // Add indices for two triangles - _IdxWritePtr[0] = (ImDrawIdx)(idx2 + 0); _IdxWritePtr[1] = (ImDrawIdx)(idx1 + 0); _IdxWritePtr[2] = (ImDrawIdx)(idx1 + 1); // Right tri - _IdxWritePtr[3] = (ImDrawIdx)(idx2 + 1); _IdxWritePtr[4] = (ImDrawIdx)(idx1 + 1); _IdxWritePtr[5] = (ImDrawIdx)(idx2 + 0); // Left tri - _IdxWritePtr += 6; - } - else - { - // Add indexes for four triangles - _IdxWritePtr[0] = (ImDrawIdx)(idx2 + 0); _IdxWritePtr[1] = (ImDrawIdx)(idx1 + 0); _IdxWritePtr[2] = (ImDrawIdx)(idx1 + 2); // Right tri 1 - _IdxWritePtr[3] = (ImDrawIdx)(idx1 + 2); _IdxWritePtr[4] = (ImDrawIdx)(idx2 + 2); _IdxWritePtr[5] = (ImDrawIdx)(idx2 + 0); // Right tri 2 - _IdxWritePtr[6] = (ImDrawIdx)(idx2 + 1); _IdxWritePtr[7] = (ImDrawIdx)(idx1 + 1); _IdxWritePtr[8] = (ImDrawIdx)(idx1 + 0); // Left tri 1 - _IdxWritePtr[9] = (ImDrawIdx)(idx1 + 0); _IdxWritePtr[10] = (ImDrawIdx)(idx2 + 0); _IdxWritePtr[11] = (ImDrawIdx)(idx2 + 1); // Left tri 2 - _IdxWritePtr += 12; - } - - idx1 = idx2; - } - - // Add vertices for each point on the line - if (use_texture) - { - // If we're using textures we only need to emit the left/right edge vertices - ImVec4 tex_uvs = _Data->TexUvLines[integer_thickness]; - /*if (fractional_thickness != 0.0f) // Currently always zero when use_texture==false! - { - const ImVec4 tex_uvs_1 = _Data->TexUvLines[integer_thickness + 1]; - tex_uvs.x = tex_uvs.x + (tex_uvs_1.x - tex_uvs.x) * fractional_thickness; // inlined ImLerp() - tex_uvs.y = tex_uvs.y + (tex_uvs_1.y - tex_uvs.y) * fractional_thickness; - tex_uvs.z = tex_uvs.z + (tex_uvs_1.z - tex_uvs.z) * fractional_thickness; - tex_uvs.w = tex_uvs.w + (tex_uvs_1.w - tex_uvs.w) * fractional_thickness; - }*/ - ImVec2 tex_uv0(tex_uvs.x, tex_uvs.z); - ImVec2 tex_uv1(tex_uvs.y, tex_uvs.z); - for (int i = 0; i < points_count; i++) - { - _VtxWritePtr[0].pos = temp_points[i * 2 + 0]; _VtxWritePtr[0].uv = tex_uv0; _VtxWritePtr[0].col = col; // Left-side outer edge - _VtxWritePtr[1].pos = temp_points[i * 2 + 1]; _VtxWritePtr[1].uv = tex_uv1; _VtxWritePtr[1].col = col; // Right-side outer edge - _VtxWritePtr += 2; - } - } - else - { - // If we're not using a texture, we need the center vertex as well - for (int i = 0; i < points_count; i++) - { - _VtxWritePtr[0].pos = points[i]; _VtxWritePtr[0].uv = opaque_uv; _VtxWritePtr[0].col = col; // Center of line - _VtxWritePtr[1].pos = temp_points[i * 2 + 0]; _VtxWritePtr[1].uv = opaque_uv; _VtxWritePtr[1].col = col_trans; // Left-side outer edge - _VtxWritePtr[2].pos = temp_points[i * 2 + 1]; _VtxWritePtr[2].uv = opaque_uv; _VtxWritePtr[2].col = col_trans; // Right-side outer edge - _VtxWritePtr += 3; - } - } - } - else - { - // [PATH 2] Non texture-based lines (thick): we need to draw the solid line core and thus require four vertices per point - const float half_inner_thickness = (thickness - AA_SIZE) * 0.5f; - - // If line is not closed, the first and last points need to be generated differently as there are no normals to blend - if (!closed) - { - const int points_last = points_count - 1; - temp_points[0] = points[0] + temp_normals[0] * (half_inner_thickness + AA_SIZE); - temp_points[1] = points[0] + temp_normals[0] * (half_inner_thickness); - temp_points[2] = points[0] - temp_normals[0] * (half_inner_thickness); - temp_points[3] = points[0] - temp_normals[0] * (half_inner_thickness + AA_SIZE); - temp_points[points_last * 4 + 0] = points[points_last] + temp_normals[points_last] * (half_inner_thickness + AA_SIZE); - temp_points[points_last * 4 + 1] = points[points_last] + temp_normals[points_last] * (half_inner_thickness); - temp_points[points_last * 4 + 2] = points[points_last] - temp_normals[points_last] * (half_inner_thickness); - temp_points[points_last * 4 + 3] = points[points_last] - temp_normals[points_last] * (half_inner_thickness + AA_SIZE); - } - - // Generate the indices to form a number of triangles for each line segment, and the vertices for the line edges - // This takes points n and n+1 and writes into n+1, with the first point in a closed line being generated from the final one (as n+1 wraps) - // FIXME-OPT: Merge the different loops, possibly remove the temporary buffer. - unsigned int idx1 = _VtxCurrentIdx; // Vertex index for start of line segment - for (int i1 = 0; i1 < count; i1++) // i1 is the first point of the line segment - { - const int i2 = (i1 + 1) == points_count ? 0 : (i1 + 1); // i2 is the second point of the line segment - const unsigned int idx2 = (i1 + 1) == points_count ? _VtxCurrentIdx : (idx1 + 4); // Vertex index for end of segment - - // Average normals - float dm_x = (temp_normals[i1].x + temp_normals[i2].x) * 0.5f; - float dm_y = (temp_normals[i1].y + temp_normals[i2].y) * 0.5f; - IM_FIXNORMAL2F(dm_x, dm_y); - float dm_out_x = dm_x * (half_inner_thickness + AA_SIZE); - float dm_out_y = dm_y * (half_inner_thickness + AA_SIZE); - float dm_in_x = dm_x * half_inner_thickness; - float dm_in_y = dm_y * half_inner_thickness; - - // Add temporary vertices - ImVec2* out_vtx = &temp_points[i2 * 4]; - out_vtx[0].x = points[i2].x + dm_out_x; - out_vtx[0].y = points[i2].y + dm_out_y; - out_vtx[1].x = points[i2].x + dm_in_x; - out_vtx[1].y = points[i2].y + dm_in_y; - out_vtx[2].x = points[i2].x - dm_in_x; - out_vtx[2].y = points[i2].y - dm_in_y; - out_vtx[3].x = points[i2].x - dm_out_x; - out_vtx[3].y = points[i2].y - dm_out_y; - - // Add indexes - _IdxWritePtr[0] = (ImDrawIdx)(idx2 + 1); _IdxWritePtr[1] = (ImDrawIdx)(idx1 + 1); _IdxWritePtr[2] = (ImDrawIdx)(idx1 + 2); - _IdxWritePtr[3] = (ImDrawIdx)(idx1 + 2); _IdxWritePtr[4] = (ImDrawIdx)(idx2 + 2); _IdxWritePtr[5] = (ImDrawIdx)(idx2 + 1); - _IdxWritePtr[6] = (ImDrawIdx)(idx2 + 1); _IdxWritePtr[7] = (ImDrawIdx)(idx1 + 1); _IdxWritePtr[8] = (ImDrawIdx)(idx1 + 0); - _IdxWritePtr[9] = (ImDrawIdx)(idx1 + 0); _IdxWritePtr[10] = (ImDrawIdx)(idx2 + 0); _IdxWritePtr[11] = (ImDrawIdx)(idx2 + 1); - _IdxWritePtr[12] = (ImDrawIdx)(idx2 + 2); _IdxWritePtr[13] = (ImDrawIdx)(idx1 + 2); _IdxWritePtr[14] = (ImDrawIdx)(idx1 + 3); - _IdxWritePtr[15] = (ImDrawIdx)(idx1 + 3); _IdxWritePtr[16] = (ImDrawIdx)(idx2 + 3); _IdxWritePtr[17] = (ImDrawIdx)(idx2 + 2); - _IdxWritePtr += 18; - - idx1 = idx2; - } - - // Add vertices - for (int i = 0; i < points_count; i++) - { - _VtxWritePtr[0].pos = temp_points[i * 4 + 0]; _VtxWritePtr[0].uv = opaque_uv; _VtxWritePtr[0].col = col_trans; - _VtxWritePtr[1].pos = temp_points[i * 4 + 1]; _VtxWritePtr[1].uv = opaque_uv; _VtxWritePtr[1].col = col; - _VtxWritePtr[2].pos = temp_points[i * 4 + 2]; _VtxWritePtr[2].uv = opaque_uv; _VtxWritePtr[2].col = col; - _VtxWritePtr[3].pos = temp_points[i * 4 + 3]; _VtxWritePtr[3].uv = opaque_uv; _VtxWritePtr[3].col = col_trans; - _VtxWritePtr += 4; - } - } - _VtxCurrentIdx += (ImDrawIdx)vtx_count; - } - else - { - // [PATH 4] Non texture-based, Non anti-aliased lines - const int idx_count = count * 6; - const int vtx_count = count * 4; // FIXME-OPT: Not sharing edges - PrimReserve(idx_count, vtx_count); - - for (int i1 = 0; i1 < count; i1++) - { - const int i2 = (i1 + 1) == points_count ? 0 : i1 + 1; - const ImVec2& p1 = points[i1]; - const ImVec2& p2 = points[i2]; - - float dx = p2.x - p1.x; - float dy = p2.y - p1.y; - IM_NORMALIZE2F_OVER_ZERO(dx, dy); - dx *= (thickness * 0.5f); - dy *= (thickness * 0.5f); - - _VtxWritePtr[0].pos.x = p1.x + dy; _VtxWritePtr[0].pos.y = p1.y - dx; _VtxWritePtr[0].uv = opaque_uv; _VtxWritePtr[0].col = col; - _VtxWritePtr[1].pos.x = p2.x + dy; _VtxWritePtr[1].pos.y = p2.y - dx; _VtxWritePtr[1].uv = opaque_uv; _VtxWritePtr[1].col = col; - _VtxWritePtr[2].pos.x = p2.x - dy; _VtxWritePtr[2].pos.y = p2.y + dx; _VtxWritePtr[2].uv = opaque_uv; _VtxWritePtr[2].col = col; - _VtxWritePtr[3].pos.x = p1.x - dy; _VtxWritePtr[3].pos.y = p1.y + dx; _VtxWritePtr[3].uv = opaque_uv; _VtxWritePtr[3].col = col; - _VtxWritePtr += 4; - - _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx + 1); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx + 2); - _IdxWritePtr[3] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[4] = (ImDrawIdx)(_VtxCurrentIdx + 2); _IdxWritePtr[5] = (ImDrawIdx)(_VtxCurrentIdx + 3); - _IdxWritePtr += 6; - _VtxCurrentIdx += 4; - } - } -} - // - We intentionally avoid using ImVec2 and its math operators here to reduce cost to a minimum for debug/non-inlined builds. // - Filled shapes must always use clockwise winding order. The anti-aliasing fringe depends on it. Counter-clockwise shapes will have "inward" anti-aliasing. void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_count, ImU32 col, ImDrawFlags flags) @@ -1577,12 +1314,6 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun if (points_count < 3 || (col & IM_COL32_A_MASK) == 0) return; -/* if (ImGui::GetIO().KeyShift) - { - AddConvexPolyFilledLegacy(points, points_count, col); - return; - }*/ - const ImVec2 uv = _Data->TexUvWhitePixel; flags |= Flags; if (flags & ImDrawFlags_AAFill) @@ -1763,88 +1494,6 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun } } -void ImDrawList::AddConvexPolyFilledLegacy(const ImVec2* points, const int points_count, ImU32 col) -{ - if (points_count < 3 || (col & IM_COL32_A_MASK) == 0) - return; - - const ImVec2 uv = _Data->TexUvWhitePixel; - - if (Flags & ImDrawFlags_AAFill) - { - // Anti-aliased Fill - const float AA_SIZE = _FringeScale; - const ImU32 col_trans = col & ~IM_COL32_A_MASK; - const int idx_count = (points_count - 2)*3 + points_count * 6; - const int vtx_count = (points_count * 2); - PrimReserve(idx_count, vtx_count); - - // Add indexes for fill - unsigned int vtx_inner_idx = _VtxCurrentIdx; - unsigned int vtx_outer_idx = _VtxCurrentIdx + 1; - for (int i = 2; i < points_count; i++) - { - _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx + ((i - 1) << 1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_inner_idx + (i << 1)); - _IdxWritePtr += 3; - } - - // Compute normals - _Data->TempBuffer.reserve_discard(points_count); - ImVec2* temp_normals = _Data->TempBuffer.Data; - for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) - { - const ImVec2& p0 = points[i0]; - const ImVec2& p1 = points[i1]; - float dx = p1.x - p0.x; - float dy = p1.y - p0.y; - IM_NORMALIZE2F_OVER_ZERO(dx, dy); - temp_normals[i0].x = dy; - temp_normals[i0].y = -dx; - } - - for (int i0 = points_count - 1, i1 = 0; i1 < points_count; i0 = i1++) - { - // Average normals - const ImVec2& n0 = temp_normals[i0]; - const ImVec2& n1 = temp_normals[i1]; - float dm_x = (n0.x + n1.x) * 0.5f; - float dm_y = (n0.y + n1.y) * 0.5f; - IM_FIXNORMAL2F(dm_x, dm_y); - dm_x *= AA_SIZE * 0.5f; - dm_y *= AA_SIZE * 0.5f; - - // Add vertices - _VtxWritePtr[0].pos.x = (points[i1].x - dm_x); _VtxWritePtr[0].pos.y = (points[i1].y - dm_y); _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; // Inner - _VtxWritePtr[1].pos.x = (points[i1].x + dm_x); _VtxWritePtr[1].pos.y = (points[i1].y + dm_y); _VtxWritePtr[1].uv = uv; _VtxWritePtr[1].col = col_trans; // Outer - _VtxWritePtr += 2; - - // Add indexes for fringes - _IdxWritePtr[0] = (ImDrawIdx)(vtx_inner_idx + (i1 << 1)); _IdxWritePtr[1] = (ImDrawIdx)(vtx_inner_idx + (i0 << 1)); _IdxWritePtr[2] = (ImDrawIdx)(vtx_outer_idx + (i0 << 1)); - _IdxWritePtr[3] = (ImDrawIdx)(vtx_outer_idx + (i0 << 1)); _IdxWritePtr[4] = (ImDrawIdx)(vtx_outer_idx + (i1 << 1)); _IdxWritePtr[5] = (ImDrawIdx)(vtx_inner_idx + (i1 << 1)); - _IdxWritePtr += 6; - } - _VtxCurrentIdx += (ImDrawIdx)vtx_count; - } - else - { - // Non Anti-aliased Fill - const int idx_count = (points_count - 2)*3; - const int vtx_count = points_count; - PrimReserve(idx_count, vtx_count); - for (int i = 0; i < vtx_count; i++) - { - _VtxWritePtr[0].pos = points[i]; _VtxWritePtr[0].uv = uv; _VtxWritePtr[0].col = col; - _VtxWritePtr++; - } - for (int i = 2; i < points_count; i++) - { - _IdxWritePtr[0] = (ImDrawIdx)(_VtxCurrentIdx); _IdxWritePtr[1] = (ImDrawIdx)(_VtxCurrentIdx + i - 1); _IdxWritePtr[2] = (ImDrawIdx)(_VtxCurrentIdx + i); - _IdxWritePtr += 3; - } - _VtxCurrentIdx += (ImDrawIdx)vtx_count; - } -} - // Calculates arc step and step count for 90 degree arc. The calculated arc step will complete a 90 degree arc when stepped arc_step_count times. IM_MSVC_RUNTIME_CHECKS_OFF static void CalcArcStepAndCount(int segment_count, int& arc_step, int& arc_step_count) From eb02a7f24ceae9766860bc58cc2d80f012b17d05 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 29 Jul 2026 14:16:21 +0200 Subject: [PATCH 091/120] DrawList: support altering RoundCorners flags in scope as an undocumented trick/hack. (#9489) Not documented because it could become troublesome if treated as a guaranteed styling feature. --- imgui.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/imgui.h b/imgui.h index bb1f9452f..93513c85d 100644 --- a/imgui.h +++ b/imgui.h @@ -3364,8 +3364,8 @@ enum ImDrawFlags_ ImDrawFlags_UseVtxOffset = 1 << 23, // -- OK(1) // Can emit 'VtxOffset > 0' to allow large meshes with 16-bit ImDrawIdx. Used by default when 'ImGuiBackendFlags_RendererHasVtxOffset' is enabled by the backend. // [Internal] - ImDrawFlags_AllowedInScope_ = ImDrawFlags_AAFill | ImDrawFlags_AALines | ImDrawFlags_AALineEnds | ImDrawFlags_StrokeLegacy | ImDrawFlags_TextNoPixelSnap | ImDrawFlags_UseTexForRoundCorners | ImDrawFlags_UseTexForStrokeLegacy | ImDrawFlags_UseVtxOffset, // [Internal] Values allowed in PushDrawFlag() scope. ImDrawFlags_RoundCornersMask_ = ImDrawFlags_RoundCornersAll | ImDrawFlags_RoundCornersNone, // [Internal] + ImDrawFlags_AllowedInScope_ = ImDrawFlags_AAFill | ImDrawFlags_AALines | ImDrawFlags_AALineEnds | ImDrawFlags_StrokeLegacy | ImDrawFlags_TextNoPixelSnap | ImDrawFlags_UseTexForRoundCorners | ImDrawFlags_UseTexForStrokeLegacy | ImDrawFlags_UseVtxOffset | ImDrawFlags_RoundCornersMask_, // [Internal] Values allowed in PushDrawFlag() scope. ImDrawFlags_StrokeMask_ = 0x07 << 17, // [Internal] ImDrawFlags_InvalidMask_ = ~0x7FFFFFF0, // [Internal] == 0x8000000F. Reserved to detect misuses. }; From b220bffdbb1175b7666f916cdec9703b57532ee6 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 29 Jul 2026 15:02:15 +0200 Subject: [PATCH 092/120] Checkbox, Menus: check marks use anti-aliased ends. Currently not a satisfying resolution. --- docs/CHANGELOG.txt | 1 + imgui_draw.cpp | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 9c435e85b..bc7c6bbb5 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -182,6 +182,7 @@ HOW TO UPDATE? - Tree: hierarchy lines (when thickness>1.0f). - TabBar: selected overline border ((when thickness>1.0f). - Demo: Custom Rendering: canvas lines. +- Checkbox, Menus: check marks use anti-aliased ends. - Demo: Custom Rendering: exposed new flags; Showcasing overlapping strokes and filled shapes; Added an option to animation thickness; Added new shapes to showcase new rendering features that previously hit limitations. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 2d09bf270..df4b7d21f 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -7618,7 +7618,7 @@ void ImGui::RenderCheckMark(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float draw_list->PathLineTo(ImVec2(bx - third, by - third)); draw_list->PathLineTo(ImVec2(bx, by)); draw_list->PathLineTo(ImVec2(bx + third * 2.0f, by - third * 2.0f)); - draw_list->PathStroke(col, thickness); + draw_list->PathStroke(col, thickness, (draw_list->Flags & ImDrawFlags_AALines) ? ImDrawFlags_AALineEnds : 0); // FIXME: Design idiomatic selection/threshold for this? } // Render an arrow. 'pos' is position of the arrow tip. half_sz.x is length from base to tip. half_sz.y is length on each side. From ee66cfcdf35cca4f20763bd47e0c08aa87fa1141 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 29 Jul 2026 16:27:29 +0200 Subject: [PATCH 093/120] TabBar: simpify TabItem rendering using stroke flags. May tend toward using _StrokeInside more but _StrokeCenterBiased is closer to current behavior. --- imgui_widgets.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 81262b9d2..8a210c415 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -11052,14 +11052,14 @@ void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabI const float rounding = ImMax(0.0f, ImMin((flags & ImGuiTabItemFlags_Button) ? g.Style.FrameRounding : g.Style.TabRounding, width * 0.5f - 1.0f)); const float y1 = bb.Min.y + 1.0f; // Leave a bit of room in title bars. const float y2 = bb.Max.y - g.Style.TabBarBorderSize; - draw_list->AddRectFilled(bb.Min, ImVec2(bb.Max.x, y2), col, rounding, ImDrawFlags_RoundCornersTop); + draw_list->AddRectFilled(ImVec2(bb.Min.x, y1), ImVec2(bb.Max.x, y2), col, rounding, ImDrawFlags_RoundCornersTop); if (g.Style.TabBorderSize > 0.0f) { - draw_list->PathLineTo(ImVec2(bb.Min.x + 0.5f, y2)); - draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding + 0.5f, y1 + rounding + 0.5f), rounding, 6, 9); - draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding - 0.5f, y1 + rounding + 0.5f), rounding, 9, 12); - draw_list->PathLineTo(ImVec2(bb.Max.x - 0.5f, y2)); - draw_list->PathStroke(GetColorU32(ImGuiCol_Border), g.Style.TabBorderSize); + draw_list->PathLineTo(ImVec2(bb.Min.x, y2)); + draw_list->PathArcToFast(ImVec2(bb.Min.x + rounding, y1 + rounding), rounding, 6, 9); + draw_list->PathArcToFast(ImVec2(bb.Max.x - rounding, y1 + rounding), rounding, 9, 12); + draw_list->PathLineTo(ImVec2(bb.Max.x, y2)); + draw_list->PathStroke(GetColorU32(ImGuiCol_Border), g.Style.TabBorderSize, ImDrawFlags_StrokeCenterBiased); // Use _StrokeInside? } } From 7a510274cec2a2e5f59a85d4b015db20874cfcff Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 29 Jul 2026 17:26:42 +0200 Subject: [PATCH 094/120] DrawList: Comments. Atlas's NoBakedLines automatically disable AA at Style level. Fix passing non-Legacy stroke mask to PushDrawFlag(). Demo: Custom Rendering: use LiveEdit everywhere. --- docs/CHANGELOG.txt | 8 ++++---- imgui.cpp | 2 ++ imgui.h | 4 ++-- imgui_draw.cpp | 15 +++++++++------ 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index bc7c6bbb5..00f31e1de 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -103,7 +103,7 @@ HOW TO UPDATE? - Helps fills minor gaps if combining multiple individual lines. - Enabling AA Ends by default would be more "correct", but it is slower and increase vertex/index data.s - Added `ImDrawFlags_MiterOnly` flag to disable using beveled corner (closer to legacy - rendering, slightly faster, but corners sharpers than 90 degrees may expand far out). + rendering, slightly faster, but corners sharper than 90 degrees may expand far out). Automatically used by AddRect(), AddCircle(), AddNgon(), AddEllipse() etc functions since we know that the input path does not contains sharper corners. - Added `ImDrawFlags_SquareCap` flag to use extend lines ends using square caps. @@ -127,9 +127,9 @@ HOW TO UPDATE? - Added flags to specify stroke position in all `ImDrawList` stroking functions: - `ImDrawFlags_StrokeInside` (default for closed primitives and AddLineH, AddLineV) - `ImDrawFlags_StrokeCenter` (default for paths, bezier and AddLine) - - `ImDrawFlags_StrokeCenterAligned` + - `ImDrawFlags_StrokeCenterBiased` - `ImDrawFlags_StrokeOutside` - - `ImDrawFlags_StrokeLegacy` (may be set as default for a given scope using PushItemFlag()). + - `ImDrawFlags_StrokeLegacy` (may be set as default for a given scope using PushDrawFlag()). - Legacy code was generally applying +0.50f offset 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 slightly initial offset. @@ -139,7 +139,7 @@ HOW TO UPDATE? regardless of thickness, as long as input coordinates/sizes are integers. (#9359) - Read our Wiki guides! Recap: ------------------------------------------------------------------------ - Legacy Inside Outside Center CenterAligned + Legacy Inside Outside Center CenterBiased -------------------------------------------------------------------------------- Thickness=1.0f sharp sharp sharp blurry sharp Thickness=2.0f blurry sharp sharp sharp sharp diff --git a/imgui.cpp b/imgui.cpp index 23ef0bd6b..e03bc6f19 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -5626,6 +5626,8 @@ static void SetupDrawListSharedData() g.DrawListSharedData.CurveTessellationMaxError = g.Style.CurveTessellationMaxError; g.DrawListSharedData.SetCircleTessellationMaxError(g.Style.CircleTessellationMaxError); g.DrawListSharedData.InitialDrawFlags = ImDrawFlags_None; + if (g.IO.Fonts->Flags & ImFontAtlasFlags_NoBakedLines) + g.Style.AntiAliasedLines = g.Style.AntiAliasedLineEnds = false; if (g.Style.AntiAliasedFill) g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_AAFill; if (g.Style.AntiAliasedLines) diff --git a/imgui.h b/imgui.h index 93513c85d..737c9921e 100644 --- a/imgui.h +++ b/imgui.h @@ -3434,7 +3434,7 @@ struct ImDrawList IMGUI_API void AddRectFilledMultiColor(const ImVec2& p_min, const ImVec2& p_max, ImU32 col_upr_left, ImU32 col_upr_right, ImU32 col_bot_right, ImU32 col_bot_left); IMGUI_API void AddQuad(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col, float thickness = 1.0f, ImDrawFlags flags = 0); IMGUI_API void AddQuadFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col); - IMGUI_API void AddTriangle(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness = 1.0f, ImDrawFlags = 0); + IMGUI_API void AddTriangle(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col, float thickness = 1.0f, ImDrawFlags flags = 0); IMGUI_API void AddTriangleFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col); IMGUI_API void AddCircle(const ImVec2& center, float radius, ImU32 col, int num_segments = 0, float thickness = 1.0f, ImDrawFlags flags = 0); IMGUI_API void AddCircleFilled(const ImVec2& center, float radius, ImU32 col, int num_segments = 0); @@ -3770,7 +3770,7 @@ enum ImFontAtlasFlags_ ImFontAtlasFlags_None = 0, ImFontAtlasFlags_NoPowerOfTwoHeight = 1 << 0, // Don't round the height to next power of two ImFontAtlasFlags_NoMouseCursors = 1 << 1, // Don't build software mouse cursors into the atlas (save a little texture memory) - ImFontAtlasFlags_NoBakedLines = 1 << 2, // [OBSOLETE] Don't build line textures into the atlas (save a little texture memory). SINCE 1.93.0 THIS PREVENT ANTI-ALIASED STROKES FROM WORKING. + ImFontAtlasFlags_NoBakedLines = 1 << 2, // [OBSOLETE] Don't build line textures into the atlas (save a little texture memory). SINCE 1.93.0 THIS PREVENT ANTI-ALIASED STROKES FROM WORKING AND WILL DISABLE AA. ImFontAtlasFlags_NoBakedRoundCorners= 1 << 3, // Don't build round corners into the atlas. }; diff --git a/imgui_draw.cpp b/imgui_draw.cpp index df4b7d21f..6c905163b 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -717,6 +717,7 @@ void ImDrawList::PopTexture() void ImDrawList::PushDrawFlag(ImDrawFlags flags, bool enabled) { IM_ASSERT((flags & ~ImDrawFlags_AllowedInScope_) == 0); + IM_ASSERT((flags & ImDrawFlags_StrokeMask_) == 0 || (flags & ImDrawFlags_StrokeMask_) == ImDrawFlags_StrokeLegacy); _DrawFlagsStack.push_back(Flags); if (enabled) Flags |= flags; @@ -726,7 +727,7 @@ void ImDrawList::PushDrawFlag(ImDrawFlags flags, bool enabled) void ImDrawList::PopDrawFlag() { - IM_ASSERT_USER_ERROR_RET(_DrawFlagsStack.Size > 0, "Calling PopDrawFlags() too many times!"); + IM_ASSERT_USER_ERROR_RET(_DrawFlagsStack.Size > 0, "Calling PopDrawFlag() too many times!"); Flags = _DrawFlagsStack.back(); _DrawFlagsStack.pop_back(); } @@ -894,7 +895,7 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE IM_MSVC_RUNTIME_CHECKS_OFF static ImU32 ImAlphaMultiply(ImU32 col, float alpha_mul) { - IM_ASSERT_PARANOID(a >= 0.0f && a < 1.0f); // We don't clamp! + IM_ASSERT_PARANOID(alpha_mul >= 0.0f && alpha_mul < 1.0f); // We don't clamp! ImU32 a = (col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT; a = (ImU32)(a * alpha_mul); // We don't need to clamp 0..255 because alpha is in 0..1 range. return (col & ~IM_COL32_A_MASK) | (a << IM_COL32_A_SHIFT); @@ -2022,7 +2023,7 @@ void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float th } else { - // Replace stroke pos with the already calculated one. + // Replace stroke pos with the already calculated one, as AddPolyline() default is different. flags = (flags & ~ImDrawFlags_StrokeMask_) | stroke_pos; // For generic case use line, since it needs less triangles than AA rectangle. @@ -2068,7 +2069,7 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th } else { - // Replace stroke pos with the already calculated one. + // Replace stroke pos with the already calculated one, as AddPolyline() default is different. flags = (flags & ~ImDrawFlags_StrokeMask_) | stroke_pos; // For generic case use line, since it needs less triangles than AA rectangle. @@ -2617,8 +2618,9 @@ void ImDrawList::AddQuad(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, c if ((col & IM_COL32_A_MASK) == 0) return; + //flags |= Flags; // Not needed. ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); - flags = (flags & ~ImDrawFlags_StrokeMask_) | stroke_pos; + flags = (flags & ~ImDrawFlags_StrokeMask_) | stroke_pos; // As AddPolyline() default is different const ImVec2 points[4] = { p1, p2, p3, p4 }; AddPolyline(points, 4, col, thickness, flags | ImDrawFlags_Closed); @@ -2638,8 +2640,9 @@ void ImDrawList::AddTriangle(const ImVec2& p1, const ImVec2& p2, const ImVec2& p if ((col & IM_COL32_A_MASK) == 0) return; + //flags |= Flags; // Not needed. ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); - flags = (flags & ~ImDrawFlags_StrokeMask_) | stroke_pos; + flags = (flags & ~ImDrawFlags_StrokeMask_) | stroke_pos; // As AddPolyline() default is different const ImVec2 points[3] = { p1, p2, p3 }; AddPolyline(points, 3, col, thickness, flags | ImDrawFlags_Closed); From ba918bc45c6c85e877187f83faf8a9124668be68 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Wed, 29 Jul 2026 19:02:14 +0300 Subject: [PATCH 095/120] DrawList: fixed filled rect baked AA. Always add 1px to the fill side of the rounded corner to prevent the corner AA to bleed into the rectangle edges. --- imgui_draw.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 6c905163b..7ae1a0b29 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2575,9 +2575,10 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c if (allow_tex_corners && s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) { IM_ASSERT_PARANOID(!(_Data->Font->OwnerAtlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners)); - const int size = ImMax(2, s_rounding); // This is matching the baking calculations. - const int idx = (s_rounding - 1); - IM_ASSERT_PARANOID(idx >= 0 && idx < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX); + // This is matching the baking calculations. The rounding clamping above makes sure that there's enough space for the extra pixel. + const int size = s_rounding + 1; + const int idx = (s_rounding - 1); + IM_ASSERT_PARANOID(idx >= 0 && idx < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX); ImVec4 tex_uvs = _Data->TexUvCorners[idx]; _AddRectFilledBaked(p_min, p_max, col, (float)size * _FringeScale, tex_uvs, flags); return; @@ -4919,7 +4920,7 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) row_height = 0; for (int i = 0; i < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX; i++) { - const int size = ImMax(2, ImMax(i + 1, thickness)) + 2; + const int size = ImMax(i + 2, thickness) + 2; row_width += size; row_height = ImMax(row_height, size); } @@ -4940,8 +4941,8 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) // Filled for (int n = 0; n < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX; n++) { - const int rounding = n+1; - const int s = ImMax(2, rounding); // We need at least 2px so that stretching has solid color. + const int rounding = n + 1; + const int s = rounding + 1; // Always add 1px to the fill side of the rounded corner to prevent the corner AA to bleed into the rectangle edges. const int w = s + 2; const int h = s + 2; From a597beaf954bc90ad286b8a5599cfec43ab23430 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 30 Jul 2026 14:37:23 +0200 Subject: [PATCH 096/120] DrawList: fixed dropping prim flags in some paths (most for ImDrawFlags_AAXXX flags). --- imgui_draw.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 7ae1a0b29..06838cdd3 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -959,6 +959,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 // - 1.92.8 (2025/05): swapped two last parameters order: flags, thickness --> thickness, flags. This should normally be caught by compile-time type-checking. // - 1.92.8 (2025/05): changed value of ImDrawList_Closed which was previously guaranteed to be == 1. Hardcoded use of 1 or true should be replaced. // Read more details near AddRect() + see "API BREAKING CHANGES" section for 1.82, 1.90 and 1.92.8. + flags |= Flags; IM_ASSERT_USER_ERROR_RET((flags & ImDrawFlags_InvalidMask_) == 0, "Incorrect parameter. Did you swap 'thickness' and 'flags'?"); // Allocate data for temp buffers @@ -993,7 +994,6 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 sqr_lengths[points_count - 1] = 0.0f; } - flags |= Flags; ImVec4 tex_uvs; float fringe; _SelectFringeTexture(screen_thickness, &tex_uvs, &fringe, flags); @@ -2337,11 +2337,11 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeInside); if (stroke_pos == ImDrawFlags_StrokeLegacy) IM_UNLIKELY { - if (Flags & ImDrawFlags_AALines) + if (flags & ImDrawFlags_AALines) PathRect(ImVec2(p_min.x + 0.50f, p_min.y + 0.50f), ImVec2(p_max.x - 0.50f, p_max.y - 0.50f), rounding, flags); else PathRect(ImVec2(p_min.x + 0.50f, p_min.y + 0.50f), ImVec2(p_max.x - 0.49f, p_max.y - 0.49f), rounding, flags); // Better looking lower-right corner and rounded non-AA shapes. - PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | ImDrawFlags_StrokeCenter); + PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | ImDrawFlags_StrokeCenter | (flags & ~ImDrawFlags_StrokeMask_)); return; } @@ -2441,7 +2441,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl if (!has_rounding || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) { const ImVec2 points[4] = { outer_min, ImVec2(outer_max.x, outer_min.y), outer_max, ImVec2(outer_min.x, outer_max.y) }; - AddPolyline(points, 4, col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | ImDrawFlags_StrokeInside); + AddPolyline(points, 4, col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | ImDrawFlags_StrokeInside | (flags & ~ImDrawFlags_StrokeMask_)); } else { @@ -2461,7 +2461,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl PathArcToFast(ImVec2(outer_max.x - rounding_tr, outer_min.y + rounding_tr), rounding_tr, 9, 12); PathArcToFast(ImVec2(outer_max.x - rounding_br, outer_max.y - rounding_br), rounding_br, 0, 3); PathArcToFast(ImVec2(outer_min.x + rounding_bl, outer_max.y - rounding_bl), rounding_bl, 3, 6); - PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | ImDrawFlags_StrokeInside); + PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | ImDrawFlags_StrokeInside | (flags & ~ImDrawFlags_StrokeMask_)); } } From 20f17eeb6f06f68722994aa0689baebf58c148eb Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 30 Jul 2026 14:48:28 +0200 Subject: [PATCH 097/120] DrawList: fixed using ImDrawFlags_UseTexForRoundCorners when ImFontAtlasFlags_NoBakedRoundCorners has also been set. --- imgui.cpp | 2 +- imgui.h | 4 +++- imgui_demo.cpp | 4 ++-- imgui_draw.cpp | 10 +++++----- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index e03bc6f19..fa5dfe7e4 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -5639,7 +5639,7 @@ static void SetupDrawListSharedData() g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_UseTexForStrokeLegacy; #endif if (!(g.IO.Fonts->Flags & ImFontAtlasFlags_NoBakedRoundCorners)) - g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_UseTexForRoundCorners; + g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_UseTexForRoundCorners | ImDrawFlags_AllowTexForRoundCorners_; if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_UseVtxOffset; } diff --git a/imgui.h b/imgui.h index 737c9921e..3eef737d2 100644 --- a/imgui.h +++ b/imgui.h @@ -3362,10 +3362,12 @@ enum ImDrawFlags_ ImDrawFlags_UseTexForRoundCorners = 1 << 21, // -- OK(1) // Enable using textures instead of strokes to draw rounded corners/circles where possible (faster). Used by default unless 'ImFontAtlasFlags_NoBakedRoundCorners' is enabled in the font atlas. ImDrawFlags_UseTexForStrokeLegacy = 1 << 22, // -- OK(1) // Enable anti-aliased lines/borders using textures when using legacy strokes. Require backend to render with bilinear filtering (NOT point/nearest filtering). Used by default unless 'ImFontAtlasFlags_NoBakedLines' is set in the font atlas. ImDrawFlags_UseVtxOffset = 1 << 23, // -- OK(1) // Can emit 'VtxOffset > 0' to allow large meshes with 16-bit ImDrawIdx. Used by default when 'ImGuiBackendFlags_RendererHasVtxOffset' is enabled by the backend. + ImDrawFlags_AllowTexForRoundCorners_= 1 << 24, // -- OK(1) // [Internal] // [Internal] ImDrawFlags_RoundCornersMask_ = ImDrawFlags_RoundCornersAll | ImDrawFlags_RoundCornersNone, // [Internal] - ImDrawFlags_AllowedInScope_ = ImDrawFlags_AAFill | ImDrawFlags_AALines | ImDrawFlags_AALineEnds | ImDrawFlags_StrokeLegacy | ImDrawFlags_TextNoPixelSnap | ImDrawFlags_UseTexForRoundCorners | ImDrawFlags_UseTexForStrokeLegacy | ImDrawFlags_UseVtxOffset | ImDrawFlags_RoundCornersMask_, // [Internal] Values allowed in PushDrawFlag() scope. + ImDrawFlags_AllowInPushScope_ = ImDrawFlags_AAFill | ImDrawFlags_AALines | ImDrawFlags_AALineEnds | ImDrawFlags_StrokeLegacy | ImDrawFlags_TextNoPixelSnap | ImDrawFlags_UseTexForRoundCorners | ImDrawFlags_UseTexForStrokeLegacy | ImDrawFlags_UseVtxOffset | ImDrawFlags_RoundCornersMask_, // [Internal] Values allowed in PushDrawFlag() scope. + ImDrawFlags_AllowInFrameScope_ = ImDrawFlags_AllowInPushScope_ | ImDrawFlags_AllowTexForRoundCorners_, ImDrawFlags_StrokeMask_ = 0x07 << 17, // [Internal] ImDrawFlags_InvalidMask_ = ~0x7FFFFFF0, // [Internal] == 0x8000000F. Reserved to detect misuses. }; diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 5a5c4892b..041cee6b4 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10453,7 +10453,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) //ImGui::CheckboxFlags("UseTexForRoundCorners", &prim_other_flags, ImDrawFlags_UseTexForRoundCorners); ImGui::SameLine(); //ImGui::CheckboxFlags("UseTexForStrokeLegacy", &prim_other_flags, ImDrawFlags_UseTexForStrokeLegacy); - static ImDrawFlags scope_flags = draw_list->Flags; // First init copy current state. + static ImDrawFlags scope_flags = draw_list->Flags & ImDrawFlags_AllowInPushScope_; // First init copy current state. ImGui::SeparatorText("Current ImDrawList flags (e.g. PushDrawFlag function)"); ImGui::PushID("ScopeFlags"); ImGui::CheckboxFlags("AAFill", &scope_flags, ImDrawFlags_AAFill); ImGui::SameLine(); @@ -10465,7 +10465,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::PopID(); ImGui::Spacing(); - draw_list->PushDrawFlag(ImDrawFlags_AllowedInScope_, false); // Reset all existing (very unusual: done for this demo) + draw_list->PushDrawFlag(ImDrawFlags_AllowInPushScope_, false); // Reset all existing (very unusual: done for this demo) draw_list->PushDrawFlag(scope_flags, true); const ImDrawFlags flags = prim_stroke_flags | prim_other_flags; const ImDrawFlags fill_flags = flags & (ImDrawFlags_AAFill | ImDrawFlags_UseTexForRoundCorners); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 06838cdd3..77ce95140 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -462,7 +462,7 @@ void ImDrawList::_ResetForNewFrame() IdxBuffer.resize(0); VtxBuffer.resize(0); Flags = _Data->InitialDrawFlags; - IM_ASSERT((Flags & ~ImDrawFlags_AllowedInScope_) == 0); + IM_ASSERT((Flags & ~ImDrawFlags_AllowInFrameScope_) == 0); memset(&_CmdHeader, 0, sizeof(_CmdHeader)); _VtxCurrentIdx = 0; _VtxWritePtr = NULL; @@ -716,7 +716,7 @@ void ImDrawList::PopTexture() // IMPORTANT: currently limited to 3 deep void ImDrawList::PushDrawFlag(ImDrawFlags flags, bool enabled) { - IM_ASSERT((flags & ~ImDrawFlags_AllowedInScope_) == 0); + IM_ASSERT((flags & ~ImDrawFlags_AllowInPushScope_) == 0); IM_ASSERT((flags & ImDrawFlags_StrokeMask_) == 0 || (flags & ImDrawFlags_StrokeMask_) == ImDrawFlags_StrokeLegacy); _DrawFlagsStack.push_back(Flags); if (enabled) @@ -2423,7 +2423,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl return; } // Textured corners are baked with AA, do not use them if no-AA is requested. - const bool allow_tex_corners = (flags & (ImDrawFlags_AALines | ImDrawFlags_UseTexForRoundCorners)) == (ImDrawFlags_AALines | ImDrawFlags_UseTexForRoundCorners); + const bool allow_tex_corners = (flags & (ImDrawFlags_AALines | ImDrawFlags_UseTexForRoundCorners | ImDrawFlags_AllowTexForRoundCorners_)) == (ImDrawFlags_AALines | ImDrawFlags_UseTexForRoundCorners | ImDrawFlags_AllowTexForRoundCorners_); if (allow_tex_corners && s_thickness < IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX && s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) { // Pixel aligned rect with round corners rendered using baked textures. @@ -2571,10 +2571,10 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c return; } // Textured corners are baked with AA, do not use them if no-AA is requested. - const bool allow_tex_corners = (flags & (ImDrawFlags_AAFill | ImDrawFlags_UseTexForRoundCorners)) == (ImDrawFlags_AAFill | ImDrawFlags_UseTexForRoundCorners); + const bool allow_tex_corners = (flags & (ImDrawFlags_AAFill | ImDrawFlags_UseTexForRoundCorners | ImDrawFlags_AllowTexForRoundCorners_)) == (ImDrawFlags_AAFill | ImDrawFlags_UseTexForRoundCorners | ImDrawFlags_AllowTexForRoundCorners_); if (allow_tex_corners && s_rounding <= IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) { - IM_ASSERT_PARANOID(!(_Data->Font->OwnerAtlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners)); + IM_ASSERT_PARANOID(!(_Data->Font->OwnerAtlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners)); // This is matching the baking calculations. The rounding clamping above makes sure that there's enough space for the extra pixel. const int size = s_rounding + 1; const int idx = (s_rounding - 1); From deb9c70acec3b1d06b9606ea64a2e1bba214e679 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 30 Jul 2026 15:02:53 +0200 Subject: [PATCH 098/120] DrawList: ImDrawFlags_AALineEnds also requires ImDrawFlags_AALine, which makes it easier for widgets to opt-in AAEnds. --- imgui.h | 2 +- imgui_demo.cpp | 3 ++- imgui_draw.cpp | 12 +++++++----- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/imgui.h b/imgui.h index 3eef737d2..de6fec28a 100644 --- a/imgui.h +++ b/imgui.h @@ -3343,7 +3343,7 @@ enum ImDrawFlags_ // Stroke/Fill Anti-aliasing ------------------ // Prim/Scope? ImDrawFlags_AAFill = 1 << 12, // OK* OK(1) // Enable anti-aliasing for Filled shapes. ImDrawFlags_AALines = 1 << 13, // OK* OK(1) // Enable anti-aliasing for Strokes (lines, borders). - ImDrawFlags_AALineEnds = 1 << 14, // OK OK(0) // Enable anti-aliasing for Strokes Ends. Useful on thick strokes or for precise continuity of multiple lines. A little more costly. + ImDrawFlags_AALineEnds = 1 << 14, // OK OK(0) // Enable anti-aliasing for Strokes Ends. Requires AALines to also be enabled. Useful on thick strokes or for precise continuity of multiple lines. A little more costly. //ImDrawFlags_NoAA = 1 << 15, // OK -- // Disable anti-aliasing for a given primitive. //ImDrawFlags_NoAALineEnds = 1 << 16, // OK -- // Disable anti-aliasing ends for a given primitive. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 041cee6b4..efd35bb0a 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -8911,8 +8911,9 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) Checkbox("Anti-aliased lines", &style.AntiAliasedLines); SameLine(); HelpMarker("When disabling anti-aliasing lines, you'll probably want to disable borders in your style as well."); - + BeginDisabled(style.AntiAliasedLines == false); Checkbox("Anti-aliased line ends", &style.AntiAliasedLineEnds); + EndDisabled(); #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS BeginDisabled(); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 77ce95140..b1cad1505 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1045,6 +1045,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 const float half_aa = _FringeScale * 0.5f; // Used for end caps, does not use "fringe" since end cap AA is not using the texture. const float half_thickness = thickness * 0.5f; + const bool use_aa_ends = (flags & (ImDrawFlags_AALines | ImDrawFlags_AALineEnds)) == (ImDrawFlags_AALines | ImDrawFlags_AALineEnds); const int first_vtx_offset = (int)(_VtxWritePtr - VtxBuffer.Data); int base_idx = (int)_VtxCurrentIdx; @@ -1073,7 +1074,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 p1.y -= dir.y * half_thickness; } - if ((flags & ImDrawFlags_AALineEnds) == 0) + if (!use_aa_ends) { base_idx = (int)_VtxCurrentIdx; IM_APPEND_VTX(p1.x - n1.x * thickness0, p1.y - n1.y * thickness0, uv0, col); @@ -1259,7 +1260,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 p1.y += dir.y * half_thickness; } - if ((flags & ImDrawFlags_AALineEnds) == 0) + if (!use_aa_ends) { IM_APPEND_VTX(p1.x - n1.x * thickness0, p1.y - n1.y * thickness0, uv0, col); IM_APPEND_VTX(p1.x + n1.x * thickness1, p1.y + n1.y * thickness1, uv1, col); @@ -1889,8 +1890,9 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t const float half_aa = _FringeScale * 0.5f; // Used for end caps, does not use "fringe" since end cap AA is not using the texture. const float half_thickness = thickness * 0.5f; + const bool use_aa_ends = (flags & (ImDrawFlags_AALines | ImDrawFlags_AALineEnds)) == (ImDrawFlags_AALines | ImDrawFlags_AALineEnds); - if ((flags & ImDrawFlags_AALineEnds) == 0) + if (!use_aa_ends) PrimReserve(2*3, 4); else PrimReserve(6*3, 8); @@ -1910,7 +1912,7 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t } // Start cap - if ((flags & ImDrawFlags_AALineEnds) == 0) + if (!use_aa_ends) { base_idx = (int)_VtxCurrentIdx; IM_APPEND_VTX(p1_x - norm_x * thickness0, p1_y - norm_y * thickness0, uv0, col); @@ -7622,7 +7624,7 @@ void ImGui::RenderCheckMark(ImDrawList* draw_list, ImVec2 pos, ImU32 col, float draw_list->PathLineTo(ImVec2(bx - third, by - third)); draw_list->PathLineTo(ImVec2(bx, by)); draw_list->PathLineTo(ImVec2(bx + third * 2.0f, by - third * 2.0f)); - draw_list->PathStroke(col, thickness, (draw_list->Flags & ImDrawFlags_AALines) ? ImDrawFlags_AALineEnds : 0); // FIXME: Design idiomatic selection/threshold for this? + draw_list->PathStroke(col, thickness, ImDrawFlags_AALineEnds); } // Render an arrow. 'pos' is position of the arrow tip. half_sz.x is length from base to tip. half_sz.y is length on each side. From 059017188d145988441b4859c4d26f121daa8851 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 30 Jul 2026 17:56:04 +0200 Subject: [PATCH 099/120] DrawList: baking half-texel in TexUvLines[] now that AddPolylineLegacy() was removed. --- imgui_draw.cpp | 31 ++++++++++++------------------- imgui_internal.h | 2 +- 2 files changed, 13 insertions(+), 20 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index b1cad1505..05b94231f 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1033,12 +1033,11 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 } PrimReserve(idx_count, vtx_count); - const float half_texel = 0.5f * _Data->FontAtlas->TexUvScale.x; const float ratio = thickness0 / (thickness0 + thickness1); // The points that use uv2 are placed directly on the path (offset = 0), calculate the texture position from stroke offsets. ImVec2 uv0, uv1, uv2; - uv0.x = tex_uvs.x + half_texel; + uv0.x = tex_uvs.x; uv0.y = tex_uvs.z; - uv1.x = tex_uvs.y - half_texel; + uv1.x = tex_uvs.y; uv1.y = tex_uvs.z; uv2.x = uv0.x + (uv1.x - uv0.x) * ratio; uv2.y = tex_uvs.z; @@ -1877,13 +1876,11 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t thickness0 = fringe * 0.5f; float thickness1 = (thickness + fringe) - thickness0; - - const float half_texel = 0.5f * _Data->FontAtlas->TexUvScale.x; const float ratio = thickness0 / (thickness0 + thickness1); // The points using uv2 are placed on the path, calculate the position from stroke offets. ImVec2 uv0, uv1, uv2; - uv0.x = tex_uvs.x + half_texel; + uv0.x = tex_uvs.x; uv0.y = tex_uvs.z; - uv1.x = tex_uvs.y - half_texel; + uv1.x = tex_uvs.y; uv1.y = tex_uvs.z; uv2.x = uv0.x + (uv1.x - uv0.x) * ratio; uv2.y = tex_uvs.z; @@ -2244,12 +2241,11 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, rounding += half_fringe; const float stem_offset = ImMin(rounding, thickness); // The function might get called with rounding slightly bigger than thickness, which is ok, but we should keep the stem inside the shape. - const float half_texel = 0.5f * _Data->FontAtlas->TexUvScale.x; const float ratio = stem_offset / thickness; ImVec2 inner_uv, outer_uv, stem_uv; - outer_uv.x = tex_uvs.x + half_texel; + outer_uv.x = tex_uvs.x; outer_uv.y = tex_uvs.z; - inner_uv.x = tex_uvs.y - half_texel; + inner_uv.x = tex_uvs.y; inner_uv.y = tex_uvs.z; stem_uv.x = outer_uv.x + (inner_uv.x - outer_uv.x) * ratio; stem_uv.y = tex_uvs.z; @@ -2434,8 +2430,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl const int idx = (s_thickness * IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX) + s_rounding - 1; IM_ASSERT_PARANOID((_Data->Font->OwnerAtlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners) == 0); IM_ASSERT_PARANOID(idx >= 0 && idx < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX * IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX); - const ImVec4 tex_uvs = _Data->TexUvCorners[idx]; - _AddRectBaked(outer_min, outer_max, col, (float)size * _FringeScale, (float)thickness, tex_uvs, flags); + _AddRectBaked(outer_min, outer_max, col, (float)size * _FringeScale, (float)thickness, _Data->TexUvCorners[idx], flags); return; } } @@ -2581,8 +2576,7 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c const int size = s_rounding + 1; const int idx = (s_rounding - 1); IM_ASSERT_PARANOID(idx >= 0 && idx < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX); - ImVec4 tex_uvs = _Data->TexUvCorners[idx]; - _AddRectFilledBaked(p_min, p_max, col, (float)size * _FringeScale, tex_uvs, flags); + _AddRectFilledBaked(p_min, p_max, col, (float)size * _FringeScale, _Data->TexUvCorners[idx], flags); return; } } @@ -5103,8 +5097,8 @@ static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) } // Refresh UV coordinates - ImVec2 uv0 = ImVec2((float)(r.x + pad_left - 1), (float)(r.y + y)) * atlas->TexUvScale; - ImVec2 uv1 = ImVec2((float)(r.x + pad_left + line_width + 1), (float)(r.y + y + 1)) * atlas->TexUvScale; + ImVec2 uv0 = ImVec2((float)(r.x + pad_left - 0.5f), (float)(r.y + y)) * atlas->TexUvScale; + ImVec2 uv1 = ImVec2((float)(r.x + pad_left + line_width + 0.5f), (float)(r.y + y + 1)) * atlas->TexUvScale; float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts // Store inverse of the line-width (thickness) to the w component so that we can avoid division when selecting texture. float inv_line_width = (line_width > 0) ? (1.0f / (float)line_width) : 0.0f; @@ -5113,7 +5107,6 @@ static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) } { - ImU8 ramp[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT]; for (int n = 0; n < IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT; n++) ramp[n] = (ImU8)(((float)n / IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT) * 255.0f); @@ -5172,8 +5165,8 @@ static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) } // Refresh UV coordinates - ImVec2 uv0 = ImVec2((float)(r.x), (float)(r.y + y)) * atlas->TexUvScale; - ImVec2 uv1 = ImVec2((float)(r.x + IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT * 2 + line_width), (float)(r.y + y + 1)) * atlas->TexUvScale; + ImVec2 uv0 = ImVec2((float)(r.x + 0.5f), (float)(r.y + y)) * atlas->TexUvScale; + ImVec2 uv1 = ImVec2((float)(r.x - 0.5f + IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT * 2 + line_width), (float)(r.y + y + 1)) * atlas->TexUvScale; float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts // Store inverse of the line-width (thickness) to the w component so that we can avoid division when selecting texture. float inv_line_width = 1.0f / (1.0f + (float)n / (float)IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT); diff --git a/imgui_internal.h b/imgui_internal.h index 2a246cd7f..9024a6c90 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -4087,7 +4087,7 @@ struct ImFontAtlasBuilder ImFontAtlasRectId PackIdCornersTexData; // Cached UV coordinates - ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines + ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines (u0, u1, v, 1/thickness) ImVec4 TexUvCorners[IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX * IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX]; // UVs for baked anti-aliased corners (0= fill, 1> stroke thickness) ImFontAtlasBuilder() { memset((void*)this, 0, sizeof(*this)); FrameCount = -1; RectsIndexFreeListStart = -1; PackIdMouseCursors = PackIdLinesTexData = PackIdLineFractTexData = PackIdCornersTexData = -1; } From a547deed9b015b2fa96dac48955d6b39bbf2ccb9 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 30 Jul 2026 18:12:53 +0200 Subject: [PATCH 100/120] DrawList: fixed AddRectFilled() path for minimum dimension. --- imgui_draw.cpp | 21 ++++----------------- 1 file changed, 4 insertions(+), 17 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 05b94231f..7513cecc3 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1415,6 +1415,8 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun if (bevel) { // Limit inner bevel so that it is does not shoot out outside the polygon. + // (using ImMax is intentional: using min limited too much in Mikko's tests when you have short edge next to long one. + // The idea of that check is to keep that worst case overshoot no bigger than the polygon) const float ref_thickness_sqr = half_aa * half_aa; const float limit_sqr = ImMax(temp_sqr_lengths[i0], temp_sqr_lengths[i1]); const float ref_miter_dist_sqr = miter_distance_sqr * ref_thickness_sqr; @@ -2535,7 +2537,7 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c col = ImAlphaMultiply(col, height * _InvFringeScale); height = _FringeScale; } - const ImVec2 points[4] = { p_min, ImVec2(p_min.x + width, p_min.y), ImVec2(p_min.x + width, p_min.y + height), ImVec2(p_min.x, p_max.y + height) }; + const ImVec2 points[4] = { p_min, ImVec2(p_min.x + width, p_min.y), ImVec2(p_min.x + width, p_min.y + height), ImVec2(p_min.x, p_min.y + height) }; AddConvexPolyFilled(points, 4, col, ImDrawFlags_MiterOnly); return; } @@ -5058,9 +5060,7 @@ static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) } { - // Register texture region for thick lines - // The +2 here is to give space for the end caps, whilst height +1 is to accommodate the fact we have a zero-width row - // This generates a triangular shape in the texture, with the various line widths stacked on top of each other to allow interpolation between them + // FIXME: Lines 0 to IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH(4) are currently never used and could be ditched, saving DETAILED_WIDTH*WIDTH_MAX for (int n = 0; n < IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1; n++) // +1 because of the zero-width row { // Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle @@ -5076,10 +5076,8 @@ static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) ImU8* write_ptr = (ImU8*)tex->GetPixelsAt(r.x, r.y + y); for (int i = 0; i < pad_left; i++) *(write_ptr + i) = 0x00; - for (int i = 0; i < line_width; i++) *(write_ptr + pad_left + i) = 0xFF; - for (int i = 0; i < pad_right; i++) *(write_ptr + pad_left + line_width + i) = 0x00; } @@ -5088,10 +5086,8 @@ static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) ImU32* write_ptr = (ImU32*)(void*)tex->GetPixelsAt(r.x, r.y + y); for (int i = 0; i < pad_left; i++) *(write_ptr + i) = IM_COL32(255, 255, 255, 0); - for (int i = 0; i < line_width; i++) *(write_ptr + pad_left + i) = IM_COL32_WHITE; - for (int i = 0; i < pad_right; i++) *(write_ptr + pad_left + line_width + i) = IM_COL32(255, 255, 255, 0); } @@ -5132,34 +5128,25 @@ static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) { ImU8* write_ptr = (ImU8*)tex->GetPixelsAt(r.x, r.y + y); int idx = 0; - for (int i = 0; i < IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT; i++) write_ptr[idx++] = ramp[i]; - for (int i = 0; i < line_width; i++) write_ptr[idx++] = 0xFF; - for (int i = IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT - 1; i >= 0; i--) write_ptr[idx++] = ramp[i]; - while (idx < r.w) write_ptr[idx++] = 0x0; } else if (add_and_draw && tex->Format == ImTextureFormat_RGBA32) { ImU32* write_ptr = (ImU32*)(void*)tex->GetPixelsAt(r.x, r.y + y); - int idx = 0; - for (int i = 0; i < IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT; i++) write_ptr[idx++] = IM_COL32(255, 255, 255, ramp[i]); - for (int i = 0; i < line_width; i++) write_ptr[idx++] = IM_COL32(255, 255, 255, 255); - for (int i = IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT - 1; i >= 0; i--) write_ptr[idx++] = IM_COL32(255, 255, 255, ramp[i]); - while (idx < r.w) write_ptr[idx++] = IM_COL32(255, 255, 255, 0); } From 640e9312845d4cdebdadb521a7a4aaf16da74be3 Mon Sep 17 00:00:00 2001 From: thedmd Date: Thu, 30 Jul 2026 21:25:37 +0200 Subject: [PATCH 101/120] DrawList: fixed passing large thickness value (e.g. 1e20f) to thickness crashing the code. --- imgui_draw.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 7513cecc3..703c196ca 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -915,8 +915,10 @@ void ImDrawList::_SelectFringeTexture(float screen_thickness, ImVec4* out_tex_uv int texture_idx; if (screen_thickness <= IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH) IM_LIKELY texture_idx = (int)((screen_thickness - 1.0f) * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + 0.5f) + (IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1); + else if (screen_thickness < (float)IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1.5f) IM_LIKELY + texture_idx = (int)(screen_thickness + 0.5f); else - texture_idx = ImMin((int)(screen_thickness + 0.5f), IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1); + texture_idx = IM_DRAWLIST_TEX_LINES_WIDTH_MAX - 1; // Catches large values of screen_thickness (and +inf/NaN) that a ImMin() wouldn't. // Scale the fringe so that the texture matches the line thickness. ImVec4 tex_uvs = _Data->TexUvLines[texture_idx]; From 475660f53c4fb000aa1d90a5a33fedafcaf8abd0 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Fri, 31 Jul 2026 11:39:16 +0300 Subject: [PATCH 102/120] DrawList: fixed overdraw issue for filled baked corner rendering. Require all corners to fit inside the rect to avoid overdraw (or fallback to path rendering). eg. AddRectFilled() w/ size=10, rounding=8 or 10 would break. --- imgui_draw.cpp | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 703c196ca..bf4ff1a82 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2577,11 +2577,15 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c { IM_ASSERT_PARANOID(!(_Data->Font->OwnerAtlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners)); // This is matching the baking calculations. The rounding clamping above makes sure that there's enough space for the extra pixel. - const int size = s_rounding + 1; - const int idx = (s_rounding - 1); - IM_ASSERT_PARANOID(idx >= 0 && idx < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX); - _AddRectFilledBaked(p_min, p_max, col, (float)size * _FringeScale, _Data->TexUvCorners[idx], flags); - return; + const int corner_size = s_rounding + 1; + // Check that the baked corners do not overlap (this could be made more accurate if tested for x and y separately). + if ((float)corner_size * 2.0f * _FringeScale < min_dim) + { + const int idx = (s_rounding - 1); + IM_ASSERT_PARANOID(idx >= 0 && idx < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX); + _AddRectFilledBaked(p_min, p_max, col, (float)corner_size * _FringeScale, _Data->TexUvCorners[idx], flags); + return; + } } } From 91dc4f456a63812a6b64fd158b10f056a3ec1b8d Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Fri, 31 Jul 2026 11:45:41 +0300 Subject: [PATCH 103/120] DrawList: fixed rendering issues on small and degenerate shapes. The "does stroke cover the whole shape" used to be overly pessimistic to stroking artifacts. This was due to the fringe, where the geometry would mess up before visually closing the shape. Fixed the issue by adding max inner offest for the AddPolyline(), which limits the inner offset and also adjusts the texture coords. Second part of the fix was to calculate the pass the max inner offset from the common shapes (circle, ngon, ellipse, rect). While this does not fix all the ellipse rendering issues, it limits the issue a lot. e.g. AddRect() w/ size=5, thickness=2 used to trigger AddRectFilled() fallback because threshold was pessimistic to prevent the AddPolyline() issues. --- imgui.h | 5 ++-- imgui_draw.cpp | 76 +++++++++++++++++++++++++++++++++++++------------- 2 files changed, 60 insertions(+), 21 deletions(-) diff --git a/imgui.h b/imgui.h index de6fec28a..1e0246ece 100644 --- a/imgui.h +++ b/imgui.h @@ -3452,7 +3452,7 @@ struct ImDrawList // General polygon // - Only simple polygons are supported by filling functions (no self-intersections, no holes). // - Concave polygon fill is more expensive than convex one: it has O(N^2) complexity. Provided as a convenience for the user but not used by the main library. - IMGUI_API void AddPolyline(const ImVec2* points, int num_points, ImU32 col, float thickness, ImDrawFlags flags = 0); + inline void AddPolyline(const ImVec2* points, int num_points, ImU32 col, float thickness, ImDrawFlags flags = 0) { _AddPolyline(points, num_points, col, thickness, flags, FLT_MAX); } IMGUI_API void AddConvexPolyFilled(const ImVec2* points, int num_points, ImU32 col, ImDrawFlags flags = 0); IMGUI_API void AddConcavePolyFilled(const ImVec2* points, int num_points, ImU32 col); @@ -3472,7 +3472,7 @@ struct ImDrawList inline void PathLineToMergeDuplicate(const ImVec2& pos) { if (_Path.Size == 0 || memcmp(&_Path.Data[_Path.Size - 1], &pos, 8) != 0) _Path.push_back(pos); } inline void PathFillConvex(ImU32 col, ImDrawFlags flags = 0) { AddConvexPolyFilled(_Path.Data, _Path.Size, col, flags); _Path.Size = 0; } inline void PathFillConcave(ImU32 col) { AddConcavePolyFilled(_Path.Data, _Path.Size, col); _Path.Size = 0; } - inline void PathStroke(ImU32 col, float thickness = 1.0f, ImDrawFlags flags = 0) { AddPolyline(_Path.Data, _Path.Size, col, thickness, flags); _Path.Size = 0; } + inline void PathStroke(ImU32 col, float thickness = 1.0f, ImDrawFlags flags = 0) { _AddPolyline(_Path.Data, _Path.Size, col, thickness, flags, FLT_MAX); _Path.Size = 0; } IMGUI_API void PathArcTo(const ImVec2& center, float radius, float a_min, float a_max, int num_segments = 0); IMGUI_API void PathArcToFast(const ImVec2& center, float radius, int a_min_of_12, int a_max_of_12); // Use precomputed angles for a 12 steps circle IMGUI_API void PathEllipticalArcTo(const ImVec2& center, const ImVec2& radius, float rot, float a_min, float a_max, int num_segments = 0); // Ellipse @@ -3556,6 +3556,7 @@ struct ImDrawList IMGUI_API void _AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags); IMGUI_API void _SelectFringeTexture(float screen_thickness, ImVec4* out_tex_uvs, float* out_fringe, ImDrawFlags flags); IMGUI_API float _CalculateCenterBiasedOffset(float thickness); + IMGUI_API void _AddPolyline(const ImVec2* points, int num_points, ImU32 col, float thickness, ImDrawFlags flags, float max_inner_thickness); }; // All draw data to render a Dear ImGui frame diff --git a/imgui_draw.cpp b/imgui_draw.cpp index bf4ff1a82..6fd56bb04 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -942,7 +942,7 @@ static inline ImDrawFlags _GetStrokePos(ImDrawFlags flags, ImDrawFlags default_s IM_MSVC_RUNTIME_CHECKS_RESTORE -void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags) +void ImDrawList::_AddPolyline(const ImVec2* points, const int points_count, ImU32 col, float thickness, ImDrawFlags flags, float max_inner_offset) { if (points_count < 2 || (col & IM_COL32_A_MASK) == 0) return; @@ -1044,6 +1044,16 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 uv2.x = uv0.x + (uv1.x - uv0.x) * ratio; uv2.y = tex_uvs.z; + // Clamp inner offset and adjust texture coordinates to match the change. + // This is used by some of the convex shape rendering to avoid rendering artifacts when thickness is about to fill the whole shape. + if (thickness1 > max_inner_offset) + { + thickness1 = max_inner_offset; + const float s = (thickness0 + thickness1) / (thickness + fringe); + const float du = uv1.x - uv0.x; + uv1.x = uv0.x + du * s; + } + const float half_aa = _FringeScale * 0.5f; // Used for end caps, does not use "fringe" since end cap AA is not using the texture. const float half_thickness = thickness * 0.5f; const bool use_aa_ends = (flags & (ImDrawFlags_AALines | ImDrawFlags_AALineEnds)) == (ImDrawFlags_AALines | ImDrawFlags_AALineEnds); @@ -2369,7 +2379,8 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl const float width = outer_max.x - outer_min.x; const float height = outer_max.y - outer_min.y; - if (ImMin(width, height) < (thickness + _FringeScale) * 2.0f) IM_UNLIKELY + const float half_min_dim = ImMin(width, height) * 0.5f; + if (half_min_dim <= thickness) IM_UNLIKELY { // Rectangle has collapsed into filled rectangle. AddRectFilled(outer_min, outer_max, col, outer_rounding, flags); @@ -2442,7 +2453,8 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl if (!has_rounding || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) { const ImVec2 points[4] = { outer_min, ImVec2(outer_max.x, outer_min.y), outer_max, ImVec2(outer_min.x, outer_max.y) }; - AddPolyline(points, 4, col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | ImDrawFlags_StrokeInside | (flags & ~ImDrawFlags_StrokeMask_)); + _AddPolyline(points, 4, col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | ImDrawFlags_StrokeInside, half_min_dim); + _Path.Size = 0; } else { @@ -2462,7 +2474,9 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl PathArcToFast(ImVec2(outer_max.x - rounding_tr, outer_min.y + rounding_tr), rounding_tr, 9, 12); PathArcToFast(ImVec2(outer_max.x - rounding_br, outer_max.y - rounding_br), rounding_br, 0, 3); PathArcToFast(ImVec2(outer_min.x + rounding_bl, outer_max.y - rounding_bl), rounding_bl, 3, 6); - PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | ImDrawFlags_StrokeInside | (flags & ~ImDrawFlags_StrokeMask_)); + + _AddPolyline(_Path.Data, _Path.Size, col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | ImDrawFlags_StrokeInside | (flags & ~ImDrawFlags_StrokeMask_), half_min_dim); + _Path.Size = 0; } } @@ -2628,7 +2642,7 @@ void ImDrawList::AddQuad(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, c flags = (flags & ~ImDrawFlags_StrokeMask_) | stroke_pos; // As AddPolyline() default is different const ImVec2 points[4] = { p1, p2, p3, p4 }; - AddPolyline(points, 4, col, thickness, flags | ImDrawFlags_Closed); + _AddPolyline(points, 4, col, thickness, flags | ImDrawFlags_Closed, FLT_MAX); } void ImDrawList::AddQuadFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, const ImVec2& p4, ImU32 col) @@ -2650,7 +2664,7 @@ void ImDrawList::AddTriangle(const ImVec2& p1, const ImVec2& p2, const ImVec2& p flags = (flags & ~ImDrawFlags_StrokeMask_) | stroke_pos; // As AddPolyline() default is different const ImVec2 points[3] = { p1, p2, p3 }; - AddPolyline(points, 3, col, thickness, flags | ImDrawFlags_Closed); + _AddPolyline(points, 3, col, thickness, flags | ImDrawFlags_Closed, FLT_MAX); } void ImDrawList::AddTriangleFilled(const ImVec2& p1, const ImVec2& p2, const ImVec2& p3, ImU32 col) @@ -2662,6 +2676,14 @@ void ImDrawList::AddTriangleFilled(const ImVec2& p1, const ImVec2& p2, const ImV AddConvexPolyFilled(points, 3, col); } +static float ApproxApothem(int num_segments) +{ + // Calculates approximation (within 0.001%) distance from the center of the circumscribed circle to the edge of the polygon, cos(pi / n) + IM_ASSERT_PARANOID(num_segments >= 3); + const float u = 1.0f / num_segments; + return ((4.058712f * u * u) - 4.934802f) * u * u + 1.0f; +} + void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int num_segments, float thickness, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) @@ -2685,7 +2707,8 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu outer_radius = radius; else if (stroke_pos == ImDrawFlags_StrokeOutside) outer_radius = radius + thickness; - if (outer_radius < (thickness + _FringeScale)) IM_UNLIKELY + + if (outer_radius <= thickness) IM_UNLIKELY { // The circle has collapsed into a filled circle. AddCircleFilled(center, outer_radius, col, num_segments); @@ -2709,7 +2732,15 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu PathArcTo(center, radius, 0.0f, a_max, num_segments - 1); } - PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | stroke_pos); + if (_Path.Size < 3) + return; + + // Calculate distance to the edge of the polygon describing the circle. + // This will be used by _AddPolyline() to clamp the inner stroke expansion, to avoid creating rendering artifacts. + const float unit_apothem = ApproxApothem(_Path.Size); + const float edge_dist = radius * unit_apothem; + _AddPolyline(_Path.Data, _Path.Size, col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | stroke_pos, edge_dist); + _Path.Size = 0; } void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col, int num_segments) @@ -2760,22 +2791,24 @@ void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_ } radius = ImMax(0.01f, radius); - // Check overestimate first before testing details (+1.0f is intended, not FringeScale) - if (radius < (thickness * 2.0f + 1.0f)) - { - const float unit_apothem = ImCos(IM_PI / (float)num_segments); - const float miter_thickness = thickness / unit_apothem; + // Calculate distance to the edge of the polygon describing the circle. + // This will be used by _AddPolyline() to clamp the inner stroke expansion, to avoid creating rendering artifacts. + const float unit_apothem = ApproxApothem(num_segments); + const float edge_dist = radius * unit_apothem; + // Check overestimate first before testing details. + if (radius < thickness * 2.0f) + { float outer_radius = radius; if (stroke_pos == ImDrawFlags_StrokeCenter) - outer_radius = radius + miter_thickness * 0.5f; + outer_radius = radius + thickness * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeCenterBiased) - outer_radius = radius + _CalculateCenterBiasedOffset(miter_thickness); + outer_radius = radius + _CalculateCenterBiasedOffset(thickness); else if (stroke_pos == ImDrawFlags_StrokeInside) outer_radius = radius; else if (stroke_pos == ImDrawFlags_StrokeOutside) - outer_radius = radius + miter_thickness; - if (outer_radius < (miter_thickness + _FringeScale)) IM_UNLIKELY + outer_radius = radius + thickness; + if (outer_radius * unit_apothem <= thickness) IM_UNLIKELY { // The polygon has collapsed into a filled polygon. AddNgonFilled(center, outer_radius, col, num_segments); @@ -2786,7 +2819,8 @@ void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_ // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; PathArcTo(center, radius, 0.0f, a_max, num_segments - 1); - PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | stroke_pos); + _AddPolyline(_Path.Data, _Path.Size, col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | stroke_pos, edge_dist); + _Path.Size = 0; } // Guaranteed to honor 'num_segments' @@ -2832,7 +2866,11 @@ void ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 co // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = IM_PI * 2.0f * ((float)num_segments - 1.0f) / (float)num_segments; PathEllipticalArcTo(center, rad, rot, 0.0f, a_max, num_segments - 1); - PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | stroke_pos); + + // Prevent the inside part of the stroke to be expanded too much to prevent some rendering artifacts. + const float max_inner_offset = ImMin(rad.x, rad.y); + _AddPolyline(_Path.Data, _Path.Size, col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | stroke_pos, max_inner_offset); + _Path.Size = 0; } void ImDrawList::AddEllipseFilled(const ImVec2& center, const ImVec2& radius, ImU32 col, float rot, int num_segments) From c6c352f77d2fc81c27cb9414ae05dfe4053957bc Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Fri, 31 Jul 2026 12:39:29 +0300 Subject: [PATCH 104/120] DrawList: fixed too many detailed line textures being created. Fixed issue where were creating detailed line textures up to detailed witdh + 1 Changed IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX to IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_COUNT which no accounts for all the detailed line textures. --- imgui_draw.cpp | 11 ++++++++--- imgui_internal.h | 6 +++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 6fd56bb04..2f1ecea62 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -911,7 +911,7 @@ void ImDrawList::_SelectFringeTexture(float screen_thickness, ImVec4* out_tex_uv { // Handle the thickness between 1.0 - IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH]. // We use super sampled textures in this range to make texture changes less noticeable. - // There are IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX+1 textures, where 0 maps to 1.0 and IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX maps to IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH (4.0). + // There are IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_COUNT textures, where 0 maps to 1.0 and IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_COUNT-1 maps to IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH (4.0). int texture_idx; if (screen_thickness <= IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH) IM_LIKELY texture_idx = (int)((screen_thickness - 1.0f) * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + 0.5f) + (IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1); @@ -5098,7 +5098,9 @@ static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) bool add_and_draw = atlas->GetCustomRect(builder->PackIdLinesTexData, &r) == false; if (add_and_draw) { - ImVec2i pack_size = ImVec2i(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX + 1); + // Register texture region for thick lines + // The +2 here is to give space for the end caps, whilst height +1 is to accommodate the fact we have a zero-width row + ImVec2i pack_size = ImVec2i(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_COUNT); builder->PackIdLinesTexData = atlas->AddCustomRect(pack_size.x, pack_size.y, &r); IM_ASSERT(builder->PackIdLinesTexData != ImFontAtlasRectId_Invalid); } @@ -5147,11 +5149,14 @@ static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) } { + // Calculate super sampled lin textures for smaller line widths to make transition between sizes smoother. + + // Calc ramp used for all the textures. ImU8 ramp[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT]; for (int n = 0; n < IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT; n++) ramp[n] = (ImU8)(((float)n / IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT) * 255.0f); - for (int n = 0; n < IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX + 1; n++) + for (int n = 0; n < IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_COUNT; n++) { const int y = IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + n; // For integer thickness lines the one pixel line texture looks like this: diff --git a/imgui_internal.h b/imgui_internal.h index 9024a6c90..1f2e26268 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -938,7 +938,7 @@ IMGUI_API ImGuiStoragePair* ImLowerBound(ImGuiStoragePair* in_begin, ImGuiStorag #ifndef IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX #define IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX (4) // 0: fill, 1-3: strokes thickness #endif -#define IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX (IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT) +#define IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_COUNT ((IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH - 1) * IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + 1) // Data shared between all ImDrawList instances // Conceptually this could have been called e.g. ImDrawListSharedContext @@ -4087,8 +4087,8 @@ struct ImFontAtlasBuilder ImFontAtlasRectId PackIdCornersTexData; // Cached UV coordinates - ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines (u0, u1, v, 1/thickness) - ImVec4 TexUvCorners[IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX * IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX]; // UVs for baked anti-aliased corners (0= fill, 1> stroke thickness) + ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1 + IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH_COUNT]; // UVs for baked anti-aliased lines (u0, u1, v, 1/thickness) + ImVec4 TexUvCorners[IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX * IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX]; // UVs for baked anti-aliased corners (0= fill, 1> stroke thickness) ImFontAtlasBuilder() { memset((void*)this, 0, sizeof(*this)); FrameCount = -1; RectsIndexFreeListStart = -1; PackIdMouseCursors = PackIdLinesTexData = PackIdLineFractTexData = PackIdCornersTexData = -1; } }; From 3dbb849acf074a1840b61cba3b9bc0f847218e26 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Fri, 31 Jul 2026 16:45:01 +0300 Subject: [PATCH 105/120] DrawList: fixed PathArcTo to not emit small segments. - Changed PathArcTo to select start/end sample in quatized multiples of arc fast sample count - This is done in order to avoid small segments at the start and end of the path, which causes issues with line AA ends - Renamed CalcArcStepAndCount to CalcCornerArcStepAndCount to make it more clear that it returns the values for 90 degree arc --- imgui_draw.cpp | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 2f1ecea62..109336105 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1511,7 +1511,7 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun // Calculates arc step and step count for 90 degree arc. The calculated arc step will complete a 90 degree arc when stepped arc_step_count times. IM_MSVC_RUNTIME_CHECKS_OFF -static void CalcArcStepAndCount(int segment_count, int& arc_step, int& arc_step_count) +static void CalcCornerArcStepAndCount(int segment_count, int& arc_step, int& arc_step_count) { arc_step = ImClamp(IM_DRAWLIST_ARCFAST_SAMPLE_MAX / segment_count, 1, IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4); arc_step_count = (IM_DRAWLIST_ARCFAST_TABLE_SIZE / 4) / arc_step; @@ -1534,7 +1534,7 @@ void ImDrawList::_PathArcToFastEx(const ImVec2& center, float radius, int a_min_ // Calculate step size using the common function. No need to clamp, the step is already in range. const int auto_seg_count = _CalcCircleAutoSegmentCount(radius); int arc_step_count; - CalcArcStepAndCount(auto_seg_count, a_step, arc_step_count); + CalcCornerArcStepAndCount(auto_seg_count, a_step, arc_step_count); } else { @@ -1664,16 +1664,25 @@ void ImDrawList::PathArcTo(const ImVec2& center, float radius, float a_min, floa // Automatic segment count if (radius <= _Data->ArcFastRadiusCutoff) - { + { const bool a_is_reverse = a_max < a_min; + // Calculate number of segments per circle. + // First we calculate the step count as usual, then match to the precomputed directions. + // This will give us the spacing between the samples (step), and how many segments to use in total for a full circle. + const int auto_seg_count = _CalcCircleAutoSegmentCount(radius); + int arc_step, arc_step_count; + CalcCornerArcStepAndCount(auto_seg_count, arc_step, arc_step_count); + const int arc_seg_count = arc_step_count * 4; // Seg count for full circle. + // We are going to use precomputed values for mid samples. // Determine first and last sample in lookup table that belong to the arc. - const float a_min_sample_f = IM_DRAWLIST_ARCFAST_SAMPLE_MAX * a_min / (IM_PI * 2.0f); - const float a_max_sample_f = IM_DRAWLIST_ARCFAST_SAMPLE_MAX * a_max / (IM_PI * 2.0f); + // In first step we use the coarser segment count, and after rounding, convert back to the precomputed array index. + const float a_min_sample_f = arc_seg_count * a_min / (IM_PI * 2.0f); + const float a_max_sample_f = arc_seg_count * a_max / (IM_PI * 2.0f); - const int a_min_sample = a_is_reverse ? (int)ImFloor(a_min_sample_f) : (int)ImCeil(a_min_sample_f); - const int a_max_sample = a_is_reverse ? (int)ImCeil(a_max_sample_f) : (int)ImFloor(a_max_sample_f); + const int a_min_sample = (a_is_reverse ? (int)ImFloor(a_min_sample_f) : (int)ImCeil(a_min_sample_f)) * arc_step; + const int a_max_sample = (a_is_reverse ? (int)ImCeil(a_max_sample_f) : (int)ImFloor(a_max_sample_f)) * arc_step; const int a_mid_samples = a_is_reverse ? ImMax(a_min_sample - a_max_sample, 0) : ImMax(a_max_sample - a_min_sample, 0); const float a_min_segment_angle = a_min_sample * IM_PI * 2.0f / IM_DRAWLIST_ARCFAST_SAMPLE_MAX; @@ -1685,7 +1694,7 @@ void ImDrawList::PathArcTo(const ImVec2& center, float radius, float a_min, floa if (a_emit_start) _Path.push_back(ImVec2(center.x + ImCos(a_min) * radius, center.y + ImSin(a_min) * radius)); if (a_mid_samples > 0) - _PathArcToFastEx(center, radius, a_min_sample, a_max_sample, 0); + _PathArcToFastEx(center, radius, a_min_sample, a_max_sample, arc_step); if (a_emit_end) _Path.push_back(ImVec2(center.x + ImCos(a_max) * radius, center.y + ImSin(a_max) * radius)); } @@ -2231,7 +2240,7 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, const int auto_seg_count = _CalcCircleAutoSegmentCount(rounding); int arc_step, arc_step_count; - CalcArcStepAndCount(auto_seg_count, arc_step, arc_step_count); + CalcCornerArcStepAndCount(auto_seg_count, arc_step, arc_step_count); const int arc_point_count = arc_step_count + 1; int vtx_count = (2 + arc_point_count) * 4 + 3; @@ -4847,7 +4856,7 @@ static int InitCornerGeometry(float cx, float cy, float rounding, float circle_s const int auto_seg_count = IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_CALC(rounding, circle_segment_max_error); int arc_step, arc_step_count; - CalcArcStepAndCount(auto_seg_count, arc_step, arc_step_count); + CalcCornerArcStepAndCount(auto_seg_count, arc_step, arc_step_count); const int arc_point_count = arc_step_count + 1; // Build arc of points, with straight segments at each end. From 230cd6feb0ac32a27b8e2119a7ddaadc95197033 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 31 Jul 2026 17:52:34 +0200 Subject: [PATCH 106/120] DrawList: simplify ImFontAtlasBuildUpdateTexDataCorners() by merging fill/strokes loops. --- imgui_draw.cpp | 128 +++++++++++++------------------------------------ 1 file changed, 34 insertions(+), 94 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 109336105..0b0e5c648 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -4910,20 +4910,22 @@ static float SampleConvex(float x, float y, const ImVec2* line_normals, const fl return result; } -static ImU8 SampleCorner(float x, float y, const ImVec2* line_normals, const float* line_distances, int num_lines) +static ImU8 SampleCornerFillOrStroke(float x, float y, float thickness, const ImVec2* line_normals, const float* line_distances, int num_lines) { const float d = SampleConvex(x, y, line_normals, line_distances, num_lines); const float aa_width = 1.0f; - return (ImU8)(ImClamp(1.0f - (d + aa_width * 0.5f) / aa_width, 0.0f, 1.0f) * 255.0f); -} - -static ImU8 SampleCornerStroke(float x, float y, float thickness, const ImVec2* line_normals, const float* line_distances, int num_lines) -{ - const float d = SampleConvex(x, y, line_normals, line_distances, num_lines); - const float aa_width = 1.0f; - const float outer = 1.0f - (d + aa_width * 0.5f) / aa_width; - const float inner = (d + thickness + aa_width * 0.5f) / aa_width; - return (ImU8)(ImClamp(ImMin(inner, outer), 0.0f, 1.0f) * 255.0f); + if (thickness == 0.0f) + { + // Fill + return (ImU8)(ImClamp(1.0f - (d + aa_width * 0.5f) / aa_width, 0.0f, 1.0f) * 255.0f); + } + else + { + // Stroke + const float outer = 1.0f - (d + aa_width * 0.5f) / aa_width; + const float inner = (d + thickness + aa_width * 0.5f) / aa_width; + return (ImU8)(ImClamp(ImMin(inner, outer), 0.0f, 1.0f) * 255.0f); + } } #if 0 // Set to 1 to visualize coverage of textured corners @@ -4948,30 +4950,21 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) // if (atlas->OwnerContext) // circle_segment_max_error = atlas->OwnerContext->DrawListSharedData.CircleSegmentMaxError; + // Calculate size and pack ImFontAtlasRect r; bool add_and_draw = atlas->GetCustomRect(builder->PackIdCornersTexData, &r) == false; if (add_and_draw) { + // (thickness 0 for filled, thickness >0 for strokes) ImVec2i pack_size(0, 0); - // Filled corners - int row_width = 0; - int row_height = 0; - for (int i = 0; i < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX; i++) + for (int thickness = 0; thickness < IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX; thickness++) { - const int size = ImMax(2, i+1) + 2; - row_width += size; - row_height = ImMax(row_height, size); - } - pack_size.x = ImMax(pack_size.x, row_width); - pack_size.y += row_height; - // Rounded corners - for (int thickness = 1; thickness < IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX; thickness++) - { - row_width = 0; - row_height = 0; + const bool is_filled = (thickness == 0); + int row_width = 0; + int row_height = 0; for (int i = 0; i < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX; i++) { - const int size = ImMax(i + 2, thickness) + 2; + const int size = is_filled ? ImMax(i + 1, 2) + 2 : ImMax(i + 2, thickness) + 2; row_width += size; row_height = ImMax(row_height, size); } @@ -4982,80 +4975,27 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) IM_ASSERT(builder->PackIdCornersTexData != ImFontAtlasRectId_Invalid); } - int x = r.x; + // Render int y = r.y; - int row_height = 0; - ImVec2 line_normals[IM_DRAWLIST_CORNER_POINTS_MAX]; float line_distances[IM_DRAWLIST_CORNER_POINTS_MAX]; - - // Filled - for (int n = 0; n < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX; n++) + for (int thickness = 0; thickness < IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX; thickness++) { - const int rounding = n + 1; - const int s = rounding + 1; // Always add 1px to the fill side of the rounded corner to prevent the corner AA to bleed into the rectangle edges. - const int w = s + 2; - const int h = s + 2; - - // Corner circle - const float cx = 1.0f + (float)(n+1); - const float cy = 1.0f + (float)(n+1); - IM_ASSERT((x + w) <= (r.x + r.w) && (y + h) <= (r.y + r.h)); // Make sure we're inside the texture bounds before we start writing pixels - - const int num_lines = InitCornerGeometry(cx, cy, (float)rounding, circle_segment_max_error, line_normals, line_distances); - - // Write each slice - if (add_and_draw && tex->Format == ImTextureFormat_Alpha8) - { - ImU8* write_ptr = (ImU8*)tex->GetPixelsAt(x, y); - const int pitch = tex->Width; - for (int ly = 0; ly < h; ly++) - { - for (int lx = 0; lx < w; lx++) - write_ptr[lx] = SampleCorner((float)lx + 0.5f, (float)ly + 0.5f, line_normals, line_distances, num_lines) & DEBUG_TEX_CORNER_U8(lx, h); - write_ptr += pitch; - } - } - else if (add_and_draw && tex->Format == ImTextureFormat_RGBA32) - { - ImU32* write_ptr = (ImU32*)(void*)tex->GetPixelsAt(x, y); - const int pitch = tex->Width; - for (int ly = 0; ly < h; ly++) - { - for (int lx = 0; lx < w; lx++) - write_ptr[lx] = IM_COL32(255, 255, 255, SampleCorner((float)lx + 0.5f, (float)ly + 0.5f, line_normals, line_distances, num_lines)) & DEBUG_TEX_CORNER_U32(lx, h); - write_ptr += pitch; - } - } - - // Refresh UV coordinates - ImVec2 uv0 = ImVec2((float)(x + 1), (float)(y + 1)) * atlas->TexUvScale; - ImVec2 uv1 = ImVec2((float)(x + 1 + s), (float)(y + 1 + s)) * atlas->TexUvScale; - builder->TexUvCorners[n] = ImVec4(uv0.x, uv0.y, uv1.x, uv1.y); - - x += w; - row_height = ImMax(row_height, h); - } - y += row_height; - - // Stroked - for (int thickness = 1; thickness < IM_DRAWLIST_TEX_CORNERS_THICKNESS_MAX; thickness++) - { - x = r.x; - row_height = 0; + const bool is_filled = (thickness == 0); + int x = r.x; + int row_height = 0; for (int n = 0; n < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX; n++) { - const int rounding = n+1; - const int s = ImMax(rounding, thickness); + // Filled: Always add 1px to the fill side of the rounded corner to prevent the corner AA to bleed into the rectangle edges. + const int rounding = n + 1; + const int s = is_filled ? (rounding + 1) : ImMax(rounding, thickness); const int w = s + 2; const int h = s + 2; // Corner circle const float cx = 1.0f + (float)(n + 1); const float cy = 1.0f + (float)(n + 1); - - IM_ASSERT((x + w) <= (r.x + r.w) && (y + h) <= (r.y + r.h)); // Make sure we're inside the texture bounds before we start writing pixels - + IM_ASSERT((x + w) <= (r.x + r.w) && (y + h) <= (r.y + r.h)); // Make sure we're inside the texture bounds const int num_lines = InitCornerGeometry(cx, cy, (float)rounding, circle_segment_max_error, line_normals, line_distances); // Write each slice @@ -5065,8 +5005,8 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) const int pitch = tex->Width; for (int ly = 0; ly < h; ly++) { - for (int lx = 0; lx < h; lx++) - write_ptr[lx] = SampleCornerStroke((float)lx + 0.5f, (float)ly + 0.5f, (float)thickness, line_normals, line_distances, num_lines) & DEBUG_TEX_CORNER_U8(lx, h); + for (int lx = 0; lx < w; lx++) + write_ptr[lx] = SampleCornerFillOrStroke((float)lx + 0.5f, (float)ly + 0.5f, (float)thickness, line_normals, line_distances, num_lines) & DEBUG_TEX_CORNER_U8(lx, h); write_ptr += pitch; } } @@ -5076,8 +5016,8 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) const int pitch = tex->Width; for (int ly = 0; ly < h; ly++) { - for (int lx = 0; lx < h; lx++) - write_ptr[lx] = IM_COL32(255, 255, 255, SampleCornerStroke((float)lx + 0.5f, (float)ly + 0.5f, (float)thickness, line_normals, line_distances, num_lines)) & DEBUG_TEX_CORNER_U32(lx, h); + for (int lx = 0; lx < w; lx++) + write_ptr[lx] = IM_COL32(255, 255, 255, SampleCornerFillOrStroke((float)lx + 0.5f, (float)ly + 0.5f, (float)thickness, line_normals, line_distances, num_lines)) & DEBUG_TEX_CORNER_U32(lx, h); write_ptr += pitch; } } @@ -5085,7 +5025,7 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) // Refresh UV coordinates ImVec2 uv0 = ImVec2((float)(x + 1), (float)(y + 1)) * atlas->TexUvScale; ImVec2 uv1 = ImVec2((float)(x + 1 + s), (float)(y + 1 + s)) * atlas->TexUvScale; - builder->TexUvCorners[thickness * IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX + n] = ImVec4(uv0.x, uv0.y, uv1.x, uv1.y); + builder->TexUvCorners[n + thickness * IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX] = ImVec4(uv0.x, uv0.y, uv1.x, uv1.y); x += w; row_height = ImMax(row_height, h); From 451e107b3fa30b65c609a4f06ce149fb895dd370 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 31 Jul 2026 21:36:28 +0200 Subject: [PATCH 107/120] DrawList: removed ImDrawFlags_UseTexForStrokeLegacy as it doesn't have any use now that legacy code is out of the way. --- imgui.cpp | 5 ----- imgui.h | 10 ++++------ 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index fa5dfe7e4..9735c8320 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -398,7 +398,6 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: 2026/10/07 (1.93.0) - merged ImDrawListFlags into ImDrawFlags. obsoleted ImDrawListFlags (which were rarely used directly): - ImDrawListFlags_AntiAliasedLines -> ImDrawFlags_AALines, - ImDrawListFlags_AntiAliasedFill -> ImDrawFlags_AAFill, - - ImDrawListFlags_AntiAliasedLinesUseTex -> ImDrawFlags_UseTexForStrokeLegacy - ImDrawListFlags_AllowVtxOffset -> ImDrawFlags_UseVtxOffset, - ImDrawListFlags_TextNoPixelSnap -> ImDrawFlags_TextNoPixelSnap, unifying them allows easily using them for both per-primitives alterations and scope alterations. @@ -5634,10 +5633,6 @@ static void SetupDrawListSharedData() g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_AALines; if (g.Style.AntiAliasedLineEnds) g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_AALineEnds; -#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS - if (g.Style.AntiAliasedLinesUseTex && !(g.IO.Fonts->Flags & ImFontAtlasFlags_NoBakedLines)) - g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_UseTexForStrokeLegacy; -#endif if (!(g.IO.Fonts->Flags & ImFontAtlasFlags_NoBakedRoundCorners)) g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_UseTexForRoundCorners | ImDrawFlags_AllowTexForRoundCorners_; if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) diff --git a/imgui.h b/imgui.h index 1e0246ece..2cc63c679 100644 --- a/imgui.h +++ b/imgui.h @@ -3360,13 +3360,12 @@ enum ImDrawFlags_ // Other Options ------------------------------ // Prim/Scope? ImDrawFlags_TextNoPixelSnap = 1 << 20, // OK OK(0) // Disable automatically snapping AddText() calls to pixel boundaries. ImDrawFlags_UseTexForRoundCorners = 1 << 21, // -- OK(1) // Enable using textures instead of strokes to draw rounded corners/circles where possible (faster). Used by default unless 'ImFontAtlasFlags_NoBakedRoundCorners' is enabled in the font atlas. - ImDrawFlags_UseTexForStrokeLegacy = 1 << 22, // -- OK(1) // Enable anti-aliased lines/borders using textures when using legacy strokes. Require backend to render with bilinear filtering (NOT point/nearest filtering). Used by default unless 'ImFontAtlasFlags_NoBakedLines' is set in the font atlas. - ImDrawFlags_UseVtxOffset = 1 << 23, // -- OK(1) // Can emit 'VtxOffset > 0' to allow large meshes with 16-bit ImDrawIdx. Used by default when 'ImGuiBackendFlags_RendererHasVtxOffset' is enabled by the backend. - ImDrawFlags_AllowTexForRoundCorners_= 1 << 24, // -- OK(1) // [Internal] + ImDrawFlags_UseVtxOffset = 1 << 22, // -- OK(1) // Can emit 'VtxOffset > 0' to allow large meshes with 16-bit ImDrawIdx. Used by default when 'ImGuiBackendFlags_RendererHasVtxOffset' is enabled by the backend. + ImDrawFlags_AllowTexForRoundCorners_= 1 << 23, // -- OK(1) // [Internal] // [Internal] ImDrawFlags_RoundCornersMask_ = ImDrawFlags_RoundCornersAll | ImDrawFlags_RoundCornersNone, // [Internal] - ImDrawFlags_AllowInPushScope_ = ImDrawFlags_AAFill | ImDrawFlags_AALines | ImDrawFlags_AALineEnds | ImDrawFlags_StrokeLegacy | ImDrawFlags_TextNoPixelSnap | ImDrawFlags_UseTexForRoundCorners | ImDrawFlags_UseTexForStrokeLegacy | ImDrawFlags_UseVtxOffset | ImDrawFlags_RoundCornersMask_, // [Internal] Values allowed in PushDrawFlag() scope. + ImDrawFlags_AllowInPushScope_ = ImDrawFlags_AAFill | ImDrawFlags_AALines | ImDrawFlags_AALineEnds | ImDrawFlags_StrokeLegacy | ImDrawFlags_TextNoPixelSnap | ImDrawFlags_UseTexForRoundCorners | ImDrawFlags_UseVtxOffset | ImDrawFlags_RoundCornersMask_, // [Internal] Values allowed in PushDrawFlag() scope. ImDrawFlags_AllowInFrameScope_ = ImDrawFlags_AllowInPushScope_ | ImDrawFlags_AllowTexForRoundCorners_, ImDrawFlags_StrokeMask_ = 0x07 << 17, // [Internal] ImDrawFlags_InvalidMask_ = ~0x7FFFFFF0, // [Internal] == 0x8000000F. Reserved to detect misuses. @@ -4295,7 +4294,6 @@ namespace ImGui #define ImDrawCallback_ResetRenderState (ImDrawCallback)(-8) // OBSOLETED in 1.92.8: Use ImGui::GetPlatformIO().DrawCallback_ResetRenderState - // -- RENAMED in 1.93.0: Merged ImDrawListFlags into ImDrawFlags. typedef ImDrawFlags ImDrawListFlags; enum ImDrawListFlags_ @@ -4303,7 +4301,7 @@ enum ImDrawListFlags_ ImDrawListFlags_None = ImDrawFlags_None, ImDrawListFlags_AntiAliasedFill = ImDrawFlags_AAFill, ImDrawListFlags_AntiAliasedLines = ImDrawFlags_AALines, - ImDrawListFlags_AntiAliasedLinesUseTex = ImDrawFlags_UseTexForStrokeLegacy,// Only apply to StrokeLegacy mode! + ImDrawListFlags_AntiAliasedLinesUseTex = 0, // Only apply to StrokeLegacy mode! ImDrawListFlags_AllowVtxOffset = ImDrawFlags_UseVtxOffset, ImDrawListFlags_TextNoPixelSnap = ImDrawFlags_TextNoPixelSnap, }; From dee0e34504cdc1c42f875fca53835f10cec43043 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 31 Jul 2026 21:47:46 +0200 Subject: [PATCH 108/120] DrawList: amend PushDrawFlag() to report reaching capacity. --- imgui.h | 1 + imgui_draw.cpp | 1 + 2 files changed, 2 insertions(+) diff --git a/imgui.h b/imgui.h index 2cc63c679..aa0d0f079 100644 --- a/imgui.h +++ b/imgui.h @@ -2896,6 +2896,7 @@ struct ImSmallStack_ inline T& back() { IM_ASSERT(Size > 0); return LocalData[Size - 1]; } inline void push_back(const T& v) { IM_ASSERT(Size < Capacity); memcpy(&LocalData[Size], &v, sizeof(v)); Size++; } inline void pop_back() { IM_ASSERT(Size > 0); Size--; } + inline int capacity() const { return Capacity; } }; // Flags for ImGuiListClipper (currently not fully exposed in function calls: a future refactor will likely add this to ImGuiListClipper::Begin function equivalent) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 0b0e5c648..1882eb160 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -718,6 +718,7 @@ void ImDrawList::PushDrawFlag(ImDrawFlags flags, bool enabled) { IM_ASSERT((flags & ~ImDrawFlags_AllowInPushScope_) == 0); IM_ASSERT((flags & ImDrawFlags_StrokeMask_) == 0 || (flags & ImDrawFlags_StrokeMask_) == ImDrawFlags_StrokeLegacy); + IM_ASSERT_USER_ERROR_RET(_DrawFlagsStack.Size < _DrawFlagsStack.capacity(), "PushDrawFlag() has a depth limit. Contact us."); _DrawFlagsStack.push_back(Flags); if (enabled) Flags |= flags; From 7415edbad103f47d4537be3ead7c6cdd78cfb217 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 31 Jul 2026 21:49:55 +0200 Subject: [PATCH 109/120] DrawList: fixed ImFontAtlasBuildUpdateTexDataCorners() packing vs render to match. --- imgui_draw.cpp | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 1882eb160..8b409f1dd 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -4963,11 +4963,12 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) const bool is_filled = (thickness == 0); int row_width = 0; int row_height = 0; - for (int i = 0; i < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX; i++) + for (int n = 0; n < IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX; n++) { - const int size = is_filled ? ImMax(i + 1, 2) + 2 : ImMax(i + 2, thickness) + 2; - row_width += size; - row_height = ImMax(row_height, size); + const int rounding = n + 1; + const int size = (is_filled ? (rounding + 1) : ImMax(rounding, thickness)); + row_width += size + 2; + row_height = ImMax(row_height, size + 2); } pack_size.x = ImMax(pack_size.x, row_width); pack_size.y += row_height; @@ -4989,9 +4990,9 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) { // Filled: Always add 1px to the fill side of the rounded corner to prevent the corner AA to bleed into the rectangle edges. const int rounding = n + 1; - const int s = is_filled ? (rounding + 1) : ImMax(rounding, thickness); - const int w = s + 2; - const int h = s + 2; + const int size = (is_filled ? (rounding + 1) : ImMax(rounding, thickness)); + const int w = size + 2; + const int h = size + 2; // Corner circle const float cx = 1.0f + (float)(n + 1); @@ -5025,7 +5026,7 @@ static void ImFontAtlasBuildUpdateTexDataCorners(ImFontAtlas* atlas) // Refresh UV coordinates ImVec2 uv0 = ImVec2((float)(x + 1), (float)(y + 1)) * atlas->TexUvScale; - ImVec2 uv1 = ImVec2((float)(x + 1 + s), (float)(y + 1 + s)) * atlas->TexUvScale; + ImVec2 uv1 = ImVec2((float)(x + 1 + size), (float)(y + 1 + size)) * atlas->TexUvScale; builder->TexUvCorners[n + thickness * IM_DRAWLIST_TEX_CORNERS_ROUNDING_MAX] = ImVec4(uv0.x, uv0.y, uv1.x, uv1.y); x += w; From c52f84c71914e6517336883bf3c6d436ac47d371 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 4 Aug 2026 15:33:54 +0200 Subject: [PATCH 110/120] DrawList: rework _SelectFringeTexture() for use by third-party extensions + rename to _SelectLineTexture(). --- imgui.h | 2 +- imgui_draw.cpp | 45 ++++++++++++++------------------------------- 2 files changed, 15 insertions(+), 32 deletions(-) diff --git a/imgui.h b/imgui.h index aa0d0f079..eafe71d32 100644 --- a/imgui.h +++ b/imgui.h @@ -3554,7 +3554,7 @@ struct ImDrawList IMGUI_API void _AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float r, float t, const ImVec4& tex_uvs, ImDrawFlags flags); IMGUI_API void _AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float thickness, ImDrawFlags flags); IMGUI_API void _AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags); - IMGUI_API void _SelectFringeTexture(float screen_thickness, ImVec4* out_tex_uvs, float* out_fringe, ImDrawFlags flags); + IMGUI_API void _SelectLineTexture(float screen_thickness, ImVec2* out_uv0, ImVec2* out_uv1, float* out_fringe, ImDrawFlags flags); IMGUI_API float _CalculateCenterBiasedOffset(float thickness); IMGUI_API void _AddPolyline(const ImVec2* points, int num_points, ImU32 col, float thickness, ImDrawFlags flags, float max_inner_thickness); }; diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 8b409f1dd..8f53a8120 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -903,7 +903,7 @@ static ImU32 ImAlphaMultiply(ImU32 col, float alpha_mul) } // Select texture and adjust fringe size for specified line thickness. -void ImDrawList::_SelectFringeTexture(float screen_thickness, ImVec4* out_tex_uvs, float* out_fringe, ImDrawFlags flags) +void ImDrawList::_SelectLineTexture(float screen_thickness, ImVec2* out_uv0, ImVec2* out_uv1, float* out_fringe, ImDrawFlags flags) { // We assume that the screen_thickness has been clamped to 1.0 by the caller. IM_ASSERT_PARANOID(screen_thickness >= 1.0f); @@ -923,13 +923,15 @@ void ImDrawList::_SelectFringeTexture(float screen_thickness, ImVec4* out_tex_uv // Scale the fringe so that the texture matches the line thickness. ImVec4 tex_uvs = _Data->TexUvLines[texture_idx]; - *out_tex_uvs = tex_uvs; + out_uv0->x = tex_uvs.x; + out_uv1->x = tex_uvs.y; + out_uv0->y = out_uv1->y = tex_uvs.z; *out_fringe = _FringeScale * screen_thickness * tex_uvs.w; // tex_uvs.w = 1 / thickness } else { // For no anti-alias we use fully opaque texture, and no fringe expansion. - *out_tex_uvs = ImVec4(_Data->TexUvWhitePixel.x, _Data->TexUvWhitePixel.x, _Data->TexUvWhitePixel.y, 0.0f); // u0, u1, v, 1/thickness (unused) + *out_uv0 = *out_uv1 = _Data->TexUvWhitePixel; *out_fringe = 0.0f; } } @@ -997,9 +999,9 @@ void ImDrawList::_AddPolyline(const ImVec2* points, const int points_count, ImU3 sqr_lengths[points_count - 1] = 0.0f; } - ImVec4 tex_uvs; + ImVec2 uv0, uv1; float fringe; - _SelectFringeTexture(screen_thickness, &tex_uvs, &fringe, flags); + _SelectLineTexture(screen_thickness, &uv0, &uv1, &fringe, flags); const ImU32 col_trans = col & ~IM_COL32_A_MASK; @@ -1037,13 +1039,7 @@ void ImDrawList::_AddPolyline(const ImVec2* points, const int points_count, ImU3 PrimReserve(idx_count, vtx_count); const float ratio = thickness0 / (thickness0 + thickness1); // The points that use uv2 are placed directly on the path (offset = 0), calculate the texture position from stroke offsets. - ImVec2 uv0, uv1, uv2; - uv0.x = tex_uvs.x; - uv0.y = tex_uvs.z; - uv1.x = tex_uvs.y; - uv1.y = tex_uvs.z; - uv2.x = uv0.x + (uv1.x - uv0.x) * ratio; - uv2.y = tex_uvs.z; + const ImVec2 uv2(uv0.x + (uv1.x - uv0.x) * ratio, uv0.y); // Clamp inner offset and adjust texture coordinates to match the change. // This is used by some of the convex shape rendering to avoid rendering artifacts when thickness is about to fill the whole shape. @@ -1872,9 +1868,9 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t } flags |= Flags; - ImVec4 tex_uvs; + ImVec2 uv0, uv1; float fringe; - _SelectFringeTexture(screen_thickness, &tex_uvs, &fringe, flags); + _SelectLineTexture(screen_thickness, &uv0, &uv1, &fringe, flags); float dir_x = p2.x - p1.x; float dir_y = p2.y - p1.y; @@ -1901,13 +1897,7 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t float thickness1 = (thickness + fringe) - thickness0; const float ratio = thickness0 / (thickness0 + thickness1); // The points using uv2 are placed on the path, calculate the position from stroke offets. - ImVec2 uv0, uv1, uv2; - uv0.x = tex_uvs.x; - uv0.y = tex_uvs.z; - uv1.x = tex_uvs.y; - uv1.y = tex_uvs.z; - uv2.x = uv0.x + (uv1.x - uv0.x) * ratio; - uv2.y = tex_uvs.z; + const ImVec2 uv2(uv0.x + (uv1.x - uv0.x) * ratio, uv0.y); const float half_aa = _FringeScale * 0.5f; // Used for end caps, does not use "fringe" since end cap AA is not using the texture. const float half_thickness = thickness * 0.5f; @@ -1988,7 +1978,6 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t // AA cap IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); - } } @@ -2235,9 +2224,9 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, } flags |= Flags; - ImVec4 tex_uvs; + ImVec2 outer_uv, inner_uv; float fringe; - _SelectFringeTexture(screen_thickness, &tex_uvs, &fringe, flags); + _SelectLineTexture(screen_thickness, &outer_uv, &inner_uv, &fringe, flags); const int auto_seg_count = _CalcCircleAutoSegmentCount(rounding); int arc_step, arc_step_count; @@ -2266,13 +2255,7 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, const float stem_offset = ImMin(rounding, thickness); // The function might get called with rounding slightly bigger than thickness, which is ok, but we should keep the stem inside the shape. const float ratio = stem_offset / thickness; - ImVec2 inner_uv, outer_uv, stem_uv; - outer_uv.x = tex_uvs.x; - outer_uv.y = tex_uvs.z; - inner_uv.x = tex_uvs.y; - inner_uv.y = tex_uvs.z; - stem_uv.x = outer_uv.x + (inner_uv.x - outer_uv.x) * ratio; - stem_uv.y = tex_uvs.z; + const ImVec2 stem_uv(outer_uv.x + (inner_uv.x - outer_uv.x) * ratio, outer_uv.y); // Previous vertices, copied from the last vertices. int base_idx = (int)_VtxCurrentIdx; From 0e756a649d19996d6ca91d64e10a6aaf09f969a5 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 6 Oct 2026 16:33:42 +0200 Subject: [PATCH 111/120] (Breaking) DrawList: renamed ImDrawFlags_RoundCornersXXX to ImDrawFlags_RoundXXX. (#9504) --- docs/CHANGELOG.txt | 13 +++++ imgui.cpp | 14 ++++-- imgui.h | 63 +++++++++++++++--------- imgui_demo.cpp | 2 +- imgui_draw.cpp | 120 ++++++++++++++++++++++----------------------- imgui_widgets.cpp | 12 ++--- 6 files changed, 130 insertions(+), 94 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 00f31e1de..83e95c2ff 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -189,6 +189,19 @@ HOW TO UPDATE? Breaking Changes: +- DrawList: + - Renamed all `ImDrawFlags_RoundCornersXXX` flags to `ImDrawFlags_RoundXXX`. + - `ImDrawFlags_RoundCornersNone` -> `ImDrawFlags_RoundNone` + - `ImDrawFlags_RoundCornersAll` -> `ImDrawFlags_RoundAll` + - `ImDrawFlags_RoundCornersTopLeft` -> `ImDrawFlags_RoundTopLeft` + - `ImDrawFlags_RoundCornersTopRight` -> `ImDrawFlags_RoundTopRight` + - `ImDrawFlags_RoundCornersBottomLeft` -> `ImDrawFlags_RoundBottomLeft` + - `ImDrawFlags_RoundCornersBottomRight` -> `ImDrawFlags_RoundBottomRight` + - `ImDrawFlags_RoundCornersTop` -> `ImDrawFlags_RoundTop` + - `ImDrawFlags_RoundCornersBottom` -> `ImDrawFlags_RoundBottom` + - `ImDrawFlags_RoundCornersLeft` -> `ImDrawFlags_RoundLeft` + - `ImDrawFlags_RoundCornersRight` -> `ImDrawFlags_RoundRight` + Kept redirection enums (will obsolete). - Style: obsoleted `style.CurveTessellationTol (default 1.25)` which was in Pixels² unit in favor of `style.CurveTessellationMaxError` (default 1.12)` which is in Pixels unit. It is easier to tweak + this allows us to easily scale error threshold on high density screens. diff --git a/imgui.cpp b/imgui.cpp index 9735c8320..e51cde0e3 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -401,6 +401,14 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: - ImDrawListFlags_AllowVtxOffset -> ImDrawFlags_UseVtxOffset, - ImDrawListFlags_TextNoPixelSnap -> ImDrawFlags_TextNoPixelSnap, unifying them allows easily using them for both per-primitives alterations and scope alterations. + - renamed ImDrawFlags_RoundCornersXXXX to ImDrawFlags_RoundXXXX. + - ImDrawFlags_RoundCornersAll -> ImDrawFlags_RoundAll, + - ImDrawFlags_RoundCornersNone -> ImDrawFlags_RoundNone, + - ImDrawFlags_RoundCornersTopLeft -> ImDrawFlags_RoundTopLeft, + - ImDrawFlags_RoundCornersTop -> ImDrawFlags_RoundTop, + - ImDrawFlags_RoundCornersBottom -> ImDrawFlags_RoundBottom, + etc. + kept redirection enums (will obsolete). - obsoleted style.AntiAliasedLinesUseTex which now only makes sense for legacy strokes. - 2026/09/30 (1.93.0) - ImFont: renamed `AddRemapChar()` to `AddRemapCodepoint()` (rarely used, marked internal). (#609, #5748) - 2026/09/18 (1.93.0) - ImGuiTextFilter: removed `float width` parameter of `Draw(const char* filter, float width)`: prefer using `SetNextItemWidth(float)` which is standard. Kept inline redirection function. @@ -7365,7 +7373,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar if (bg_col & IM_COL32_A_MASK) { ImRect bg_rect(window->Pos + ImVec2(0, window->TitleBarHeight), window->Pos + window->Size); - ImDrawFlags bg_rounding_flags = (flags & ImGuiWindowFlags_NoTitleBar) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersBottom; + ImDrawFlags bg_rounding_flags = (flags & ImGuiWindowFlags_NoTitleBar) ? ImDrawFlags_RoundAll : ImDrawFlags_RoundBottom; ImDrawList* bg_draw_list = window->DrawList; bg_draw_list->AddRectFilled(bg_rect.Min, bg_rect.Max, bg_col, window_rounding, bg_rounding_flags); } @@ -7375,7 +7383,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar if (!(flags & ImGuiWindowFlags_NoTitleBar)) { ImU32 title_bar_col = GetColorU32(title_bar_is_highlight ? ImGuiCol_TitleBgActive : ImGuiCol_TitleBg); - window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, window_rounding, ImDrawFlags_RoundCornersTop); + window->DrawList->AddRectFilled(title_bar_rect.Min, title_bar_rect.Max, title_bar_col, window_rounding, ImDrawFlags_RoundTop); } // Menu bar @@ -7383,7 +7391,7 @@ void ImGui::RenderWindowDecorations(ImGuiWindow* window, const ImRect& title_bar { ImRect menu_bar_rect = window->MenuBarRect(); menu_bar_rect.ClipWith(window->Rect()); // Soft clipping, in particular child window don't have minimum size covering the menu bar so this is useful for them. - window->DrawList->AddRectFilled(menu_bar_rect.Min, menu_bar_rect.Max, GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawFlags_RoundCornersTop); + window->DrawList->AddRectFilled(menu_bar_rect.Min, menu_bar_rect.Max, GetColorU32(ImGuiCol_MenuBarBg), (flags & ImGuiWindowFlags_NoTitleBar) ? window_rounding : 0.0f, ImDrawFlags_RoundTop); if (style.FrameBorderSize > 0.0f && menu_bar_rect.Max.y < window->Pos.y + window->Size.y) window->DrawList->AddLineH(menu_bar_rect.Min.x + window_border_size, menu_bar_rect.Max.x - window_border_size, menu_bar_rect.Max.y, GetColorU32(ImGuiCol_Border), style.FrameBorderSize, ImDrawFlags_StrokeCenterBiased); } diff --git a/imgui.h b/imgui.h index eafe71d32..f3eeea91f 100644 --- a/imgui.h +++ b/imgui.h @@ -3317,19 +3317,19 @@ enum ImDrawFlags_ // - "Scope" column: 'OK' = initialized by ImGui based on Style options (e.g. whether anti-aliased is enabled) + may be modified using ImDrawList::PushDrawFlag(). // OK(0)/OK(1) indicates whether this flag is set in the default ImGui Style settings. - // - Rounding default to ImDrawFlags_RoundCornersAll when 'rounding > 0'. - // - So you only need to use the _RoundCorners flags if you want a special configuration (e.g. a rectangle with one rounded corner). + // - Rounding default to ImDrawFlags_RoundAll when 'rounding > 0'. + // - So you only need to use the ImDrawFlags_RoundXXX flags if you want a special configuration (e.g. a rectangle with one rounded corner). // Rounding for AddRectXXX(), PathRect() ------ // Prim/Scope? - ImDrawFlags_RoundCornersTopLeft = 1 << 4, // OK -- // Round top-left corner only (when 'rounding > 0.0f', we default to all corners). - ImDrawFlags_RoundCornersTopRight = 1 << 5, // OK -- // Round top-right corner only (when 'rounding > 0.0f', we default to all corners). - ImDrawFlags_RoundCornersBottomLeft = 1 << 6, // OK -- // Round bottom-left corner only (when 'rounding > 0.0f', we default to all corners). - ImDrawFlags_RoundCornersBottomRight = 1 << 7, // OK -- // Round bottom-right corner only (when 'rounding > 0.0f', we default to all corners). - ImDrawFlags_RoundCornersNone = 1 << 8, // OK -- // Disable rounding even when 'rounding > 0.0f'. This value is NOT zero, it is NOT an implicit flag! - ImDrawFlags_RoundCornersAll = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight | ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight, // (Default!!) - ImDrawFlags_RoundCornersTop = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersTopRight, - ImDrawFlags_RoundCornersBottom = ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersBottomRight, - ImDrawFlags_RoundCornersLeft = ImDrawFlags_RoundCornersBottomLeft | ImDrawFlags_RoundCornersTopLeft, - ImDrawFlags_RoundCornersRight = ImDrawFlags_RoundCornersBottomRight | ImDrawFlags_RoundCornersTopRight, + ImDrawFlags_RoundTopLeft = 1 << 4, // OK -- // Round top-left corner only (when 'rounding > 0.0f', we default to all corners). + ImDrawFlags_RoundTopRight = 1 << 5, // OK -- // Round top-right corner only (when 'rounding > 0.0f', we default to all corners). + ImDrawFlags_RoundBottomLeft = 1 << 6, // OK -- // Round bottom-left corner only (when 'rounding > 0.0f', we default to all corners). + ImDrawFlags_RoundBottomRight = 1 << 7, // OK -- // Round bottom-right corner only (when 'rounding > 0.0f', we default to all corners). + ImDrawFlags_RoundNone = 1 << 8, // OK -- // Disable rounding even when 'rounding > 0.0f'. This value is NOT zero, it is NOT an implicit flag! + ImDrawFlags_RoundAll = ImDrawFlags_RoundTopLeft | ImDrawFlags_RoundTopRight | ImDrawFlags_RoundBottomLeft | ImDrawFlags_RoundBottomRight, // (Default!!) + ImDrawFlags_RoundTop = ImDrawFlags_RoundTopLeft | ImDrawFlags_RoundTopRight, + ImDrawFlags_RoundBottom = ImDrawFlags_RoundBottomLeft | ImDrawFlags_RoundBottomRight, + ImDrawFlags_RoundLeft = ImDrawFlags_RoundBottomLeft | ImDrawFlags_RoundTopLeft, + ImDrawFlags_RoundRight = ImDrawFlags_RoundBottomRight | ImDrawFlags_RoundTopRight, // Stroke Options ----------------------------- // Prim/Scope? ImDrawFlags_Closed = 1 << 9, // OK -- // PathStroke(), AddPolyline(): specify that shape should be closed. @@ -3365,11 +3365,26 @@ enum ImDrawFlags_ ImDrawFlags_AllowTexForRoundCorners_= 1 << 23, // -- OK(1) // [Internal] // [Internal] - ImDrawFlags_RoundCornersMask_ = ImDrawFlags_RoundCornersAll | ImDrawFlags_RoundCornersNone, // [Internal] - ImDrawFlags_AllowInPushScope_ = ImDrawFlags_AAFill | ImDrawFlags_AALines | ImDrawFlags_AALineEnds | ImDrawFlags_StrokeLegacy | ImDrawFlags_TextNoPixelSnap | ImDrawFlags_UseTexForRoundCorners | ImDrawFlags_UseVtxOffset | ImDrawFlags_RoundCornersMask_, // [Internal] Values allowed in PushDrawFlag() scope. + ImDrawFlags_RoundMask_ = ImDrawFlags_RoundAll | ImDrawFlags_RoundNone, // [Internal] + ImDrawFlags_AllowInPushScope_ = ImDrawFlags_AAFill | ImDrawFlags_AALines | ImDrawFlags_AALineEnds | ImDrawFlags_StrokeLegacy | ImDrawFlags_TextNoPixelSnap | ImDrawFlags_UseTexForRoundCorners | ImDrawFlags_UseVtxOffset | ImDrawFlags_RoundMask_, // [Internal] Values allowed in PushDrawFlag() scope. ImDrawFlags_AllowInFrameScope_ = ImDrawFlags_AllowInPushScope_ | ImDrawFlags_AllowTexForRoundCorners_, ImDrawFlags_StrokeMask_ = 0x07 << 17, // [Internal] ImDrawFlags_InvalidMask_ = ~0x7FFFFFF0, // [Internal] == 0x8000000F. Reserved to detect misuses. + + // Obsolete names +#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS + // RENAMED in 1.93.0 (Oct 2026): ImDrawFlags_RoundCornersXXXX -> ImDrawFlags_RoundXXXX. + ImDrawFlags_RoundCornersTopLeft = ImDrawFlags_RoundTopLeft, + ImDrawFlags_RoundCornersTopRight = ImDrawFlags_RoundTopRight, + ImDrawFlags_RoundCornersBottomLeft = ImDrawFlags_RoundBottomLeft, + ImDrawFlags_RoundCornersBottomRight = ImDrawFlags_RoundBottomRight, + ImDrawFlags_RoundCornersNone = ImDrawFlags_RoundNone, + ImDrawFlags_RoundCornersAll = ImDrawFlags_RoundAll, + ImDrawFlags_RoundCornersTop = ImDrawFlags_RoundTop, + ImDrawFlags_RoundCornersBottom = ImDrawFlags_RoundBottom, + ImDrawFlags_RoundCornersLeft = ImDrawFlags_RoundLeft, + ImDrawFlags_RoundCornersRight = ImDrawFlags_RoundRight, +#endif }; // Draw command list @@ -4330,16 +4345,16 @@ typedef ImFontAtlasRect ImFontAtlasCustomRect; //typedef ImDrawFlags ImDrawCornerFlags; //enum ImDrawCornerFlags_ //{ -// ImDrawCornerFlags_None = ImDrawFlags_RoundCornersNone, // Was == 0 prior to 1.82, this is now == ImDrawFlags_RoundCornersNone which is != 0 and not implicit -// ImDrawCornerFlags_TopLeft = ImDrawFlags_RoundCornersTopLeft, // Was == 0x01 (1 << 0) prior to 1.82. Order matches ImDrawFlags_NoRoundCorner* flag (we exploit this internally). -// ImDrawCornerFlags_TopRight = ImDrawFlags_RoundCornersTopRight, // Was == 0x02 (1 << 1) prior to 1.82. -// ImDrawCornerFlags_BotLeft = ImDrawFlags_RoundCornersBottomLeft, // Was == 0x04 (1 << 2) prior to 1.82. -// ImDrawCornerFlags_BotRight = ImDrawFlags_RoundCornersBottomRight, // Was == 0x08 (1 << 3) prior to 1.82. -// ImDrawCornerFlags_All = ImDrawFlags_RoundCornersAll, // Was == 0x0F prior to 1.82 -// ImDrawCornerFlags_Top = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_TopRight, -// ImDrawCornerFlags_Bot = ImDrawCornerFlags_BotLeft | ImDrawCornerFlags_BotRight, -// ImDrawCornerFlags_Left = ImDrawCornerFlags_TopLeft | ImDrawCornerFlags_BotLeft, -// ImDrawCornerFlags_Right = ImDrawCornerFlags_TopRight | ImDrawCornerFlags_BotRight, +// ImDrawCornerFlags_None = ImDrawFlags_RoundNone, // Was == 0 prior to 1.82, this is now == ImDrawFlags_RoundNone which is != 0 and not implicit +// ImDrawCornerFlags_TopLeft = ImDrawFlags_RoundTopLeft, // Was == 0x01 (1 << 0) prior to 1.82. Order matches ImDrawFlags_NoRoundCorner* flag (we exploit this internally). +// ImDrawCornerFlags_TopRight = ImDrawFlags_RoundTopRight, // Was == 0x02 (1 << 1) prior to 1.82. +// ImDrawCornerFlags_BotLeft = ImDrawFlags_RoundBottomLeft, // Was == 0x04 (1 << 2) prior to 1.82. +// ImDrawCornerFlags_BotRight = ImDrawFlags_RoundBottomRight, // Was == 0x08 (1 << 3) prior to 1.82. +// ImDrawCornerFlags_All = ImDrawFlags_RoundAll, // Was == 0x0F prior to 1.82 +// ImDrawCornerFlags_Top = ImDrawFlags_RoundTopLeft | ImDrawFlags_RoundTopRight, +// ImDrawCornerFlags_Bot = ImDrawFlags_RoundBottomLeft | ImDrawFlags_RoundBottomRight, +// ImDrawCornerFlags_Left = ImDrawFlags_RoundTopLeft | ImDrawFlags_RoundBottomLeft, +// ImDrawCornerFlags_Right = ImDrawFlags_RoundTopRight | ImDrawFlags_RoundBottomRight, //}; // RENAMED and MERGED both ImGuiKey_ModXXX and ImGuiModFlags_XXX into ImGuiMod_XXX (from September 2022) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index efd35bb0a..2f5450e09 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10474,7 +10474,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImVec2 start_pos = ImGui::GetCursorScreenPos(); const float pi = 3.141592f; const float spacing = 10.0f; - const ImDrawFlags corners_tl_br = ImDrawFlags_RoundCornersTopLeft | ImDrawFlags_RoundCornersBottomRight; + const ImDrawFlags corners_tl_br = ImDrawFlags_RoundTopLeft | ImDrawFlags_RoundBottomRight; const float half_sz = sz * 0.5f; const float half_szi = (float)(int)(sz * 0.5f); const int circle_segments = circle_segments_override ? circle_segments_override_v : 0; diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 8f53a8120..a696fade0 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1826,13 +1826,13 @@ void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDr const bool has_rounding = IM_HAS_ROUNDING(rounding); if (has_rounding) { - if ((flags & ImDrawFlags_RoundCornersMask_) == 0) - flags |= ImDrawFlags_RoundCornersAll; + if ((flags & ImDrawFlags_RoundMask_) == 0) + flags |= ImDrawFlags_RoundAll; - rounding = ImMin(rounding, ImFabs(b.x - a.x) * (((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f) - 1.0f); - rounding = ImMin(rounding, ImFabs(b.y - a.y) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f); + rounding = ImMin(rounding, ImFabs(b.x - a.x) * (((flags & ImDrawFlags_RoundTop) == ImDrawFlags_RoundTop) || ((flags & ImDrawFlags_RoundBottom) == ImDrawFlags_RoundBottom) ? 0.5f : 1.0f) - 1.0f); + rounding = ImMin(rounding, ImFabs(b.y - a.y) * (((flags & ImDrawFlags_RoundLeft) == ImDrawFlags_RoundLeft) || ((flags & ImDrawFlags_RoundRight) == ImDrawFlags_RoundRight) ? 0.5f : 1.0f) - 1.0f); } - if (!has_rounding || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) + if (!has_rounding || (flags & ImDrawFlags_RoundMask_) == ImDrawFlags_RoundNone) { PathLineTo(a); PathLineTo(ImVec2(b.x, a.y)); @@ -1841,10 +1841,10 @@ void ImDrawList::PathRect(const ImVec2& a, const ImVec2& b, float rounding, ImDr } else { - const float rounding_tl = (flags & ImDrawFlags_RoundCornersTopLeft) ? rounding : 0.0f; - const float rounding_tr = (flags & ImDrawFlags_RoundCornersTopRight) ? rounding : 0.0f; - const float rounding_br = (flags & ImDrawFlags_RoundCornersBottomRight) ? rounding : 0.0f; - const float rounding_bl = (flags & ImDrawFlags_RoundCornersBottomLeft) ? rounding : 0.0f; + const float rounding_tl = (flags & ImDrawFlags_RoundTopLeft) ? rounding : 0.0f; + const float rounding_tr = (flags & ImDrawFlags_RoundTopRight) ? rounding : 0.0f; + const float rounding_br = (flags & ImDrawFlags_RoundBottomRight) ? rounding : 0.0f; + const float rounding_bl = (flags & ImDrawFlags_RoundBottomLeft) ? rounding : 0.0f; PathArcToFast(ImVec2(a.x + rounding_tl, a.y + rounding_tl), rounding_tl, 6, 9); PathArcToFast(ImVec2(b.x - rounding_tr, a.y + rounding_tr), rounding_tr, 9, 12); PathArcToFast(ImVec2(b.x - rounding_br, b.y - rounding_br), rounding_br, 0, 3); @@ -2103,7 +2103,7 @@ void ImDrawList::_AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 c ImDrawVert* start_vtx_ptr = _VtxWritePtr; ImDrawIdx* start_idx_ptr = _IdxWritePtr; - if (flags & ImDrawFlags_RoundCornersTopLeft) + if (flags & ImDrawFlags_RoundTopLeft) { ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; IM_APPEND_VTX(p_min.x, p_min.y, uv_tl, col); @@ -2114,7 +2114,7 @@ void ImDrawList::_AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 c IM_APPEND_TRI(idx + 0, idx + 2, idx + 3); } - if (flags & ImDrawFlags_RoundCornersTopRight) + if (flags & ImDrawFlags_RoundTopRight) { ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; IM_APPEND_VTX(p_max.x - r, p_min.y, uv_tr, col); @@ -2125,7 +2125,7 @@ void ImDrawList::_AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 c IM_APPEND_TRI(idx + 0, idx + 2, idx + 3); } - if (flags & ImDrawFlags_RoundCornersBottomRight) + if (flags & ImDrawFlags_RoundBottomRight) { ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; IM_APPEND_VTX(p_max.x - r, p_max.y - r, uv_br, col); @@ -2136,7 +2136,7 @@ void ImDrawList::_AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 c IM_APPEND_TRI(idx + 0, idx + 2, idx + 3); } - if (flags & ImDrawFlags_RoundCornersBottomLeft) + if (flags & ImDrawFlags_RoundBottomLeft) { ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; IM_APPEND_VTX(p_min.x, p_max.y - r, uv_bl, col); @@ -2147,10 +2147,10 @@ void ImDrawList::_AddRectBaked(const ImVec2& p_min, const ImVec2& p_max, ImU32 c IM_APPEND_TRI(idx + 0, idx + 2, idx + 3); } - const float r_tl = (flags & ImDrawFlags_RoundCornersTopLeft) ? r : 0; - const float r_tr = (flags & ImDrawFlags_RoundCornersTopRight) ? r : 0; - const float r_br = (flags & ImDrawFlags_RoundCornersBottomRight) ? r : 0; - const float r_bl = (flags & ImDrawFlags_RoundCornersBottomLeft) ? r : 0; + const float r_tl = (flags & ImDrawFlags_RoundTopLeft) ? r : 0; + const float r_tr = (flags & ImDrawFlags_RoundTopRight) ? r : 0; + const float r_br = (flags & ImDrawFlags_RoundBottomRight) ? r : 0; + const float r_bl = (flags & ImDrawFlags_RoundBottomLeft) ? r : 0; const ImVec2 opaque_uv = _Data->TexUvWhitePixel; { @@ -2241,7 +2241,7 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, ImDrawIdx* start_idx_ptr = _IdxWritePtr; static const ImVec2 corner_offset[4] = { {1,1}, {-1,1}, {-1,-1}, {1,-1} }; - static const ImDrawFlags corner_flags[4] = { ImDrawFlags_RoundCornersTopLeft, ImDrawFlags_RoundCornersTopRight, ImDrawFlags_RoundCornersBottomRight, ImDrawFlags_RoundCornersBottomLeft }; + static const ImDrawFlags corner_flags[4] = { ImDrawFlags_RoundTopLeft, ImDrawFlags_RoundTopRight, ImDrawFlags_RoundBottomRight, ImDrawFlags_RoundBottomLeft }; const float half_fringe = fringe * 0.5f; const ImVec2 corner_pos[4] = { @@ -2327,11 +2327,11 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl // If this assert triggers on legacy code: // - 1.92.8 (2025/05): swapped two last parameters order: flags, thickness --> thickness, flags. This should normally be caught by compile-time type-checking. // - 1.92.8 (2025/05): changed value of ImDrawList_Closed which was previously guaranteed to be == 1. Hardcoded use of 1 or true should be replaced. - // - 1.82.0 (2021/03): changed ImDrawCornerFlags to ImDrawFlags_RoundCornersXXX values. + // - 1.82.0 (2021/03): changed ImDrawCornerFlags to ImDrawFlags_RoundXXX values. // If you used hard-coded 1 to 15 or ~0 in flags to configure corner rounding use the new flags! - // - Hard coded support for ~0 == ImDrawFlags_RoundCornersAll. + // - Hard coded support for ~0 == ImDrawFlags_RoundAll. // - Hard coded support for values 0x01 to 0x0F (matching 15 out of 16 old flags combinations) --> see FixRectCornerFlags() in <1.90 code. - // - Hard coded 0x00 with 'float rounding > 0.0f' --> replace with ImDrawFlags_RoundCornersNone or use 'float rounding = 0.0f'. + // - Hard coded 0x00 with 'float rounding > 0.0f' --> replace with ImDrawFlags_RoundNone or use 'float rounding = 0.0f'. // See "API BREAKING CHANGES" section for 1.82, 1.90 and 1.92.8. IM_ASSERT_USER_ERROR_RET((flags & ImDrawFlags_InvalidMask_) == 0, "Incorrect parameter. Did you swap 'thickness' and 'flags'?"); // Or misuse of legacy hard-coded ImDrawCornerFlags values @@ -2380,13 +2380,13 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl return; } - if ((flags & ImDrawFlags_RoundCornersMask_) == 0) - flags |= ImDrawFlags_RoundCornersAll; + if ((flags & ImDrawFlags_RoundMask_) == 0) + flags |= ImDrawFlags_RoundAll; if (has_rounding) { // Constrain rounding to rect dimensions - outer_rounding = ImMin(outer_rounding, ImFabs(width) * (((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f) - 1.0f); - outer_rounding = ImMin(outer_rounding, ImFabs(height) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f); + outer_rounding = ImMin(outer_rounding, ImFabs(width) * (((flags & ImDrawFlags_RoundTop) == ImDrawFlags_RoundTop) || ((flags & ImDrawFlags_RoundBottom) == ImDrawFlags_RoundBottom) ? 0.5f : 1.0f) - 1.0f); + outer_rounding = ImMin(outer_rounding, ImFabs(height) * (((flags & ImDrawFlags_RoundLeft) == ImDrawFlags_RoundLeft) || ((flags & ImDrawFlags_RoundRight) == ImDrawFlags_RoundRight) ? 0.5f : 1.0f) - 1.0f); } const bool is_truncated = IM_IS_TRUNCATED4(outer_min.x, outer_min.y, outer_max.x, outer_max.y) && IM_IS_TRUNCATED4(outer_rounding, thickness, 0.0f, 0.0f); //-V501 @@ -2397,7 +2397,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl if (s_thickness <= 0) return; - if (s_rounding <= 0 || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) + if (s_rounding <= 0 || (flags & ImDrawFlags_RoundMask_) == ImDrawFlags_RoundNone) { // Pixel aligned non-rounded rect. const ImVec2 opaque_uv = _Data->TexUvWhitePixel; @@ -2443,7 +2443,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl } } - if (!has_rounding || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) + if (!has_rounding || (flags & ImDrawFlags_RoundMask_) == ImDrawFlags_RoundNone) { const ImVec2 points[4] = { outer_min, ImVec2(outer_max.x, outer_min.y), outer_max, ImVec2(outer_min.x, outer_max.y) }; _AddPolyline(points, 4, col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | ImDrawFlags_StrokeInside, half_min_dim); @@ -2459,10 +2459,10 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl _AddRectTinyRounding(outer_min, outer_max, col, outer_rounding, thickness, flags); return; } - const float rounding_tl = (flags & ImDrawFlags_RoundCornersTopLeft) ? outer_rounding : 0.0f; - const float rounding_tr = (flags & ImDrawFlags_RoundCornersTopRight) ? outer_rounding : 0.0f; - const float rounding_br = (flags & ImDrawFlags_RoundCornersBottomRight) ? outer_rounding : 0.0f; - const float rounding_bl = (flags & ImDrawFlags_RoundCornersBottomLeft) ? outer_rounding : 0.0f; + const float rounding_tl = (flags & ImDrawFlags_RoundTopLeft) ? outer_rounding : 0.0f; + const float rounding_tr = (flags & ImDrawFlags_RoundTopRight) ? outer_rounding : 0.0f; + const float rounding_br = (flags & ImDrawFlags_RoundBottomRight) ? outer_rounding : 0.0f; + const float rounding_bl = (flags & ImDrawFlags_RoundBottomLeft) ? outer_rounding : 0.0f; PathArcToFast(ImVec2(outer_min.x + rounding_tl, outer_min.y + rounding_tl), rounding_tl, 6, 9); PathArcToFast(ImVec2(outer_max.x - rounding_tr, outer_min.y + rounding_tr), rounding_tr, 9, 12); PathArcToFast(ImVec2(outer_max.x - rounding_br, outer_max.y - rounding_br), rounding_br, 0, 3); @@ -2483,10 +2483,10 @@ void ImDrawList::_AddRectFilledBaked(const ImVec2& p_min, const ImVec2& p_max, I PrimReserve(9 * 2 * 3, 4 * 4); - const float r_tl = (flags & ImDrawFlags_RoundCornersTopLeft) ? r : 0.0f; - const float r_tr = (flags & ImDrawFlags_RoundCornersTopRight) ? r : 0.0f; - const float r_br = (flags & ImDrawFlags_RoundCornersBottomRight) ? r : 0.0f; - const float r_bl = (flags & ImDrawFlags_RoundCornersBottomLeft) ? r : 0.0f; + const float r_tl = (flags & ImDrawFlags_RoundTopLeft) ? r : 0.0f; + const float r_tr = (flags & ImDrawFlags_RoundTopRight) ? r : 0.0f; + const float r_br = (flags & ImDrawFlags_RoundBottomRight) ? r : 0.0f; + const float r_bl = (flags & ImDrawFlags_RoundBottomLeft) ? r : 0.0f; ImDrawIdx idx = (ImDrawIdx)_VtxCurrentIdx; IM_APPEND_VTX(p_min.x, p_min.y, uv_tl, col); @@ -2552,7 +2552,7 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c } // When rounding non-integer and under threshold, has_rounding may be false but still affect is_truncated. Likely not worth catering for. - const bool has_rounding = (flags & ImDrawFlags_RoundCornersMask_) != ImDrawFlags_RoundCornersNone && IM_HAS_ROUNDING(rounding); + const bool has_rounding = (flags & ImDrawFlags_RoundMask_) != ImDrawFlags_RoundNone && IM_HAS_ROUNDING(rounding); const bool is_truncated = IM_IS_TRUNCATED4(p_min.x, p_min.y, p_max.x, p_max.y) && IM_IS_TRUNCATED(rounding); if (_FringeScaleIsInteger && is_truncated) @@ -2565,11 +2565,11 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c return; } - if ((flags & ImDrawFlags_RoundCornersMask_) == 0) - flags |= ImDrawFlags_RoundCornersAll; + if ((flags & ImDrawFlags_RoundMask_) == 0) + flags |= ImDrawFlags_RoundAll; - rounding = ImMin(rounding, ImFabs(width) * (((flags & ImDrawFlags_RoundCornersTop) == ImDrawFlags_RoundCornersTop) || ((flags & ImDrawFlags_RoundCornersBottom) == ImDrawFlags_RoundCornersBottom) ? 0.5f : 1.0f) - 1.0f); - rounding = ImMin(rounding, ImFabs(height) * (((flags & ImDrawFlags_RoundCornersLeft) == ImDrawFlags_RoundCornersLeft) || ((flags & ImDrawFlags_RoundCornersRight) == ImDrawFlags_RoundCornersRight) ? 0.5f : 1.0f) - 1.0f); + rounding = ImMin(rounding, ImFabs(width) * (((flags & ImDrawFlags_RoundTop) == ImDrawFlags_RoundTop) || ((flags & ImDrawFlags_RoundBottom) == ImDrawFlags_RoundBottom) ? 0.5f : 1.0f) - 1.0f); + rounding = ImMin(rounding, ImFabs(height) * (((flags & ImDrawFlags_RoundLeft) == ImDrawFlags_RoundLeft) || ((flags & ImDrawFlags_RoundRight) == ImDrawFlags_RoundRight) ? 0.5f : 1.0f) - 1.0f); const int s_rounding = (int)(rounding * _InvFringeScale); if (s_rounding <= 0) @@ -2994,10 +2994,10 @@ void ImDrawList::AddImageRounded(ImTextureRef tex_ref, const ImVec2& p_min, cons return; IM_ASSERT((flags & 0x0F) == 0 && "Misuse of legacy hardcoded ImDrawCornerFlags values!"); // If this assert triggers on legacy code: see comments in ImDrawList::PathRect(). - if ((flags & ImDrawFlags_RoundCornersMask_) == 0) - flags |= ImDrawFlags_RoundCornersAll; + if ((flags & ImDrawFlags_RoundMask_) == 0) + flags |= ImDrawFlags_RoundAll; - if (rounding < 0.5f || (flags & ImDrawFlags_RoundCornersMask_) == ImDrawFlags_RoundCornersNone) + if (rounding < 0.5f || (flags & ImDrawFlags_RoundMask_) == ImDrawFlags_RoundNone) { AddImage(tex_ref, p_min, p_max, uv_min, uv_max, col); return; @@ -7678,14 +7678,14 @@ void ImGui::RenderRectFilledWithHole(ImDrawList* draw_list, const ImRect& outer, const bool fill_R = (inner.Max.x < outer.Max.x); const bool fill_U = (inner.Min.y > outer.Min.y); const bool fill_D = (inner.Max.y < outer.Max.y); - if (fill_L) draw_list->AddRectFilled(ImVec2(outer.Min.x, inner.Min.y), ImVec2(inner.Min.x, inner.Max.y), col, rounding, ImDrawFlags_RoundCornersNone | (fill_U ? 0 : ImDrawFlags_RoundCornersTopLeft) | (fill_D ? 0 : ImDrawFlags_RoundCornersBottomLeft)); - if (fill_R) draw_list->AddRectFilled(ImVec2(inner.Max.x, inner.Min.y), ImVec2(outer.Max.x, inner.Max.y), col, rounding, ImDrawFlags_RoundCornersNone | (fill_U ? 0 : ImDrawFlags_RoundCornersTopRight) | (fill_D ? 0 : ImDrawFlags_RoundCornersBottomRight)); - if (fill_U) draw_list->AddRectFilled(ImVec2(inner.Min.x, outer.Min.y), ImVec2(inner.Max.x, inner.Min.y), col, rounding, ImDrawFlags_RoundCornersNone | (fill_L ? 0 : ImDrawFlags_RoundCornersTopLeft) | (fill_R ? 0 : ImDrawFlags_RoundCornersTopRight)); - if (fill_D) draw_list->AddRectFilled(ImVec2(inner.Min.x, inner.Max.y), ImVec2(inner.Max.x, outer.Max.y), col, rounding, ImDrawFlags_RoundCornersNone | (fill_L ? 0 : ImDrawFlags_RoundCornersBottomLeft) | (fill_R ? 0 : ImDrawFlags_RoundCornersBottomRight)); - if (fill_L && fill_U) draw_list->AddRectFilled(ImVec2(outer.Min.x, outer.Min.y), ImVec2(inner.Min.x, inner.Min.y), col, rounding, ImDrawFlags_RoundCornersTopLeft); - if (fill_R && fill_U) draw_list->AddRectFilled(ImVec2(inner.Max.x, outer.Min.y), ImVec2(outer.Max.x, inner.Min.y), col, rounding, ImDrawFlags_RoundCornersTopRight); - if (fill_L && fill_D) draw_list->AddRectFilled(ImVec2(outer.Min.x, inner.Max.y), ImVec2(inner.Min.x, outer.Max.y), col, rounding, ImDrawFlags_RoundCornersBottomLeft); - if (fill_R && fill_D) draw_list->AddRectFilled(ImVec2(inner.Max.x, inner.Max.y), ImVec2(outer.Max.x, outer.Max.y), col, rounding, ImDrawFlags_RoundCornersBottomRight); + if (fill_L) draw_list->AddRectFilled(ImVec2(outer.Min.x, inner.Min.y), ImVec2(inner.Min.x, inner.Max.y), col, rounding, ImDrawFlags_RoundNone | (fill_U ? 0 : ImDrawFlags_RoundTopLeft) | (fill_D ? 0 : ImDrawFlags_RoundBottomLeft)); + if (fill_R) draw_list->AddRectFilled(ImVec2(inner.Max.x, inner.Min.y), ImVec2(outer.Max.x, inner.Max.y), col, rounding, ImDrawFlags_RoundNone | (fill_U ? 0 : ImDrawFlags_RoundTopRight) | (fill_D ? 0 : ImDrawFlags_RoundBottomRight)); + if (fill_U) draw_list->AddRectFilled(ImVec2(inner.Min.x, outer.Min.y), ImVec2(inner.Max.x, inner.Min.y), col, rounding, ImDrawFlags_RoundNone | (fill_L ? 0 : ImDrawFlags_RoundTopLeft) | (fill_R ? 0 : ImDrawFlags_RoundTopRight)); + if (fill_D) draw_list->AddRectFilled(ImVec2(inner.Min.x, inner.Max.y), ImVec2(inner.Max.x, outer.Max.y), col, rounding, ImDrawFlags_RoundNone | (fill_L ? 0 : ImDrawFlags_RoundBottomLeft) | (fill_R ? 0 : ImDrawFlags_RoundBottomRight)); + if (fill_L && fill_U) draw_list->AddRectFilled(ImVec2(outer.Min.x, outer.Min.y), ImVec2(inner.Min.x, inner.Min.y), col, rounding, ImDrawFlags_RoundTopLeft); + if (fill_R && fill_U) draw_list->AddRectFilled(ImVec2(inner.Max.x, outer.Min.y), ImVec2(outer.Max.x, inner.Min.y), col, rounding, ImDrawFlags_RoundTopRight); + if (fill_L && fill_D) draw_list->AddRectFilled(ImVec2(outer.Min.x, inner.Max.y), ImVec2(inner.Min.x, outer.Max.y), col, rounding, ImDrawFlags_RoundBottomLeft); + if (fill_R && fill_D) draw_list->AddRectFilled(ImVec2(inner.Max.x, inner.Max.y), ImVec2(outer.Max.x, outer.Max.y), col, rounding, ImDrawFlags_RoundBottomRight); } ImDrawFlags ImGui::CalcRoundingFlagsForRectInRect(const ImRect& r_in, const ImRect& r_outer, float threshold) @@ -7694,9 +7694,9 @@ ImDrawFlags ImGui::CalcRoundingFlagsForRectInRect(const ImRect& r_in, const ImRe bool round_r = r_in.Max.x >= r_outer.Max.x - threshold; bool round_t = r_in.Min.y <= r_outer.Min.y + threshold; bool round_b = r_in.Max.y >= r_outer.Max.y - threshold; - return ImDrawFlags_RoundCornersNone - | ((round_t && round_l) ? ImDrawFlags_RoundCornersTopLeft : 0) | ((round_t && round_r) ? ImDrawFlags_RoundCornersTopRight : 0) - | ((round_b && round_l) ? ImDrawFlags_RoundCornersBottomLeft : 0) | ((round_b && round_r) ? ImDrawFlags_RoundCornersBottomRight : 0); + return ImDrawFlags_RoundNone + | ((round_t && round_l) ? ImDrawFlags_RoundTopLeft : 0) | ((round_t && round_r) ? ImDrawFlags_RoundTopRight : 0) + | ((round_b && round_l) ? ImDrawFlags_RoundBottomLeft : 0) | ((round_b && round_r) ? ImDrawFlags_RoundBottomRight : 0); } // Helper for ColorPicker4() @@ -7704,8 +7704,8 @@ ImDrawFlags ImGui::CalcRoundingFlagsForRectInRect(const ImRect& r_in, const ImRe // Spent a non reasonable amount of time trying to getting this right for ColorButton with rounding+anti-aliasing+ImGuiColorEditFlags_HalfAlphaPreview flag + various grid sizes and offsets, and eventually gave up... probably more reasonable to disable rounding altogether. void ImGui::RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p_min, ImVec2 p_max, ImU32 col, float alpha, float grid_step, ImVec2 grid_off, float rounding, ImDrawFlags flags) { - if ((flags & ImDrawFlags_RoundCornersMask_) == 0) - flags = ImDrawFlags_RoundCornersAll; + if ((flags & ImDrawFlags_RoundMask_) == 0) + flags = ImDrawFlags_RoundAll; if (((col & IM_COL32_A_MASK) >> IM_COL32_A_SHIFT) < 0xFF) { IM_ASSERT(alpha >= 0.0f && alpha <= 1.0f); @@ -7731,12 +7731,12 @@ void ImGui::RenderColorRectWithAlphaCheckerboard(ImDrawList* draw_list, ImVec2 p float x1 = ImClamp((float)(int)x, p_min.x, p_max.x), x2 = ImMin((float)(int)(x + grid_step), p_max.x); if (x2 <= x1) continue; - ImDrawFlags cell_flags = ImDrawFlags_RoundCornersNone; // FIXME: Could use CalcRoundingFlagsForRectInRect() - if (y1 <= p_min.y) { if (x1 <= p_min.x) cell_flags |= ImDrawFlags_RoundCornersTopLeft; if (x2 >= p_max.x) cell_flags |= ImDrawFlags_RoundCornersTopRight; } - if (y2 >= p_max.y) { if (x1 <= p_min.x) cell_flags |= ImDrawFlags_RoundCornersBottomLeft; if (x2 >= p_max.x) cell_flags |= ImDrawFlags_RoundCornersBottomRight; } + ImDrawFlags cell_flags = ImDrawFlags_RoundNone; // FIXME: Could use CalcRoundingFlagsForRectInRect() + if (y1 <= p_min.y) { if (x1 <= p_min.x) cell_flags |= ImDrawFlags_RoundTopLeft; if (x2 >= p_max.x) cell_flags |= ImDrawFlags_RoundTopRight; } + if (y2 >= p_max.y) { if (x1 <= p_min.x) cell_flags |= ImDrawFlags_RoundBottomLeft; if (x2 >= p_max.x) cell_flags |= ImDrawFlags_RoundBottomRight; } // Combine flags - cell_flags = (flags == ImDrawFlags_RoundCornersNone || cell_flags == ImDrawFlags_RoundCornersNone) ? ImDrawFlags_RoundCornersNone : (cell_flags & flags); + cell_flags = (flags == ImDrawFlags_RoundNone || cell_flags == ImDrawFlags_RoundNone) ? ImDrawFlags_RoundNone : (cell_flags & flags); ImU32 col_bg = dual_layer ? col_bg2 : (((yi + xi) & 1) ? col_bg2 : col_bg1); draw_list->AddRectFilled(ImVec2(x1, y1), ImVec2(x2, y2), col_bg, rounding, cell_flags); } diff --git a/imgui_widgets.cpp b/imgui_widgets.cpp index 8a210c415..287d94062 100644 --- a/imgui_widgets.cpp +++ b/imgui_widgets.cpp @@ -1980,12 +1980,12 @@ bool ImGui::BeginCombo(const char* label, const char* preview_value, ImGuiComboF const float value_x2 = ImMax(bb.Min.x, bb.Max.x - arrow_size); RenderNavCursor(bb, id); if (!(flags & ImGuiComboFlags_NoPreview)) - window->DrawList->AddRectFilled(bb.Min, ImVec2(value_x2, bb.Max.y), frame_col, style.FrameRounding, (flags & ImGuiComboFlags_NoArrowButton) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersLeft); + window->DrawList->AddRectFilled(bb.Min, ImVec2(value_x2, bb.Max.y), frame_col, style.FrameRounding, (flags & ImGuiComboFlags_NoArrowButton) ? ImDrawFlags_RoundAll : ImDrawFlags_RoundLeft); if (!(flags & ImGuiComboFlags_NoArrowButton)) { ImU32 bg_col = GetColorU32((popup_open || hovered) ? ImGuiCol_ButtonHovered : ImGuiCol_Button); ImU32 text_col = GetColorU32(ImGuiCol_Text); - window->DrawList->AddRectFilled(ImVec2(value_x2, bb.Min.y), bb.Max, bg_col, style.FrameRounding, (w <= arrow_size) ? ImDrawFlags_RoundCornersAll : ImDrawFlags_RoundCornersRight); + window->DrawList->AddRectFilled(ImVec2(value_x2, bb.Min.y), bb.Max, bg_col, style.FrameRounding, (w <= arrow_size) ? ImDrawFlags_RoundAll : ImDrawFlags_RoundRight); if (value_x2 + arrow_size - style.FramePadding.x <= bb.Max.x) RenderArrow(window->DrawList, ImVec2(value_x2 + style.FramePadding.y, bb.Min.y + style.FramePadding.y), text_col, ImGuiDir_Down, 1.0f); } @@ -6623,10 +6623,10 @@ bool ImGui::ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFl { float mid_x = IM_ROUND((bb.Min.x + bb.Max.x) * 0.5f); if ((flags & ImGuiColorEditFlags_AlphaNoBg) == 0) - RenderColorRectWithAlphaCheckerboard(window->DrawList, ImVec2(bb.Min.x + grid_step, bb.Min.y), bb.Max, ColorConvertFloat4ToU32(col_rgb), g.Style.Alpha, grid_step, ImVec2(-grid_step, 0.0f), rounding, ImDrawFlags_RoundCornersRight); + RenderColorRectWithAlphaCheckerboard(window->DrawList, ImVec2(bb.Min.x + grid_step, bb.Min.y), bb.Max, ColorConvertFloat4ToU32(col_rgb), g.Style.Alpha, grid_step, ImVec2(-grid_step, 0.0f), rounding, ImDrawFlags_RoundRight); else - window->DrawList->AddRectFilled(ImVec2(bb.Min.x + grid_step, bb.Min.y), bb.Max, GetColorU32(col_rgb), rounding, ImDrawFlags_RoundCornersRight); - window->DrawList->AddRectFilled(bb.Min, ImVec2(mid_x, bb.Max.y), GetColorU32(col_rgb_without_alpha), rounding, ImDrawFlags_RoundCornersLeft); + window->DrawList->AddRectFilled(ImVec2(bb.Min.x + grid_step, bb.Min.y), bb.Max, GetColorU32(col_rgb), rounding, ImDrawFlags_RoundRight); + window->DrawList->AddRectFilled(bb.Min, ImVec2(mid_x, bb.Max.y), GetColorU32(col_rgb_without_alpha), rounding, ImDrawFlags_RoundLeft); } else { @@ -11052,7 +11052,7 @@ void ImGui::TabItemBackground(ImDrawList* draw_list, const ImRect& bb, ImGuiTabI const float rounding = ImMax(0.0f, ImMin((flags & ImGuiTabItemFlags_Button) ? g.Style.FrameRounding : g.Style.TabRounding, width * 0.5f - 1.0f)); const float y1 = bb.Min.y + 1.0f; // Leave a bit of room in title bars. const float y2 = bb.Max.y - g.Style.TabBarBorderSize; - draw_list->AddRectFilled(ImVec2(bb.Min.x, y1), ImVec2(bb.Max.x, y2), col, rounding, ImDrawFlags_RoundCornersTop); + draw_list->AddRectFilled(ImVec2(bb.Min.x, y1), ImVec2(bb.Max.x, y2), col, rounding, ImDrawFlags_RoundTop); if (g.Style.TabBorderSize > 0.0f) { draw_list->PathLineTo(ImVec2(bb.Min.x, y2)); From 475b793baabb3e989c4ebcbfec1e45f4ebf6d0f9 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 31 Jul 2026 19:44:00 +0200 Subject: [PATCH 112/120] Docs: reworked Changelog, comments. --- docs/CHANGELOG.txt | 391 +++++++++++++++++++++++++++------------------ imgui.cpp | 30 +++- imgui.h | 20 +-- imgui_demo.cpp | 8 +- imgui_draw.cpp | 14 +- imgui_internal.h | 4 +- 6 files changed, 283 insertions(+), 184 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 83e95c2ff..70a89b246 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -39,160 +39,118 @@ HOW TO UPDATE? VERSION 1.93.0 WIP (In Progress) ----------------------------------------------------------------------- -- In 1.93, the ImDrawList rendering 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, - and the coordinates passed to the API are now consistent. - 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. - 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. -- Most of the work courtesy of @memononen, with bits from @ocornut, @thedmd, @potocpav. -- Refer to our full guides: - - ImDrawList Vector Rendering Reference: - https://github.com/ocornut/imgui/wiki/Draw-List - - 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 -- (Breaking) 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. But you might as well apply the offset manually! - - Generally better to use to newly introduced `AddLineH()`, `AddLineV()` functions. - 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, y1}, {x2, y2}, col, thickness, ImDrawFlags_StrokeLegacy); // Offset by +0.5f + disable AA ends. - == AddLine({x1 + 0.5f, y1 + 0.5f}, {x2+ 0.5f, y2 + 0.5f}, col, thickness); // Same - This reapplies the old offset, and should get you exactly the same result you previously got for thickness=1.0f lines. - But said result was sometimes ambiguous and renderer dependent. (#3116, #3258, #2441) - - Old `AddLine()` did not have 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 could - have added fudge offsets here and there which are not necessary anymore or may be simplified. -- AddPolyline(), PathStroke(), AddTriangle(): the algorithm to render lines got overhauled. - (#2183, #2964, #7972) - - 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 bevelled 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. - - New rendering code fixes (subtle) inconsistencies between graphics API (e.g. DirectX vs OpenGL) - using different diamond exits rules. New code should work precisely the same regardless - of graphics API. (#3116, #3258, #2441) - - Added `ImDrawFlags_AALineEnds` flag to enable anti-aliased line ends. - - Most noticeable for thick lines. - - Helps fills minor gaps if combining multiple individual lines. - - Enabling AA Ends by default would be more "correct", but it is slower and increase vertex/index data.s - - Added `ImDrawFlags_MiterOnly` flag to disable using beveled corner (closer to legacy - rendering, slightly faster, but corners sharper than 90 degrees may expand far out). - Automatically used by AddRect(), AddCircle(), AddNgon(), AddEllipse() etc functions - since we know that the input path does not contains sharper corners. - - Added `ImDrawFlags_SquareCap` flag to use extend lines ends using square caps. -- 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_MiterOnly`, to facilitate matching - a stroke using the same flag. -- AddRect(), AddRectFilled(): various optimization for rounded rectangles. (#1962) - - Integer-aligned coordinates and thicknesses will automatically use baked textures, - saving on both CPU and vertex/index data. - - Added `ImFontAtlasFlags_NoBakedRoundCorners` to disable baking round corners in font atlas. - - Added `ImDrawFlags_UseTexForRoundCorners` to enable using round corners. -- AddRectFilled(): non-integer coordinates will now display anti-aliased edges. (#6971) -- Improved minor mismatches when overlapping strokes and filled shapes, - e.g. when using inside strokes, - - `AddRect()` and `AddRectFilled()` with rounding now overlap better. (#3656) - - `AddCircle()` and `AddCircleFilled()` now overlap better. -- Added flags to specify stroke position in all `ImDrawList` stroking functions: - - `ImDrawFlags_StrokeInside` (default for closed primitives and AddLineH, AddLineV) - - `ImDrawFlags_StrokeCenter` (default for paths, bezier and AddLine) - - `ImDrawFlags_StrokeCenterBiased` - - `ImDrawFlags_StrokeOutside` - - `ImDrawFlags_StrokeLegacy` (may be set as default for a given scope using PushDrawFlag()). - - Legacy code was generally applying +0.50f offset 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 slightly 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! Recap: - ------------------------------------------------------------------------ - Legacy Inside Outside Center CenterBiased - -------------------------------------------------------------------------------- - Thickness=1.0f sharp sharp sharp blurry sharp - Thickness=2.0f blurry sharp sharp sharp sharp - Thickness=3.0f sharp sharp sharp blurry sharp - ------------------------------------------------------------------------ -- Added ImDrawList::PushDrawFlag()/PopDrawFlag() to alter certain flags for a scope, - e.g. `PushDrawFlag(ImDrawFlags_StrokeLegacy, true);` enforce legacy tweaks. -- (Breaking) AddRect, AddCircle, AddNgon, AddEllipse: 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. -- (Breaking) Merged `ImDrawListFlags` into `ImDrawFlags`. - `ImDrawListFlags` was very rarely used directly. Keep inline redirection (will obsolete). - - `ImDrawListFlags_AntiAliasedLines` -> `ImDrawFlags_AALines` - - `ImDrawListFlags_AntiAliasedFill` -> `ImDrawFlags_AAFill` - - `ImDrawListFlags_AntiAliasedLinesUseTex` -> `ImDrawFlags_UseTexForStrokeLegacy` - - `ImDrawListFlags_AllowVtxOffset` -> `ImDrawFlags_UseVtxOffset` - - `ImDrawListFlags_TextNoPixelSnap` -> `ImDrawFlags_TextNoPixelSnap` - Unifying them allows easily using them for both per-primitives and scope alterations. -- (Breaking) Enabling `ImFontAtlasFlags_NoBakedLines` in the font atlas will disable - support for anti-aliased lines. -- About `ImDrawFlags_StrokeLegacy`: - - This is designed to emulate old coordinates: - - Most shapes are "StrokeCenter". - - AddLine() adds a +0.5f,+0.5f offset. - - Closed shapes user miter angles. - - But there are known difference between legacy code and ImDrawFlags_StrokeLegacy: - - Thick shapes with acute angles will now preserve thickness. - - Thick shapes with acute angles will protrude a little more. -- About disabling anti-aliasing: - - Disabling anti-aliasing is now emulated by using different UV coordinates. - It does not result in a performance increase any more (however the tendency - is that the new code behave better than old code). - - 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) -- Tweaked line rendering in various locations to avoid blurryness: - - Windows: title-bar and menu-bar border (when thickness>1.0f). - - Tables: borders (when thickness>1.0f). - - SeparatorText: border (when thickness>1.0f). - - Tree: hierarchy lines (when thickness>1.0f). - - TabBar: selected overline border ((when thickness>1.0f). - - Demo: Custom Rendering: canvas lines. -- Checkbox, Menus: check marks use anti-aliased ends. -- Demo: Custom Rendering: exposed new flags; Showcasing overlapping strokes and - filled shapes; Added an option to animation thickness; Added new shapes to - showcase new rendering features that previously hit limitations. +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 Dear 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. +The varying code-paths are designed to look indistinguishable from each others. + +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. +(note that using _StrokeLegacy mode can lead to similar inconsistencies) + +New rendering code also deals better with animating thickness and rounding +(integer values are recommended). + +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. + +Main work courtesy of @memononen. +Additional work/reviewing/guidance by @ocornut. +With legacy bits from @thedmd, @potocpav. + +Refer to our 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). + +- ImDrawList 1.92.9 vs 1.93.0 Interactive Testbed + https://www.dearimgui.com/docs/drawlist_v193 + (interactively discuss and compare differences between 1.92.9 and 1.93.9) + (downloadable HTML file, may be used offline) + +- 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 + +- Main topic: + https://github.com/ocornut/imgui/issues/9504 + (if you have a problem to discuss, please open a new issue) Breaking Changes: - DrawList: + - AddLine(): removed the (+0.5f,+0.5f) offset that was sneakily added to input coordinates. (#9504) + - 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, simple and faster to use `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. + - AddCircle(), AddNgon(): removed +0.5f offset added to radius. + - This fixes inconsistencies in the API. + - Use `ImDrawFlags_StrokeLegacy` to use old method if required. Or you can apply the offset manually! + - AddRectFilled(): non-integer coordinates will now display anti-aliased edges. + Previously, non-integer coordinates rendered with aliased edges snapped by the + rasterizer. (#6971) + - AddRect(), AddRectFilled(): rectangles with inverted coordinates won't be visible + unless using ImDrawList_StrokeLegacy mode. With inverted coordinates (min > max): + - Legacy `AddRect()` rounding off -> visible but incorrect outer size. + - Legacy `AddRect()` rounding on -> visible but very glitchy. + - Legacy `AddRectFilled()` rounding off -> visible and correct. + - Legacy `AddRectFilled()` rounding on -> visible but very glitchy. + - 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. + Kept redirection enums (will obsolete). - Renamed all `ImDrawFlags_RoundCornersXXX` flags to `ImDrawFlags_RoundXXX`. - - `ImDrawFlags_RoundCornersNone` -> `ImDrawFlags_RoundNone` - `ImDrawFlags_RoundCornersAll` -> `ImDrawFlags_RoundAll` + - `ImDrawFlags_RoundCornersNone` -> `ImDrawFlags_RoundNone` - `ImDrawFlags_RoundCornersTopLeft` -> `ImDrawFlags_RoundTopLeft` - `ImDrawFlags_RoundCornersTopRight` -> `ImDrawFlags_RoundTopRight` - `ImDrawFlags_RoundCornersBottomLeft` -> `ImDrawFlags_RoundBottomLeft` @@ -202,6 +160,10 @@ Breaking Changes: - `ImDrawFlags_RoundCornersLeft` -> `ImDrawFlags_RoundLeft` - `ImDrawFlags_RoundCornersRight` -> `ImDrawFlags_RoundRight` Kept redirection enums (will obsolete). + - Marked `ImDrawListFlags_AntiAliasedLinesUseTex` and `style.AntiAliasedLinesUseTex` as + obsolete, as the new line rendering code always uses textures. + - Enabling `ImFontAtlasFlags_NoBakedLines` in the font atlas will now disable support for + anti-aliased lines by clearing `style.AntiAliasedLines`/`style.AntiAliasedLineEnds`. - Style: obsoleted `style.CurveTessellationTol (default 1.25)` which was in Pixels² unit in favor of `style.CurveTessellationMaxError` (default 1.12)` which is in Pixels unit. It is easier to tweak + this allows us to easily scale error threshold on high density screens. @@ -218,8 +180,119 @@ Breaking Changes: sub-optimal and not reliable on some setup. Use `ImGui_ImplOpenGL3_SetClipOrigin(0)` to enable auto-detection, or e.g. `ImGui_ImplOpenGL3_SetClipOrigin(GL_UPPER_LEFT)`. + Other Changes: +- DrawList: + - Added flags to specify stroke position in all `ImDrawList` stroking functions: (#9504) [@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) 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 + ------------------------------------------------------------------------ + - Read our Wiki guides for more details! + https://github.com/ocornut/imgui/wiki/Draw-List + - And interactive 1.92.9 vs 1.93.0 comparing testbed: + https://www.dearimgui.com/docs/drawlist_v193 + - AddPolyline(), PathStroke(), AddTriangle(): the algorithm to render lines got overhauled. + (#9504, #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 bevelled 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. + - This is disabled by default! 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_MiterOnly` 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_SquareCap` 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_MiterOnly`, to facilitate matching + a stroke using the same flag. + - AddRect(), AddRectFilled(): various optimizations for rounded rectangles. (#1962, #9504) + - 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. + - 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. + - AddRect() and AddRectFilled() allow inverted positions. + - AddRectFilled() has no AA when passed non-integer coordinates. + - 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) + - Per-viewport FramebufferScale for Apple/Retina screen is applied to draw lists: + - AA Fringe is scaled accordingly. + - Circle and Curves tessellation error are scaled accordingly. + - Reworked assert in PushTexture() to allow convenience grace period of using a + texture that was queue for destroy during the frame. Make it safe to stick to + an existing texture during the frame. (#9528, #8465) + - Added optional `platform_io.DrawCallback_SetSamplerFromTex` standard backend-agnostic + draw callback for requesting to use the sampler associated already associated to a texture. + Currently only supported by the OpenGL2/3 and SDLRenderer3 backend. (#9378) + - Fixed dangling ImDrawList::_OwnerName when using animated label. (#9552) [@ubi-dcastonguay] + - Drags, Sliders: - Fixed an overflow using `ImGuiSliderFlags_Logarithmic` with S32/S64 types which leads to clamped interactions. (#9526, #3361, #1823, #1316, #642) [@lailoken] @@ -297,19 +370,19 @@ Other Changes: the hashed data. (#9557, #9560) [@sthalik] - IO: restore default clipboard/ime/shell platform handlers on ClearPlatformHandlers(), fixing issues switching backends mid-session to one relying on default. (#9537, #8945, #2769) [@lstalmir] -- DrawList: - - Per-viewport FramebufferScale for Apple/Retina screen is applied to draw lists: - - AA Fringe is scaled accordingly. - - Circle and Curves tessellation error are scaled accordingly. - - Reworked assert in PushTexture() to allow convenience grace period of using a - texture that was queue for destroy during the frame. Make it safe to stick to - an existing texture during the frame. (#9528, #8465) - - Added optional `platform_io.DrawCallback_SetSamplerFromTex` standard backend-agnostic - draw callback for requesting to use the sampler associated already associated to a texture. - Currently only supported by the OpenGL2/3 and SDLRenderer3 backend. (#9378) - - Fixed dangling ImDrawList::_OwnerName when using animated label. (#9552) [@ubi-dcastonguay] +- 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: - Added `Widgets->Mixed Values` section. (#5518, #5677, #6865) + - Custom Rendering section: 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. - Style Editor: fixed style/colors combo box applying style when opening Combo. - Backends: - QNX: added QNX Screen backend. (#9492) [@mgorchak-blackberry] diff --git a/imgui.cpp b/imgui.cpp index e51cde0e3..ddef7ec4d 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -395,7 +395,29 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: When you are not sure about an old symbol or function name, try using the Search/Find function of your IDE to look for comments or references in all imgui files. You can read releases logs https://github.com/ocornut/imgui/releases for more details. - 2026/10/07 (1.93.0) - merged ImDrawListFlags into ImDrawFlags. obsoleted ImDrawListFlags (which were rarely used directly): + 2026/10/07 (1.93.0) - large cleanup/refactor of ImDrawList. many issues and inconsistencies fixed! (#9504) + BELOW IS A SIMPLIFIED/SHORTENED RECAP OF CHANGES. READ CHANGELOG AND LINKS ABOVE FOR MORE DETAILS! + useful links: + - ImDrawList 1.92.9 <> 1.93 Interactive Testbed: https://www.dearimgui.com/docs/drawlist_v193 (<-- you can download this locally) + - ImDrawList Wiki Reference: https://github.com/ocornut/imgui/wiki/Draw-List + - Discussion thread: https://github.com/ocornut/imgui/issues/9504 + - Use ImDrawFlags_StrokeLegacy mode to emulate old rendering per-primitive or per-scope. + - AddLine: removed the (+0.5f,+0.5f) offset that was sneakily added to input coordinates by this function, and led to lots of inconsistencies. + - 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, simpler and faster to use `AddLineH()`, `AddLineV()` functions added in 1.92.8. + - IF YOU ARE MINDFUL OF PIXEL-PERFECTNESS IN YOUR CUSTOM RENDERING/WIDGETS read Changelog and links above for details. + - 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. + - AddCircle(), AddNgon(): removed +0.5f offset added to radius. This fixes inconsistencies in the API. Use `ImDrawFlags_StrokeLegacy` to use old method if required. Or you can apply the offset manually! + - AddRectFilled(): non-integer coordinates will now display anti-aliased edges. Previously, non-integer coordinates rendered with aliased edges snapped by the rasterizer. + - AddRect(), AddRectFilled(): rectangles with inverted coordinates won't be visible unless using ImDrawList_StrokeLegacy mode. + With inverted coordinates: + - Legacy `AddRect()` rounding off -> visible but incorrect outer size. + - Legacy `AddRect()` rounding on -> visible but very glitchy. + - Legacy `AddRectFilled()` rounding off -> visible and correct. + - Legacy `AddRectFilled()` rounding on -> visible but very glitchy. + - merged ImDrawListFlags into ImDrawFlags. obsoleted ImDrawListFlags (which were rarely used directly): - ImDrawListFlags_AntiAliasedLines -> ImDrawFlags_AALines, - ImDrawListFlags_AntiAliasedFill -> ImDrawFlags_AAFill, - ImDrawListFlags_AllowVtxOffset -> ImDrawFlags_UseVtxOffset, @@ -405,11 +427,11 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: - ImDrawFlags_RoundCornersAll -> ImDrawFlags_RoundAll, - ImDrawFlags_RoundCornersNone -> ImDrawFlags_RoundNone, - ImDrawFlags_RoundCornersTopLeft -> ImDrawFlags_RoundTopLeft, + - ImDrawFlags_RoundCornersTopRight -> ImDrawFlags_RoundTopRight, - ImDrawFlags_RoundCornersTop -> ImDrawFlags_RoundTop, - ImDrawFlags_RoundCornersBottom -> ImDrawFlags_RoundBottom, - etc. - kept redirection enums (will obsolete). - - obsoleted style.AntiAliasedLinesUseTex which now only makes sense for legacy strokes. + etc. kept redirection enums (will obsolete). + - obsoleted style.AntiAliasedLinesUseTex and ImDrawListFlags_AntiAliasedLinesUseTex, as the new line rendering code always uses textures. - 2026/09/30 (1.93.0) - ImFont: renamed `AddRemapChar()` to `AddRemapCodepoint()` (rarely used, marked internal). (#609, #5748) - 2026/09/18 (1.93.0) - ImGuiTextFilter: removed `float width` parameter of `Draw(const char* filter, float width)`: prefer using `SetNextItemWidth(float)` which is standard. Kept inline redirection function. - 2026/08/03 (1.93.0) - Style: obsoleted `style.CurveTessellationTol (default 1.25)` which was in Pixels² unit in favor of `style.CurveTessellationMaxError` (default 1.12)` which is in Pixels unit. diff --git a/imgui.h b/imgui.h index f3eeea91f..8296daa49 100644 --- a/imgui.h +++ b/imgui.h @@ -2395,7 +2395,7 @@ struct ImGuiStyle float MouseCursorScale; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). We apply per-monitor DPI scaling over this scale. May be removed later. // Rendering & Tessellation - bool AntiAliasedLines; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. Latched at the beginning of the frame (copied to ImDrawList). bool AntiAliasedLines; // Enable anti-aliased lines/borders. Used at the beginning of the frame to set ImDrawFlags_AALines in all draw-lists. + bool AntiAliasedLines; // Enable anti-aliased lines/borders. Used at the beginning of the frame to set ImDrawFlags_AALines in all draw-lists. bool AntiAliasedLineEnds; // Enable anti-aliased lines/borders ends. Nicer for thick lines but more expensive. Used at the beginning of the frame to set ImDrawFlags_AALineEnds in all draw-lists. bool AntiAliasedFill; // Enable anti-aliased edges around filled shapes (rounded rectangles, circles, etc.). Used at the beginning of the frame to set ImDrawFlags_AAFill in all draw-lists. float CurveTessellationMaxError; // Maximum error (in pixels) when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. @@ -3337,7 +3337,7 @@ enum ImDrawFlags_ ImDrawFlags_SquareCap = 1 << 11, // OK -- // PathStroke(), AddPolyline(): use square cap line ends. // About Stroke Ends: - // - Rendering is optimized for fast pixel-perfect UI, so line ends are not anti-aliased by default. It cheaper (we can emit less vertices). + // - Rendering is optimized for fast pixel-perfect UI, so line ends are not anti-aliased by default. It's cheaper (we can emit less vertices). // - For more free-form drawings, light graphs and markers, or when using thick strokes: you can turn them on using the ImDrawFlags_AALineEnds flag. // - See 'Demo->Examples->Custom Rendering' to interactively toy with those flags. // - 'OK*' indicates using this at the AddXXX() call site is unlikely: it would only makes sense if (1) the option is disabled in style/scope and (2) you want to forcefully enable it for a single primitives. Possible but unlikely! Only supported for functions taking flags inputs. @@ -3349,12 +3349,12 @@ enum ImDrawFlags_ //ImDrawFlags_NoAALineEnds = 1 << 16, // OK -- // Disable anti-aliasing ends for a given primitive. // About Stroke Position: - // - Read https://github.com/ocornut/imgui/wiki/Draw-List + // - Read https://github.com/ocornut/imgui/wiki/Draw-List (this guide includes a precise list of differences between 1.92.9 and 1.93.0) // - Read https://github.com/ocornut/imgui/wiki/Pixel-Perfect-Rendering // Stroke Position relative to shape outline -- // Prim/Scope? ImDrawFlags_StrokeInside = 1 << 17, // OK -- // Draw stroke inside of the shape outline (default for closed shapes and AddLineH, AddLineV functions) ImDrawFlags_StrokeCenter = 2 << 17, // OK -- // Draw stroke at the center of the shape outline (default for paths, bezier, and AddLine functions) - ImDrawFlags_StrokeCenterBiased = 3 << 17, // OK -- // Draw stroke at the center of the shape outline, so that half thickness rounded down will be outside, and rest inside the shape outline. Useful for axis-aligned shapes: AddLineH, AddLineV, AddRect. Does not animate well! + ImDrawFlags_StrokeCenterBiased = 3 << 17, // OK -- // Draw stroke at the center of the shape outline, so that half thickness rounded down will be outside, and the rest inside the shape outline. Useful for axis-aligned shapes: AddLineH, AddLineV, AddRect. Does not animate well! ImDrawFlags_StrokeOutside = 4 << 17, // OK -- // Draw stroke outside of the shape outline ImDrawFlags_StrokeLegacy = 7 << 17, // OK OK(0) // Use legacy positioning + enable MiterOnly + disable AALineEnds. Must be all bits set. @@ -3368,8 +3368,8 @@ enum ImDrawFlags_ ImDrawFlags_RoundMask_ = ImDrawFlags_RoundAll | ImDrawFlags_RoundNone, // [Internal] ImDrawFlags_AllowInPushScope_ = ImDrawFlags_AAFill | ImDrawFlags_AALines | ImDrawFlags_AALineEnds | ImDrawFlags_StrokeLegacy | ImDrawFlags_TextNoPixelSnap | ImDrawFlags_UseTexForRoundCorners | ImDrawFlags_UseVtxOffset | ImDrawFlags_RoundMask_, // [Internal] Values allowed in PushDrawFlag() scope. ImDrawFlags_AllowInFrameScope_ = ImDrawFlags_AllowInPushScope_ | ImDrawFlags_AllowTexForRoundCorners_, - ImDrawFlags_StrokeMask_ = 0x07 << 17, // [Internal] - ImDrawFlags_InvalidMask_ = ~0x7FFFFFF0, // [Internal] == 0x8000000F. Reserved to detect misuses. + ImDrawFlags_StrokeMask_ = 0x07 << 17, // [Internal] + ImDrawFlags_InvalidMask_ = ~0x7FFFFFF0, // [Internal] == 0x8000000F. Reserved to detect misuses. // Obsolete names #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS @@ -3402,7 +3402,7 @@ struct ImDrawList ImVector CmdBuffer; // Draw commands. Typically 1 command = 1 GPU draw call, unless the command is a callback. ImVector IdxBuffer; // Index buffer. Each command consume ImDrawCmd::ElemCount of those ImVector VtxBuffer; // Vertex buffer. - ImDrawFlags Flags; // Current flags for drawing primitives. You may poke into these to adjust anti-aliasing settings per-primitive. Alter with PushDrawFlag(). + ImDrawFlags Flags; // Current flags for drawing primitives. Alter with PushDrawFlag()/PopDrawFlags() to adjust defaults to a given scope. // [Internal, used while building lists] unsigned int _VtxCurrentIdx; // [Internal] generally == VtxBuffer.Size unless we are past 64K vertices, in which case this gets reset to 0. @@ -3571,7 +3571,7 @@ struct ImDrawList IMGUI_API void _AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, float rounding, float thickness, ImDrawFlags flags); IMGUI_API void _SelectLineTexture(float screen_thickness, ImVec2* out_uv0, ImVec2* out_uv1, float* out_fringe, ImDrawFlags flags); IMGUI_API float _CalculateCenterBiasedOffset(float thickness); - IMGUI_API void _AddPolyline(const ImVec2* points, int num_points, ImU32 col, float thickness, ImDrawFlags flags, float max_inner_thickness); + IMGUI_API void _AddPolyline(const ImVec2* points, int num_points, ImU32 col, float thickness, ImDrawFlags flags, float max_inner_offset); }; // All draw data to render a Dear ImGui frame @@ -3788,7 +3788,7 @@ enum ImFontAtlasFlags_ ImFontAtlasFlags_None = 0, ImFontAtlasFlags_NoPowerOfTwoHeight = 1 << 0, // Don't round the height to next power of two ImFontAtlasFlags_NoMouseCursors = 1 << 1, // Don't build software mouse cursors into the atlas (save a little texture memory) - ImFontAtlasFlags_NoBakedLines = 1 << 2, // [OBSOLETE] Don't build line textures into the atlas (save a little texture memory). SINCE 1.93.0 THIS PREVENT ANTI-ALIASED STROKES FROM WORKING AND WILL DISABLE AA. + ImFontAtlasFlags_NoBakedLines = 1 << 2, // Don't build anti-aliased line textures into the atlas (save a little texture memory). SINCE 1.93.0 THIS PREVENTS ANTI-ALIASED LINES FROM WORKING AND WILL DISABLE THEM. ImFontAtlasFlags_NoBakedRoundCorners= 1 << 3, // Don't build round corners into the atlas. }; @@ -4317,7 +4317,7 @@ enum ImDrawListFlags_ ImDrawListFlags_None = ImDrawFlags_None, ImDrawListFlags_AntiAliasedFill = ImDrawFlags_AAFill, ImDrawListFlags_AntiAliasedLines = ImDrawFlags_AALines, - ImDrawListFlags_AntiAliasedLinesUseTex = 0, // Only apply to StrokeLegacy mode! + ImDrawListFlags_AntiAliasedLinesUseTex = ImDrawFlags_AALines, // No effect, anti-aliased rendering always uses textures from 1.93+ ImDrawListFlags_AllowVtxOffset = ImDrawFlags_UseVtxOffset, ImDrawListFlags_TextNoPixelSnap = ImDrawFlags_TextNoPixelSnap, }; diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 2f5450e09..29dcb7f91 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10451,8 +10451,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::CheckboxFlags("AALineEnds", &prim_other_flags, ImDrawFlags_AALineEnds); ImGui::SameLine(); ImGui::CheckboxFlags("MiterOnly", &prim_other_flags, ImDrawFlags_MiterOnly); ImGui::SameLine(); ImGui::CheckboxFlags("SquareCap", &prim_other_flags, ImDrawFlags_SquareCap); - //ImGui::CheckboxFlags("UseTexForRoundCorners", &prim_other_flags, ImDrawFlags_UseTexForRoundCorners); ImGui::SameLine(); - //ImGui::CheckboxFlags("UseTexForStrokeLegacy", &prim_other_flags, ImDrawFlags_UseTexForStrokeLegacy); + //ImGui::CheckboxFlags("UseTexForRoundCorners", &prim_other_flags, ImDrawFlags_UseTexForRoundCorners); static ImDrawFlags scope_flags = draw_list->Flags & ImDrawFlags_AllowInPushScope_; // First init copy current state. ImGui::SeparatorText("Current ImDrawList flags (e.g. PushDrawFlag function)"); @@ -10461,8 +10460,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::CheckboxFlags("AALines", &scope_flags, ImDrawFlags_AALines); ImGui::SameLine(); ImGui::CheckboxFlags("AALineEnds", &scope_flags, ImDrawFlags_AALineEnds); ImGui::SameLine(); ImGui::CheckboxFlags("StrokeLegacy", &scope_flags, ImDrawFlags_StrokeLegacy); - //ImGui::CheckboxFlags("UseTexForRoundCorners", &scope_flags, ImDrawFlags_UseTexForRoundCorners); ImGui::SameLine(); - //ImGui::CheckboxFlags("UseTexForStrokeLegacy", &scope_flags, ImDrawFlags_UseTexForStrokeLegacy); + //ImGui::CheckboxFlags("UseTexForRoundCorners", &scope_flags, ImDrawFlags_UseTexForRoundCorners); ImGui::PopID(); ImGui::Spacing(); @@ -10485,7 +10483,7 @@ static void ShowExampleAppCustomRendering(bool* p_open) for (int side = 0; side < 4; side++) { float t = (float)ImGui::GetTime() * 0.1f + side * pi * 0.5f; - rotating_square[side] = { (float)cosf(t) * half_sz, (float)sin(t) * half_sz }; + rotating_square[side] = { (float)cosf(t) * half_sz, (float)sinf(t) * half_sz }; } float x = start_pos.x; diff --git a/imgui_draw.cpp b/imgui_draw.cpp index a696fade0..b92af6f37 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1997,6 +1997,9 @@ void ImDrawList::AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float th _AddLine(p1, p2, col, thickness, flags); } +// AddLineH(10, 20, 5, col, 1.0f) == AddLine( {10,5.5}, {20,5.5}, col, 1.0f) +// == AddLine( {10,5}, {20,5}, col, 1.0f, ImDrawFlags_StrokeLegacy); // or before 1.93.0 +// Thickness extend inside/bottom by default. Faster than equivalent AddLine() call. void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float thickness, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) @@ -2043,6 +2046,9 @@ void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float th } } +// AddLineV(5, 10, 20, col, 1.0f) == AddLine( {5.5,10}, {5.5,20}, col, 1.0f) +// == AddLine( {5,10}, {5,20}, col, 1.0f, ImDrawFlags_StrokeLegacy); // or before 1.93.0 +// Thickness extend inside/right by default. Faster than equivalent AddLine() call. void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float thickness, ImDrawFlags flags) { if ((col & IM_COL32_A_MASK) == 0) @@ -4834,7 +4840,7 @@ static void ImFontAtlasBuildUpdateTexDataBasic(ImFontAtlas* atlas) static int InitCornerGeometry(float cx, float cy, float rounding, float circle_segment_max_error, ImVec2* line_normals, float* line_distances) { - // This tries to quite faithfully replication how we render the corners using tessellation. + // This tries to quite faithfully replicate how we render the corners using tessellation. // Older code sampled a circle SDF directly, but it had clearly visible discrepancy between the baked corners // and tessellated corners due to circle_segment_max_error. @@ -5083,9 +5089,9 @@ static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) } { - // Calculate super sampled lin textures for smaller line widths to make transition between sizes smoother. + // Calculate super sampled line textures for smaller line widths to make transition between sizes smoother. - // Calc ramp used for all the textures. + // Calculate ramp used for all the textures. ImU8 ramp[IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT]; for (int n = 0; n < IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT; n++) ramp[n] = (ImU8)(((float)n / IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT) * 255.0f); @@ -5102,7 +5108,7 @@ static void ImFontAtlasBuildUpdateTexDataLines(ImFontAtlas* atlas) // To super 2x sample that signal, we end up with following texture: // [ 0 | .5 | 1 | .5 | 0 ] // :.......................: - // One might think that there should be 2 opaque pixels the the middle section, but no singe we're super sampling the triangle signal. + // One might think that there should be 2 opaque pixels in the middle section, but no since we're super sampling the triangle signal. const int line_width = 1 + n; IM_ASSERT(IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT + line_width + IM_DRAWLIST_TEX_LINES_SAMPLE_COUNT <= r.w && y < r.h); // Make sure we're inside the texture bounds before we start writing pixels diff --git a/imgui_internal.h b/imgui_internal.h index 1f2e26268..74d91a7d4 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -506,7 +506,7 @@ inline float ImSign(float x) { return (x < 0.0f) ? -1.0f : (x > 0.0f inline double ImSign(double x) { return (x < 0.0) ? -1.0 : (x > 0.0) ? 1.0 : 0.0; } #ifdef IMGUI_ENABLE_SSE inline float ImRsqrt(float x) { return _mm_cvtss_f32(_mm_rsqrt_ss(_mm_set_ss(x))); } -// Converge to more precise solution using single step of Newton-Raphson method, repeating increase precision +// Converge to more precise solution using single step of Newton-Raphson method, repeating increases precision inline float ImRsqrtPrecise(float x) { const float r = _mm_cvtss_f32(_mm_rsqrt_ss(_mm_set_ss(x))); return r * (1.5f - x * 0.5f * r * r); } #else inline float ImRsqrt(float x) { return 1.0f / sqrtf(x); } @@ -924,7 +924,7 @@ IMGUI_API ImGuiStoragePair* ImLowerBound(ImGuiStoragePair* in_begin, ImGuiStorag #define IM_DRAWLIST_ARCFAST_SAMPLE_MAX IM_DRAWLIST_ARCFAST_TABLE_SIZE // Sample index _PathArcToFastEx() for 360 angle. #ifndef IM_DRAWLIST_TEX_LINES_WIDTH_MAX -#define IM_DRAWLIST_TEX_LINES_WIDTH_MAX (32) // The maximum line width to bake anti-aliased textures for. Build atlas with ImFontAtlasFlags_NoBakedLines to disable baking. +#define IM_DRAWLIST_TEX_LINES_WIDTH_MAX (32) // The maximum line width to bake anti-aliased textures for. #endif #ifndef IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH #define IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH (4) // Calculate detailed textures for width [1..IM_DRAWLIST_TEX_LINES_DETAILED_WIDTH] From 7bccc3bc1e738d141f9cb2ec16378d9881d422eb Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 5 Aug 2026 16:55:48 +0200 Subject: [PATCH 113/120] DrawList: AddCircle, AddCircleFilled: fixed large circles being under-tessellated (when above ~140 with default CircleTessellationMaxError). --- docs/CHANGELOG.txt | 2 ++ imgui_draw.cpp | 12 ++++++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 70a89b246..56be6b6eb 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -255,6 +255,8 @@ Other Changes: 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. + - 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, diff --git a/imgui_draw.cpp b/imgui_draw.cpp index b92af6f37..515816da9 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2714,7 +2714,7 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu return; } - if (num_segments <= 0) + if (num_segments <= 0 && outer_radius <= _Data->ArcFastRadiusCutoff) { // Use arc with automatic segment count const int a_step = IM_DRAWLIST_ARCFAST_SAMPLE_MAX / _CalcCircleAutoSegmentCount(outer_radius); // Use outer_radius for segment count to be consistent with rounded rect. @@ -2723,7 +2723,9 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu } else { - // Explicit segment count (still clamp to avoid drawing insanely tessellated shapes) + // Explicit segment count or above fast arc cutoff (still clamp to avoid drawing insanely tessellated shapes) + if (num_segments <= 0) + num_segments = _CalcCircleAutoSegmentCount(outer_radius); num_segments = ImClamp(num_segments, 3, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX); // Because we are filling a closed shape we remove 1 from the count of segments/points @@ -2756,7 +2758,7 @@ void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col, radius = _FringeScale * 0.5f; } - if (num_segments <= 0) + if (num_segments <= 0 && radius <= _Data->ArcFastRadiusCutoff) { // Use arc with automatic segment count _PathArcToFastEx(center, radius, 0, IM_DRAWLIST_ARCFAST_SAMPLE_MAX, 0); @@ -2764,7 +2766,9 @@ void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col, } else { - // Explicit segment count (still clamp to avoid drawing insanely tessellated shapes) + // Explicit segment count or above cutoff (still clamp to avoid drawing insanely tessellated shapes) + if (num_segments <= 0) + num_segments = _CalcCircleAutoSegmentCount(radius); num_segments = ImClamp(num_segments, 3, IM_DRAWLIST_CIRCLE_AUTO_SEGMENT_MAX); // Because we are filling a closed shape we remove 1 from the count of segments/points From cb66fcdaac0a6ca6a904406018529424c585caa9 Mon Sep 17 00:00:00 2001 From: ocornut Date: Wed, 5 Aug 2026 23:05:42 +0200 Subject: [PATCH 114/120] DrawList: rename ImDrawFlags_MiterOnly->ImDrawFlags_JoinMiter, ImDrawFlags_SquareCap->ImDrawFlags_CapSquare. --- docs/CHANGELOG.txt | 6 +++--- imgui.h | 6 +++--- imgui_demo.cpp | 4 ++-- imgui_draw.cpp | 44 ++++++++++++++++++++++---------------------- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 56be6b6eb..8b7f2d6a7 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -235,18 +235,18 @@ Other Changes: 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_MiterOnly` flag to disable using beveled corner. + - 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_SquareCap` flag to extend lines ends using square caps. (#9360) + - 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_MiterOnly`, to facilitate matching + 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, #9504) - Integer-aligned coordinates and thicknesses will automatically use baked textures, diff --git a/imgui.h b/imgui.h index 8296daa49..5c47d94e1 100644 --- a/imgui.h +++ b/imgui.h @@ -3333,8 +3333,8 @@ enum ImDrawFlags_ // Stroke Options ----------------------------- // Prim/Scope? ImDrawFlags_Closed = 1 << 9, // OK -- // PathStroke(), AddPolyline(): specify that shape should be closed. - ImDrawFlags_MiterOnly = 1 << 10, // OK -- // PathStroke(), AddPolyline(): use miter corners only. This assumes that the input polyline does not have corners sharper than 90 degrees. Slightly faster. - ImDrawFlags_SquareCap = 1 << 11, // OK -- // PathStroke(), AddPolyline(): use square cap line ends. + ImDrawFlags_JoinMiter = 1 << 10, // OK -- // PathStroke(), AddPolyline(): use miter joins/corners only. This assumes that the input polyline does not have corners sharper than 90 degrees. Slightly faster. + ImDrawFlags_CapSquare = 1 << 11, // OK -- // PathStroke(), AddPolyline(): use square cap line ends. // About Stroke Ends: // - Rendering is optimized for fast pixel-perfect UI, so line ends are not anti-aliased by default. It's cheaper (we can emit less vertices). @@ -3356,7 +3356,7 @@ enum ImDrawFlags_ ImDrawFlags_StrokeCenter = 2 << 17, // OK -- // Draw stroke at the center of the shape outline (default for paths, bezier, and AddLine functions) ImDrawFlags_StrokeCenterBiased = 3 << 17, // OK -- // Draw stroke at the center of the shape outline, so that half thickness rounded down will be outside, and the rest inside the shape outline. Useful for axis-aligned shapes: AddLineH, AddLineV, AddRect. Does not animate well! ImDrawFlags_StrokeOutside = 4 << 17, // OK -- // Draw stroke outside of the shape outline - ImDrawFlags_StrokeLegacy = 7 << 17, // OK OK(0) // Use legacy positioning + enable MiterOnly + disable AALineEnds. Must be all bits set. + ImDrawFlags_StrokeLegacy = 7 << 17, // OK OK(0) // Use legacy positioning + enable JoinMiter + disable AALineEnds. Must be all bits set. // Other Options ------------------------------ // Prim/Scope? ImDrawFlags_TextNoPixelSnap = 1 << 20, // OK OK(0) // Disable automatically snapping AddText() calls to pixel boundaries. diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 29dcb7f91..f1a66034a 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10449,8 +10449,8 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::CheckboxFlags("AAFill", &prim_other_flags, ImDrawFlags_AAFill); ImGui::SameLine(); // Note: Only for functions taking flags. ImGui::CheckboxFlags("AALines", &prim_other_flags, ImDrawFlags_AALines); ImGui::SameLine(); // Note: Only for functions taking flags. ImGui::CheckboxFlags("AALineEnds", &prim_other_flags, ImDrawFlags_AALineEnds); ImGui::SameLine(); - ImGui::CheckboxFlags("MiterOnly", &prim_other_flags, ImDrawFlags_MiterOnly); ImGui::SameLine(); - ImGui::CheckboxFlags("SquareCap", &prim_other_flags, ImDrawFlags_SquareCap); + ImGui::CheckboxFlags("JoinMiter", &prim_other_flags, ImDrawFlags_JoinMiter); ImGui::SameLine(); + ImGui::CheckboxFlags("CapSquare", &prim_other_flags, ImDrawFlags_CapSquare); //ImGui::CheckboxFlags("UseTexForRoundCorners", &prim_other_flags, ImDrawFlags_UseTexForRoundCorners); static ImDrawFlags scope_flags = draw_list->Flags & ImDrawFlags_AllowInPushScope_; // First init copy current state. diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 515816da9..76918f2e5 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1007,10 +1007,10 @@ void ImDrawList::_AddPolyline(const ImVec2* points, const int points_count, ImU3 const ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeCenter); if (stroke_pos == ImDrawFlags_StrokeLegacy) - flags = (flags | ImDrawFlags_MiterOnly) & ~ImDrawFlags_AALineEnds; + flags = (flags | ImDrawFlags_JoinMiter) & ~ImDrawFlags_AALineEnds; const bool closed = (flags & ImDrawFlags_Closed) != 0; - const bool miters_only = (flags & ImDrawFlags_MiterOnly) != 0; + const bool miters_only = (flags & ImDrawFlags_JoinMiter) != 0; const float miter_distance_limit_sqr = IM_POLYLINE_MITER_LIMIT * IM_POLYLINE_MITER_LIMIT; float thickness0 = (thickness + fringe) * 0.5f; // Center (or legacy) @@ -1076,7 +1076,7 @@ void ImDrawList::_AddPolyline(const ImVec2* points, const int points_count, ImU3 dir.x = n1.y; dir.y = -n1.x; - if (flags & ImDrawFlags_SquareCap) + if (flags & ImDrawFlags_CapSquare) { p1.x -= dir.x * half_thickness; p1.y -= dir.y * half_thickness; @@ -1262,7 +1262,7 @@ void ImDrawList::_AddPolyline(const ImVec2* points, const int points_count, ImU3 // End cap dir.x = n1.y; dir.y = -n1.x; - if (flags & ImDrawFlags_SquareCap) + if (flags & ImDrawFlags_CapSquare) { p1.x += dir.x * half_thickness; p1.y += dir.y * half_thickness; @@ -1330,7 +1330,7 @@ void ImDrawList::AddConvexPolyFilled(const ImVec2* points, const int points_coun { const float half_aa = _FringeScale * 0.5f; ImU32 col_trans = col & ~IM_COL32_A_MASK; - const bool miters_only = (flags & ImDrawFlags_MiterOnly) != 0; + const bool miters_only = (flags & ImDrawFlags_JoinMiter) != 0; const float miter_distance_limit_sqr = IM_POLYLINE_MITER_LIMIT * IM_POLYLINE_MITER_LIMIT; const int idx_count = ((points_count - 2) + points_count * 2 + IM_POLYLINE_CONVEX_POLY_MAX_BEVELS) * 3; @@ -1885,7 +1885,7 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t const ImDrawFlags stroke_pos = _GetStrokePos(flags, ImDrawFlags_StrokeCenter); if (stroke_pos == ImDrawFlags_StrokeLegacy) - flags = (flags | ImDrawFlags_MiterOnly) & ~ImDrawFlags_AALineEnds; + flags = (flags | ImDrawFlags_JoinMiter) & ~ImDrawFlags_AALineEnds; float thickness0 = (thickness + fringe) * 0.5f; // Center (or legacy) if (stroke_pos == ImDrawFlags_StrokeOutside) @@ -1914,7 +1914,7 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t float p1_y = p1.y; float p2_x = p2.x; float p2_y = p2.y; - if (flags & ImDrawFlags_SquareCap) + if (flags & ImDrawFlags_CapSquare) { p1_x -= dir_x * half_thickness; p1_y -= dir_y * half_thickness; @@ -2022,7 +2022,7 @@ void ImDrawList::AddLineH(float min_x, float max_x, float y, ImU32 col, float th top_y -= thickness; const bool is_truncated = IM_IS_TRUNCATED4(min_x, max_x, top_y, thickness); - if (_FringeScaleIsInteger && is_truncated && (flags & ImDrawFlags_SquareCap) == 0) + if (_FringeScaleIsInteger && is_truncated && (flags & ImDrawFlags_CapSquare) == 0) { if (thickness < _FringeScale) IM_UNLIKELY { @@ -2071,7 +2071,7 @@ void ImDrawList::AddLineV(float x, float min_y, float max_y, ImU32 col, float th left_x -= thickness; const bool is_truncated = IM_IS_TRUNCATED4(left_x, min_y, max_y, thickness); - if (_FringeScaleIsInteger && is_truncated && (flags & ImDrawFlags_SquareCap) == 0) + if (_FringeScaleIsInteger && is_truncated && (flags & ImDrawFlags_CapSquare) == 0) { if (thickness < _FringeScale) IM_UNLIKELY { @@ -2352,7 +2352,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl PathRect(ImVec2(p_min.x + 0.50f, p_min.y + 0.50f), ImVec2(p_max.x - 0.50f, p_max.y - 0.50f), rounding, flags); else PathRect(ImVec2(p_min.x + 0.50f, p_min.y + 0.50f), ImVec2(p_max.x - 0.49f, p_max.y - 0.49f), rounding, flags); // Better looking lower-right corner and rounded non-AA shapes. - PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | ImDrawFlags_StrokeCenter | (flags & ~ImDrawFlags_StrokeMask_)); + PathStroke(col, thickness, ImDrawFlags_Closed | ImDrawFlags_JoinMiter | ImDrawFlags_StrokeCenter | (flags & ~ImDrawFlags_StrokeMask_)); return; } @@ -2452,7 +2452,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl if (!has_rounding || (flags & ImDrawFlags_RoundMask_) == ImDrawFlags_RoundNone) { const ImVec2 points[4] = { outer_min, ImVec2(outer_max.x, outer_min.y), outer_max, ImVec2(outer_min.x, outer_max.y) }; - _AddPolyline(points, 4, col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | ImDrawFlags_StrokeInside, half_min_dim); + _AddPolyline(points, 4, col, thickness, ImDrawFlags_Closed | ImDrawFlags_JoinMiter | ImDrawFlags_StrokeInside, half_min_dim); _Path.Size = 0; } else @@ -2474,7 +2474,7 @@ void ImDrawList::AddRect(const ImVec2& p_min, const ImVec2& p_max, ImU32 col, fl PathArcToFast(ImVec2(outer_max.x - rounding_br, outer_max.y - rounding_br), rounding_br, 0, 3); PathArcToFast(ImVec2(outer_min.x + rounding_bl, outer_max.y - rounding_bl), rounding_bl, 3, 6); - _AddPolyline(_Path.Data, _Path.Size, col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | ImDrawFlags_StrokeInside | (flags & ~ImDrawFlags_StrokeMask_), half_min_dim); + _AddPolyline(_Path.Data, _Path.Size, col, thickness, ImDrawFlags_Closed | ImDrawFlags_JoinMiter | ImDrawFlags_StrokeInside | (flags & ~ImDrawFlags_StrokeMask_), half_min_dim); _Path.Size = 0; } } @@ -2553,7 +2553,7 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c height = _FringeScale; } const ImVec2 points[4] = { p_min, ImVec2(p_min.x + width, p_min.y), ImVec2(p_min.x + width, p_min.y + height), ImVec2(p_min.x, p_min.y + height) }; - AddConvexPolyFilled(points, 4, col, ImDrawFlags_MiterOnly); + AddConvexPolyFilled(points, 4, col, ImDrawFlags_JoinMiter); return; } @@ -2605,12 +2605,12 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c if (!has_rounding) { const ImVec2 points[4] = { p_min, ImVec2(p_max.x, p_min.y), p_max, ImVec2(p_min.x, p_max.y) }; - AddConvexPolyFilled(points, 4, col, ImDrawFlags_MiterOnly); + AddConvexPolyFilled(points, 4, col, ImDrawFlags_JoinMiter); } else { PathRect(p_min, p_max, rounding, flags); - PathFillConvex(col, ImDrawFlags_MiterOnly); + PathFillConvex(col, ImDrawFlags_JoinMiter); } } @@ -2740,7 +2740,7 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu // This will be used by _AddPolyline() to clamp the inner stroke expansion, to avoid creating rendering artifacts. const float unit_apothem = ApproxApothem(_Path.Size); const float edge_dist = radius * unit_apothem; - _AddPolyline(_Path.Data, _Path.Size, col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | stroke_pos, edge_dist); + _AddPolyline(_Path.Data, _Path.Size, col, thickness, ImDrawFlags_Closed | ImDrawFlags_JoinMiter | stroke_pos, edge_dist); _Path.Size = 0; } @@ -2776,7 +2776,7 @@ void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col, PathArcTo(center, radius, 0.0f, a_max, num_segments - 1); } - PathFillConvex(col, ImDrawFlags_MiterOnly); + PathFillConvex(col, ImDrawFlags_JoinMiter); } // Guaranteed to honor 'num_segments' @@ -2822,7 +2822,7 @@ void ImDrawList::AddNgon(const ImVec2& center, float radius, ImU32 col, int num_ // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; PathArcTo(center, radius, 0.0f, a_max, num_segments - 1); - _AddPolyline(_Path.Data, _Path.Size, col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | stroke_pos, edge_dist); + _AddPolyline(_Path.Data, _Path.Size, col, thickness, ImDrawFlags_Closed | ImDrawFlags_JoinMiter | stroke_pos, edge_dist); _Path.Size = 0; } @@ -2844,7 +2844,7 @@ void ImDrawList::AddNgonFilled(const ImVec2& center, float radius, ImU32 col, in // Because we are filling a closed shape we remove 1 from the count of segments/points const float a_max = (IM_PI * 2.0f) * ((float)num_segments - 1.0f) / (float)num_segments; PathArcTo(center, radius, 0.0f, a_max, num_segments - 1); - PathFillConvex(col, ImDrawFlags_MiterOnly); + PathFillConvex(col, ImDrawFlags_JoinMiter); } // Ellipse @@ -2872,7 +2872,7 @@ void ImDrawList::AddEllipse(const ImVec2& center, const ImVec2& radius, ImU32 co // Prevent the inside part of the stroke to be expanded too much to prevent some rendering artifacts. const float max_inner_offset = ImMin(rad.x, rad.y); - _AddPolyline(_Path.Data, _Path.Size, col, thickness, ImDrawFlags_Closed | ImDrawFlags_MiterOnly | stroke_pos, max_inner_offset); + _AddPolyline(_Path.Data, _Path.Size, col, thickness, ImDrawFlags_Closed | ImDrawFlags_JoinMiter | stroke_pos, max_inner_offset); _Path.Size = 0; } @@ -3019,7 +3019,7 @@ void ImDrawList::AddImageRounded(ImTextureRef tex_ref, const ImVec2& p_min, cons int vert_start_idx = VtxBuffer.Size; PathRect(p_min, p_max, rounding, flags); - PathFillConvex(col, ImDrawFlags_MiterOnly); + PathFillConvex(col, ImDrawFlags_JoinMiter); int vert_end_idx = VtxBuffer.Size; ImGui::ShadeVertsLinearUV(this, vert_start_idx, vert_end_idx, p_min, p_max, uv_min, uv_max, true); @@ -7679,7 +7679,7 @@ void ImGui::RenderRectFilledInRangeH(ImDrawList* draw_list, const ImRect& rect, draw_list->PathArcTo(ImVec2(x1, p1.y - rounding), rounding, +arc1_b, +arc1_e); // BR } } - draw_list->PathFillConvex(col, ImDrawFlags_MiterOnly); + draw_list->PathFillConvex(col, ImDrawFlags_JoinMiter); } void ImGui::RenderRectFilledWithHole(ImDrawList* draw_list, const ImRect& outer, const ImRect& inner, ImU32 col, float rounding) From f84d77c424249b9d3438aa8cca0b57f11b5f0450 Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 6 Aug 2026 18:09:43 +0200 Subject: [PATCH 115/120] DrawList: changed _GetStrokePos() to a macro. --- imgui_draw.cpp | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 76918f2e5..0562635d7 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -936,12 +936,7 @@ void ImDrawList::_SelectLineTexture(float screen_thickness, ImVec2* out_uv0, ImV } } -static inline ImDrawFlags _GetStrokePos(ImDrawFlags flags, ImDrawFlags default_stroke_pos) -{ - if (flags & ImDrawFlags_StrokeMask_) - return (flags & ImDrawFlags_StrokeMask_); - return default_stroke_pos; -} +#define _GetStrokePos(_FLAGS, _DEFAULT_STROKE_POS) (((_FLAGS) & ImDrawFlags_StrokeMask_) ? ((_FLAGS) & ImDrawFlags_StrokeMask_) : _DEFAULT_STROKE_POS) IM_MSVC_RUNTIME_CHECKS_RESTORE @@ -1052,7 +1047,6 @@ void ImDrawList::_AddPolyline(const ImVec2* points, const int points_count, ImU3 } const float half_aa = _FringeScale * 0.5f; // Used for end caps, does not use "fringe" since end cap AA is not using the texture. - const float half_thickness = thickness * 0.5f; const bool use_aa_ends = (flags & (ImDrawFlags_AALines | ImDrawFlags_AALineEnds)) == (ImDrawFlags_AALines | ImDrawFlags_AALineEnds); const int first_vtx_offset = (int)(_VtxWritePtr - VtxBuffer.Data); @@ -1078,8 +1072,8 @@ void ImDrawList::_AddPolyline(const ImVec2* points, const int points_count, ImU3 dir.y = -n1.x; if (flags & ImDrawFlags_CapSquare) { - p1.x -= dir.x * half_thickness; - p1.y -= dir.y * half_thickness; + p1.x -= dir.x * thickness * 0.5f; + p1.y -= dir.y * thickness * 0.5f; } if (!use_aa_ends) @@ -1245,7 +1239,6 @@ void ImDrawList::_AddPolyline(const ImVec2* points, const int points_count, ImU3 // Connect prev to next IM_APPEND_TRI(base_idx + 0, base_idx + 2, base_idx + 3); IM_APPEND_TRI(base_idx + 0, base_idx + 3, base_idx + 1); - base_idx = next_base_idx; } @@ -1264,8 +1257,8 @@ void ImDrawList::_AddPolyline(const ImVec2* points, const int points_count, ImU3 dir.y = -n1.x; if (flags & ImDrawFlags_CapSquare) { - p1.x += dir.x * half_thickness; - p1.y += dir.y * half_thickness; + p1.x += dir.x * thickness * 0.5f; + p1.y += dir.y * thickness * 0.5f; } if (!use_aa_ends) @@ -1894,13 +1887,12 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t thickness0 = _CalculateCenterBiasedOffset(thickness) + fringe * 0.5f; else if (stroke_pos == ImDrawFlags_StrokeInside) thickness0 = fringe * 0.5f; - float thickness1 = (thickness + fringe) - thickness0; + const float thickness1 = (thickness + fringe) - thickness0; - const float ratio = thickness0 / (thickness0 + thickness1); // The points using uv2 are placed on the path, calculate the position from stroke offets. + const float ratio = thickness0 / (thickness0 + thickness1); // The points using uv2 are placed on the path, calculate the position from stroke offsets. const ImVec2 uv2(uv0.x + (uv1.x - uv0.x) * ratio, uv0.y); const float half_aa = _FringeScale * 0.5f; // Used for end caps, does not use "fringe" since end cap AA is not using the texture. - const float half_thickness = thickness * 0.5f; const bool use_aa_ends = (flags & (ImDrawFlags_AALines | ImDrawFlags_AALineEnds)) == (ImDrawFlags_AALines | ImDrawFlags_AALineEnds); if (!use_aa_ends) @@ -1916,6 +1908,7 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t float p2_y = p2.y; if (flags & ImDrawFlags_CapSquare) { + const float half_thickness = thickness * 0.5f; p1_x -= dir_x * half_thickness; p1_y -= dir_y * half_thickness; p2_x += dir_x * half_thickness; @@ -1928,7 +1921,6 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t base_idx = (int)_VtxCurrentIdx; IM_APPEND_VTX(p1_x - norm_x * thickness0, p1_y - norm_y * thickness0, uv0, col); IM_APPEND_VTX(p1_x + norm_x * thickness1, p1_y + norm_y * thickness1, uv1, col); - IM_APPEND_VTX(p2_x - norm_x * thickness0, p2_y - norm_y * thickness0, uv0, col); IM_APPEND_VTX(p2_x + norm_x * thickness1, p2_y + norm_y * thickness1, uv1, col); @@ -1966,7 +1958,6 @@ void ImDrawList::_AddLine(const ImVec2& p1, const ImVec2& p2, ImU32 col, float t next_base_idx = (int)_VtxCurrentIdx; IM_APPEND_VTX(pa_x - norm_x * thickness0, pa_y - norm_y * thickness0, uv0, col); IM_APPEND_VTX(pa_x + norm_x * thickness1, pa_y + norm_y * thickness1, uv1, col); - IM_APPEND_VTX(pb_x - norm_x * thickness0, pb_y - norm_y * thickness0, uv0, col_trans); IM_APPEND_VTX(pb_x + norm_x * thickness1, pb_y + norm_y * thickness1, uv1, col_trans); From d23a638613a5ab23fae38b595aadc2983c736eab Mon Sep 17 00:00:00 2001 From: ocornut Date: Thu, 6 Aug 2026 15:58:15 +0200 Subject: [PATCH 116/120] Demo: Custom Rendering: added zig-zag concave shape, tweak color, tidy up section. Demo: Custom Rendering: tweak animated rounding. --- imgui_demo.cpp | 196 ++++++++++++++++++++++++++----------------------- 1 file changed, 106 insertions(+), 90 deletions(-) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index f1a66034a..5078c11d7 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -10338,13 +10338,10 @@ static void ShowExampleAppWindowTitles(bool*) // [SECTION] Example App: Custom Rendering using ImDrawList API / ShowExampleAppCustomRendering() //----------------------------------------------------------------------------- -// Add a |_| looking shape -static void PathConcaveShape(ImDrawList* draw_list, float x, float y, float sz) -{ - const ImVec2 pos_norms[] = { { 0.0f, 0.0f }, { 0.3f, 0.0f }, { 0.3f, 0.7f }, { 0.7f, 0.7f }, { 0.7f, 0.0f }, { 1.0f, 0.0f }, { 1.0f, 1.0f }, { 0.0f, 1.0f } }; - for (const ImVec2& p : pos_norms) - draw_list->PathLineTo(ImVec2(x + 0.5f + (int)(sz * p.x), y + 0.5f + (int)(sz * p.y))); -} +// See our Wiki guides: +// - ImDrawList Vector Rendering Reference: https://github.com/ocornut/imgui/wiki/Draw-List +// - 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 // Demonstrate using the low-level ImDrawList to draw custom shapes. static void ShowExampleAppCustomRendering(bool* p_open) @@ -10376,20 +10373,16 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::Text("Gradients"); ImVec2 gradient_size = ImVec2(ImGui::CalcItemWidth(), ImGui::GetFrameHeight()); { - ImVec2 p0 = ImGui::GetCursorScreenPos(); - ImVec2 p1 = ImVec2(p0.x + gradient_size.x, p0.y + gradient_size.y); + ImGui::InvisibleButton("##gradient1", gradient_size); ImU32 col_a = ImGui::GetColorU32(IM_COL32(0, 0, 0, 255)); ImU32 col_b = ImGui::GetColorU32(IM_COL32(255, 255, 255, 255)); - draw_list->AddRectFilledMultiColor(p0, p1, col_a, col_b, col_b, col_a); - ImGui::InvisibleButton("##gradient1", gradient_size); + draw_list->AddRectFilledMultiColor(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), col_a, col_b, col_b, col_a); } { - ImVec2 p0 = ImGui::GetCursorScreenPos(); - ImVec2 p1 = ImVec2(p0.x + gradient_size.x, p0.y + gradient_size.y); + ImGui::InvisibleButton("##gradient2", gradient_size); ImU32 col_a = ImGui::GetColorU32(IM_COL32(0, 255, 0, 255)); ImU32 col_b = ImGui::GetColorU32(IM_COL32(255, 0, 0, 255)); - draw_list->AddRectFilledMultiColor(p0, p1, col_a, col_b, col_b, col_a); - ImGui::InvisibleButton("##gradient2", gradient_size); + draw_list->AddRectFilledMultiColor(ImGui::GetItemRectMin(), ImGui::GetItemRectMax(), col_a, col_b, col_b, col_a); } // Draw a bunch of primitives @@ -10404,36 +10397,32 @@ static void ShowExampleAppCustomRendering(bool* p_open) static int circle_segments_override_v = 12; static bool curve_segments_override = false; static int curve_segments_override_v = 8; - static ImVec4 colf_stroke = ImVec4(1.00f, 1.00f, 0.40f, 1.0f); - static ImVec4 colf_fill = ImVec4(0.70f, 0.18f, 0.62f, 1.0f); + static ImVec4 colf_stroke = ImVec4(1.000f, 0.384f, 0.169f, 1.000f); + static ImVec4 colf_fill = ImVec4(0.416f, 0.378f, 0.420f, 1.000f); ImGui::DragFloat("Size", &sz, 0.2f, 0.2f, 100.0f, "%.0f"); - const float rounding_anim_period = 8.0f; + const float rounding_anim_period = 3.0f; const float rounding_wave = 1.0f - fabsf(fmodf((float)ImGui::GetTime(), rounding_anim_period) * (2.0f / rounding_anim_period) - 1.0f); - const float rounding = animate_rounding ? (float)((int)(rounding_wave * base_rounding * 10.0f)) / 10.0f : base_rounding; - ImGui::DragFloat("Rounding", &base_rounding, 0.02f, 0.0f, 30.0f, "%.02f"); - ImGui::SameLine(); - ImGui::Checkbox("Animate##rounding", &animate_rounding); - ImGui::SameLine(); + const float rounding = animate_rounding ? (float)(int)(rounding_wave * base_rounding * 10.0f) / 10.0f : base_rounding; // Sub-integer rounding works but has discontinuities in small values, so best disable we + ImGui::DragFloat("Rounding", &base_rounding, 0.02f, 0.0f, 32.0f, "%.1f"); ImGui::SameLine(); + ImGui::Checkbox("Animate##rounding", &animate_rounding); ImGui::SameLine(); ImGui::Text("%.2f", rounding); const float thickness_anim_period = 5.0f; const float thickness_wave = 1.0f - fabsf(fmodf((float)ImGui::GetTime(), thickness_anim_period) * (2.0f / thickness_anim_period) - 1.0f); const float thickness = animate_thickness ? thickness_wave * base_thickness : base_thickness; - ImGui::DragFloat("Thickness", &base_thickness, 0.02f, 0.0f, 30.0f, "%.02f"); - ImGui::SameLine(); - ImGui::Checkbox("Animate##thickness", &animate_thickness); - ImGui::SameLine(); + ImGui::DragFloat("Thickness", &base_thickness, 0.02f, 0.0f, 32.0f, "%.02f"); ImGui::SameLine(); + ImGui::Checkbox("Animate##thickness", &animate_thickness); ImGui::SameLine(); ImGui::Text("%.2f", thickness); ImGui::SliderInt("N-gon sides", &ngon_segments, 3, 12); - ImGui::Checkbox("##circlesegmentoverride", &circle_segments_override); + ImGui::Checkbox("##CircleSegmentOverride", &circle_segments_override); ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); - circle_segments_override |= ImGui::SliderInt("Circle segments override", &circle_segments_override_v, 3, 40); - ImGui::Checkbox("##curvessegmentoverride", &curve_segments_override); + circle_segments_override |= ImGui::SliderInt("Circle segments override", &circle_segments_override_v, 3, 30); + ImGui::Checkbox("##CurvesSegmentOverride", &curve_segments_override); ImGui::SameLine(0.0f, ImGui::GetStyle().ItemInnerSpacing.x); - curve_segments_override |= ImGui::SliderInt("Curves segments override", &curve_segments_override_v, 3, 40); + curve_segments_override |= ImGui::SliderInt("Curves segments override", &curve_segments_override_v, 3, 30); ImGui::ColorEdit4("Stroke Color", &colf_stroke.x); ImGui::ColorEdit4("Fill Color", &colf_fill.x); @@ -10467,18 +10456,18 @@ static void ShowExampleAppCustomRendering(bool* p_open) draw_list->PushDrawFlag(ImDrawFlags_AllowInPushScope_, false); // Reset all existing (very unusual: done for this demo) draw_list->PushDrawFlag(scope_flags, true); const ImDrawFlags flags = prim_stroke_flags | prim_other_flags; - const ImDrawFlags fill_flags = flags & (ImDrawFlags_AAFill | ImDrawFlags_UseTexForRoundCorners); - ImVec2 start_pos = ImGui::GetCursorScreenPos(); + const ImVec2 start_pos = ImGui::GetCursorScreenPos(); const float pi = 3.141592f; - const float spacing = 10.0f; + const float step = sz + 10.0f; const ImDrawFlags corners_tl_br = ImDrawFlags_RoundTopLeft | ImDrawFlags_RoundBottomRight; const float half_sz = sz * 0.5f; - const float half_szi = (float)(int)(sz * 0.5f); const int circle_segments = circle_segments_override ? circle_segments_override_v : 0; const int curve_segments = curve_segments_override ? curve_segments_override_v : 0; - const ImVec2 cp3[3] = { ImVec2(0.0f, sz * 0.6f), ImVec2(sz * 0.5f, -sz * 0.4f), ImVec2(sz, sz) }; // Control points for curves - const ImVec2 cp4[4] = { ImVec2(0.0f, 0.0f), ImVec2(sz * 1.3f, sz * 0.3f), ImVec2(sz - sz * 1.3f, sz - sz * 0.3f), ImVec2(sz, sz) }; + const ImVec2 cp3[3] = { { 0.0f, sz * 0.6f }, { sz * 0.5f, -sz * 0.4f }, { sz, sz } }; // Control points for curves + const ImVec2 cp4[4] = { { 0.0f, 0.0f }, { sz * 1.3f, sz * 0.3f }, { sz - sz * 1.3f, sz - sz * 0.3f }, { sz, sz } }; + const ImVec2 concave_shape[] = { { 0.0f, 0.0f }, { 0.3f, 0.0f }, { 0.3f, 0.7f }, { 0.7f, 0.7f }, { 0.7f, 0.0f }, { 1.0f, 0.0f }, { 1.0f, 1.0f }, { 0.0f, 1.0f } }; // |_| looking shape. Normalized. + const ImVec2 zigzag_shape[] = { { 0.0f, 0.0f }, { 0.9f, 0.0f }, { 1.0f, 0.1f }, { 1.0f, 0.9f }, { 0.9f, 1.0f }, { 0.3f, 1.0f }, { 0.3f, 0.4f }, { 0.9f, 0.4f } }; ImVec2 rotating_square[4]; for (int side = 0; side < 4; side++) { @@ -10497,46 +10486,57 @@ static void ShowExampleAppCustomRendering(bool* p_open) { // FILLED SHAPES const ImU32 col = ImColor(colf_fill); - draw_list->AddNgonFilled({ x + half_sz, y + half_sz }, half_sz, col, ngon_segments); x += sz + spacing; // N-gon - draw_list->AddCircleFilled({ x + half_sz, y + half_sz }, half_sz, col, circle_segments); x += sz + spacing; // Circle - draw_list->AddEllipseFilled({ x + half_sz, y + half_sz }, { sz * 0.5f, sz * 0.3f }, col, -0.3f, circle_segments); x += sz + spacing;// Ellipse - draw_list->AddRectFilled({ x, y }, { x + sz, y + sz }, col, 0.0f, fill_flags); x += sz + spacing; // Square - draw_list->AddRectFilled({ x, y }, { x + sz, y + sz }, col, rounding, fill_flags); x += sz + spacing; // Square with all rounded corners - draw_list->AddRectFilled({ x, y }, { x + sz, y + sz }, col, rounding, corners_tl_br | fill_flags); x += sz + spacing; // Square with two rounded corners - draw_list->AddTriangleFilled({ x + sz * 0.5f, y }, { x + sz, y + sz }, { x, y + sz }, col); x += sz + spacing; // Triangle - draw_list->AddTriangleFilled({ x + sz * 0.2f, y }, { x + sz * 0.4f, y + sz }, { x, y + sz }, col); x += (float)(int)(sz * 0.4f) + spacing; // Thin triangle - PathConcaveShape(draw_list, x, y, sz); draw_list->PathFillConcave(col); x += sz + spacing; // Concave shape - draw_list->AddRectFilled({ x, y }, { x + sz, y + thickness }, col); x += sz + spacing; // Horizontal line (faster than AddLine, but only handle integer thickness) - draw_list->AddRectFilled({ x, y }, { x + thickness, y + sz }, col); x += half_szi + spacing; // Vertical line (faster than AddLine, but only handle integer thickness) + draw_list->AddNgonFilled({ x + half_sz, y + half_sz }, half_sz, col, ngon_segments); x += step; // N-gon + draw_list->AddCircleFilled({ x + half_sz, y + half_sz }, half_sz, col, circle_segments); x += step; // Circle + draw_list->AddEllipseFilled({ x + half_sz, y + half_sz }, { half_sz, sz * 0.3f }, col, -0.3f, circle_segments); x += step;// Ellipse + draw_list->AddRectFilled({ x, y }, { x + sz, y + sz }, col, 0.0f, flags); x += step; // Square + draw_list->AddRectFilled({ x, y }, { x + sz, y + sz }, col, rounding, flags); x += step; // Square with all rounded corners + draw_list->AddRectFilled({ x, y }, { x + sz, y + sz }, col, rounding, flags | corners_tl_br); x += step; // Square with two rounded corners + draw_list->AddTriangleFilled({ x + sz * 0.5f, y }, { x + sz, y + sz }, { x, y + sz }, col); x += step; // Triangle + draw_list->AddTriangleFilled({ x + sz * 0.2f, y }, { x + sz * 0.4f, y + sz }, { x, y + sz }, col); x += step - (float)(int)(sz * 0.6f); // Thin triangle + + // Concave shape: U + for (const ImVec2& p : concave_shape) + draw_list->PathLineTo({ x + (int)(sz * p.x), y + (int)(sz * p.y) }); + draw_list->PathFillConcave(col); + x += step; + + // Concave shape: Zig-Zag with sharp angles (see difference with/without JoinMiter) + for (const ImVec2& p : zigzag_shape) + draw_list->PathLineTo({ x + (int)(sz * p.x), y + (int)(sz * p.y) }); + draw_list->PathFillConcave(col); + x += step; + + // Horizontal/Vertical lines + // (here for consistency with the strokes section. AddRectFilled() is roughly close to AddLineH(), AddLineV() + draw_list->AddRectFilled({ x, y }, { x + sz, y + thickness }, col); x += step; + draw_list->AddRectFilled({ x, y }, { x + thickness, y + sz }, col); x += step - (float)(int)half_sz; if (!(draw_fill && draw_strokes)) //-V560 - { - draw_list->AddRectFilled({ x + 0, y }, { x + 0 + 1, y + 1 }, col); // Pixels (faster than AddLine) - draw_list->AddRectFilled({ x + 2, y }, { x + 2 + 2, y + 2 }, col); - draw_list->AddRectFilled({ x + 5, y }, { x + 5 + 3, y + 3 }, col); - } - x += spacing + sz; + for (int block_sz = 1, off = 0; block_sz < 16 && off + block_sz <= sz; off += block_sz + 1, block_sz++) + draw_list->AddRectFilled({ x, y + off }, { x + block_sz, y + off + block_sz }, col); + x += step - (float)(int)half_sz; // Rotating square for (int n = 0; n < 4; n++) draw_list->PathLineTo({ x + half_sz + rotating_square[n].x, y + half_sz + rotating_square[n].y }); draw_list->PathFillConvex(col, flags); - x += sz + spacing; + x += step; // Path - draw_list->PathArcTo({ x + half_sz, y + half_sz }, half_sz, pi * -0.5f, pi); + draw_list->PathArcTo({ x + half_sz, y + half_sz }, half_sz, pi * -0.5f, pi * 1.1f); draw_list->PathFillConvex(col); - x += sz + spacing; + x += step; // Quadratic Bezier Curve (3 control points) draw_list->PathLineTo({ x + cp3[0].x, y + cp3[0].y }); draw_list->PathBezierQuadraticCurveTo({ x + cp3[1].x, y + cp3[1].y }, { x + cp3[2].x, y + cp3[2].y }, curve_segments); draw_list->PathFillConvex(col); - x += sz + spacing; + x += step; if (!(draw_fill && draw_strokes)) //-V560 { draw_list->AddRectFilledMultiColor({ x, y }, { x + sz, y + sz }, IM_COL32(0, 0, 0, 255), IM_COL32(255, 0, 0, 255), IM_COL32(255, 255, 0, 255), IM_COL32(0, 255, 0, 255)); - x += sz + spacing; + x += step; } } if (draw_fill && draw_strokes) @@ -10547,28 +10547,41 @@ static void ShowExampleAppCustomRendering(bool* p_open) // First row uses a thickness of 1.0f, second row uses the configurable thickness const ImU32 col = ImColor(colf_stroke); float th = (row == 0) ? 1.0f : thickness; - draw_list->AddNgon({ x + half_sz, y + half_sz }, half_sz, col, ngon_segments, th, flags); x += sz + spacing; // N-gon - draw_list->AddCircle({ x + half_sz, y + half_sz }, half_sz, col, circle_segments, th, flags); x += sz + spacing; // Circle - draw_list->AddEllipse({ x + half_sz, y + half_sz }, { sz * 0.5f, sz * 0.3f }, col, -0.3f, circle_segments, th, flags); x += sz + spacing; // Ellipse - draw_list->AddRect({ x, y }, { x + sz, y + sz }, col, 0.0f, th, flags); x += sz + spacing; // Square - draw_list->AddRect({ x, y }, { x + sz, y + sz }, col, rounding, th, flags); x += sz + spacing; // Square with all rounded corners - draw_list->AddRect({ x, y }, { x + sz, y + sz }, col, rounding, th, flags | corners_tl_br); x += sz + spacing; // Square with two rounded corners - draw_list->AddTriangle({ x + sz * 0.5f, y }, { x + sz, y + sz }, { x, y + sz }, col, th, flags); x += sz + spacing; // Triangle - draw_list->AddTriangle({ x + sz * 0.2f, y }, { x + sz * 0.4f, y + sz }, { x, y + sz }, col, th, flags); x += (float)(int)(sz * 0.4f) + spacing; // Thin triangle - PathConcaveShape(draw_list, x, y, sz); draw_list->PathStroke(col, th, flags | ImDrawFlags_Closed); x += sz + spacing; // Concave Shape - //draw_list->AddPolyline(concave_shape, IM_COUNTOF(concave_shape), col, ImDrawFlags_Closed, th); + draw_list->AddNgon({ x + half_sz, y + half_sz }, half_sz, col, ngon_segments, th, flags); x += step; // N-gon + draw_list->AddCircle({ x + half_sz, y + half_sz }, half_sz, col, circle_segments, th, flags); x += step; // Circle + draw_list->AddEllipse({ x + half_sz, y + half_sz }, { half_sz, sz * 0.3f }, col, -0.3f, circle_segments, th, flags); x += step; // Ellipse + draw_list->AddRect({ x, y }, { x + sz, y + sz }, col, 0.0f, th, flags); x += step; // Square + draw_list->AddRect({ x, y }, { x + sz, y + sz }, col, rounding, th, flags); x += step; // Square with all rounded corners + draw_list->AddRect({ x, y }, { x + sz, y + sz }, col, rounding, th, flags | corners_tl_br); x += step; // Square with two rounded corners + draw_list->AddTriangle({ x + sz * 0.5f, y }, { x + sz, y + sz }, { x, y + sz }, col, th, flags); x += step; // Triangle + draw_list->AddTriangle({ x + sz * 0.2f, y }, { x + sz * 0.4f, y + sz }, { x, y + sz }, col, th, flags); x += step - (float)(int)(sz * 0.6f); // Thin triangle - draw_list->AddLineH(x, x + sz, y, col, th, flags); // Horizontal line - draw_list->AddLine({ x, y + half_szi }, { x + sz, y + half_szi }, col, th, flags); - x += sz + spacing; + // Concave shape: U + for (const ImVec2& p : concave_shape) + draw_list->PathLineTo({ x + (int)(sz * p.x), y + (int)(sz * p.y) }); + draw_list->PathStroke(col, th, flags | ImDrawFlags_Closed); + x += step; - draw_list->AddLineV(x, y, y + sz, col, th, flags); // Vertical line (note: drawing a filled rectangle will be faster!) - draw_list->AddLine({ x + half_szi, y + sz }, { x + half_szi, y }, col, th, flags); - x += half_sz + spacing; + // Concave shape: Zig-Zag with sharp angles (see difference with/without JoinMiter) + for (const ImVec2& p : zigzag_shape) + draw_list->PathLineTo({ x + (int)(sz * p.x), y + (int)(sz * p.y) }); + draw_list->PathStroke(col, th, flags | ImDrawFlags_Closed); + x += step; + // Horizontal/Vertical/Diagonal lines + const float off = (float)(int)(sz * 0.4f); + draw_list->AddLineH(x, x + sz, y, col, th, flags); + draw_list->AddLine({ x, y + off }, { x + sz, y + off }, col, th, flags); + x += step; + draw_list->AddLineV(x, y, y + sz, col, th, flags); + draw_list->AddLine({ x + off, y + sz }, { x + off, y }, col, th, flags); + x += step - (float)(int)half_sz; if (!(draw_fill && draw_strokes)) //-V560 - draw_list->AddLine({ x, y }, { x + sz, y + sz }, col, th, flags); // Diagonal line - x += sz + spacing; + { + draw_list->AddLine({ x, y }, { x + half_sz, y + sz }, col, th, flags); + draw_list->AddLine({ x, y + sz }, { x + half_sz, y }, col, th, flags); + } + x += step - (float)(int)half_sz; // Rotating square for (int n = 0; n < 4; n++) @@ -10576,31 +10589,34 @@ static void ShowExampleAppCustomRendering(bool* p_open) draw_list->PathStroke(col, th, flags | ImDrawFlags_Closed); //for (int n = 0; n < 4; n++) // ...made of individual line // draw_list->AddLine({ x + half_sz + rotating_square[n].x, y + half_sz + rotating_square[n].y }, { x + half_sz + rotating_square[(n + 1) & 3].x, y + half_sz + rotating_square[(n + 1) & 3].y }, col, th, flags); - x += sz + spacing; + x += step; // Path - draw_list->PathArcTo({ x + half_sz, y + half_sz }, half_sz, pi * -0.5f, pi); + draw_list->PathArcTo({ x + half_sz, y + half_sz }, half_sz, pi * -0.5f, pi * 1.1f); draw_list->PathStroke(col, th, flags); - x += sz + spacing; + x += step; // Quadratic Bezier Curve (3 control points) draw_list->AddBezierQuadratic({ x + cp3[0].x, y + cp3[0].y }, { x + cp3[1].x, y + cp3[1].y }, { x + cp3[2].x, y + cp3[2].y }, col, th, curve_segments, flags); - x += sz + spacing; + x += step; // Cubic Bezier Curve (4 control points) if (!(draw_fill && draw_strokes)) //-V560 { draw_list->AddBezierCubic({ x + cp4[0].x, y + cp4[0].y }, { x + cp4[1].x, y + cp4[1].y }, { x + cp4[2].x, y + cp4[2].y }, { x + cp4[3].x, y + cp4[3].y }, col, th, curve_segments, flags); - x += sz + spacing; + x += step; } } - y += sz + spacing; + y += step; } - ImGui::Dummy(ImVec2((sz + spacing) * 15.0f, (sz + spacing) * 4.0f)); + ImGui::Dummy(ImVec2(step * 15.5f, step * 4.0f)); draw_list->PopDrawFlag(); draw_list->PopDrawFlag(); + ImGui::Text("ImDrawList Vector Rendering Reference:"); + ImGui::TextLinkOpenURL("https://github.com/ocornut/imgui/wiki/Draw-List"); + ImGui::PopItemFlag(); ImGui::PopItemWidth(); ImGui::EndTabItem(); @@ -10734,9 +10750,9 @@ static void ShowExampleAppCustomRendering(bool* p_open) ImGui::Text("Blue shape is drawn first: appears in back"); ImGui::Text("Red shape is drawn after: appears in front"); ImVec2 p0 = ImGui::GetCursorScreenPos(); - draw_list->AddRectFilled(ImVec2(p0.x, p0.y), ImVec2(p0.x + 50, p0.y + 50), IM_COL32(0, 0, 255, 255)); // Blue - draw_list->AddRectFilled(ImVec2(p0.x + 25, p0.y + 25), ImVec2(p0.x + 75, p0.y + 75), IM_COL32(255, 0, 0, 255)); // Red - ImGui::Dummy(ImVec2(75, 75)); + draw_list->AddRectFilled({ p0.x, p0.y }, { p0.x + 50, p0.y + 50 }, IM_COL32(0, 0, 255, 255)); // Blue + draw_list->AddRectFilled({ p0.x + 25, p0.y + 25 }, { p0.x + 75, p0.y + 75 }, IM_COL32(255, 0, 0, 255)); // Red + ImGui::Dummy({ 75, 75 }); } ImGui::Separator(); { @@ -10748,14 +10764,14 @@ static void ShowExampleAppCustomRendering(bool* p_open) // You can create any number of channels. Tables API use 1 channel per column in order to better batch draw calls. draw_list->ChannelsSplit(2); draw_list->ChannelsSetCurrent(1); - draw_list->AddRectFilled(ImVec2(p1.x, p1.y), ImVec2(p1.x + 50, p1.y + 50), IM_COL32(0, 0, 255, 255)); // Blue + draw_list->AddRectFilled({ p1.x, p1.y }, { p1.x + 50, p1.y + 50 }, IM_COL32(0, 0, 255, 255)); // Blue draw_list->ChannelsSetCurrent(0); - draw_list->AddRectFilled(ImVec2(p1.x + 25, p1.y + 25), ImVec2(p1.x + 75, p1.y + 75), IM_COL32(255, 0, 0, 255)); // Red + draw_list->AddRectFilled({ p1.x + 25, p1.y + 25 }, { p1.x + 75, p1.y + 75 }, IM_COL32(255, 0, 0, 255)); // Red // Flatten/reorder channels. Red shape is in channel 0 and it appears below the Blue shape in channel 1. // This works by copying draw indices only (vertices are not copied). draw_list->ChannelsMerge(); - ImGui::Dummy(ImVec2(75, 75)); + ImGui::Dummy({ 75, 75 }); ImGui::Text("After reordering, contents of channel 0 appears below channel 1."); } ImGui::EndTabItem(); From 9e5e0bf9b258d40669617028107d5eaac5ca4306 Mon Sep 17 00:00:00 2001 From: Mikko Mononen Date: Thu, 6 Aug 2026 19:52:54 +0300 Subject: [PATCH 117/120] DrawList: added explanation for IM_POLYLINE_CONVEX_POLY_MAX_BEVELS. --- imgui_draw.cpp | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 0562635d7..db3d003d9 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -880,9 +880,20 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE #define IM_FIXNORMAL2F_MAX_INVLEN2 100.0f // 500.0f (see #4053, #3366) #define IM_FIXNORMAL2F(VX,VY) { float d2 = VX*VX + VY*VY; if (d2 > 0.000001f) { float inv_len2 = 1.0f / d2; if (inv_len2 > IM_FIXNORMAL2F_MAX_INVLEN2) inv_len2 = IM_FIXNORMAL2F_MAX_INVLEN2; VX *= inv_len2; VY *= inv_len2; } } (void)0 +// - The miter limit describes how much the miter cross section can be scaled before we turn it into a bevel instead. +// The minimum value of miter limit is 1.0, which translates to 180 (all corners are bevels), the higher the number, +// the closer to zero the angle is (never bevel a corner). The miter limit angle can be calculated as: +// miter_limit_angle = 2 * acos(1 / miter_limit) +// - We can use the miter limit angle to figure maximum number of beveled corners in a convex polygon, +// to prevent having allocate much extra memory (for a reasonable miter limit). +// (n - 2) * 360 > k * miter_limit_angle + (n - k) * 360 +// Where, n = number of corners in a polygon, k = beveled corners. Solved for k, simplifies to: +// k < 360 / (180 - miter_limit_angle) +// To find value strictly less than: +// max_bevels = k = floor(360/(180-miter_limit_angle)) #define IM_POLYLINE_MITER_ANGLE_LIMIT (-0.9999619f) // Safeguard for miter corner calculation to avoid div by zero. cos(179.5) -#define IM_POLYLINE_MITER_LIMIT (4.0f) // How much the miter can be scaled before we turn it into a bevel instead. (equals ~29 deg corner) -#define IM_POLYLINE_CONVEX_POLY_MAX_BEVELS (2) // How many bevels there can be in a convex polygon. (int)(360/floor(180-29)) +#define IM_POLYLINE_MITER_LIMIT (4.0f) // (~29 deg corner) +#define IM_POLYLINE_CONVEX_POLY_MAX_BEVELS (2) // For miter value 4.0 // In debug builds the functions are likely not inlined, so inlined check is cheaper. #if defined(DEBUG) || defined(_DEBUG) From cbf4f054197ed439c7010bec62dedeff3c96cc87 Mon Sep 17 00:00:00 2001 From: ocornut Date: Fri, 2 Oct 2026 13:04:33 +0200 Subject: [PATCH 118/120] DrawList: added io.ConfigDebugDrawListDefaultsToStrokeLegacy. (#9504) --- docs/CHANGELOG.txt | 12 ++++++++++++ imgui.cpp | 8 +++++++- imgui.h | 5 +++++ imgui_demo.cpp | 4 ++++ 4 files changed, 28 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 8b7f2d6a7..63c7ed07b 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -67,6 +67,12 @@ While the current version doesn't make lots of visible use of the new features a 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. +Use `ImDrawFlags_StrokeLegacy` mode to emulate old rendering: +- Per-primitive: draw_list->AddLine(..., ..., ImDrawFlags_StrokeLegacy); +- For a given scope: draw_list->PushDrawFlag(ImDrawFlags_StrokeLegacy, true); draw_list->AddLine(...); draw_list->PopDrawFlag(); +- Globally: ImGui::Checkbox("DefaultsToStrokeLegacy", &io.ConfigDebugDrawListDefaultsToStrokeLegacy); // Map to a checkbox +- Globally: io.ConfigDebugDrawListDefaultsToStrokeLegacy = io.KeyShift; // Map on Shift + Main work courtesy of @memononen. Additional work/reviewing/guidance by @ocornut. With legacy bits from @thedmd, @potocpav. @@ -224,6 +230,12 @@ Other Changes: - 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] + - Debug: added `io.ConfigDebugDrawListDefaultsToStrokeLegacy` to facilitate comparing old/new + behaviors. This is equivalent to calling `PushDrawFlag(ImDrawFlags_StrokeLegacy, true)` + on every drawlist. + Suggested uses, e.g. + - `io.ConfigDebugDrawListDefaultsToStrokeLegacy = io.KeyShift; // Map on holding Shift` + - `ImGui::Checkbox("DefaultsToStrokeLegacy", &io.ConfigDebugDrawListDefaultsToStrokeLegacy);` - 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 diff --git a/imgui.cpp b/imgui.cpp index ddef7ec4d..5b7d0c068 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -401,7 +401,11 @@ IMPLEMENTING SUPPORT for ImGuiBackendFlags_RendererHasTextures: - ImDrawList 1.92.9 <> 1.93 Interactive Testbed: https://www.dearimgui.com/docs/drawlist_v193 (<-- you can download this locally) - ImDrawList Wiki Reference: https://github.com/ocornut/imgui/wiki/Draw-List - Discussion thread: https://github.com/ocornut/imgui/issues/9504 - - Use ImDrawFlags_StrokeLegacy mode to emulate old rendering per-primitive or per-scope. + - use ImDrawFlags_StrokeLegacy mode to emulate old rendering: + - Per-primitive: draw_list->AddLine(..., ..., ImDrawFlags_StrokeLegacy); + - For a given scope: draw_list->PushDrawFlag(ImDrawFlags_StrokeLegacy, true); draw_list->AddLine(...); draw_list->PopDrawFlag(); + - Globally: ImGui::Checkbox("DefaultsToStrokeLegacy", &io.ConfigDebugDrawListDefaultsToStrokeLegacy); // Map to a checkbox + - Globally: io.ConfigDebugDrawListDefaultsToStrokeLegacy = io.KeyShift; // Map on Shift - AddLine: removed the (+0.5f,+0.5f) offset that was sneakily added to input coordinates by this function, and led to lots of inconsistencies. - 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. @@ -5663,6 +5667,8 @@ static void SetupDrawListSharedData() g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_AALines; if (g.Style.AntiAliasedLineEnds) g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_AALineEnds; + if (g.IO.ConfigDebugDrawListDefaultsToStrokeLegacy) + g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_StrokeLegacy; if (!(g.IO.Fonts->Flags & ImFontAtlasFlags_NoBakedRoundCorners)) g.DrawListSharedData.InitialDrawFlags |= ImDrawFlags_UseTexForRoundCorners | ImDrawFlags_AllowTexForRoundCorners_; if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) diff --git a/imgui.h b/imgui.h index 5c47d94e1..acce04d3f 100644 --- a/imgui.h +++ b/imgui.h @@ -2559,6 +2559,11 @@ struct ImGuiIO // - Backends may have other side-effects on focus loss, so this will reduce side-effects but not necessary remove all of them. bool ConfigDebugIgnoreFocusLoss; // = false // Ignore io.AddFocusEvent(false), consequently not calling io.ClearInputKeys()/io.ClearInputMouse() in input processing. + // Option to compare < 1.93.0 rendering details vs 1.93.0 rendering details. (#9504) + // - Equivalent to calling PushDrawFlag(ImDrawList_StrokeLegacy,true) on every draw list instances. + // - Suggested use, e.g. io.ConfigDebugDrawListDefaultsToStrokeLegacy = io.KeyShift; // Map to SHIFT modifier + bool ConfigDebugDrawListDefaultsToStrokeLegacy; // false // Default all ImDrawList to ImDrawList_StrokeLegacy mode, mimicking pre-1.93.0 rendering. + //------------------------------------------------------------------ // Platform Identifiers // (the imgui_impl_xxxx backend files are setting those up for you) diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 5078c11d7..97038f1e0 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -576,6 +576,8 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::SameLine(); HelpMarker("Some calls to Begin()/BeginChild() will return false.\n\nWill cycle through window depths then repeat. Windows should be flickering while running."); ImGui::Checkbox("io.ConfigDebugIgnoreFocusLoss", &io.ConfigDebugIgnoreFocusLoss); ImGui::SameLine(); HelpMarker("Option to deactivate io.AddFocusEvent(false) handling. May facilitate interactions with a debugger when focus loss leads to clearing inputs data."); + ImGui::Checkbox("io.ConfigDebugDrawListDefaultsToStrokeLegacy", &io.ConfigDebugDrawListDefaultsToStrokeLegacy); + ImGui::SameLine(); HelpMarker("Option to default all ImDrawList to ImDrawFlags_StrokeLegacy mode, mimicking pre-1.93.0 rendering."); ImGui::Checkbox("io.ConfigDebugIniSettings", &io.ConfigDebugIniSettings); ImGui::SameLine(); HelpMarker("Option to save .ini data with extra comments (particularly helpful for Docking, but makes saving slower)."); @@ -8924,6 +8926,8 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) #endif Checkbox("Anti-aliased fill", &style.AntiAliasedFill); + Checkbox("Debug: DrawList Defaults to _StrokeLegacy", &ImGui::GetIO().ConfigDebugDrawListDefaultsToStrokeLegacy); + PushItemWidth(GetFontSize() * 8); DragFloat("Curve Tessellation Max Error", &style.CurveTessellationMaxError, 0.02f, 0.10f, 10.0f, "%.2f"); if (style.CurveTessellationMaxError < 0.10f) From 75ac1ab2c36c988f86067434fd3f178fe0fb4097 Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 6 Oct 2026 20:36:23 +0200 Subject: [PATCH 119/120] DrawList: AddCircle: fixed issue not clearing path in unlikely case where Path.Size<3. --- imgui_draw.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index db3d003d9..4cf094c57 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2735,14 +2735,14 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu PathArcTo(center, radius, 0.0f, a_max, num_segments - 1); } - if (_Path.Size < 3) - return; - - // Calculate distance to the edge of the polygon describing the circle. - // This will be used by _AddPolyline() to clamp the inner stroke expansion, to avoid creating rendering artifacts. - const float unit_apothem = ApproxApothem(_Path.Size); - const float edge_dist = radius * unit_apothem; - _AddPolyline(_Path.Data, _Path.Size, col, thickness, ImDrawFlags_Closed | ImDrawFlags_JoinMiter | stroke_pos, edge_dist); + if (_Path.Size >= 3) + { + // Calculate distance to the edge of the polygon describing the circle. + // This will be used by _AddPolyline() to clamp the inner stroke expansion, to avoid creating rendering artifacts. + const float unit_apothem = ApproxApothem(_Path.Size); + const float edge_dist = radius * unit_apothem; + _AddPolyline(_Path.Data, _Path.Size, col, thickness, ImDrawFlags_Closed | ImDrawFlags_JoinMiter | stroke_pos, edge_dist); + } _Path.Size = 0; } From b2ade5f3ada2091a7b01d93e3ddaf238a5d9247e Mon Sep 17 00:00:00 2001 From: ocornut Date: Tue, 6 Oct 2026 18:02:37 +0200 Subject: [PATCH 120/120] DrawList: AddRectFilled: add support for ImDrawFlags_StrokeLegacy to handle inverted coordinates + non-AA path. Fix cases 207 and 251 in testbed. + add braces near an IM_APPEND_TRI() to silence PVS-Studio reasonable warning "V640 The code's operational logic does not correspond with its formatting" --- docs/CHANGELOG.txt | 1 + imgui_draw.cpp | 10 ++++++++++ 2 files changed, 11 insertions(+) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index 63c7ed07b..f93dfe8d6 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -146,6 +146,7 @@ Breaking Changes: - Legacy `AddRect()` rounding on -> visible but very glitchy. - Legacy `AddRectFilled()` rounding off -> visible and correct. - Legacy `AddRectFilled()` rounding on -> visible but very glitchy. + - Use `ImDrawFlags_StrokeLegacy` to use old rendering for cases without rounding, or fix coordinates. - 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` diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 4cf094c57..83ceaddc0 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -871,6 +871,7 @@ float ImDrawList::_CalculateCenterBiasedOffset(float thickness) IM_MSVC_RUNTIME_CHECKS_RESTORE // Check if R is considered to be worthy rounding. Can be only used within ImDrawList due to use of _FringeScale. +// FIXME-OPT: Given unused padding space in ImDrawList would it make sense to store 0.25f * _FringeScale? #define IM_HAS_ROUNDING(RAD) ((RAD) >= (0.25f * _FringeScale)) // On AddPolyline() and AddConvexPolyFilled() we intentionally avoid using ImVec2 and superfluous function calls to optimize debug/non-inlined builds. @@ -2306,7 +2307,9 @@ void ImDrawList::_AddRectTinyRounding(const ImVec2& p_min, const ImVec2& p_max, // Arc if (is_rounded) for (int i = 0; i < arc_point_count - 1; i++) + { IM_APPEND_TRI(stem_idx, arc_idx + i, arc_idx + i + 1); + } // Connect with previous IM_APPEND_TRI(base_idx + 0, arc_idx, stem_idx); @@ -2539,6 +2542,13 @@ void ImDrawList::AddRectFilled(const ImVec2& p_min, const ImVec2& p_max, ImU32 c const float min_dim = ImMin(width, height); flags |= Flags; + if ((flags & ImDrawFlags_StrokeMask_) == ImDrawFlags_StrokeLegacy && rounding < 0.5f) IM_UNLIKELY + { + PrimReserve(6, 4); + PrimRect(p_min, p_max, col); + return; + } + // The rect is smaller than pixel in one dimension. if (min_dim < _FringeScale) IM_UNLIKELY {