From 1d3c3070d8462b6ca2d84e3ca0f05d28896018ec Mon Sep 17 00:00:00 2001 From: Ben Carter Date: Mon, 13 Jan 2020 14:24:55 +0900 Subject: [PATCH 1/9] Texture-based thick lines: Initial version of AA line drawing using textures (press SHIFT to enable) --- imgui.cpp | 6 ++ imgui.h | 10 ++- imgui_demo.cpp | 57 +++++++++++++++++ imgui_draw.cpp | 156 +++++++++++++++++++++++++++++++++++++++-------- imgui_internal.h | 4 ++ 5 files changed, 206 insertions(+), 27 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index d09d61ed..34b63566 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -935,6 +935,7 @@ ImGuiStyle::ImGuiStyle() DisplaySafeAreaPadding = ImVec2(3,3); // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows. MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. AntiAliasedLines = true; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. + TexturedAntiAliasedLines= true; // Draw anti-aliased lines using textures where possible. AntiAliasedFill = true; // Enable anti-aliased filled shapes (rounded rectangles, circles, etc.). 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. CircleSegmentMaxError = 1.60f; // 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. @@ -3688,10 +3689,14 @@ void ImGui::NewFrame() g.DrawListSharedData.InitialFlags = ImDrawListFlags_None; if (g.Style.AntiAliasedLines) g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLines; + if (g.Style.TexturedAntiAliasedLines) + g.DrawListSharedData.InitialFlags |= ImDrawListFlags_TexturedAALines; if (g.Style.AntiAliasedFill) g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedFill; if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AllowVtxOffset; + if ((g.Style.TexturedAntiAliasedLines) && (!(g.Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoAALines))) + g.DrawListSharedData.InitialFlags |= ImDrawListFlags_TexturedAALines; g.BackgroundDrawList._ResetForNewFrame(); g.BackgroundDrawList.PushTextureID(g.IO.Fonts->TexID); @@ -6158,6 +6163,7 @@ void ImGui::SetCurrentFont(ImFont* font) ImFontAtlas* atlas = g.Font->ContainerAtlas; g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel; + g.DrawListSharedData.TexUvAALines = &atlas->TexUvAALines; g.DrawListSharedData.Font = g.Font; g.DrawListSharedData.FontSize = g.FontSize; } diff --git a/imgui.h b/imgui.h index adb2ab1c..09d92309 100644 --- a/imgui.h +++ b/imgui.h @@ -1439,6 +1439,7 @@ struct ImGuiStyle ImVec2 DisplaySafeAreaPadding; // If you cannot see the edges of your screen (e.g. on a TV) increase the safe area padding. Apply to popups/tooltips as well regular windows. NB: Prefer configuring your TV sets correctly! float MouseCursorScale; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. bool AntiAliasedLines; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. + bool TexturedAntiAliasedLines; // Draw anti-aliased lines using textures where possible. bool AntiAliasedFill; // Enable anti-aliased edges around filled shapes (rounded rectangles, circles, etc.). Disable if you are really tight on CPU/GPU. float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. float CircleSegmentMaxError; // 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. @@ -1997,7 +1998,8 @@ 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_AntiAliasedFill = 1 << 1, // Enable anti-aliased edge around filled shapes (rounded rectangles, circles). - ImDrawListFlags_AllowVtxOffset = 1 << 2 // Can emit 'VtxOffset > 0' to allow large meshes. Set when 'ImGuiBackendFlags_RendererHasVtxOffset' is enabled. + ImDrawListFlags_AllowVtxOffset = 1 << 2, // Can emit 'VtxOffset > 0' to allow large meshes. Set when 'ImGuiBackendFlags_RendererHasVtxOffset' is enabled. + ImDrawListFlags_TexturedAALines = 1 << 3 // Should anti-aliased lines be drawn using textures where possible? }; // Draw command list @@ -2216,7 +2218,8 @@ 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_NoMouseCursors = 1 << 1, // Don't build software mouse cursors into the atlas (save a little texture memory) + ImFontAtlasFlags_NoAALines = 1 << 2 // Don't build anti-aliased line textures into the atlas }; // Load and rasterize multiple TTF/OTF fonts into a same texture. The font atlas will build a single texture holding: @@ -2320,6 +2323,9 @@ struct ImFontAtlas // [Internal] Packing data int PackIdMouseCursors; // Custom texture rectangle ID for white pixel and mouse cursors + int AALineMaxWidth; // Maximum line width to build anti-aliased textures for + ImVector AALineRectIds; // Custom texture rectangle IDs for anti-aliased lines + ImVector TexUvAALines; // UVs for anti-aliased line textures #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS typedef ImFontAtlasCustomRect CustomRect; // OBSOLETED in 1.72+ diff --git a/imgui_demo.cpp b/imgui_demo.cpp index b481c305..f06d8526 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -297,6 +297,62 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::SetNextWindowPos(ImVec2(650, 20), ImGuiCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(550, 680), ImGuiCond_FirstUseEver); + // Test lines + + if (ImGui::Begin("Lines")) + { + ImDrawList* draw_list = ImGui::GetWindowDrawList(); + + const int num_cols = 16; + const int num_rows = 3; + const float line_len = 64.0f; + const float line_spacing = 128.0f; + + static float base_rot = 0.0f; + ImGui::SliderFloat("Base rotation", &base_rot, 0.0f, 360.0f); + static float line_width = 1.0f; + ImGui::SliderFloat("Line width", &line_width, 1.0f, 10.0f); + + ImVec2 window_pos = ImGui::GetWindowPos(); + ImVec2 cursor_pos = ImGui::GetCursorPos(); + ImVec2 base_pos(window_pos.x + cursor_pos.x + (line_spacing * 0.5f), window_pos.y + cursor_pos.y); + + for (int i = 0; i < num_rows; i++) + { + const char* name = ""; + switch (i) + { + case 0: name = "No AA"; draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; break; + case 1: name = "AA no texturing"; draw_list->Flags |= ImDrawListFlags_AntiAliasedLines; draw_list->Flags &= ~ImDrawListFlags_TexturedAALines; break; + case 2: name = "AA with texturing"; draw_list->Flags |= ImDrawListFlags_AntiAliasedLines; draw_list->Flags |= ImDrawListFlags_TexturedAALines; break; + } + + int initial_vtx_count = draw_list->VtxBuffer.Size; + int initial_idx_count = draw_list->IdxBuffer.Size; + + for (int j = 0; j < num_cols; j++) + { + const float pi = 3.14159265359f; + float r = (base_rot * pi / 180.0f) + ((j * pi * 0.5f) / (num_cols - 1)); + + ImVec2 center = ImVec2(base_pos.x + (line_spacing * (j * 0.5f)), base_pos.y + (line_spacing * (i + 0.5f))); + ImVec2 start = ImVec2(center.x + (sinf(r) * line_len * 0.5f), center.y + (cosf(r) * line_len * 0.5f)); + ImVec2 end = ImVec2(center.x - (sinf(r) * line_len * 0.5f), center.y - (cosf(r) * line_len * 0.5f)); + + draw_list->AddLine(start, end, IM_COL32(255, 255, 255, 255), line_width); + } + + ImGui::SetCursorPosY(cursor_pos.y + (i * line_spacing)); + ImGui::Text("%s - %d vertices, %d indices", name, draw_list->VtxBuffer.Size - initial_vtx_count, draw_list->IdxBuffer.Size - initial_idx_count); + } + + ImGui::SetCursorPosY(cursor_pos.y + (num_rows * line_spacing)); + + //ImGui::Spacing(); ImGui::Spacing(); ImGui::Spacing(); + //ImGui::Image(ImGui::GetFont()->ContainerAtlas->TexID, ImVec2((float)ImGui::GetFont()->ContainerAtlas->TexWidth, (float)ImGui::GetFont()->ContainerAtlas->TexHeight)); + } + ImGui::End(); + // Main body of the Demo window starts here. if (!ImGui::Begin("Dear ImGui Demo", p_open, window_flags)) { @@ -3831,6 +3887,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) { ImGui::Checkbox("Anti-aliased lines", &style.AntiAliasedLines); ImGui::SameLine(); HelpMarker("When disabling anti-aliasing lines, you'll probably want to disable borders in your style as well."); + ImGui::Checkbox("Use textures for anti-aliased lines", &style.TexturedAntiAliasedLines); ImGui::Checkbox("Anti-aliased fill", &style.AntiAliasedFill); ImGui::PushItemWidth(100); ImGui::DragFloat("Curve Tessellation Tolerance", &style.CurveTessellationTol, 0.02f, 0.10f, 10.0f, "%.2f"); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 30b9b9fe..7c845f94 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -667,6 +667,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 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 > 1.0f); if (Flags & ImDrawListFlags_AntiAliasedLines) @@ -675,13 +676,24 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 const float AA_SIZE = 1.0f; const ImU32 col_trans = col & ~IM_COL32_A_MASK; - const int idx_count = thick_line ? count * 18 : count * 12; - const int vtx_count = thick_line ? points_count * 4 : points_count * 3; + const int integer_thickness = (int)thickness; + + // Do we want to draw this line using a texture? + bool use_textures = (Flags & ImDrawListFlags_TexturedAALines) && + (integer_thickness >= 1) && + (integer_thickness <= _Data->Font->ContainerAtlas->AALineMaxWidth) && + ImGui::GetIO().KeyShift; // FIXME-AALINES: Remove this debug code + + // We should never hit this, because NewFrame() doesn't set ImDrawListFlags_TexturedAALines unless ImFontAtlasFlags_NoAALines is off + IM_ASSERT_PARANOID((!use_textures) || (!(_Data->Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoAALines))); + + const int idx_count = use_textures ? (count * 6) : (thick_line ? count * 18 : count * 12); + const int vtx_count = use_textures ? (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 - ImVec2* temp_normals = (ImVec2*)alloca(points_count * (thick_line ? 5 : 3) * sizeof(ImVec2)); //-V630 + ImVec2* temp_normals = (ImVec2*)alloca(points_count * ((thick_line && !use_textures) ? 5 : 3) * sizeof(ImVec2)); //-V630 ImVec2* temp_points = temp_normals + points_count; // Calculate normals (tangents) for each line segment @@ -697,14 +709,19 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 if (!closed) temp_normals[points_count - 1] = temp_normals[points_count - 2]; - if (!thick_line) + // 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 ((!thick_line) || (use_textures)) { + // The width of the geometry we need to draw + const float half_draw_size = (!thick_line) ? AA_SIZE : (AA_SIZE + (thickness * 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) { - temp_points[0] = points[0] + temp_normals[0] * AA_SIZE; - temp_points[1] = points[0] - temp_normals[0] * AA_SIZE; - temp_points[(points_count-1)*2+0] = points[points_count-1] + temp_normals[points_count-1] * AA_SIZE; - temp_points[(points_count-1)*2+1] = points[points_count-1] - temp_normals[points_count-1] * AA_SIZE; + 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 @@ -713,15 +730,15 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 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; - const unsigned int idx2 = (i1 + 1) == points_count ? _VtxCurrentIdx : idx1 + 3; + const int i2 = (i1 + 1) == points_count ? 0 : i1 + 1; // i2 is the second point of the line segment + unsigned int idx2 = ((i1 + 1) == points_count) ? _VtxCurrentIdx : (idx1 + (use_textures ? 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 *= AA_SIZE; - dm_y *= AA_SIZE; + 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 vertexes for the outer edges ImVec2* out_vtx = &temp_points[i2 * 2]; @@ -730,28 +747,54 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 out_vtx[1].x = points[i2].x - dm_x; out_vtx[1].y = points[i2].y - dm_y; - // Add indexes for four triangles - _IdxWritePtr[0] = (ImDrawIdx)(idx2+0); _IdxWritePtr[1] = (ImDrawIdx)(idx1+0); _IdxWritePtr[2] = (ImDrawIdx)(idx1+2); - _IdxWritePtr[3] = (ImDrawIdx)(idx1+2); _IdxWritePtr[4] = (ImDrawIdx)(idx2+2); _IdxWritePtr[5] = (ImDrawIdx)(idx2+0); - _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; + if (use_textures) + { + // 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 vertexes for each point on the line - for (int i = 0; i < points_count; i++) + if (use_textures) { - _VtxWritePtr[0].pos = points[i]; _VtxWritePtr[0].uv = opaque_uv; _VtxWritePtr[0].col = col; - _VtxWritePtr[1].pos = temp_points[i*2+0]; _VtxWritePtr[1].uv = opaque_uv; _VtxWritePtr[1].col = col_trans; - _VtxWritePtr[2].pos = temp_points[i*2+1]; _VtxWritePtr[2].uv = opaque_uv; _VtxWritePtr[2].col = col_trans; - _VtxWritePtr += 3; + // If we're using textures we only need to emit the left/right edge vertices + const ImVec4 tex_uvs = (*_Data->TexUvAALines)[integer_thickness - 1]; + + for (int i = 0; i < points_count; i++) + { + _VtxWritePtr[0].pos = temp_points[i * 2 + 0]; _VtxWritePtr[0].uv = ImVec2(tex_uvs.x, tex_uvs.y); _VtxWritePtr[0].col = col; // Left-side outer edge + _VtxWritePtr[1].pos = temp_points[i * 2 + 1]; _VtxWritePtr[1].uv = ImVec2(tex_uvs.z, tex_uvs.y); _VtxWritePtr[1].col = col; // Right-side outer edge + _VtxWritePtr += 2; + } + } + else + { + // If we're not using a texture, we need the centre 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; // Centre 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 { - // Non texture-based lines (thick): we need to draw the solid line core and thus require four vertices per point + // For untextured lines that are greater than a pixel in width, 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 @@ -1664,6 +1707,8 @@ ImFontAtlas::ImFontAtlas() TexUvScale = ImVec2(0.0f, 0.0f); TexUvWhitePixel = ImVec2(0.0f, 0.0f); PackIdMouseCursors = -1; + + AALineMaxWidth = 8; } ImFontAtlas::~ImFontAtlas() @@ -2010,6 +2055,7 @@ bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) IM_ASSERT(atlas->ConfigData.Size > 0); ImFontAtlasBuildInit(atlas); + ImFontAtlasBuildRegisterAALineCustomRects(atlas); // Clear atlas atlas->TexID = (ImTextureID)NULL; @@ -2331,7 +2377,6 @@ static void ImFontAtlasBuildRenderDefaultTexData(ImFontAtlas* atlas) atlas->TexUvWhitePixel = ImVec2((r->X + 0.5f) * atlas->TexUvScale.x, (r->Y + 0.5f) * atlas->TexUvScale.y); } - // Note: this is called / shared by both the stb_truetype and the FreeType builder void ImFontAtlasBuildInit(ImFontAtlas* atlas) { @@ -2346,12 +2391,73 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas) } // This is called/shared by both the stb_truetype and the FreeType builder. +const unsigned int FONT_ATLAS_AA_LINE_TEX_HEIGHT = 1; // Technically we only need 1 pixel in the ideal case but this can be increased if necessary to give a border to avoid sampling artifacts + +void ImFontAtlasBuildRegisterAALineCustomRects(ImFontAtlas* atlas) +{ + if (atlas->AALineRectIds.size() > 0) + return; + + if ((atlas->Flags & ImFontAtlasFlags_NoAALines)) + return; + + const int max = atlas->AALineMaxWidth; + + for (int n = 0; n < max; n++) + { + const int width = n + 1; // The line width this entry corresponds to + // The "width + 3" here is interesting - +2 is to give space for the end caps, but the remaining +1 is because (empirically) to match the behaviour of the untextured render path we need to draw lines one pixel wider + atlas->AALineRectIds.push_back(atlas->AddCustomRectRegular(width + 3, FONT_ATLAS_AA_LINE_TEX_HEIGHT)); + } +} + +void ImFontAtlasBuildAALinesTexData(ImFontAtlas* atlas) +{ + IM_ASSERT(atlas->TexPixelsAlpha8 != NULL); + IM_ASSERT(atlas->TexUvAALines.size() == 0); + + if (atlas->Flags & ImFontAtlasFlags_NoAALines) + return; + + const int w = atlas->TexWidth; + const unsigned int max = atlas->AALineMaxWidth; + + for (unsigned int n = 0; n < max; n++) + { + IM_ASSERT(atlas->AALineRectIds.size() > (int)n); + ImFontAtlas::CustomRect& r = atlas->CustomRects[atlas->AALineRectIds[n]]; + IM_ASSERT(r.IsPacked()); + + // We fill as many lines as we were given, to allow for >1 lines being used to work around sampling weirdness + for (unsigned int y = 0; y < r.Height; y++) + { + unsigned char* write_ptr = &atlas->TexPixelsAlpha8[r.X + ((r.Y + y) * w)]; + + // Each line consists of two empty pixels at the ends, with a line of solid pixels in the middle + *(write_ptr++) = 0; + for (unsigned short x = 0; x < (r.Width - 2U); x++) + { + *(write_ptr++) = 0xFF; + } + *(write_ptr++) = 0; + } + + ImVec2 uv0, uv1; + atlas->CalcCustomRectUV(&r, &uv0, &uv1); + float halfV = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the texture as we want a horizontal slice (with some padding either side to avoid sampling artifacts) + atlas->TexUvAALines.push_back(ImVec4(uv0.x, halfV, uv1.x, halfV)); + } +} + void ImFontAtlasBuildFinish(ImFontAtlas* atlas) { // Render into our custom data blocks IM_ASSERT(atlas->TexPixelsAlpha8 != NULL); ImFontAtlasBuildRenderDefaultTexData(atlas); + // Render anti-aliased line textures + ImFontAtlasBuildAALinesTexData(atlas); + // Register custom rectangle glyphs for (int i = 0; i < atlas->CustomRects.Size; i++) { diff --git a/imgui_internal.h b/imgui_internal.h index b57b01b5..2035f324 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -545,6 +545,8 @@ struct IMGUI_API ImDrawListSharedData ImVec2 ArcFastVtx[12 * IM_DRAWLIST_ARCFAST_TESSELLATION_MULTIPLIER]; // FIXME: Bake rounded corners fill/borders in atlas ImU8 CircleSegmentCounts[64]; // Precomputed segment count for given radius (array index + 1) before we calculate it dynamically (to avoid calculation overhead) + ImVector* TexUvAALines; // UV of anti-aliased lines in the atlas + ImDrawListSharedData(); void SetCircleSegmentMaxError(float max_error); }; @@ -2018,8 +2020,10 @@ namespace ImGui // ImFontAtlas internals IMGUI_API bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas); +IMGUI_API void ImFontAtlasBuildRegisterAALineCustomRects(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); +IMGUI_API void ImFontAtlasBuildAALinesTexData(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildFinish(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_multiply_factor); IMGUI_API void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride); From 741ab74b5575f0b4b5db82db549f07fda130d244 Mon Sep 17 00:00:00 2001 From: Ben Carter Date: Wed, 15 Jan 2020 16:33:09 +0900 Subject: [PATCH 2/9] Texture-based thick lines: Improvements to code for drawing anti-aliased lines using textures Moved line width into a constant Removed test code (now in imgui-tests) Improved matching between geometry and texture rendering at non-integer sizes --- imgui.h | 1 - imgui_demo.cpp | 56 ------------------------------------------------ imgui_draw.cpp | 14 +++++------- imgui_internal.h | 3 +++ 4 files changed, 8 insertions(+), 66 deletions(-) diff --git a/imgui.h b/imgui.h index 09d92309..cc4be5e0 100644 --- a/imgui.h +++ b/imgui.h @@ -2323,7 +2323,6 @@ struct ImFontAtlas // [Internal] Packing data int PackIdMouseCursors; // Custom texture rectangle ID for white pixel and mouse cursors - int AALineMaxWidth; // Maximum line width to build anti-aliased textures for ImVector AALineRectIds; // Custom texture rectangle IDs for anti-aliased lines ImVector TexUvAALines; // UVs for anti-aliased line textures diff --git a/imgui_demo.cpp b/imgui_demo.cpp index f06d8526..2afa7a4f 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -297,62 +297,6 @@ void ImGui::ShowDemoWindow(bool* p_open) ImGui::SetNextWindowPos(ImVec2(650, 20), ImGuiCond_FirstUseEver); ImGui::SetNextWindowSize(ImVec2(550, 680), ImGuiCond_FirstUseEver); - // Test lines - - if (ImGui::Begin("Lines")) - { - ImDrawList* draw_list = ImGui::GetWindowDrawList(); - - const int num_cols = 16; - const int num_rows = 3; - const float line_len = 64.0f; - const float line_spacing = 128.0f; - - static float base_rot = 0.0f; - ImGui::SliderFloat("Base rotation", &base_rot, 0.0f, 360.0f); - static float line_width = 1.0f; - ImGui::SliderFloat("Line width", &line_width, 1.0f, 10.0f); - - ImVec2 window_pos = ImGui::GetWindowPos(); - ImVec2 cursor_pos = ImGui::GetCursorPos(); - ImVec2 base_pos(window_pos.x + cursor_pos.x + (line_spacing * 0.5f), window_pos.y + cursor_pos.y); - - for (int i = 0; i < num_rows; i++) - { - const char* name = ""; - switch (i) - { - case 0: name = "No AA"; draw_list->Flags &= ~ImDrawListFlags_AntiAliasedLines; break; - case 1: name = "AA no texturing"; draw_list->Flags |= ImDrawListFlags_AntiAliasedLines; draw_list->Flags &= ~ImDrawListFlags_TexturedAALines; break; - case 2: name = "AA with texturing"; draw_list->Flags |= ImDrawListFlags_AntiAliasedLines; draw_list->Flags |= ImDrawListFlags_TexturedAALines; break; - } - - int initial_vtx_count = draw_list->VtxBuffer.Size; - int initial_idx_count = draw_list->IdxBuffer.Size; - - for (int j = 0; j < num_cols; j++) - { - const float pi = 3.14159265359f; - float r = (base_rot * pi / 180.0f) + ((j * pi * 0.5f) / (num_cols - 1)); - - ImVec2 center = ImVec2(base_pos.x + (line_spacing * (j * 0.5f)), base_pos.y + (line_spacing * (i + 0.5f))); - ImVec2 start = ImVec2(center.x + (sinf(r) * line_len * 0.5f), center.y + (cosf(r) * line_len * 0.5f)); - ImVec2 end = ImVec2(center.x - (sinf(r) * line_len * 0.5f), center.y - (cosf(r) * line_len * 0.5f)); - - draw_list->AddLine(start, end, IM_COL32(255, 255, 255, 255), line_width); - } - - ImGui::SetCursorPosY(cursor_pos.y + (i * line_spacing)); - ImGui::Text("%s - %d vertices, %d indices", name, draw_list->VtxBuffer.Size - initial_vtx_count, draw_list->IdxBuffer.Size - initial_idx_count); - } - - ImGui::SetCursorPosY(cursor_pos.y + (num_rows * line_spacing)); - - //ImGui::Spacing(); ImGui::Spacing(); ImGui::Spacing(); - //ImGui::Image(ImGui::GetFont()->ContainerAtlas->TexID, ImVec2((float)ImGui::GetFont()->ContainerAtlas->TexWidth, (float)ImGui::GetFont()->ContainerAtlas->TexHeight)); - } - ImGui::End(); - // Main body of the Demo window starts here. if (!ImGui::Begin("Dear ImGui Demo", p_open, window_flags)) { diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 7c845f94..72ccb963 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -676,13 +676,11 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 const float AA_SIZE = 1.0f; const ImU32 col_trans = col & ~IM_COL32_A_MASK; - const int integer_thickness = (int)thickness; + // The -0.5f here is to better match the geometry-based code, and also shift the transition point from one width texture to another off the integer values (where it will be less noticeable) + const int integer_thickness = ImMax((int)(thickness - 0.5f), 1); // Do we want to draw this line using a texture? - bool use_textures = (Flags & ImDrawListFlags_TexturedAALines) && - (integer_thickness >= 1) && - (integer_thickness <= _Data->Font->ContainerAtlas->AALineMaxWidth) && - ImGui::GetIO().KeyShift; // FIXME-AALINES: Remove this debug code + bool use_textures = (Flags & ImDrawListFlags_TexturedAALines) && (integer_thickness <= IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX); // We should never hit this, because NewFrame() doesn't set ImDrawListFlags_TexturedAALines unless ImFontAtlasFlags_NoAALines is off IM_ASSERT_PARANOID((!use_textures) || (!(_Data->Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoAALines))); @@ -1707,8 +1705,6 @@ ImFontAtlas::ImFontAtlas() TexUvScale = ImVec2(0.0f, 0.0f); TexUvWhitePixel = ImVec2(0.0f, 0.0f); PackIdMouseCursors = -1; - - AALineMaxWidth = 8; } ImFontAtlas::~ImFontAtlas() @@ -2401,7 +2397,7 @@ void ImFontAtlasBuildRegisterAALineCustomRects(ImFontAtlas* atlas) if ((atlas->Flags & ImFontAtlasFlags_NoAALines)) return; - const int max = atlas->AALineMaxWidth; + const int max = IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX; for (int n = 0; n < max; n++) { @@ -2420,7 +2416,7 @@ void ImFontAtlasBuildAALinesTexData(ImFontAtlas* atlas) return; const int w = atlas->TexWidth; - const unsigned int max = atlas->AALineMaxWidth; + const unsigned int max = IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX; for (unsigned int n = 0; n < max; n++) { diff --git a/imgui_internal.h b/imgui_internal.h index 2035f324..643aea4d 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -529,6 +529,9 @@ struct IMGUI_API ImChunkStream #define IM_DRAWLIST_ARCFAST_TESSELLATION_MULTIPLIER 1 #endif +// The maximum line width to build anti-aliased textures for +#define IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX 65 + // Data shared between all ImDrawList instances // You may want to create your own instance of this if you want to use ImDrawList completely without ImGui. In that case, watch out for future changes to this structure. struct IMGUI_API ImDrawListSharedData From 222b7ddbfa16635905772faed268e33501ee22ae Mon Sep 17 00:00:00 2001 From: omar Date: Thu, 23 Jan 2020 15:23:23 +0100 Subject: [PATCH 3/9] Texture-based thick lines: Tweaks, fix for truetype builder. --- imgui.cpp | 8 +-- imgui.h | 4 +- imgui_demo.cpp | 3 +- imgui_draw.cpp | 140 +++++++++++++++++++++++------------------------ imgui_internal.h | 2 - 5 files changed, 74 insertions(+), 83 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index 34b63566..e6fe1184 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -935,7 +935,7 @@ ImGuiStyle::ImGuiStyle() DisplaySafeAreaPadding = ImVec2(3,3); // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows. MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. AntiAliasedLines = true; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. - TexturedAntiAliasedLines= true; // Draw anti-aliased lines using textures where possible. + AntiAliasedLinesUseTexData = true; // Draw anti-aliased lines using textures where possible. AntiAliasedFill = true; // Enable anti-aliased filled shapes (rounded rectangles, circles, etc.). 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. CircleSegmentMaxError = 1.60f; // 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. @@ -3689,14 +3689,12 @@ void ImGui::NewFrame() g.DrawListSharedData.InitialFlags = ImDrawListFlags_None; if (g.Style.AntiAliasedLines) g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLines; - if (g.Style.TexturedAntiAliasedLines) - g.DrawListSharedData.InitialFlags |= ImDrawListFlags_TexturedAALines; + if (g.Style.AntiAliasedLinesUseTexData && !(g.Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoAALines)) + g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLinesUseTexData; if (g.Style.AntiAliasedFill) g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedFill; if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AllowVtxOffset; - if ((g.Style.TexturedAntiAliasedLines) && (!(g.Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoAALines))) - g.DrawListSharedData.InitialFlags |= ImDrawListFlags_TexturedAALines; g.BackgroundDrawList._ResetForNewFrame(); g.BackgroundDrawList.PushTextureID(g.IO.Fonts->TexID); diff --git a/imgui.h b/imgui.h index cc4be5e0..f361102c 100644 --- a/imgui.h +++ b/imgui.h @@ -1439,7 +1439,7 @@ struct ImGuiStyle ImVec2 DisplaySafeAreaPadding; // If you cannot see the edges of your screen (e.g. on a TV) increase the safe area padding. Apply to popups/tooltips as well regular windows. NB: Prefer configuring your TV sets correctly! float MouseCursorScale; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. bool AntiAliasedLines; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. - bool TexturedAntiAliasedLines; // Draw anti-aliased lines using textures where possible. + bool AntiAliasedLinesUseTexData; // Draw anti-aliased lines using textures where possible. bool AntiAliasedFill; // Enable anti-aliased edges around filled shapes (rounded rectangles, circles, etc.). Disable if you are really tight on CPU/GPU. float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. float CircleSegmentMaxError; // 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. @@ -1999,7 +1999,7 @@ enum ImDrawListFlags_ 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_AntiAliasedFill = 1 << 1, // Enable anti-aliased edge around filled shapes (rounded rectangles, circles). ImDrawListFlags_AllowVtxOffset = 1 << 2, // Can emit 'VtxOffset > 0' to allow large meshes. Set when 'ImGuiBackendFlags_RendererHasVtxOffset' is enabled. - ImDrawListFlags_TexturedAALines = 1 << 3 // Should anti-aliased lines be drawn using textures where possible? + ImDrawListFlags_AntiAliasedLinesUseTexData = 1 << 3 // Should anti-aliased lines be drawn using textures where possible? }; // Draw command list diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 2afa7a4f..e90fd517 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3831,7 +3831,8 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) { ImGui::Checkbox("Anti-aliased lines", &style.AntiAliasedLines); ImGui::SameLine(); HelpMarker("When disabling anti-aliasing lines, you'll probably want to disable borders in your style as well."); - ImGui::Checkbox("Use textures for anti-aliased lines", &style.TexturedAntiAliasedLines); + ImGui::Checkbox("Anti-aliased lines use texture data", &style.AntiAliasedLinesUseTexData); + ImGui::SameLine(); HelpMarker("Faster lines using texture data. Requires texture to use bilinear sampling (not nearest)."); ImGui::Checkbox("Anti-aliased fill", &style.AntiAliasedFill); ImGui::PushItemWidth(100); ImGui::DragFloat("Curve Tessellation Tolerance", &style.CurveTessellationTol, 0.02f, 0.10f, 10.0f, "%.2f"); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 72ccb963..41d25955 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -358,6 +358,7 @@ ImDrawListSharedData::ImDrawListSharedData() ArcFastVtx[i] = ImVec2(ImCos(a), ImSin(a)); } memset(CircleSegmentCounts, 0, sizeof(CircleSegmentCounts)); // This will be set by SetCircleSegmentMaxError() + TexUvAALines = NULL; } void ImDrawListSharedData::SetCircleSegmentMaxError(float max_error) @@ -680,18 +681,18 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 const int integer_thickness = ImMax((int)(thickness - 0.5f), 1); // Do we want to draw this line using a texture? - bool use_textures = (Flags & ImDrawListFlags_TexturedAALines) && (integer_thickness <= IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX); + const bool use_texture = (Flags & ImDrawListFlags_AntiAliasedLinesUseTexData) && (integer_thickness <= IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX); - // We should never hit this, because NewFrame() doesn't set ImDrawListFlags_TexturedAALines unless ImFontAtlasFlags_NoAALines is off - IM_ASSERT_PARANOID((!use_textures) || (!(_Data->Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoAALines))); + // We should never hit this, because NewFrame() doesn't set ImDrawListFlags_AntiAliasedLinesUseTexData unless ImFontAtlasFlags_NoAALines is off + IM_ASSERT_PARANOID((!use_texture) || (!(_Data->Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoAALines))); - const int idx_count = use_textures ? (count * 6) : (thick_line ? count * 18 : count * 12); - const int vtx_count = use_textures ? (points_count * 2) : (thick_line ? points_count * 4 : points_count * 3); + 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 - ImVec2* temp_normals = (ImVec2*)alloca(points_count * ((thick_line && !use_textures) ? 5 : 3) * sizeof(ImVec2)); //-V630 + ImVec2* temp_normals = (ImVec2*)alloca(points_count * ((thick_line && !use_texture) ? 5 : 3) * sizeof(ImVec2)); //-V630 ImVec2* temp_points = temp_normals + points_count; // Calculate normals (tangents) for each line segment @@ -708,7 +709,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 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 ((!thick_line) || (use_textures)) + if (!thick_line || use_texture) { // The width of the geometry we need to draw const float half_draw_size = (!thick_line) ? AA_SIZE : (AA_SIZE + (thickness * 0.5f)); @@ -729,7 +730,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 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 - unsigned int idx2 = ((i1 + 1) == points_count) ? _VtxCurrentIdx : (idx1 + (use_textures ? 2 : 3)); // Vertex index for end of segment + 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; @@ -745,7 +746,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 out_vtx[1].x = points[i2].x - dm_x; out_vtx[1].y = points[i2].y - dm_y; - if (use_textures) + 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 @@ -766,7 +767,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 } // Add vertexes for each point on the line - if (use_textures) + if (use_texture) { // If we're using textures we only need to emit the left/right edge vertices const ImVec4 tex_uvs = (*_Data->TexUvAALines)[integer_thickness - 1]; @@ -2051,7 +2052,6 @@ bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas) IM_ASSERT(atlas->ConfigData.Size > 0); ImFontAtlasBuildInit(atlas); - ImFontAtlasBuildRegisterAALineCustomRects(atlas); // Clear atlas atlas->TexID = (ImTextureID)NULL; @@ -2373,6 +2373,59 @@ static void ImFontAtlasBuildRenderDefaultTexData(ImFontAtlas* atlas) atlas->TexUvWhitePixel = ImVec2((r->X + 0.5f) * atlas->TexUvScale.x, (r->Y + 0.5f) * atlas->TexUvScale.y); } +// This is called/shared by both the stb_truetype and the FreeType builder. +const unsigned int FONT_ATLAS_AA_LINE_TEX_HEIGHT = 1; // Technically we only need 1 pixel in the ideal case but this can be increased if necessary to give a border to avoid sampling artifacts + +static void ImFontAtlasBuildRegisterAALineCustomRects(ImFontAtlas* atlas) +{ + if (atlas->AALineRectIds.Size > 0) + return; + + if ((atlas->Flags & ImFontAtlasFlags_NoAALines)) + return; + + for (int n = 0; n < IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX; n++) + { + // The "width + 3" here is interesting. +2 is to give space for the end caps, but the remaining +1 is + // because (empirically) to match the behavior of the untextured render path we need to draw lines one pixel wider. + const int width = n + 1; // The line width this entry corresponds to + atlas->AALineRectIds.push_back(atlas->AddCustomRectRegular(width + 3, FONT_ATLAS_AA_LINE_TEX_HEIGHT)); + } +} + +static void ImFontAtlasBuildRenderAALinesTexData(ImFontAtlas* atlas) +{ + IM_ASSERT(atlas->TexPixelsAlpha8 != NULL); + IM_ASSERT(atlas->TexUvAALines.Size == 0); + + if (atlas->Flags & ImFontAtlasFlags_NoAALines) + return; + + const int w = atlas->TexWidth; + for (unsigned int n = 0; n < IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX; n++) + { + IM_ASSERT(atlas->AALineRectIds.Size > (int)n); + ImFontAtlasCustomRect& r = atlas->CustomRects[atlas->AALineRectIds[n]]; + IM_ASSERT(r.IsPacked()); + + // We fill as many lines as we were given, to allow for >1 lines being used to work around sampling weirdness + for (unsigned int y = 0; y < r.Height; y++) + { + // Each line consists of two empty pixels at the ends, with a line of solid pixels in the middle + unsigned char* write_ptr = &atlas->TexPixelsAlpha8[r.X + ((r.Y + y) * w)]; + *(write_ptr++) = 0; + for (unsigned short x = 0; x < (r.Width - 2U); x++) + *(write_ptr++) = 0xFF; + *(write_ptr++) = 0; + } + + ImVec2 uv0, uv1; + atlas->CalcCustomRectUV(&r, &uv0, &uv1); + float halfV = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the texture as we want a horizontal slice (with some padding either side to avoid sampling artifacts) + atlas->TexUvAALines.push_back(ImVec4(uv0.x, halfV, uv1.x, halfV)); + } +} + // Note: this is called / shared by both the stb_truetype and the FreeType builder void ImFontAtlasBuildInit(ImFontAtlas* atlas) { @@ -2384,75 +2437,16 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas) else atlas->PackIdMouseCursors = atlas->AddCustomRectRegular(2, 2); } + + ImFontAtlasBuildRegisterAALineCustomRects(atlas); } // This is called/shared by both the stb_truetype and the FreeType builder. -const unsigned int FONT_ATLAS_AA_LINE_TEX_HEIGHT = 1; // Technically we only need 1 pixel in the ideal case but this can be increased if necessary to give a border to avoid sampling artifacts - -void ImFontAtlasBuildRegisterAALineCustomRects(ImFontAtlas* atlas) -{ - if (atlas->AALineRectIds.size() > 0) - return; - - if ((atlas->Flags & ImFontAtlasFlags_NoAALines)) - return; - - const int max = IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX; - - for (int n = 0; n < max; n++) - { - const int width = n + 1; // The line width this entry corresponds to - // The "width + 3" here is interesting - +2 is to give space for the end caps, but the remaining +1 is because (empirically) to match the behaviour of the untextured render path we need to draw lines one pixel wider - atlas->AALineRectIds.push_back(atlas->AddCustomRectRegular(width + 3, FONT_ATLAS_AA_LINE_TEX_HEIGHT)); - } -} - -void ImFontAtlasBuildAALinesTexData(ImFontAtlas* atlas) -{ - IM_ASSERT(atlas->TexPixelsAlpha8 != NULL); - IM_ASSERT(atlas->TexUvAALines.size() == 0); - - if (atlas->Flags & ImFontAtlasFlags_NoAALines) - return; - - const int w = atlas->TexWidth; - const unsigned int max = IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX; - - for (unsigned int n = 0; n < max; n++) - { - IM_ASSERT(atlas->AALineRectIds.size() > (int)n); - ImFontAtlas::CustomRect& r = atlas->CustomRects[atlas->AALineRectIds[n]]; - IM_ASSERT(r.IsPacked()); - - // We fill as many lines as we were given, to allow for >1 lines being used to work around sampling weirdness - for (unsigned int y = 0; y < r.Height; y++) - { - unsigned char* write_ptr = &atlas->TexPixelsAlpha8[r.X + ((r.Y + y) * w)]; - - // Each line consists of two empty pixels at the ends, with a line of solid pixels in the middle - *(write_ptr++) = 0; - for (unsigned short x = 0; x < (r.Width - 2U); x++) - { - *(write_ptr++) = 0xFF; - } - *(write_ptr++) = 0; - } - - ImVec2 uv0, uv1; - atlas->CalcCustomRectUV(&r, &uv0, &uv1); - float halfV = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the texture as we want a horizontal slice (with some padding either side to avoid sampling artifacts) - atlas->TexUvAALines.push_back(ImVec4(uv0.x, halfV, uv1.x, halfV)); - } -} - void ImFontAtlasBuildFinish(ImFontAtlas* atlas) { // Render into our custom data blocks - IM_ASSERT(atlas->TexPixelsAlpha8 != NULL); ImFontAtlasBuildRenderDefaultTexData(atlas); - - // Render anti-aliased line textures - ImFontAtlasBuildAALinesTexData(atlas); + ImFontAtlasBuildRenderAALinesTexData(atlas); // Register custom rectangle glyphs for (int i = 0; i < atlas->CustomRects.Size; i++) diff --git a/imgui_internal.h b/imgui_internal.h index 643aea4d..d38d9b78 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -2023,10 +2023,8 @@ namespace ImGui // ImFontAtlas internals IMGUI_API bool ImFontAtlasBuildWithStbTruetype(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildInit(ImFontAtlas* atlas); -IMGUI_API void ImFontAtlasBuildRegisterAALineCustomRects(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); -IMGUI_API void ImFontAtlasBuildAALinesTexData(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildFinish(ImFontAtlas* atlas); IMGUI_API void ImFontAtlasBuildMultiplyCalcLookupTable(unsigned char out_table[256], float in_multiply_factor); IMGUI_API void ImFontAtlasBuildMultiplyRectAlpha8(const unsigned char table[256], unsigned char* pixels, int x, int y, int w, int h, int stride); From 403bf45245901031e112f8de4828cdd8c7eee805 Mon Sep 17 00:00:00 2001 From: Ben Carter Date: Thu, 6 Feb 2020 14:30:29 +0900 Subject: [PATCH 4/9] Texture-based thick lines: Allow interpolation between textures for non-integer line widths --- imgui.h | 2 +- imgui_draw.cpp | 88 +++++++++++++++++++++++++++++------------------- imgui_internal.h | 2 +- 3 files changed, 55 insertions(+), 37 deletions(-) diff --git a/imgui.h b/imgui.h index f361102c..47f15fb1 100644 --- a/imgui.h +++ b/imgui.h @@ -2323,7 +2323,7 @@ struct ImFontAtlas // [Internal] Packing data int PackIdMouseCursors; // Custom texture rectangle ID for white pixel and mouse cursors - ImVector AALineRectIds; // Custom texture rectangle IDs for anti-aliased lines + int AALineRectId; // Custom texture rectangle ID for anti-aliased lines ImVector TexUvAALines; // UVs for anti-aliased line textures #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 41d25955..376d4141 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -677,11 +677,12 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 const float AA_SIZE = 1.0f; const ImU32 col_trans = col & ~IM_COL32_A_MASK; - // The -0.5f here is to better match the geometry-based code, and also shift the transition point from one width texture to another off the integer values (where it will be less noticeable) - const int integer_thickness = ImMax((int)(thickness - 0.5f), 1); + // The thick_line test is an attempt to compensate for the way half_draw_size gets calculated later, which special-cases 1.0f width lines + const int integer_thickness = thick_line ? ImMax((int)(thickness), 1) : 2; + const float fractional_thickness = thick_line ? (thickness) - integer_thickness : 0.0f; // Do we want to draw this line using a texture? - const bool use_texture = (Flags & ImDrawListFlags_AntiAliasedLinesUseTexData) && (integer_thickness <= IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX); + const bool use_texture = (Flags & ImDrawListFlags_AntiAliasedLinesUseTexData) && (integer_thickness < IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX); // We should never hit this, because NewFrame() doesn't set ImDrawListFlags_AntiAliasedLinesUseTexData unless ImFontAtlasFlags_NoAALines is off IM_ASSERT_PARANOID((!use_texture) || (!(_Data->Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoAALines))); @@ -711,8 +712,9 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 // 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 (!thick_line || use_texture) { - // The width of the geometry we need to draw - const float half_draw_size = (!thick_line) ? AA_SIZE : (AA_SIZE + (thickness * 0.5f)); + // The width of the geometry we need to draw - this is essentially pixels for the line itself, plus one pixel for AA + // We don't use AA_SIZE here because the +1 is tied to the generated texture and so alternate values won't work without changes to that code + const float half_draw_size = (thickness * 0.5f) + 1; // If line is not closed, the first and last points need to be generated differently as there are no normals to blend if (!closed) @@ -770,7 +772,18 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 if (use_texture) { // If we're using textures we only need to emit the left/right edge vertices - const ImVec4 tex_uvs = (*_Data->TexUvAALines)[integer_thickness - 1]; + + ImVec4 tex_uvs; + + if (fractional_thickness == 0.0f) // Fast path for pure integer widths + tex_uvs = (*_Data->TexUvAALines)[integer_thickness]; + else + { + // Calculate UV by interpolating between the two nearest integer line widths + const ImVec4 tex_uvs_0 = (*_Data->TexUvAALines)[integer_thickness]; + const ImVec4 tex_uvs_1 = (*_Data->TexUvAALines)[integer_thickness + 1]; + tex_uvs = ImLerp(tex_uvs_0, tex_uvs_1, fractional_thickness); + } for (int i = 0; i < points_count; i++) { @@ -2373,24 +2386,14 @@ static void ImFontAtlasBuildRenderDefaultTexData(ImFontAtlas* atlas) atlas->TexUvWhitePixel = ImVec2((r->X + 0.5f) * atlas->TexUvScale.x, (r->Y + 0.5f) * atlas->TexUvScale.y); } -// This is called/shared by both the stb_truetype and the FreeType builder. -const unsigned int FONT_ATLAS_AA_LINE_TEX_HEIGHT = 1; // Technically we only need 1 pixel in the ideal case but this can be increased if necessary to give a border to avoid sampling artifacts - static void ImFontAtlasBuildRegisterAALineCustomRects(ImFontAtlas* atlas) { - if (atlas->AALineRectIds.Size > 0) - return; - if ((atlas->Flags & ImFontAtlasFlags_NoAALines)) return; - for (int n = 0; n < IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX; n++) - { - // The "width + 3" here is interesting. +2 is to give space for the end caps, but the remaining +1 is - // because (empirically) to match the behavior of the untextured render path we need to draw lines one pixel wider. - const int width = n + 1; // The line width this entry corresponds to - atlas->AALineRectIds.push_back(atlas->AddCustomRectRegular(width + 3, FONT_ATLAS_AA_LINE_TEX_HEIGHT)); - } + const int max_width = IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX; // The maximum line width we want to generate + // The "max_width + 2" here is to give space for the end caps, whilst height (IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX+1) is to accommodate the fact we have a zero-width row + atlas->AALineRectId = atlas->AddCustomRectRegular(max_width + 2, IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX + 1); } static void ImFontAtlasBuildRenderAALinesTexData(ImFontAtlas* atlas) @@ -2401,27 +2404,42 @@ static void ImFontAtlasBuildRenderAALinesTexData(ImFontAtlas* atlas) if (atlas->Flags & ImFontAtlasFlags_NoAALines) return; - const int w = atlas->TexWidth; - for (unsigned int n = 0; n < IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX; n++) - { - IM_ASSERT(atlas->AALineRectIds.Size > (int)n); - ImFontAtlasCustomRect& r = atlas->CustomRects[atlas->AALineRectIds[n]]; - IM_ASSERT(r.IsPacked()); + ImFontAtlasCustomRect& r = atlas->CustomRects[atlas->AALineRectId]; + IM_ASSERT(r.IsPacked()); - // We fill as many lines as we were given, to allow for >1 lines being used to work around sampling weirdness - for (unsigned int y = 0; y < r.Height; y++) - { - // Each line consists of two empty pixels at the ends, with a line of solid pixels in the middle - unsigned char* write_ptr = &atlas->TexPixelsAlpha8[r.X + ((r.Y + y) * w)]; + // This generates a triangular shape in the texture, with the various line widths stacked on top of each other to allow interpolation between them + const int w = atlas->TexWidth; + for (unsigned int n = 0; n < (IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX + 1); n++) // +1 because of the zero-width row + { + unsigned int y = n; + unsigned int line_width = n; + // Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle + unsigned int pad_left = (r.Width - line_width) / 2; + unsigned int pad_right = r.Width - (pad_left + line_width); + + // Make sure we're inside the texture bounds before we start writing pixels + IM_ASSERT_PARANOID(pad_left + line_width + pad_right == r.Width); + IM_ASSERT_PARANOID(y < r.Height); + + // Write each slice + unsigned char* write_ptr = &atlas->TexPixelsAlpha8[r.X + ((r.Y + y) * w)]; + for (unsigned int x = 0; x < pad_left; x++) *(write_ptr++) = 0; - for (unsigned short x = 0; x < (r.Width - 2U); x++) - *(write_ptr++) = 0xFF; + for (unsigned int x = 0; x < line_width; x++) + *(write_ptr++) = 0xFF; + for (unsigned int x = 0; x < pad_right; x++) *(write_ptr++) = 0; - } + + // Calculate UVs for this line + ImFontAtlasCustomRect line_rect = r; + line_rect.X += (unsigned short)(pad_left - 1); + line_rect.Width = (unsigned short)(line_width + 2); + line_rect.Y += (unsigned short)y; + line_rect.Height = 1; ImVec2 uv0, uv1; - atlas->CalcCustomRectUV(&r, &uv0, &uv1); - float halfV = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the texture as we want a horizontal slice (with some padding either side to avoid sampling artifacts) + atlas->CalcCustomRectUV(&line_rect, &uv0, &uv1); + float halfV = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts atlas->TexUvAALines.push_back(ImVec4(uv0.x, halfV, uv1.x, halfV)); } } diff --git a/imgui_internal.h b/imgui_internal.h index d38d9b78..4f3882c9 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -529,7 +529,7 @@ struct IMGUI_API ImChunkStream #define IM_DRAWLIST_ARCFAST_TESSELLATION_MULTIPLIER 1 #endif -// The maximum line width to build anti-aliased textures for +// The maximum line width to build anti-aliased textures for (note that this needs to be one greater than the maximum line width you want to be able to draw using the textured path) #define IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX 65 // Data shared between all ImDrawList instances From 21d9e8e1f44b52a1f12eb04f650db99d7c34ac8a Mon Sep 17 00:00:00 2001 From: Ben Carter Date: Tue, 11 Feb 2020 11:23:43 +0900 Subject: [PATCH 5/9] Texture-based thick lines: Simplified line width calculation code and removed hack for thickness 1.0 lines --- imgui_draw.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 376d4141..18ca4bd8 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -677,9 +677,11 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 const float AA_SIZE = 1.0f; const ImU32 col_trans = col & ~IM_COL32_A_MASK; - // The thick_line test is an attempt to compensate for the way half_draw_size gets calculated later, which special-cases 1.0f width lines - const int integer_thickness = thick_line ? ImMax((int)(thickness), 1) : 2; - const float fractional_thickness = thick_line ? (thickness) - integer_thickness : 0.0f; + // 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? const bool use_texture = (Flags & ImDrawListFlags_AntiAliasedLinesUseTexData) && (integer_thickness < IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX); @@ -714,7 +716,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 { // The width of the geometry we need to draw - this is essentially pixels for the line itself, plus one pixel for AA // We don't use AA_SIZE here because the +1 is tied to the generated texture and so alternate values won't work without changes to that code - const float half_draw_size = (thickness * 0.5f) + 1; + const float half_draw_size = use_texture ? ((thickness * 0.5f) + 1) : 1.0f; // If line is not closed, the first and last points need to be generated differently as there are no normals to blend if (!closed) From a07c8b69991af2988fce91fabab975ffcb976a41 Mon Sep 17 00:00:00 2001 From: Omar Date: Mon, 17 Feb 2020 11:58:22 +0100 Subject: [PATCH 6/9] Texture-based thick lines: Fixes for AddCustomRect api, add IMGUI_HAS_TEXLINES define (temporarily) to facilitate working with test cases, Demo allows growing FrameBorderSize for testing --- imgui.h | 2 ++ imgui_demo.cpp | 2 +- imgui_draw.cpp | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/imgui.h b/imgui.h index 47f15fb1..e5e54290 100644 --- a/imgui.h +++ b/imgui.h @@ -1993,6 +1993,8 @@ enum ImDrawCornerFlags_ ImDrawCornerFlags_All = 0xF // In your function calls you may use ~0 (= all bits sets) instead of ImDrawCornerFlags_All, as a convenience }; +#define IMGUI_HAS_TEXLINES 1 + enum ImDrawListFlags_ { ImDrawListFlags_None = 0, diff --git a/imgui_demo.cpp b/imgui_demo.cpp index e90fd517..f599e034 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3698,7 +3698,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) ImGui::SliderFloat("WindowBorderSize", &style.WindowBorderSize, 0.0f, 1.0f, "%.0f"); ImGui::SliderFloat("ChildBorderSize", &style.ChildBorderSize, 0.0f, 1.0f, "%.0f"); ImGui::SliderFloat("PopupBorderSize", &style.PopupBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui::SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, 1.0f, "%.0f"); + ImGui::SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, 4.0f, "%.1f"); ImGui::SliderFloat("TabBorderSize", &style.TabBorderSize, 0.0f, 1.0f, "%.0f"); ImGui::Text("Rounding"); ImGui::SliderFloat("WindowRounding", &style.WindowRounding, 0.0f, 12.0f, "%.0f"); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 18ca4bd8..a4f68f9d 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -2393,8 +2393,8 @@ static void ImFontAtlasBuildRegisterAALineCustomRects(ImFontAtlas* atlas) if ((atlas->Flags & ImFontAtlasFlags_NoAALines)) return; - const int max_width = IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX; // The maximum line width we want to generate // The "max_width + 2" here is to give space for the end caps, whilst height (IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX+1) is to accommodate the fact we have a zero-width row + const int max_width = IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX; // The maximum line width we want to generate atlas->AALineRectId = atlas->AddCustomRectRegular(max_width + 2, IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX + 1); } From 78d6bdf08036fdeea08ee9e6a0191f058f89dca0 Mon Sep 17 00:00:00 2001 From: omar Date: Sat, 16 May 2020 16:55:05 +0200 Subject: [PATCH 7/9] Texture-based thick lines: Remove unnecessary indirection in fetching UV data, removed lerp call, renames, tweaks. --- imgui.cpp | 8 ++--- imgui.h | 17 ++++++---- imgui_demo.cpp | 4 +-- imgui_draw.cpp | 84 +++++++++++++++++++++++------------------------- imgui_internal.h | 5 +-- 5 files changed, 58 insertions(+), 60 deletions(-) diff --git a/imgui.cpp b/imgui.cpp index e6fe1184..b9c300fa 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -935,7 +935,7 @@ ImGuiStyle::ImGuiStyle() DisplaySafeAreaPadding = ImVec2(3,3); // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows. MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. AntiAliasedLines = true; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. - AntiAliasedLinesUseTexData = true; // Draw anti-aliased lines using textures where possible. + AntiAliasedLinesUseTex = true; // Enable anti-aliased lines/borders using textures where possible. Requires back-end to render with bilinear filtering. AntiAliasedFill = true; // Enable anti-aliased filled shapes (rounded rectangles, circles, etc.). 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. CircleSegmentMaxError = 1.60f; // 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. @@ -3689,8 +3689,8 @@ void ImGui::NewFrame() g.DrawListSharedData.InitialFlags = ImDrawListFlags_None; if (g.Style.AntiAliasedLines) g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLines; - if (g.Style.AntiAliasedLinesUseTexData && !(g.Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoAALines)) - g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLinesUseTexData; + if (g.Style.AntiAliasedLinesUseTex && !(g.Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoAntiAliasedLines)) + g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLinesUseTex; if (g.Style.AntiAliasedFill) g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedFill; if (g.IO.BackendFlags & ImGuiBackendFlags_RendererHasVtxOffset) @@ -6161,7 +6161,7 @@ void ImGui::SetCurrentFont(ImFont* font) ImFontAtlas* atlas = g.Font->ContainerAtlas; g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel; - g.DrawListSharedData.TexUvAALines = &atlas->TexUvAALines; + g.DrawListSharedData.TexUvAALines = atlas->TexUvAALines; g.DrawListSharedData.Font = g.Font; g.DrawListSharedData.FontSize = g.FontSize; } diff --git a/imgui.h b/imgui.h index e5e54290..5e86bae5 100644 --- a/imgui.h +++ b/imgui.h @@ -1439,7 +1439,7 @@ struct ImGuiStyle ImVec2 DisplaySafeAreaPadding; // If you cannot see the edges of your screen (e.g. on a TV) increase the safe area padding. Apply to popups/tooltips as well regular windows. NB: Prefer configuring your TV sets correctly! float MouseCursorScale; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. bool AntiAliasedLines; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. - bool AntiAliasedLinesUseTexData; // Draw anti-aliased lines using textures where possible. + bool AntiAliasedLinesUseTex; // Enable anti-aliased lines/borders using textures where possible. Requires back-end to render with bilinear filtering. bool AntiAliasedFill; // Enable anti-aliased edges around filled shapes (rounded rectangles, circles, etc.). Disable if you are really tight on CPU/GPU. float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. float CircleSegmentMaxError; // 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. @@ -1897,6 +1897,11 @@ struct ImColor // 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_NoAALines to disable baking. +#ifndef IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX +#define IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX (63) +#endif + // ImDrawCallback: Draw callbacks for advanced uses [configurable type: override in imconfig.h] // NB: You most likely do NOT need to use draw callbacks just to create your own widget or customized UI rendering, // you can poke into the draw list for that! Draw callback may be useful for example to: @@ -2000,8 +2005,8 @@ 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_AntiAliasedFill = 1 << 1, // Enable anti-aliased edge around filled shapes (rounded rectangles, circles). - ImDrawListFlags_AllowVtxOffset = 1 << 2, // Can emit 'VtxOffset > 0' to allow large meshes. Set when 'ImGuiBackendFlags_RendererHasVtxOffset' is enabled. - ImDrawListFlags_AntiAliasedLinesUseTexData = 1 << 3 // Should anti-aliased lines be drawn using textures where possible? + ImDrawListFlags_AntiAliasedLinesUseTex = 1 << 2, // Should anti-aliased lines be drawn using textures where possible? + ImDrawListFlags_AllowVtxOffset = 1 << 3 // Can emit 'VtxOffset > 0' to allow large meshes. Set when 'ImGuiBackendFlags_RendererHasVtxOffset' is enabled. }; // Draw command list @@ -2221,7 +2226,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_NoAALines = 1 << 2 // Don't build anti-aliased line textures into the atlas + ImFontAtlasFlags_NoAntiAliasedLines = 1 << 2 // Don't build anti-aliased line textures into the atlas (save a little texture memory). They will be rendered using polygons (a little bit more expensive) }; // Load and rasterize multiple TTF/OTF fonts into a same texture. The font atlas will build a single texture holding: @@ -2322,11 +2327,11 @@ 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 CustomRects; // Rectangles for packing custom texture data into the atlas. ImVector ConfigData; // Configuration data + ImVec4 TexUvAALines[IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX + 1]; // UVs for anti-aliased line textures // [Internal] Packing data int PackIdMouseCursors; // Custom texture rectangle ID for white pixel and mouse cursors - int AALineRectId; // Custom texture rectangle ID for anti-aliased lines - ImVector TexUvAALines; // UVs for anti-aliased line textures + int PackIdAALines; // Custom texture rectangle ID for anti-aliased lines #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS typedef ImFontAtlasCustomRect CustomRect; // OBSOLETED in 1.72+ diff --git a/imgui_demo.cpp b/imgui_demo.cpp index f599e034..8b5d57d4 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3698,7 +3698,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) ImGui::SliderFloat("WindowBorderSize", &style.WindowBorderSize, 0.0f, 1.0f, "%.0f"); ImGui::SliderFloat("ChildBorderSize", &style.ChildBorderSize, 0.0f, 1.0f, "%.0f"); ImGui::SliderFloat("PopupBorderSize", &style.PopupBorderSize, 0.0f, 1.0f, "%.0f"); - ImGui::SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, 4.0f, "%.1f"); + ImGui::SliderFloat("FrameBorderSize", &style.FrameBorderSize, 0.0f, 1.0f, "%.0f"); ImGui::SliderFloat("TabBorderSize", &style.TabBorderSize, 0.0f, 1.0f, "%.0f"); ImGui::Text("Rounding"); ImGui::SliderFloat("WindowRounding", &style.WindowRounding, 0.0f, 12.0f, "%.0f"); @@ -3831,7 +3831,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) { ImGui::Checkbox("Anti-aliased lines", &style.AntiAliasedLines); ImGui::SameLine(); HelpMarker("When disabling anti-aliasing lines, you'll probably want to disable borders in your style as well."); - ImGui::Checkbox("Anti-aliased lines use texture data", &style.AntiAliasedLinesUseTexData); + ImGui::Checkbox("Anti-aliased lines use texture", &style.AntiAliasedLinesUseTex); ImGui::SameLine(); HelpMarker("Faster lines using texture data. Requires texture to use bilinear sampling (not nearest)."); ImGui::Checkbox("Anti-aliased fill", &style.AntiAliasedFill); ImGui::PushItemWidth(100); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index a4f68f9d..24e8a654 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -668,7 +668,6 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 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 > 1.0f); if (Flags & ImDrawListFlags_AntiAliasedLines) @@ -679,12 +678,11 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 // 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; + const int integer_thickness = (int)thickness; + const float fractional_thickness = thickness - integer_thickness; // Do we want to draw this line using a texture? - const bool use_texture = (Flags & ImDrawListFlags_AntiAliasedLinesUseTexData) && (integer_thickness < IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX); + const bool use_texture = (Flags & ImDrawListFlags_AntiAliasedLinesUseTex) && (integer_thickness < IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX); // We should never hit this, because NewFrame() doesn't set ImDrawListFlags_AntiAliasedLinesUseTexData unless ImFontAtlasFlags_NoAALines is off IM_ASSERT_PARANOID((!use_texture) || (!(_Data->Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoAALines))); @@ -712,8 +710,11 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 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 (!thick_line || use_texture) + 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 // We don't use AA_SIZE here because the +1 is tied to the generated texture and so alternate values won't work without changes to that code const float half_draw_size = use_texture ? ((thickness * 0.5f) + 1) : 1.0f; @@ -734,7 +735,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 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 - unsigned int idx2 = ((i1 + 1) == points_count) ? _VtxCurrentIdx : (idx1 + (use_texture ? 2 : 3)); // Vertex index for end of 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; @@ -774,32 +775,30 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 if (use_texture) { // If we're using textures we only need to emit the left/right edge vertices - - ImVec4 tex_uvs; - - if (fractional_thickness == 0.0f) // Fast path for pure integer widths - tex_uvs = (*_Data->TexUvAALines)[integer_thickness]; - else + ImVec4 tex_uvs = _Data->TexUvAALines[integer_thickness]; + if (fractional_thickness != 0.0f) { - // Calculate UV by interpolating between the two nearest integer line widths - const ImVec4 tex_uvs_0 = (*_Data->TexUvAALines)[integer_thickness]; - const ImVec4 tex_uvs_1 = (*_Data->TexUvAALines)[integer_thickness + 1]; - tex_uvs = ImLerp(tex_uvs_0, tex_uvs_1, fractional_thickness); + const ImVec4 tex_uvs_1 = _Data->TexUvAALines[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.y); + ImVec2 tex_uv1(tex_uvs.z, tex_uvs.w); for (int i = 0; i < points_count; i++) { - _VtxWritePtr[0].pos = temp_points[i * 2 + 0]; _VtxWritePtr[0].uv = ImVec2(tex_uvs.x, tex_uvs.y); _VtxWritePtr[0].col = col; // Left-side outer edge - _VtxWritePtr[1].pos = temp_points[i * 2 + 1]; _VtxWritePtr[1].uv = ImVec2(tex_uvs.z, tex_uvs.y); _VtxWritePtr[1].col = col; // Right-side outer edge + _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 centre vertex as well + // 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; // Centre of line + _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; @@ -808,7 +807,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 } else { - // For untextured lines that are greater than a pixel in width, we need to draw the solid line core and thus require four vertices per point + // [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 @@ -880,7 +879,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 } else { - // Non texture-based, Non anti-aliased lines + // [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); @@ -1720,7 +1719,7 @@ ImFontAtlas::ImFontAtlas() TexWidth = TexHeight = 0; TexUvScale = ImVec2(0.0f, 0.0f); TexUvWhitePixel = ImVec2(0.0f, 0.0f); - PackIdMouseCursors = -1; + PackIdMouseCursors = PackIdAALines = -1; } ImFontAtlas::~ImFontAtlas() @@ -1748,7 +1747,7 @@ void ImFontAtlas::ClearInputData() } ConfigData.clear(); CustomRects.clear(); - PackIdMouseCursors = -1; + PackIdMouseCursors = PackIdAALines = -1; } void ImFontAtlas::ClearTexData() @@ -2390,41 +2389,38 @@ static void ImFontAtlasBuildRenderDefaultTexData(ImFontAtlas* atlas) static void ImFontAtlasBuildRegisterAALineCustomRects(ImFontAtlas* atlas) { - if ((atlas->Flags & ImFontAtlasFlags_NoAALines)) + if (atlas->Flags & ImFontAtlasFlags_NoAntiAliasedLines) return; - // The "max_width + 2" here is to give space for the end caps, whilst height (IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX+1) is to accommodate the fact we have a zero-width row - const int max_width = IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX; // The maximum line width we want to generate - atlas->AALineRectId = atlas->AddCustomRectRegular(max_width + 2, IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX + 1); + // 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 + atlas->PackIdAALines = atlas->AddCustomRectRegular(IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX + 1); } static void ImFontAtlasBuildRenderAALinesTexData(ImFontAtlas* atlas) { IM_ASSERT(atlas->TexPixelsAlpha8 != NULL); - IM_ASSERT(atlas->TexUvAALines.Size == 0); - - if (atlas->Flags & ImFontAtlasFlags_NoAALines) + if (atlas->Flags & ImFontAtlasFlags_NoAntiAliasedLines) return; - ImFontAtlasCustomRect& r = atlas->CustomRects[atlas->AALineRectId]; - IM_ASSERT(r.IsPacked()); + ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(atlas->PackIdAALines); + IM_ASSERT(r->IsPacked()); // This generates a triangular shape in the texture, with the various line widths stacked on top of each other to allow interpolation between them const int w = atlas->TexWidth; - for (unsigned int n = 0; n < (IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX + 1); n++) // +1 because of the zero-width row + for (unsigned int n = 0; n < IM_DRAWLIST_TEX_AA_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 unsigned int y = n; unsigned int line_width = n; - // Each line consists of at least two empty pixels at the ends, with a line of solid pixels in the middle - unsigned int pad_left = (r.Width - line_width) / 2; - unsigned int pad_right = r.Width - (pad_left + line_width); + unsigned int pad_left = (r->Width - line_width) / 2; + unsigned int pad_right = r->Width - (pad_left + line_width); // Make sure we're inside the texture bounds before we start writing pixels IM_ASSERT_PARANOID(pad_left + line_width + pad_right == r.Width); IM_ASSERT_PARANOID(y < r.Height); // Write each slice - unsigned char* write_ptr = &atlas->TexPixelsAlpha8[r.X + ((r.Y + y) * w)]; + unsigned char* write_ptr = &atlas->TexPixelsAlpha8[r->X + ((r->Y + y) * w)]; for (unsigned int x = 0; x < pad_left; x++) *(write_ptr++) = 0; for (unsigned int x = 0; x < line_width; x++) @@ -2433,16 +2429,16 @@ static void ImFontAtlasBuildRenderAALinesTexData(ImFontAtlas* atlas) *(write_ptr++) = 0; // Calculate UVs for this line - ImFontAtlasCustomRect line_rect = r; + ImFontAtlasCustomRect line_rect = *r; line_rect.X += (unsigned short)(pad_left - 1); - line_rect.Width = (unsigned short)(line_width + 2); line_rect.Y += (unsigned short)y; + line_rect.Width = (unsigned short)(line_width + 2); line_rect.Height = 1; ImVec2 uv0, uv1; atlas->CalcCustomRectUV(&line_rect, &uv0, &uv1); - float halfV = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts - atlas->TexUvAALines.push_back(ImVec4(uv0.x, halfV, uv1.x, halfV)); + float half_v = (uv0.y + uv1.y) * 0.5f; // Calculate a constant V in the middle of the row to avoid sampling artifacts + atlas->TexUvAALines[n] = ImVec4(uv0.x, half_v, uv1.x, half_v); } } diff --git a/imgui_internal.h b/imgui_internal.h index 4f3882c9..629ddc32 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -529,9 +529,6 @@ struct IMGUI_API ImChunkStream #define IM_DRAWLIST_ARCFAST_TESSELLATION_MULTIPLIER 1 #endif -// The maximum line width to build anti-aliased textures for (note that this needs to be one greater than the maximum line width you want to be able to draw using the textured path) -#define IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX 65 - // Data shared between all ImDrawList instances // You may want to create your own instance of this if you want to use ImDrawList completely without ImGui. In that case, watch out for future changes to this structure. struct IMGUI_API ImDrawListSharedData @@ -548,7 +545,7 @@ struct IMGUI_API ImDrawListSharedData ImVec2 ArcFastVtx[12 * IM_DRAWLIST_ARCFAST_TESSELLATION_MULTIPLIER]; // FIXME: Bake rounded corners fill/borders in atlas ImU8 CircleSegmentCounts[64]; // Precomputed segment count for given radius (array index + 1) before we calculate it dynamically (to avoid calculation overhead) - ImVector* TexUvAALines; // UV of anti-aliased lines in the atlas + const ImVec4* TexUvAALines; // UV of anti-aliased lines in the atlas ImDrawListSharedData(); void SetCircleSegmentMaxError(float max_error); From b5bae9781df1204a7dc6ae8b4b1f605c05f0fd74 Mon Sep 17 00:00:00 2001 From: Ben Carter Date: Tue, 7 Jul 2020 16:19:27 +0900 Subject: [PATCH 8/9] Texture-based thick lines: Only use textured lines for integer line widths --- imgui_draw.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 24e8a654..15a6c6f2 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -681,8 +681,8 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 const int integer_thickness = (int)thickness; const float fractional_thickness = thickness - integer_thickness; - // Do we want to draw this line using a texture? - const bool use_texture = (Flags & ImDrawListFlags_AntiAliasedLinesUseTex) && (integer_thickness < IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX); + // 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) + const bool use_texture = (Flags & ImDrawListFlags_AntiAliasedLinesUseTex) && (integer_thickness < IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX) && (fractional_thickness >= -0.00001f) && (fractional_thickness <= 0.00001f); // We should never hit this, because NewFrame() doesn't set ImDrawListFlags_AntiAliasedLinesUseTexData unless ImFontAtlasFlags_NoAALines is off IM_ASSERT_PARANOID((!use_texture) || (!(_Data->Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoAALines))); From 3a6c9907cd182795f82c67df5d77e88bcfe9563c Mon Sep 17 00:00:00 2001 From: omar Date: Tue, 7 Jul 2020 13:24:10 +0200 Subject: [PATCH 9/9] Texture-based thick lines: Minor tweaks and rename toward merging in master. Changes to allow changing AA_SIZE (disable texture path). --- docs/CHANGELOG.txt | 9 +++++ imgui.cpp | 6 ++-- imgui.h | 28 +++++++-------- imgui_demo.cpp | 2 +- imgui_draw.cpp | 85 +++++++++++++++++++--------------------------- imgui_internal.h | 3 +- 6 files changed, 63 insertions(+), 70 deletions(-) diff --git a/docs/CHANGELOG.txt b/docs/CHANGELOG.txt index c62a140d..8043bf47 100644 --- a/docs/CHANGELOG.txt +++ b/docs/CHANGELOG.txt @@ -35,6 +35,15 @@ HOW TO UPDATE? VERSION 1.78 WIP (In Progress) ----------------------------------------------------------------------- +Other Changes: + +- ImDrawList: Thick anti-aliased strokes (> 1.0f) with integer thickness now use a texture-based + path, reducing the amount of vertices/indices and CPU/GPU usage. (#3245) [@Shironekoben] + - This change will facilitate the wider use of thick borders in future style changes. + - Requires an extra bit of texture space (~64x64 by default), relies on GPU bilinear filtering. + - Clear io.AntiAliasedLinesUseTex = false; to disable rendering using this method. + - Clear ImFontAtlasFlags_NoBakedLines in ImFontAtlas::Flags to disable baking data in texture. + ----------------------------------------------------------------------- VERSION 1.77 (Released 2020-06-29) diff --git a/imgui.cpp b/imgui.cpp index b9c300fa..37e611ad 100644 --- a/imgui.cpp +++ b/imgui.cpp @@ -935,7 +935,7 @@ ImGuiStyle::ImGuiStyle() DisplaySafeAreaPadding = ImVec2(3,3); // If you cannot see the edge of your screen (e.g. on a TV) increase the safe area padding. Covers popups/tooltips as well regular windows. MouseCursorScale = 1.0f; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. 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. Requires back-end to render with bilinear filtering. + AntiAliasedLinesUseTex = true; // Enable anti-aliased lines/borders using textures where possible. Require back-end to render with bilinear filtering. AntiAliasedFill = true; // Enable anti-aliased filled shapes (rounded rectangles, circles, etc.). 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. CircleSegmentMaxError = 1.60f; // 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. @@ -3689,7 +3689,7 @@ void ImGui::NewFrame() g.DrawListSharedData.InitialFlags = ImDrawListFlags_None; if (g.Style.AntiAliasedLines) g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLines; - if (g.Style.AntiAliasedLinesUseTex && !(g.Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoAntiAliasedLines)) + if (g.Style.AntiAliasedLinesUseTex && !(g.Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoBakedLines)) g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedLinesUseTex; if (g.Style.AntiAliasedFill) g.DrawListSharedData.InitialFlags |= ImDrawListFlags_AntiAliasedFill; @@ -6161,7 +6161,7 @@ void ImGui::SetCurrentFont(ImFont* font) ImFontAtlas* atlas = g.Font->ContainerAtlas; g.DrawListSharedData.TexUvWhitePixel = atlas->TexUvWhitePixel; - g.DrawListSharedData.TexUvAALines = atlas->TexUvAALines; + g.DrawListSharedData.TexUvLines = atlas->TexUvLines; g.DrawListSharedData.Font = g.Font; g.DrawListSharedData.FontSize = g.FontSize; } diff --git a/imgui.h b/imgui.h index 5e86bae5..6f4dec52 100644 --- a/imgui.h +++ b/imgui.h @@ -60,7 +60,7 @@ Index of this file: // Version // (Integer encoded as XYYZZ for use in #if preprocessor conditionals. Work in progress versions typically starts at XYY99 then bounce up to XYY00, XYY01 etc. when release tagging happens) #define IMGUI_VERSION "1.78 WIP" -#define IMGUI_VERSION_NUM 17702 +#define IMGUI_VERSION_NUM 17703 #define IMGUI_CHECKVERSION() ImGui::DebugCheckVersionAndDataLayout(IMGUI_VERSION, sizeof(ImGuiIO), sizeof(ImGuiStyle), sizeof(ImVec2), sizeof(ImVec4), sizeof(ImDrawVert), sizeof(ImDrawIdx)) // Define attributes of all API symbols declarations (e.g. for DLL under Windows) @@ -1438,9 +1438,9 @@ struct ImGuiStyle ImVec2 DisplayWindowPadding; // Window position are clamped to be visible within the display area by at least this amount. Only applies to regular windows. ImVec2 DisplaySafeAreaPadding; // If you cannot see the edges of your screen (e.g. on a TV) increase the safe area padding. Apply to popups/tooltips as well regular windows. NB: Prefer configuring your TV sets correctly! float MouseCursorScale; // Scale software rendered mouse cursor (when io.MouseDrawCursor is enabled). May be removed later. - bool AntiAliasedLines; // Enable anti-aliased lines/borders. Disable if you are really tight on CPU/GPU. - bool AntiAliasedLinesUseTex; // Enable anti-aliased lines/borders using textures where possible. Requires back-end to render with bilinear filtering. - bool AntiAliasedFill; // Enable anti-aliased edges around filled shapes (rounded rectangles, circles, etc.). Disable if you are really tight on CPU/GPU. + 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 back-end to render with bilinear 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). float CurveTessellationTol; // Tessellation tolerance when using PathBezierCurveTo() without a specific number of segments. Decrease for highly tessellated curves (higher quality, more polygons), increase to reduce quality. float CircleSegmentMaxError; // 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. ImVec4 Colors[ImGuiCol_COUNT]; @@ -1897,9 +1897,9 @@ struct ImColor // 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_NoAALines to disable baking. -#ifndef IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX -#define IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX (63) +// 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 (63) #endif // ImDrawCallback: Draw callbacks for advanced uses [configurable type: override in imconfig.h] @@ -1998,14 +1998,14 @@ enum ImDrawCornerFlags_ ImDrawCornerFlags_All = 0xF // In your function calls you may use ~0 (= all bits sets) instead of ImDrawCornerFlags_All, as a convenience }; -#define IMGUI_HAS_TEXLINES 1 - +// Flags for ImDrawList. 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_AntiAliasedFill = 1 << 1, // Enable anti-aliased edge around filled shapes (rounded rectangles, circles). - ImDrawListFlags_AntiAliasedLinesUseTex = 1 << 2, // Should anti-aliased lines be drawn using textures where possible? + ImDrawListFlags_AntiAliasedLinesUseTex = 1 << 1, // Enable anti-aliased lines/borders using textures when possible. Require back-end to render with bilinear 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. }; @@ -2226,7 +2226,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_NoAntiAliasedLines = 1 << 2 // Don't build anti-aliased line textures into the atlas (save a little texture memory). They will be rendered using polygons (a little bit more expensive) + ImFontAtlasFlags_NoBakedLines = 1 << 2 // Don't build thick line textures into the atlas (save a little texture memory). The AntiAliasedLinesUseTex features uses them, otherwise they will be rendered using polygons (more expensive for CPU/GPU). }; // Load and rasterize multiple TTF/OTF fonts into a same texture. The font atlas will build a single texture holding: @@ -2327,11 +2327,11 @@ 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 CustomRects; // Rectangles for packing custom texture data into the atlas. ImVector ConfigData; // Configuration data - ImVec4 TexUvAALines[IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX + 1]; // UVs for anti-aliased line textures + ImVec4 TexUvLines[IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1]; // UVs for baked anti-aliased lines // [Internal] Packing data int PackIdMouseCursors; // Custom texture rectangle ID for white pixel and mouse cursors - int PackIdAALines; // Custom texture rectangle ID for anti-aliased lines + int PackIdLines; // Custom texture rectangle ID for baked anti-aliased lines #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS typedef ImFontAtlasCustomRect CustomRect; // OBSOLETED in 1.72+ diff --git a/imgui_demo.cpp b/imgui_demo.cpp index 8b5d57d4..d66bf014 100644 --- a/imgui_demo.cpp +++ b/imgui_demo.cpp @@ -3832,7 +3832,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref) ImGui::Checkbox("Anti-aliased lines", &style.AntiAliasedLines); ImGui::SameLine(); HelpMarker("When disabling anti-aliasing lines, you'll probably want to disable borders in your style as well."); ImGui::Checkbox("Anti-aliased lines use texture", &style.AntiAliasedLinesUseTex); - ImGui::SameLine(); HelpMarker("Faster lines using texture data. Requires texture to use bilinear sampling (not nearest)."); + ImGui::SameLine(); HelpMarker("Faster lines using texture data. Require back-end to render with bilinear filtering (not point/nearest filtering)."); ImGui::Checkbox("Anti-aliased fill", &style.AntiAliasedFill); ImGui::PushItemWidth(100); ImGui::DragFloat("Curve Tessellation Tolerance", &style.CurveTessellationTol, 0.02f, 0.10f, 10.0f, "%.2f"); diff --git a/imgui_draw.cpp b/imgui_draw.cpp index 15a6c6f2..942c2d90 100644 --- a/imgui_draw.cpp +++ b/imgui_draw.cpp @@ -358,7 +358,7 @@ ImDrawListSharedData::ImDrawListSharedData() ArcFastVtx[i] = ImVec2(ImCos(a), ImSin(a)); } memset(CircleSegmentCounts, 0, sizeof(CircleSegmentCounts)); // This will be set by SetCircleSegmentMaxError() - TexUvAALines = NULL; + TexUvLines = NULL; } void ImDrawListSharedData::SetCircleSegmentMaxError(float max_error) @@ -681,11 +681,13 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 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) - const bool use_texture = (Flags & ImDrawListFlags_AntiAliasedLinesUseTex) && (integer_thickness < IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX) && (fractional_thickness >= -0.00001f) && (fractional_thickness <= 0.00001f); + // 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); - // We should never hit this, because NewFrame() doesn't set ImDrawListFlags_AntiAliasedLinesUseTexData unless ImFontAtlasFlags_NoAALines is off - IM_ASSERT_PARANOID((!use_texture) || (!(_Data->Font->ContainerAtlas->Flags & ImFontAtlasFlags_NoAALines))); + // We should never hit this, because NewFrame() doesn't set ImDrawListFlags_AntiAliasedLinesUseTex unless ImFontAtlasFlags_NoBakedLines is off + IM_ASSERT_PARANOID(!use_texture || !(_Data->Font->ContainerAtlas->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); @@ -693,7 +695,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 // 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 - ImVec2* temp_normals = (ImVec2*)alloca(points_count * ((thick_line && !use_texture) ? 5 : 3) * sizeof(ImVec2)); //-V630 + ImVec2* temp_normals = (ImVec2*)alloca(points_count * ((use_texture || !thick_line) ? 3 : 5) * sizeof(ImVec2)); //-V630 ImVec2* temp_points = temp_normals + points_count; // Calculate normals (tangents) for each line segment @@ -717,7 +719,7 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 // The width of the geometry we need to draw - this is essentially pixels for the line itself, plus one pixel for AA // We don't use AA_SIZE here because the +1 is tied to the generated texture and so alternate values won't work without changes to that code - const float half_draw_size = use_texture ? ((thickness * 0.5f) + 1) : 1.0f; + 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) @@ -775,10 +777,10 @@ void ImDrawList::AddPolyline(const ImVec2* points, const int points_count, ImU32 if (use_texture) { // If we're using textures we only need to emit the left/right edge vertices - ImVec4 tex_uvs = _Data->TexUvAALines[integer_thickness]; + ImVec4 tex_uvs = _Data->TexUvLines[integer_thickness]; if (fractional_thickness != 0.0f) { - const ImVec4 tex_uvs_1 = _Data->TexUvAALines[integer_thickness + 1]; + 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; @@ -1719,7 +1721,7 @@ ImFontAtlas::ImFontAtlas() TexWidth = TexHeight = 0; TexUvScale = ImVec2(0.0f, 0.0f); TexUvWhitePixel = ImVec2(0.0f, 0.0f); - PackIdMouseCursors = PackIdAALines = -1; + PackIdMouseCursors = PackIdLines = -1; } ImFontAtlas::~ImFontAtlas() @@ -1747,7 +1749,7 @@ void ImFontAtlas::ClearInputData() } ConfigData.clear(); CustomRects.clear(); - PackIdMouseCursors = PackIdAALines = -1; + PackIdMouseCursors = PackIdLines = -1; } void ImFontAtlas::ClearTexData() @@ -2387,27 +2389,15 @@ static void ImFontAtlasBuildRenderDefaultTexData(ImFontAtlas* atlas) atlas->TexUvWhitePixel = ImVec2((r->X + 0.5f) * atlas->TexUvScale.x, (r->Y + 0.5f) * atlas->TexUvScale.y); } -static void ImFontAtlasBuildRegisterAALineCustomRects(ImFontAtlas* atlas) +static void ImFontAtlasBuildRenderLinesTexData(ImFontAtlas* atlas) { - if (atlas->Flags & ImFontAtlasFlags_NoAntiAliasedLines) + if (atlas->Flags & ImFontAtlasFlags_NoBakedLines) return; - // 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 - atlas->PackIdAALines = atlas->AddCustomRectRegular(IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX + 1); -} - -static void ImFontAtlasBuildRenderAALinesTexData(ImFontAtlas* atlas) -{ - IM_ASSERT(atlas->TexPixelsAlpha8 != NULL); - if (atlas->Flags & ImFontAtlasFlags_NoAntiAliasedLines) - return; - - ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(atlas->PackIdAALines); - IM_ASSERT(r->IsPacked()); - // This generates a triangular shape in the texture, with the various line widths stacked on top of each other to allow interpolation between them - const int w = atlas->TexWidth; - for (unsigned int n = 0; n < IM_DRAWLIST_TEX_AA_LINES_WIDTH_MAX + 1; n++) // +1 because of the zero-width row + ImFontAtlasCustomRect* r = atlas->GetCustomRectByIndex(atlas->PackIdLines); + IM_ASSERT(r->IsPacked()); + for (unsigned 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 unsigned int y = n; @@ -2415,30 +2405,18 @@ static void ImFontAtlasBuildRenderAALinesTexData(ImFontAtlas* atlas) unsigned int pad_left = (r->Width - line_width) / 2; unsigned int pad_right = r->Width - (pad_left + line_width); - // Make sure we're inside the texture bounds before we start writing pixels - IM_ASSERT_PARANOID(pad_left + line_width + pad_right == r.Width); - IM_ASSERT_PARANOID(y < r.Height); - // Write each slice - unsigned char* write_ptr = &atlas->TexPixelsAlpha8[r->X + ((r->Y + y) * w)]; - for (unsigned int x = 0; x < pad_left; x++) - *(write_ptr++) = 0; - for (unsigned int x = 0; x < line_width; x++) - *(write_ptr++) = 0xFF; - for (unsigned int x = 0; x < pad_right; x++) - *(write_ptr++) = 0; + IM_ASSERT(pad_left + line_width + pad_right == r->Width && y < r->Height); // Make sure we're inside the texture bounds before we start writing pixels + unsigned char* write_ptr = &atlas->TexPixelsAlpha8[r->X + ((r->Y + y) * atlas->TexWidth)]; + memset(write_ptr, 0x00, pad_left); + memset(write_ptr + pad_left, 0xFF, line_width); + memset(write_ptr + pad_left + line_width, 0x00, pad_right); // Calculate UVs for this line - ImFontAtlasCustomRect line_rect = *r; - line_rect.X += (unsigned short)(pad_left - 1); - line_rect.Y += (unsigned short)y; - line_rect.Width = (unsigned short)(line_width + 2); - line_rect.Height = 1; - - ImVec2 uv0, uv1; - atlas->CalcCustomRectUV(&line_rect, &uv0, &uv1); + 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->TexUvAALines[n] = ImVec4(uv0.x, half_v, uv1.x, half_v); + atlas->TexUvLines[n] = ImVec4(uv0.x, half_v, uv1.x, half_v); } } @@ -2454,15 +2432,22 @@ void ImFontAtlasBuildInit(ImFontAtlas* atlas) atlas->PackIdMouseCursors = atlas->AddCustomRectRegular(2, 2); } - ImFontAtlasBuildRegisterAALineCustomRects(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 + if (atlas->PackIdLines < 0) + { + if (!(atlas->Flags & ImFontAtlasFlags_NoBakedLines)) + atlas->PackIdLines = atlas->AddCustomRectRegular(IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 2, IM_DRAWLIST_TEX_LINES_WIDTH_MAX + 1); + } } // This is called/shared by both the stb_truetype and the FreeType builder. void ImFontAtlasBuildFinish(ImFontAtlas* atlas) { // Render into our custom data blocks + IM_ASSERT(atlas->TexPixelsAlpha8 != NULL); ImFontAtlasBuildRenderDefaultTexData(atlas); - ImFontAtlasBuildRenderAALinesTexData(atlas); + ImFontAtlasBuildRenderLinesTexData(atlas); // Register custom rectangle glyphs for (int i = 0; i < atlas->CustomRects.Size; i++) diff --git a/imgui_internal.h b/imgui_internal.h index 629ddc32..a6245d1a 100644 --- a/imgui_internal.h +++ b/imgui_internal.h @@ -544,8 +544,7 @@ struct IMGUI_API ImDrawListSharedData // [Internal] Lookup tables ImVec2 ArcFastVtx[12 * IM_DRAWLIST_ARCFAST_TESSELLATION_MULTIPLIER]; // FIXME: Bake rounded corners fill/borders in atlas ImU8 CircleSegmentCounts[64]; // Precomputed segment count for given radius (array index + 1) before we calculate it dynamically (to avoid calculation overhead) - - const ImVec4* TexUvAALines; // UV of anti-aliased lines in the atlas + const ImVec4* TexUvLines; // UV of anti-aliased lines in the atlas ImDrawListSharedData(); void SetCircleSegmentMaxError(float max_error);