Texture-based round corners: Refactoring and upgrades

Changed texture-based rounded rectangle edges to draw using thin polygons
Rearranged data for texture-based rounded rectangles to reduce allocations and pre-calculate parametric coordinate
Made max texture-based corner size a constant
Added API for drawing n-gons
Added support in circle API to auto-calculate suitable segment count
This commit is contained in:
Ben Carter 2019-12-02 20:33:02 +09:00 committed by ocornut
parent a0ef8c9187
commit 8e97a4a724
5 changed files with 176 additions and 59 deletions

View File

@ -1070,7 +1070,7 @@ ImGuiStyle::ImGuiStyle()
AntiAliasedLines = true; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. 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. AntiAliasedLinesUseTex = true; // Enable anti-aliased lines/borders using textures where possible. Require backend to render with bilinear filtering.
AntiAliasedFill = true; // Enable anti-aliased filled shapes (rounded rectangles, circles, etc.). AntiAliasedFill = true; // Enable anti-aliased filled shapes (rounded rectangles, circles, etc.).
TexturedRoundCorners = true; // Enable using textures instead of strokes to draw rounded corners/circles where possible. TexturedRoundCorners = true; // Enable using textures instead of strokes to draw rounded corners/circles where possible.
CurveTessellationTol = 1.25f; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. CurveTessellationTol = 1.25f; // Tessellation tolerance 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. 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.
@ -5566,11 +5566,11 @@ static bool AddResizeGrip(ImDrawList* dl, const ImVec2& corner, unsigned int rad
if (dl->_Data->Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners) // No data in font if (dl->_Data->Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners) // No data in font
return false; return false;
if (rad < 1 || rad > (unsigned int)dl->_Data->Font->ContainerAtlas->RoundCornersMaxSize) // Radius 0 will cause issues with the UV lookup below if (rad < 1 || rad > ImFontAtlasRoundCornersMaxSize) // Radius 0 will cause issues with the UV lookup below
return false; return false;
// Calculate UVs for the three points we are interested in from the texture // Calculate UVs for the three points we are interested in from the texture
const ImVec4& uvs = (*dl->_Data->TexUvRoundCornerFilled)[rad - 1]; const ImVec4& uvs = (*dl->_Data->TexRoundCornerData)[rad - 1].TexUvFilled;
// uv[0] is the mid-point from the corner towards the centre of the circle (solid) // uv[0] is the mid-point from the corner towards the centre of the circle (solid)
// uv[1] is a solid point on the edge of the circle // uv[1] is a solid point on the edge of the circle
// uv[2] is the outer edge (blank, outside the circle) // uv[2] is the outer edge (blank, outside the circle)
@ -6947,8 +6947,7 @@ void ImGui::SetCurrentFont(ImFont* font)
ImFontAtlas* atlas = g.Font->ContainerAtlas; ImFontAtlas* atlas = g.Font->ContainerAtlas;
g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel; g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel;
g.DrawListSharedData.TexUvLines = atlas->TexUvLines; g.DrawListSharedData.TexUvLines = atlas->TexUvLines;
g.DrawListSharedData.TexUvRoundCornerFilled = &atlas->TexUvRoundCornerFilled; g.DrawListSharedData.TexRoundCornerData = &atlas->TexRoundCornerData;
g.DrawListSharedData.TexUvRoundCornerStroked = &atlas->TexUvRoundCornerStroked;
g.DrawListSharedData.Font = g.Font; g.DrawListSharedData.Font = g.Font;
g.DrawListSharedData.FontSize = g.FontSize; g.DrawListSharedData.FontSize = g.FontSize;
} }

16
imgui.h
View File

@ -2701,6 +2701,15 @@ struct ImFontGlyphRangesBuilder
IMGUI_API void BuildRanges(ImVector<ImWchar>* out_ranges); // Output new ranges IMGUI_API void BuildRanges(ImVector<ImWchar>* out_ranges); // Output new ranges
}; };
// Data for texture-based rounded corners for a given radius
struct ImFontRoundedCornerData
{
ImVec4 TexUvFilled; // UV of filled round corner quad in the atlas
ImVec4 TexUvStroked; // UV of stroked round corner quad in the atlas
float ParametricStrokeWidth; // Pre-calculated value for stroke width divided by the radius
int RectId; // Rect ID in the atlas
};
// See ImFontAtlas::AddCustomRectXXX functions. // See ImFontAtlas::AddCustomRectXXX functions.
struct ImFontAtlasCustomRect struct ImFontAtlasCustomRect
{ {
@ -2835,12 +2844,7 @@ struct ImFontAtlas
int PackIdMouseCursors; // Custom texture rectangle ID for white pixel and mouse cursors int PackIdMouseCursors; // Custom texture rectangle ID for white pixel and mouse cursors
int PackIdLines; // Custom texture rectangle ID for baked anti-aliased lines int PackIdLines; // Custom texture rectangle ID for baked anti-aliased lines
// FIXME-ROUNDCORNERS: WIP ImVector<ImFontRoundedCornerData> TexRoundCornerData; // Data for texture-based round corners indexed by size [0] is 1px, [n] is (n+1)px (index up to ImFontAtlasRoundCornersMaxSize - 1).
// FIXME: avoid so many allocations, statically sized buffer removing an indirection may be beneficial here?
int RoundCornersMaxSize; // Max pixel size of round corner textures to generate
ImVector<int> RoundCornersRectIds; // Ids of custom rects for round corners indexed by size [0] is 1px, [n] is (n+1)px (index up to RoundCornersMaxSize - 1).
ImVector<ImVec4> TexUvRoundCornerFilled; // Texture coordinates to filled round corner quads
ImVector<ImVec4> TexUvRoundCornerStroked;// Texture coordinates to stroked round corner quads
// [Obsolete] // [Obsolete]
//typedef ImFontAtlasCustomRect CustomRect; // OBSOLETED in 1.72+ //typedef ImFontAtlasCustomRect CustomRect; // OBSOLETED in 1.72+

View File

@ -258,12 +258,7 @@ static void GetVtxIdxDelta(ImDrawList* dl, int* vtx, int *idx)
} }
// https://github.com/ocornut/imgui/issues/1962 // https://github.com/ocornut/imgui/issues/1962
// FIXME-ROUNDCORNERS: Sizes aren't matching.
// FIXME-ROUNDCORNERS: Lift the RoundCornersMaxSize limitation, fallback on existing renderer.
// FIXME-ROUNDCORNERS: Work on reducing filtrate for stroked shapes (may need to trade some cpu/vtx to reduce fill-rate for e.g. simple stroked circle) > https://github.com/ocornut/imgui/issues/1962#issuecomment-411507917
// FIXME-ROUNDCORNERS: Figure out how to support multiple thickness, might hard-code common steps (1.0, 1.5, 2.0, 3.0), not super satisfactory but may be best // FIXME-ROUNDCORNERS: Figure out how to support multiple thickness, might hard-code common steps (1.0, 1.5, 2.0, 3.0), not super satisfactory but may be best
// FIXME-ROUNDCORNERS: AddCircle* API relying on num_segments may need rework, might obsolete this parameter, or make it 0 default and rely on automatic subdivision similar to style.CurveTessellationTol for Bezier
// FIXME-ROUNDCORNERS: Intentional "low segment count" shapes (e.g. hexagon) currently achieved with AddCircle may need a new API (AddNgon?)
static void TestTextureBasedRender() static void TestTextureBasedRender()
{ {
ImGuiIO& io = ImGui::GetIO(); ImGuiIO& io = ImGui::GetIO();
@ -273,10 +268,11 @@ static void TestTextureBasedRender()
ImGui::Text("Hold SHIFT to toggle (%s)", io.KeyShift ? "SHIFT ON -- Using textures." : "SHIFT OFF -- Old method."); ImGui::Text("Hold SHIFT to toggle (%s)", io.KeyShift ? "SHIFT ON -- Using textures." : "SHIFT OFF -- Old method.");
static float radius = io.Fonts->RoundCornersMaxSize * 0.5f; static float radius = 16.0f; // ImFontAtlasRoundCornersMaxSize * 0.5f;
static int segments = 20; static int segments = 20;
static int ngon_segments = 6;
ImGui::SliderFloat("radius", &radius, 0.0f, (float)io.Fonts->RoundCornersMaxSize, "%.0f"); ImGui::SliderFloat("radius", &radius, 0.0f, (float)64 /*ImFontAtlasRoundCornersMaxSize*/, "%.0f");
int vtx_n = 0; int vtx_n = 0;
int idx_n = 0; int idx_n = 0;
@ -286,7 +282,7 @@ static void TestTextureBasedRender()
ImGui::BeginGroup(); ImGui::BeginGroup();
ImGui::PushItemWidth(120); ImGui::PushItemWidth(120);
ImGui::SliderInt("segments", &segments, 3, 100); ImGui::SliderInt("segments", &segments, 0, 100);
ImGui::PopItemWidth(); ImGui::PopItemWidth();
{ {
@ -347,6 +343,37 @@ static void TestTextureBasedRender()
ImGui::EndGroup(); ImGui::EndGroup();
} }
ImGui::SameLine();
{
ImGui::BeginGroup();
ImGui::PushItemWidth(120);
ImGui::SliderInt("ngon_segments", &ngon_segments, 3, 16);
ImGui::PopItemWidth();
{
ImGui::Button("##3", ImVec2(200, 200));
GetVtxIdxDelta(draw_list, &vtx_n, &idx_n);
ImVec2 min = ImGui::GetItemRectMin();
ImVec2 size = ImGui::GetItemRectSize();
draw_list->AddNgonFilled(ImVec2(min.x + size.x * 0.5f, min.y + size.y * 0.5f), radius, IM_COL32(255, 0, 255, 255), ngon_segments);
GetVtxIdxDelta(draw_list, &vtx_n, &idx_n);
ImGui::Text("AddNgonFilled\n %d vtx, %d idx", vtx_n, idx_n);
}
{
ImGui::Button("##4", ImVec2(200, 200));
GetVtxIdxDelta(draw_list, &vtx_n, &idx_n);
ImVec2 min = ImGui::GetItemRectMin();
ImVec2 size = ImGui::GetItemRectSize();
draw_list->AddNgon(ImVec2(min.x + size.x * 0.5f, min.y + size.y * 0.5f), radius, IM_COL32(255, 0, 255, 255), ngon_segments);
GetVtxIdxDelta(draw_list, &vtx_n, &idx_n);
ImGui::Text("AddNgon\n %d vtx, %d idx", vtx_n, idx_n);
}
ImGui::EndGroup();
}
ImGui::Separator(); ImGui::Separator();
ImGui::Text("Style"); ImGui::Text("Style");

View File

@ -1412,7 +1412,7 @@ inline bool AddRoundCornerRect(ImDrawList* draw_list, const ImVec2& a, const ImV
return false; return false;
if ((rad <= 0) || // Zero radius causes issues with the [rad - 1] UV lookup below if ((rad <= 0) || // Zero radius causes issues with the [rad - 1] UV lookup below
(rad > data->Font->ContainerAtlas->RoundCornersMaxSize)) (rad > ImFontAtlasRoundCornersMaxSize))
{ {
// We can't handle this // We can't handle this
return false; return false;
@ -1425,6 +1425,13 @@ inline bool AddRoundCornerRect(ImDrawList* draw_list, const ImVec2& a, const ImV
ImTextureID tex_id = data->Font->ContainerAtlas->TexID; ImTextureID tex_id = data->Font->ContainerAtlas->TexID;
IM_ASSERT(tex_id == draw_list->_TextureIdStack.back()); // Use high-level ImGui::PushFont() or low-level ImDrawList::PushTextureId() to change font. IM_ASSERT(tex_id == draw_list->_TextureIdStack.back()); // Use high-level ImGui::PushFont() or low-level ImDrawList::PushTextureId() to change font.
// The width of our stroke for unfilled mode
// Something of a placeholder at the moment - used for calculations but without appropriately-generated
// textures won't actually achieve anything
const float stroke_width = 1.0f;
ImFontRoundedCornerData& round_corner_data = (*data->TexRoundCornerData)[rad - 1];
// Calculate UVs for the three points we are interested in from the texture // Calculate UVs for the three points we are interested in from the texture
// corner_uv[0] is the innermost point of the circle (solid for filled circles) // corner_uv[0] is the innermost point of the circle (solid for filled circles)
// corner_uv[1] is either straight down or across from it (depending on if we are using the filled or stroked version) // corner_uv[1] is either straight down or across from it (depending on if we are using the filled or stroked version)
@ -1432,7 +1439,7 @@ inline bool AddRoundCornerRect(ImDrawList* draw_list, const ImVec2& a, const ImV
// corner_uv[1] is always solid (either inside the circle or on the line), whilst corner_uv[2] is always blank // corner_uv[1] is always solid (either inside the circle or on the line), whilst corner_uv[2] is always blank
// This represents a 45 degree "wedge" of circle, which then gets mirrored here to produce a 90 degree curve // This represents a 45 degree "wedge" of circle, which then gets mirrored here to produce a 90 degree curve
// See ImFontAtlasBuildRenderRoundCornersTexData() for more details of the texture contents // See ImFontAtlasBuildRenderRoundCornersTexData() for more details of the texture contents
const ImVec4& uvs = (*(fill ? data->TexUvRoundCornerFilled : data->TexUvRoundCornerStroked))[rad - 1]; const ImVec4& uvs = fill ? round_corner_data.TexUvFilled : round_corner_data.TexUvStroked;
const ImVec2 corner_uv[3] = const ImVec2 corner_uv[3] =
{ {
ImVec2(uvs.x, uvs.y), ImVec2(uvs.x, uvs.y),
@ -1465,19 +1472,26 @@ inline bool AddRoundCornerRect(ImDrawList* draw_list, const ImVec2& a, const ImV
// MDX ID--------IC MCX // MDX ID--------IC MCX
// | | // | |
// CD--MDY--------MCY--CC // CD--MDY--------MCY--CC
//
// MAX2/MAY2/etc are those vertices offset inwards by the line width
// (only used for unfilled rectangles)
const ImVec2 ca(a.x, a.y), cb(b.x, a.y); const ImVec2 ca(a.x, a.y), cb(b.x, a.y);
const ImVec2 may(ca.x + rad, ca.y), mby(cb.x - rad, cb.y); const ImVec2 may(ca.x + rad, ca.y), mby(cb.x - rad, cb.y);
const ImVec2 may2(may.x, may.y + stroke_width), mby2(mby.x, mby.y + stroke_width);
const ImVec2 max(ca.x, ca.y + rad), mbx(cb.x, cb.y + rad); const ImVec2 max(ca.x, ca.y + rad), mbx(cb.x, cb.y + rad);
const ImVec2 max2(max.x + stroke_width, max.y), mbx2(mbx.x - stroke_width, mbx.y);
const ImVec2 ia(ca.x + rad, ca.y + rad), ib(cb.x - rad, cb.y + rad); const ImVec2 ia(ca.x + rad, ca.y + rad), ib(cb.x - rad, cb.y + rad);
const ImVec2 cc(b.x, b.y), cd(a.x, b.y); const ImVec2 cc(b.x, b.y), cd(a.x, b.y);
const ImVec2 mdx(cd.x, cd.y - rad), mcx(cc.x, cc.y - rad); const ImVec2 mdx(cd.x, cd.y - rad), mcx(cc.x, cc.y - rad);
const ImVec2 mdx2(mdx.x + stroke_width, mdx.y), mcx2(mcx.x - stroke_width, mcx.y);
const ImVec2 mdy(cd.x + rad, cd.y), mcy(cc.x - rad, cc.y); const ImVec2 mdy(cd.x + rad, cd.y), mcy(cc.x - rad, cc.y);
const ImVec2 mdy2(mdy.x, mdy.y - stroke_width), mcy2(mcy.x, mcy.y - stroke_width);
const ImVec2 id(cd.x + rad, cd.y - rad), ic(cc.x - rad, cc.y - rad); const ImVec2 id(cd.x + rad, cd.y - rad), ic(cc.x - rad, cc.y - rad);
// Reserve enough space for the vertices/indices // Reserve enough space for the vertices/indices
const int vtcs = 16; const int vtcs = fill ? 16 : 24;
const int idcs = fill ? (18 * 3) : (16 * 3); const int idcs = fill ? (18 * 3) : (16 * 3);
draw_list->PrimReserve(idcs, vtcs); draw_list->PrimReserve(idcs, vtcs);
@ -1490,6 +1504,35 @@ inline bool AddRoundCornerRect(ImDrawList* draw_list, const ImVec2& a, const ImV
draw_list->_VtxWritePtr[d].uv = corner_uv[(i)]; \ draw_list->_VtxWritePtr[d].uv = corner_uv[(i)]; \
draw_list->_VtxWritePtr[d].col = col draw_list->_VtxWritePtr[d].col = col
// Write a vertex using an interpolated position and UVs, where
// px and py are the parametric position within the corner
// (0,0 at the inside, 1,1 at the outside).
// "inside" here corresponds to ia/ib/ic/id, whilst "outside" is ca/cb/cc/cd
// Corner gives the corner (a/b/c/d) to use
// d is the vertex index to write to
// The px<py check is necessary because we need to mirror the texture across
// the diagonal (as we only have 45 degrees' worth of actual valid pixel data)
// This needs to be done the opposite way around for filled vs unfilled as they
// each occupy one side of the texture
#define VTX_WRITE_LERPED(d, corner, px, py) \
draw_list->_VtxWritePtr[d].pos = ImVec2(ImLerp(i##corner.x, c##corner.x, px), ImLerp(i##corner.y, c##corner.y, py)); \
draw_list->_VtxWritePtr[d].uv = ((px < py) ^ fill) ? \
ImVec2(ImLerp(corner_uv[0].x, corner_uv[b##corner ? 2 : 1].x, py), ImLerp(corner_uv[0].y, corner_uv[b##corner ? 2 : 1].y, px)) : \
ImVec2(ImLerp(corner_uv[0].x, corner_uv[b##corner ? 2 : 1].x, px), ImLerp(corner_uv[0].y, corner_uv[b##corner ? 2 : 1].y, py)); \
draw_list->_VtxWritePtr[d].col = col
// Optimised versions of the above, for the cases where either px or py is always zero
#define VTX_WRITE_LERPED_X(d, corner, px) \
draw_list->_VtxWritePtr[d].pos = ImVec2(ImLerp(i##corner.x, c##corner.x, px), i##corner.y); \
draw_list->_VtxWritePtr[d].uv = ImVec2(ImLerp(corner_uv[0].x, corner_uv[b##corner ? 2 : 1].x, px), corner_uv[0].y); \
draw_list->_VtxWritePtr[d].col = col
#define VTX_WRITE_LERPED_Y(d, corner, py) \
draw_list->_VtxWritePtr[d].pos = ImVec2(i##corner.x, ImLerp(i##corner.y, c##corner.y, py)); \
draw_list->_VtxWritePtr[d].uv = ImVec2(ImLerp(corner_uv[0].x, corner_uv[b##corner ? 2 : 1].x, py), corner_uv[0].y); \
draw_list->_VtxWritePtr[d].col = col
// Set up the outer corners (vca-vcd being the four outermost corners) // Set up the outer corners (vca-vcd being the four outermost corners)
// If the corner is rounded we use the "empty" corner UV, if not we use the "filled" one. // If the corner is rounded we use the "empty" corner UV, if not we use the "filled" one.
const int vca = 0, vcb = 1, vcc = 2, vcd = 3; const int vca = 0, vcb = 1, vcc = 2, vcd = 3;
@ -1547,6 +1590,39 @@ inline bool AddRoundCornerRect(ImDrawList* draw_list, const ImVec2& a, const ImV
VTX_WRITE(vid, id, 0); VTX_WRITE(vid, id, 0);
dv += 12; dv += 12;
// The unfilled version needs these vertices for the edges
int vya2 = vca, vxa2 = vca;
int vyb2 = vcb, vxb2 = vcb;
int vyc2 = vcc, vxc2 = vcc;
int vyd2 = vcd, vxd2 = vcd;
if (!fill)
{
vya2 = dv;
vxa2 = dv + 1;
vyb2 = dv + 2;
vxb2 = dv + 3;
vyc2 = dv + 4;
vxc2 = dv + 5;
vyd2 = dv + 6;
vxd2 = dv + 7;
const float width_offset_parametric = round_corner_data.ParametricStrokeWidth; // Edge width in our parametric coordinate space
const float parametric_offset = 1.0f - width_offset_parametric; // Offset from the centre-most edge
VTX_WRITE_LERPED_X(vxa2, a, parametric_offset);
VTX_WRITE_LERPED_Y(vya2, a, parametric_offset);
VTX_WRITE_LERPED_X(vxb2, b, parametric_offset);
VTX_WRITE_LERPED_Y(vyb2, b, parametric_offset);
VTX_WRITE_LERPED_X(vxc2, c, parametric_offset);
VTX_WRITE_LERPED_Y(vyc2, c, parametric_offset);
VTX_WRITE_LERPED_X(vxd2, d, parametric_offset);
VTX_WRITE_LERPED_Y(vyd2, d, parametric_offset);
dv += 8;
}
// Here we emit the actual triangles // Here we emit the actual triangles
int di = 0; // The number of indices we have written int di = 0; // The number of indices we have written
@ -1607,20 +1683,20 @@ inline bool AddRoundCornerRect(ImDrawList* draw_list, const ImVec2& a, const ImV
// Unfilled version // Unfilled version
// Top edge // Top edge
IDX_WRITE_TRI(vya, via, vib); IDX_WRITE_TRI(vya, vya2, vyb2);
IDX_WRITE_TRI(vya, vyb, vib); IDX_WRITE_TRI(vya, vyb, vyb2);
// Bottom edge // Bottom edge
IDX_WRITE_TRI(vyd, vid, vic); IDX_WRITE_TRI(vyd, vyd2, vyc2);
IDX_WRITE_TRI(vyd, vyc, vic); IDX_WRITE_TRI(vyd, vyc, vyc2);
// Left edge // Left edge
IDX_WRITE_TRI(vxa, via, vid); IDX_WRITE_TRI(vxa, vxa2, vxd2);
IDX_WRITE_TRI(vxa, vxd, vid); IDX_WRITE_TRI(vxa, vxd, vxd2);
// Right edge // Right edge
IDX_WRITE_TRI(vxb, vib, vic); IDX_WRITE_TRI(vxb, vxb2, vxc2);
IDX_WRITE_TRI(vxb, vxc, vic); IDX_WRITE_TRI(vxb, vxc, vxc2);
// Corners // Corners
@ -1654,6 +1730,9 @@ inline bool AddRoundCornerRect(ImDrawList* draw_list, const ImVec2& a, const ImV
#undef IDX_WRITE_TRI #undef IDX_WRITE_TRI
#undef VTX_WRITE #undef VTX_WRITE
#undef VTX_WRITE_LERPED
#undef VTX_WRITE_LERPED_X
#undef VTX_WRITE_LERPED_Y
return true; return true;
} }
@ -1788,13 +1867,15 @@ inline bool AddRoundCornerCircle(ImDrawList* draw_list, const ImVec2& center, fl
return false; return false;
const int rad = (int)radius; const int rad = (int)radius;
if (rad < 1 || rad > data->Font->ContainerAtlas->RoundCornersMaxSize) // Radius 0 will cause issues with the UV lookup below if (rad < 1 || rad > ImFontAtlasRoundCornersMaxSize) // Radius 0 will cause issues with the UV lookup below
return false; // We can't handle this return false; // We can't handle this
// Debug command to force this render path to only execute when shift is held // Debug command to force this render path to only execute when shift is held
if (!ImGui::GetIO().KeyShift) if (!ImGui::GetIO().KeyShift)
return false; return false;
ImFontRoundedCornerData& round_corner_data = (*data->TexRoundCornerData)[rad - 1];
// Calculate UVs for the three points we are interested in from the texture // Calculate UVs for the three points we are interested in from the texture
// corner_uv[0] is the innermost point of the circle (solid for filled circles) // corner_uv[0] is the innermost point of the circle (solid for filled circles)
// corner_uv[1] is either straight down or across from it (depending on if we are using the filled or stroked version) // corner_uv[1] is either straight down or across from it (depending on if we are using the filled or stroked version)
@ -1802,7 +1883,7 @@ inline bool AddRoundCornerCircle(ImDrawList* draw_list, const ImVec2& center, fl
// corner_uv[1] is always solid (either inside the circle or on the line), whilst corner_uv[2] is always blank // corner_uv[1] is always solid (either inside the circle or on the line), whilst corner_uv[2] is always blank
// This represents a 45 degree "wedge" of circle, which then gets mirrored here to produce a 90 degree curve // This represents a 45 degree "wedge" of circle, which then gets mirrored here to produce a 90 degree curve
// See ImFontAtlasBuildRenderRoundCornersTexData() for more details of the texture contents // See ImFontAtlasBuildRenderRoundCornersTexData() for more details of the texture contents
const ImVec4& uvs = (*(fill ? data->TexUvRoundCornerFilled : data->TexUvRoundCornerStroked))[rad - 1]; const ImVec4& uvs = fill ? round_corner_data.TexUvFilled : round_corner_data.TexUvStroked;
const ImVec2 corner_uv[3] = const ImVec2 corner_uv[3] =
{ {
ImVec2(uvs.x, uvs.y), ImVec2(uvs.x, uvs.y),
@ -1810,8 +1891,8 @@ inline bool AddRoundCornerCircle(ImDrawList* draw_list, const ImVec2& center, fl
ImVec2(uvs.z, uvs.w), ImVec2(uvs.z, uvs.w),
}; };
// Our line width (requires a texture with the appropriate line width to actually do anything) // Our stroke width (requires a texture with the appropriate stroke width to actually do anything)
const float line_width = 1.0f; const float stroke_width = 1.0f;
// Calculate the circle bounds // Calculate the circle bounds
const ImVec2& c = center; const ImVec2& c = center;
@ -1820,7 +1901,7 @@ inline bool AddRoundCornerCircle(ImDrawList* draw_list, const ImVec2& center, fl
// Some useful constants for our calculations // Some useful constants for our calculations
const float half_sqrt_two = 0.70710678f; // sqrtf(2.0f) * 0.5f const float half_sqrt_two = 0.70710678f; // sqrtf(2.0f) * 0.5f
const float width_offset_parametric = line_width / rad; // Line width in our parametric coordinate space const float width_offset_parametric = round_corner_data.ParametricStrokeWidth; // Stroke width in our parametric coordinate space
const int num_verts = fill ? 9 : 16; // Number of vertices we are going to write const int num_verts = fill ? 9 : 16; // Number of vertices we are going to write
const int num_indices = fill ? 24 : 48; // Number of indices we are going to write const int num_indices = fill ? 24 : 48; // Number of indices we are going to write
@ -1866,13 +1947,13 @@ inline bool AddRoundCornerCircle(ImDrawList* draw_list, const ImVec2& center, fl
ImVec2 uvi_cardinal = ImVec2(ImLerp(corner_uv[0].x, corner_uv[2].x, 1.0f - width_offset_parametric), corner_uv[0].y); ImVec2 uvi_cardinal = ImVec2(ImLerp(corner_uv[0].x, corner_uv[2].x, 1.0f - width_offset_parametric), corner_uv[0].y);
// Inner vertices, starting from the left // Inner vertices, starting from the left
VTX_WRITE(8, ImVec2(tl.x + line_width, c.y), uvi_cardinal); VTX_WRITE(8, ImVec2(tl.x + stroke_width, c.y), uvi_cardinal);
VTX_WRITE(9, tlbi, uvbi); VTX_WRITE(9, tlbi, uvbi);
VTX_WRITE(10, ImVec2(c.x, tl.y + line_width), uvi_cardinal); VTX_WRITE(10, ImVec2(c.x, tl.y + stroke_width), uvi_cardinal);
VTX_WRITE(11, trbi, uvbi); VTX_WRITE(11, trbi, uvbi);
VTX_WRITE(12, ImVec2(br.x - line_width, c.y), uvi_cardinal); VTX_WRITE(12, ImVec2(br.x - stroke_width, c.y), uvi_cardinal);
VTX_WRITE(13, brbi, uvbi); VTX_WRITE(13, brbi, uvbi);
VTX_WRITE(14, ImVec2(c.x, br.y - line_width), uvi_cardinal); VTX_WRITE(14, ImVec2(c.x, br.y - stroke_width), uvi_cardinal);
VTX_WRITE(15, blbi, uvbi); VTX_WRITE(15, blbi, uvbi);
} }
@ -1967,6 +2048,7 @@ void ImDrawList::AddCircle(const ImVec2& center, float radius, ImU32 col, int nu
void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col, int num_segments) void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col, int num_segments)
{ {
if ((col & IM_COL32_A_MASK) == 0 || radius <= 0.0f) if ((col & IM_COL32_A_MASK) == 0 || radius <= 0.0f)
return;
// First try the fast texture-based renderer, and only if that can't handle this fall back to paths // First try the fast texture-based renderer, and only if that can't handle this fall back to paths
if (AddRoundCornerCircle(this, center, radius, col, true)) if (AddRoundCornerCircle(this, center, radius, col, true))
@ -1994,7 +2076,7 @@ void ImDrawList::AddCircleFilled(const ImVec2& center, float radius, ImU32 col,
// Guaranteed to honor 'num_segments' // 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)
{ {
if ((col & IM_COL32_A_MASK) == 0 || num_segments <= 2) if ((col & IM_COL32_A_MASK) == 0 || (num_segments <= 2) || (radius <= 0.0f))
return; 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
@ -2441,8 +2523,6 @@ ImFontAtlas::ImFontAtlas()
memset(this, 0, sizeof(*this)); memset(this, 0, sizeof(*this));
TexGlyphPadding = 1; TexGlyphPadding = 1;
PackIdMouseCursors = PackIdLines = -1; PackIdMouseCursors = PackIdLines = -1;
RoundCornersMaxSize = 32;
} }
ImFontAtlas::~ImFontAtlas() ImFontAtlas::~ImFontAtlas()
@ -2471,7 +2551,7 @@ void ImFontAtlas::ClearInputData()
ConfigData.clear(); ConfigData.clear();
CustomRects.clear(); CustomRects.clear();
PackIdMouseCursors = PackIdLines = -1; PackIdMouseCursors = PackIdLines = -1;
RoundCornersRectIds.clear(); TexRoundCornerData.clear();
// Important: we leave TexReady untouched // Important: we leave TexReady untouched
} }
@ -2485,8 +2565,6 @@ void ImFontAtlas::ClearTexData()
TexPixelsAlpha8 = NULL; TexPixelsAlpha8 = NULL;
TexPixelsRGBA32 = NULL; TexPixelsRGBA32 = NULL;
TexPixelsUseColors = false; TexPixelsUseColors = false;
TexUvRoundCornerFilled.clear();
TexUvRoundCornerStroked.clear();
// Important: we leave TexReady untouched // Important: we leave TexReady untouched
} }
@ -3257,18 +3335,23 @@ const int FONT_ATLAS_ROUNDED_CORNER_TEX_CENTER_PADDING = 4;
// Register the rectangles we need for the rounded corner images // Register the rectangles we need for the rounded corner images
static void ImFontAtlasBuildRegisterRoundCornersCustomRects(ImFontAtlas* atlas) static void ImFontAtlasBuildRegisterRoundCornersCustomRects(ImFontAtlas* atlas)
{ {
if (atlas->RoundCornersRectIds.Size > 0) if (atlas->TexRoundCornerData.Size > 0)
return; return;
if ((atlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners)) if ((atlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners))
return; return;
const int pad = FONT_ATLAS_ROUNDED_CORNER_TEX_PADDING; const int pad = FONT_ATLAS_ROUNDED_CORNER_TEX_PADDING;
const int max = atlas->RoundCornersMaxSize; const unsigned int max = ImFontAtlasRoundCornersMaxSize;
for (int n = 0; n < max; n++)
atlas->TexRoundCornerData.reserve(max);
for (unsigned int n = 0; n < max; n++)
{ {
const int width = n + 1 + pad * 2; const int width = n + 1 + pad * 2;
const int height = n + 1 + FONT_ATLAS_ROUNDED_CORNER_TEX_CENTER_PADDING + pad * 2; const int height = n + 1 + FONT_ATLAS_ROUNDED_CORNER_TEX_CENTER_PADDING + pad * 2;
atlas->RoundCornersRectIds.push_back(atlas->AddCustomRectRegular(width, height)); ImFontRoundedCornerData corner_data;
corner_data.RectId = atlas->AddCustomRectRegular(width, height);
atlas->TexRoundCornerData.push_back(corner_data);
} }
} }
@ -3276,20 +3359,19 @@ static void ImFontAtlasBuildRegisterRoundCornersCustomRects(ImFontAtlas* atlas)
static void ImFontAtlasBuildRenderRoundCornersTexData(ImFontAtlas* atlas) static void ImFontAtlasBuildRenderRoundCornersTexData(ImFontAtlas* atlas)
{ {
IM_ASSERT(atlas->TexPixelsAlpha8 != NULL); IM_ASSERT(atlas->TexPixelsAlpha8 != NULL);
IM_ASSERT(atlas->TexUvRoundCornerFilled.Size == 0);
IM_ASSERT(atlas->TexUvRoundCornerStroked.Size == 0);
if (atlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners) if (atlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners)
return; return;
// Render the texture // Render the texture
const int w = atlas->TexWidth; const int w = atlas->TexWidth;
const unsigned int max = atlas->RoundCornersMaxSize; const unsigned int max = ImFontAtlasRoundCornersMaxSize;
const int pad = FONT_ATLAS_ROUNDED_CORNER_TEX_PADDING; const int pad = FONT_ATLAS_ROUNDED_CORNER_TEX_PADDING;
IM_ASSERT(atlas->TexRoundCornerData.Size == (int)max); // ImFontAtlasBuildRegisterRoundCornersCustomRects() will have created this for us
for (unsigned int n = 0; n < max; n++) for (unsigned int n = 0; n < max; n++)
{ {
const unsigned int id = n; const unsigned int id = n;
IM_ASSERT((int)n < atlas->RoundCornersRectIds.Size); ImFontRoundedCornerData& data = atlas->TexRoundCornerData[id];
ImFontAtlasCustomRect& r = atlas->CustomRects[atlas->RoundCornersRectIds[id]]; ImFontAtlasCustomRect& r = atlas->CustomRects[data.RectId];
IM_ASSERT(r.IsPacked()); IM_ASSERT(r.IsPacked());
IM_ASSERT(r.Width == n + 1 + pad * 2 && r.Height == n + 1 + FONT_ATLAS_ROUNDED_CORNER_TEX_CENTER_PADDING + pad * 2); IM_ASSERT(r.Width == n + 1 + pad * 2 && r.Height == n + 1 + FONT_ATLAS_ROUNDED_CORNER_TEX_CENTER_PADDING + pad * 2);
@ -3303,6 +3385,9 @@ static void ImFontAtlasBuildRenderRoundCornersTexData(ImFontAtlas* atlas)
const int radius = (int)(r.Width - pad * 2); const int radius = (int)(r.Width - pad * 2);
const float stroke_width = 1.0f; const float stroke_width = 1.0f;
// Pre-calcuate the parameteric stroke width
data.ParametricStrokeWidth = stroke_width / (float)radius;
for (int y = -pad; y < (int)(radius + FONT_ATLAS_ROUNDED_CORNER_TEX_CENTER_PADDING); y++) for (int y = -pad; y < (int)(radius + FONT_ATLAS_ROUNDED_CORNER_TEX_CENTER_PADDING); y++)
for (int x = -pad; x < (int)(radius); x++) for (int x = -pad; x < (int)(radius); x++)
{ {
@ -3351,8 +3436,11 @@ static void ImFontAtlasBuildRenderRoundCornersTexData(ImFontAtlas* atlas)
ImVec2 uv0, uv1; ImVec2 uv0, uv1;
atlas->CalcCustomRectUV(&stage_rect, &uv0, &uv1); atlas->CalcCustomRectUV(&stage_rect, &uv0, &uv1);
ImVector<ImVec4>& uvs = (filled ? atlas->TexUvRoundCornerFilled : atlas->TexUvRoundCornerStroked);
uvs.push_back(ImVec4(uv0.x, uv0.y, uv1.x, uv1.y)); if (stage == 0)
data.TexUvFilled = ImVec4(uv0.x, uv0.y, uv1.x, uv1.y);
else
data.TexUvStroked = ImVec4(uv0.x, uv0.y, uv1.x, uv1.y);
} }
} }
} }

View File

@ -726,8 +726,7 @@ struct IMGUI_API ImDrawListSharedData
const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas
// FIXME-ROUNDCORNERS: WIP + need to remove CircleVtx12 before PR // FIXME-ROUNDCORNERS: WIP + need to remove CircleVtx12 before PR
ImVector<ImVec4>* TexUvRoundCornerFilled; // UV of filled round corner quad in the atlas ImVector<ImFontRoundedCornerData>* TexRoundCornerData; // Data for texture-based rounded corners, indexed by radius
ImVector<ImVec4>* TexUvRoundCornerStroked; // UV of stroked round corner quad in the atlas
ImDrawListSharedData(); ImDrawListSharedData();
void SetCircleTessellationMaxError(float max_error); void SetCircleTessellationMaxError(float max_error);
@ -2876,7 +2875,6 @@ namespace ImGui
} // namespace ImGui } // namespace ImGui
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// [SECTION] ImFontAtlas internal API // [SECTION] ImFontAtlas internal API
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
@ -2891,6 +2889,7 @@ struct ImFontBuilderIO
#ifdef IMGUI_ENABLE_STB_TRUETYPE #ifdef IMGUI_ENABLE_STB_TRUETYPE
IMGUI_API const ImFontBuilderIO* ImFontAtlasGetBuilderForStbTruetype(); IMGUI_API const ImFontBuilderIO* ImFontAtlasGetBuilderForStbTruetype();
#endif #endif
const unsigned int ImFontAtlasRoundCornersMaxSize = 32; // Maximum size of rounded corner texture to generate in fonts
IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas);
IMGUI_API void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent); IMGUI_API void ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent);
IMGUI_API void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opaque); IMGUI_API void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opaque);