From 8e97a4a724dfd3ee5cb46393e5c2294952dd3250 Mon Sep 17 00:00:00 2001 From: Ben Carter Date: Mon, 2 Dec 2019 20:33:02 +0900 Subject: [PATCH] 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 --- imgui.cpp | 9 ++- imgui.h | 16 +++-- imgui_demo.cpp | 43 ++++++++++--- imgui_draw.cpp | 162 ++++++++++++++++++++++++++++++++++++----------- imgui_internal.h | 5 +- 5 files changed, 176 insertions(+), 59 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 064d58c5..aa5dcb35 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -1070,7 +1070,7 @@ ImGuiStyle::ImGuiStyle() 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. 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. 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 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; // 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[1] is a solid point on the edge of 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; g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel; g.DrawListSharedData.TexUvLines = atlas->TexUvLines; - g.DrawListSharedData.TexUvRoundCornerFilled = &atlas->TexUvRoundCornerFilled; - g.DrawListSharedData.TexUvRoundCornerStroked = &atlas->TexUvRoundCornerStroked; + g.DrawListSharedData.TexRoundCornerData = &atlas->TexRoundCornerData; g.DrawListSharedData.Font = g.Font; g.DrawListSharedData.FontSize = g.FontSize; } diff --git a/imgui.h b/imgui.h index a1ad71ec..c7d119df 100644 --- a/imgui.h +++ b/imgui.h @@ -2701,6 +2701,15 @@ struct ImFontGlyphRangesBuilder IMGUI_API void BuildRanges(ImVector* 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. struct ImFontAtlasCustomRect { @@ -2835,12 +2844,7 @@ struct ImFontAtlas int PackIdMouseCursors; // Custom texture rectangle ID for white pixel and mouse cursors int PackIdLines; // Custom texture rectangle ID for baked anti-aliased lines - // FIXME-ROUNDCORNERS: WIP - // 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 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 TexUvRoundCornerFilled; // Texture coordinates to filled round corner quads - ImVector TexUvRoundCornerStroked;// Texture coordinates to stroked round corner quads + ImVector TexRoundCornerData; // Data for texture-based round corners indexed by size [0] is 1px, [n] is (n+1)px (index up to ImFontAtlasRoundCornersMaxSize - 1). // [Obsolete] //typedef ImFontAtlasCustomRect CustomRect; // OBSOLETED in 1.72+ diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 5d778bb1..b8d145da 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -258,12 +258,7 @@ static void GetVtxIdxDelta(ImDrawList* dl, int* vtx, int *idx) } // 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: 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() { 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."); - static float radius = io.Fonts->RoundCornersMaxSize * 0.5f; + static float radius = 16.0f; // ImFontAtlasRoundCornersMaxSize * 0.5f; 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 idx_n = 0; @@ -286,7 +282,7 @@ static void TestTextureBasedRender() ImGui::BeginGroup(); ImGui::PushItemWidth(120); - ImGui::SliderInt("segments", &segments, 3, 100); + ImGui::SliderInt("segments", &segments, 0, 100); ImGui::PopItemWidth(); { @@ -347,6 +343,37 @@ static void TestTextureBasedRender() 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::Text("Style"); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 4c08e289..b50eafdd 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -1412,7 +1412,7 @@ inline bool AddRoundCornerRect(ImDrawList* draw_list, const ImVec2& a, const ImV return false; 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 return false; @@ -1425,6 +1425,13 @@ inline bool AddRoundCornerRect(ImDrawList* draw_list, const ImVec2& a, const ImV 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. + // 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 // 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) @@ -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 // 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 - 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] = { ImVec2(uvs.x, uvs.y), @@ -1465,19 +1472,26 @@ inline bool AddRoundCornerRect(ImDrawList* draw_list, const ImVec2& a, const ImV // MDX ID--------IC MCX // | | // 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 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 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 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 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 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); // 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); 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].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_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) // 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; @@ -1547,6 +1590,39 @@ inline bool AddRoundCornerRect(ImDrawList* draw_list, const ImVec2& a, const ImV VTX_WRITE(vid, id, 0); 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 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 // Top edge - IDX_WRITE_TRI(vya, via, vib); - IDX_WRITE_TRI(vya, vyb, vib); + IDX_WRITE_TRI(vya, vya2, vyb2); + IDX_WRITE_TRI(vya, vyb, vyb2); // Bottom edge - IDX_WRITE_TRI(vyd, vid, vic); - IDX_WRITE_TRI(vyd, vyc, vic); + IDX_WRITE_TRI(vyd, vyd2, vyc2); + IDX_WRITE_TRI(vyd, vyc, vyc2); // Left edge - IDX_WRITE_TRI(vxa, via, vid); - IDX_WRITE_TRI(vxa, vxd, vid); + IDX_WRITE_TRI(vxa, vxa2, vxd2); + IDX_WRITE_TRI(vxa, vxd, vxd2); // Right edge - IDX_WRITE_TRI(vxb, vib, vic); - IDX_WRITE_TRI(vxb, vxc, vic); + IDX_WRITE_TRI(vxb, vxb2, vxc2); + IDX_WRITE_TRI(vxb, vxc, vxc2); // Corners @@ -1654,6 +1730,9 @@ inline bool AddRoundCornerRect(ImDrawList* draw_list, const ImVec2& a, const ImV #undef IDX_WRITE_TRI #undef VTX_WRITE + #undef VTX_WRITE_LERPED + #undef VTX_WRITE_LERPED_X + #undef VTX_WRITE_LERPED_Y return true; } @@ -1788,13 +1867,15 @@ inline bool AddRoundCornerCircle(ImDrawList* draw_list, const ImVec2& center, fl return false; 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 // Debug command to force this render path to only execute when shift is held if (!ImGui::GetIO().KeyShift) return false; + ImFontRoundedCornerData& round_corner_data = (*data->TexRoundCornerData)[rad - 1]; + // 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[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 // 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 - 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] = { ImVec2(uvs.x, uvs.y), @@ -1810,8 +1891,8 @@ inline bool AddRoundCornerCircle(ImDrawList* draw_list, const ImVec2& center, fl ImVec2(uvs.z, uvs.w), }; - // Our line width (requires a texture with the appropriate line width to actually do anything) - const float line_width = 1.0f; + // Our stroke width (requires a texture with the appropriate stroke width to actually do anything) + const float stroke_width = 1.0f; // Calculate the circle bounds const ImVec2& c = center; @@ -1820,7 +1901,7 @@ inline bool AddRoundCornerCircle(ImDrawList* draw_list, const ImVec2& center, fl // Some useful constants for our calculations 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_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); // 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(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(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(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); } @@ -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) { 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 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' 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; // 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)); TexGlyphPadding = 1; PackIdMouseCursors = PackIdLines = -1; - - RoundCornersMaxSize = 32; } ImFontAtlas::~ImFontAtlas() @@ -2471,7 +2551,7 @@ void ImFontAtlas::ClearInputData() ConfigData.clear(); CustomRects.clear(); PackIdMouseCursors = PackIdLines = -1; - RoundCornersRectIds.clear(); + TexRoundCornerData.clear(); // Important: we leave TexReady untouched } @@ -2485,8 +2565,6 @@ void ImFontAtlas::ClearTexData() TexPixelsAlpha8 = NULL; TexPixelsRGBA32 = NULL; TexPixelsUseColors = false; - TexUvRoundCornerFilled.clear(); - TexUvRoundCornerStroked.clear(); // 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 static void ImFontAtlasBuildRegisterRoundCornersCustomRects(ImFontAtlas* atlas) { - if (atlas->RoundCornersRectIds.Size > 0) + if (atlas->TexRoundCornerData.Size > 0) return; if ((atlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners)) return; const int pad = FONT_ATLAS_ROUNDED_CORNER_TEX_PADDING; - const int max = atlas->RoundCornersMaxSize; - for (int n = 0; n < max; n++) + const unsigned int max = ImFontAtlasRoundCornersMaxSize; + + atlas->TexRoundCornerData.reserve(max); + + for (unsigned int n = 0; n < max; n++) { const int width = n + 1 + 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) { IM_ASSERT(atlas->TexPixelsAlpha8 != NULL); - IM_ASSERT(atlas->TexUvRoundCornerFilled.Size == 0); - IM_ASSERT(atlas->TexUvRoundCornerStroked.Size == 0); if (atlas->Flags & ImFontAtlasFlags_NoBakedRoundCorners) return; // Render the texture 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; + IM_ASSERT(atlas->TexRoundCornerData.Size == (int)max); // ImFontAtlasBuildRegisterRoundCornersCustomRects() will have created this for us for (unsigned int n = 0; n < max; n++) { const unsigned int id = n; - IM_ASSERT((int)n < atlas->RoundCornersRectIds.Size); - ImFontAtlasCustomRect& r = atlas->CustomRects[atlas->RoundCornersRectIds[id]]; + ImFontRoundedCornerData& data = atlas->TexRoundCornerData[id]; + ImFontAtlasCustomRect& r = atlas->CustomRects[data.RectId]; 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); @@ -3303,6 +3385,9 @@ static void ImFontAtlasBuildRenderRoundCornersTexData(ImFontAtlas* atlas) const int radius = (int)(r.Width - pad * 2); 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 x = -pad; x < (int)(radius); x++) { @@ -3351,8 +3436,11 @@ static void ImFontAtlasBuildRenderRoundCornersTexData(ImFontAtlas* atlas) ImVec2 uv0, uv1; atlas->CalcCustomRectUV(&stage_rect, &uv0, &uv1); - ImVector& 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); } } } diff --git a/imgui_internal.h b/imgui_internal.h index 5a221b1b..2c489fa3 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -726,8 +726,7 @@ struct IMGUI_API ImDrawListSharedData const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas // FIXME-ROUNDCORNERS: WIP + need to remove CircleVtx12 before PR - ImVector* TexUvRoundCornerFilled; // UV of filled round corner quad in the atlas - ImVector* TexUvRoundCornerStroked; // UV of stroked round corner quad in the atlas + ImVector* TexRoundCornerData; // Data for texture-based rounded corners, indexed by radius ImDrawListSharedData(); void SetCircleTessellationMaxError(float max_error); @@ -2876,7 +2875,6 @@ namespace ImGui } // namespace ImGui - //----------------------------------------------------------------------------- // [SECTION] ImFontAtlas internal API //----------------------------------------------------------------------------- @@ -2891,6 +2889,7 @@ struct ImFontBuilderIO #ifdef IMGUI_ENABLE_STB_TRUETYPE IMGUI_API const ImFontBuilderIO* ImFontAtlasGetBuilderForStbTruetype(); #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 ImFontAtlasBuildSetupFont(ImFontAtlas* atlas, ImFont* font, ImFontConfig* font_config, float ascent, float descent); IMGUI_API void ImFontAtlasBuildPackCustomRects(ImFontAtlas* atlas, void* stbrp_context_opaque);