perf(charts): fill chart areas in one linear pass
The filled sparklines and the market price chart built their area fill with PathFillConcave, which runs a per-scanline pass over the whole filled region. For a simple area under an x-monotone curve that work is unnecessary. - A new helper fills the area between an x-monotone polyline and a horizontal baseline directly as a triangle strip, in one pass over the points. The market sparklines, the market price chart and the mining hashrate sparkline all use it. The fill is flat and non-anti-aliased; the anti-aliased curve drawn on top hides the stepped top edge, and the faint side and bottom edges are imperceptible at these alphas. Tests: the strip's vertex and index counts, index bounds, and that its triangle area matches the trapezoid area under the polyline. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
#include "tooltip_style.h"
|
||||
#include "scroll_edge_fade.h"
|
||||
#include "rect_cull.h"
|
||||
#include "fill_strip.h"
|
||||
#include "../layout.h"
|
||||
#include "../schema/element_styles.h"
|
||||
#include "../schema/color_var_resolver.h"
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
#include "imgui.h"
|
||||
|
||||
namespace dragonx { namespace ui { namespace material {
|
||||
|
||||
inline int FillStripVertexCount(int n) { return n < 2 ? 0 : 2 * n; }
|
||||
inline int FillStripIndexCount(int n) { return n < 2 ? 0 : 6 * (n - 1); }
|
||||
|
||||
/// Index list for the strip: vertices come in (top_i, bottom_i) pairs, 2 triangles per column.
|
||||
/// `base` is the first vertex index; `out` must hold FillStripIndexCount(n) entries.
|
||||
inline void BuildFillStripIndices(int n, unsigned base, unsigned* out) {
|
||||
for (int i = 0; i + 1 < n; ++i) {
|
||||
const unsigned a = base + 2u * (unsigned)i, b = a + 1, c = a + 2, d = a + 3;
|
||||
*out++ = a; *out++ = b; *out++ = d;
|
||||
*out++ = a; *out++ = d; *out++ = c;
|
||||
}
|
||||
}
|
||||
|
||||
/// Fills the area between an x-monotone polyline and the horizontal line y = baseY with a flat colour,
|
||||
/// in O(n). Non-anti-aliased: draw the AA polyline on top (it hides the stepped top edge; the side/bottom
|
||||
/// edges are imperceptible at the ~10% alpha these fills use). Not for concave/self-overlapping shapes.
|
||||
inline void FillUnderPolyline(ImDrawList* dl, const ImVec2* pts, int n, float baseY, ImU32 col) {
|
||||
if (n < 2 || (col & IM_COL32_A_MASK) == 0) return;
|
||||
const ImVec2 uv = ImGui::GetFontTexUvWhitePixel();
|
||||
dl->PrimReserve(FillStripIndexCount(n), FillStripVertexCount(n));
|
||||
const unsigned base = dl->_VtxCurrentIdx; // read AFTER PrimReserve (it may roll the vertex offset)
|
||||
for (int i = 0; i < n; ++i) {
|
||||
dl->PrimWriteVtx(pts[i], uv, col);
|
||||
dl->PrimWriteVtx(ImVec2(pts[i].x, baseY), uv, col);
|
||||
}
|
||||
for (int i = 0; i + 1 < n; ++i) {
|
||||
const unsigned a = base + 2u * (unsigned)i;
|
||||
dl->PrimWriteIdx((ImDrawIdx)a); dl->PrimWriteIdx((ImDrawIdx)(a + 1)); dl->PrimWriteIdx((ImDrawIdx)(a + 3));
|
||||
dl->PrimWriteIdx((ImDrawIdx)a); dl->PrimWriteIdx((ImDrawIdx)(a + 3)); dl->PrimWriteIdx((ImDrawIdx)(a + 2));
|
||||
}
|
||||
}
|
||||
|
||||
}}} // namespace
|
||||
@@ -183,10 +183,7 @@ static void pfDrawSparklineFilled(ImDrawList* dl, ImVec2 mn, ImVec2 mx,
|
||||
std::vector<ImVec2> pts;
|
||||
if (!pfProjectSeries(hist, mn, mx, pts)) return;
|
||||
int n = (int)pts.size();
|
||||
for (int i = 0; i < n; i++) dl->PathLineTo(pts[i]);
|
||||
dl->PathLineTo(ImVec2(pts[n - 1].x, mx.y));
|
||||
dl->PathLineTo(ImVec2(pts[0].x, mx.y));
|
||||
dl->PathFillConcave(WithAlpha(lineCol, 28));
|
||||
FillUnderPolyline(dl, pts.data(), n, mx.y, WithAlpha(lineCol, 28));
|
||||
dl->AddPolyline(pts.data(), n, WithAlpha(lineCol, 220), ImDrawFlags_None, 1.4f * dp);
|
||||
}
|
||||
|
||||
@@ -1826,10 +1823,7 @@ static void mktDrawPriceChart(const MktCtx& cx)
|
||||
points[i] = ImVec2(plotLeft + t * plotW, yOf(s_mkt.history[i]));
|
||||
}
|
||||
// Flat translucent area fill under the curve (matches the portfolio group sparklines).
|
||||
for (size_t i = 0; i < n; i++) dl->PathLineTo(points[i]);
|
||||
dl->PathLineTo(ImVec2(points[n - 1].x, plotBottom));
|
||||
dl->PathLineTo(ImVec2(points[0].x, plotBottom));
|
||||
dl->PathFillConcave(WithAlpha(dirCol, 28));
|
||||
FillUnderPolyline(dl, points.data(), (int)n, plotBottom, WithAlpha(dirCol, 28));
|
||||
// Line (no per-point dots — a clean curve).
|
||||
dl->AddPolyline(points.data(), (int)points.size(), lineCol, ImDrawFlags_None, S.drawElement("tabs.market", "chart-line-thickness").size);
|
||||
}
|
||||
|
||||
@@ -102,11 +102,7 @@ static void DrawHashrateSparkline(ImDrawList* dl, ImVec2 regionMin, ImVec2 regio
|
||||
}
|
||||
|
||||
if (points.size() >= 2) {
|
||||
for (size_t i = 0; i < points.size(); i++)
|
||||
dl->PathLineTo(points[i]);
|
||||
dl->PathLineTo(ImVec2(points.back().x, plotBottom));
|
||||
dl->PathLineTo(ImVec2(points.front().x, plotBottom));
|
||||
dl->PathFillConcave(WithAlpha(Success(), 25));
|
||||
FillUnderPolyline(dl, points.data(), (int)points.size(), plotBottom, WithAlpha(Success(), 25));
|
||||
}
|
||||
|
||||
dl->AddPolyline(points.data(), (int)points.size(), WithAlpha(Success(), 200),
|
||||
|
||||
Reference in New Issue
Block a user