DrawList: use dedicated larger texture for fractional thickness lines.

This commit is contained in:
Mikko Mononen
2026-04-27 15:00:04 +03:00
committed by ocornut
parent 08e5215737
commit 4c017c67e9
3 changed files with 63 additions and 16 deletions

View File

@@ -3321,6 +3321,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
@@ -3650,8 +3651,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);
};
@@ -4015,6 +4016,7 @@ struct ImFontAtlas
ImVector<ImFont*> 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<ImFontConfig> 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

View File

@@ -877,7 +877,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;
@@ -1190,7 +1190,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;
@@ -1199,7 +1199,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.
@@ -1235,7 +1235,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);
@@ -1532,22 +1532,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);
}
}
@@ -1848,7 +1848,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;
@@ -1879,7 +1879,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;
@@ -2612,6 +2612,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.
@@ -2639,7 +2643,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);
@@ -5476,6 +5480,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);
}
//-----------------------------------------------------------------------------------------------------------------------------
@@ -5849,6 +5891,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;
}

View File

@@ -919,6 +919,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
@@ -4327,9 +4328,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);