LibGfx: Clip edges above or below the visible area in the rasterizer
This avoids doing pointless plotting for scanlines that will never be seen. Note: This currently can only clip edges vertically. Horizontal clipping is more tricky, since edges that are not visible can still change how things accumulate across the scanline. Fixes #22382 Sadly, this causes a bunch of LibWeb test churn as this change imperceptibly changes the final rasterized output.
Before Width: | Height: | Size: 288 KiB After Width: | Height: | Size: 288 KiB |
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 264 B |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 2 KiB |
Before Width: | Height: | Size: 5.7 KiB After Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 54 KiB After Width: | Height: | Size: 53 KiB |
Before Width: | Height: | Size: 350 KiB After Width: | Height: | Size: 350 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 1 KiB |
Before Width: | Height: | Size: 685 KiB After Width: | Height: | Size: 507 KiB |
Before Width: | Height: | Size: 83 KiB After Width: | Height: | Size: 83 KiB |
Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 861 KiB After Width: | Height: | Size: 857 KiB |
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Before Width: | Height: | Size: 81 KiB After Width: | Height: | Size: 45 KiB |
|
@ -31,10 +31,18 @@
|
|||
|
||||
namespace Gfx {
|
||||
|
||||
static Vector<Detail::Edge> prepare_edges(ReadonlySpan<FloatLine> lines, unsigned samples_per_pixel, FloatPoint origin)
|
||||
static Vector<Detail::Edge> prepare_edges(ReadonlySpan<FloatLine> lines, unsigned samples_per_pixel, FloatPoint origin,
|
||||
int top_clip_scanline, int bottom_clip_scanline, int& min_edge_y, int& max_edge_y)
|
||||
{
|
||||
Vector<Detail::Edge> edges;
|
||||
edges.ensure_capacity(lines.size());
|
||||
// The first visible y value.
|
||||
auto top_clip = top_clip_scanline * int(samples_per_pixel);
|
||||
// The last visible y value.
|
||||
auto bottom_clip = (bottom_clip_scanline + 1) * int(samples_per_pixel) - 1;
|
||||
min_edge_y = bottom_clip;
|
||||
max_edge_y = top_clip;
|
||||
|
||||
for (auto& line : lines) {
|
||||
auto p0 = line.a() - origin;
|
||||
auto p1 = line.b() - origin;
|
||||
|
@ -54,6 +62,14 @@ static Vector<Detail::Edge> prepare_edges(ReadonlySpan<FloatLine> lines, unsigne
|
|||
|
||||
auto min_y = static_cast<int>(p0.y());
|
||||
auto max_y = static_cast<int>(p1.y());
|
||||
|
||||
// Clip edges that start below the bottom clip...
|
||||
if (min_y > bottom_clip)
|
||||
continue;
|
||||
// ...and edges that end before the top clip.
|
||||
if (max_y < top_clip)
|
||||
continue;
|
||||
|
||||
float start_x = p0.x();
|
||||
float end_x = p1.x();
|
||||
|
||||
|
@ -61,6 +77,18 @@ static Vector<Detail::Edge> prepare_edges(ReadonlySpan<FloatLine> lines, unsigne
|
|||
auto dy = max_y - min_y;
|
||||
auto dxdy = dx / dy;
|
||||
|
||||
// Trim off the non-visible portions of the edge.
|
||||
if (min_y < top_clip) {
|
||||
start_x += (top_clip - min_y) * dxdy;
|
||||
min_y = top_clip;
|
||||
}
|
||||
if (max_y > bottom_clip) {
|
||||
max_y = bottom_clip;
|
||||
}
|
||||
|
||||
min_edge_y = min(min_y, min_edge_y);
|
||||
max_edge_y = max(max_y, max_edge_y);
|
||||
|
||||
edges.unchecked_append(Detail::Edge {
|
||||
start_x,
|
||||
min_y,
|
||||
|
@ -76,8 +104,8 @@ template<unsigned SamplesPerPixel>
|
|||
EdgeFlagPathRasterizer<SamplesPerPixel>::EdgeFlagPathRasterizer(IntSize size)
|
||||
: m_size(size.width() + 1, size.height() + 1)
|
||||
{
|
||||
// FIXME: Clip the scanline width to the visible section (tricky).
|
||||
m_scanline.resize(m_size.width());
|
||||
m_edge_table.resize(m_size.height());
|
||||
}
|
||||
|
||||
template<unsigned SamplesPerPixel>
|
||||
|
@ -122,25 +150,24 @@ void EdgeFlagPathRasterizer<SamplesPerPixel>::fill_internal(Painter& painter, Pa
|
|||
if (lines.is_empty())
|
||||
return;
|
||||
|
||||
auto edges = prepare_edges(lines, SamplesPerPixel, origin);
|
||||
int min_edge_y = 0;
|
||||
int max_edge_y = 0;
|
||||
auto top_clip_scanline = m_clip.top() - m_blit_origin.y();
|
||||
auto bottom_clip_scanline = m_clip.bottom() - m_blit_origin.y() - 1;
|
||||
auto edges = prepare_edges(lines, SamplesPerPixel, origin, top_clip_scanline, bottom_clip_scanline, min_edge_y, max_edge_y);
|
||||
if (edges.is_empty())
|
||||
return;
|
||||
|
||||
int min_scanline = m_size.height();
|
||||
int max_scanline = 0;
|
||||
int min_scanline = min_edge_y / SamplesPerPixel;
|
||||
int max_scanline = max_edge_y / SamplesPerPixel;
|
||||
m_edge_table.set_scanline_range(min_scanline, max_scanline);
|
||||
for (auto& edge : edges) {
|
||||
int start_scanline = edge.min_y / SamplesPerPixel;
|
||||
int end_scanline = edge.max_y / SamplesPerPixel;
|
||||
|
||||
// Create a linked-list of edges starting on this scanline:
|
||||
int start_scanline = edge.min_y / SamplesPerPixel;
|
||||
edge.next_edge = m_edge_table[start_scanline];
|
||||
m_edge_table[start_scanline] = &edge;
|
||||
|
||||
min_scanline = min(min_scanline, start_scanline);
|
||||
max_scanline = max(max_scanline, end_scanline);
|
||||
}
|
||||
|
||||
// FIXME: We could probably clip some of the egde plotting if we know it won't be shown.
|
||||
// Though care would have to be taken to ensure the active edges are correct at the first drawn scaline.
|
||||
|
||||
auto empty_edge_extent = [&] {
|
||||
return EdgeExtent { m_size.width() - 1, 0 };
|
||||
};
|
||||
|
@ -288,14 +315,6 @@ void EdgeFlagPathRasterizer<SamplesPerPixel>::write_pixel(Painter& painter, int
|
|||
template<unsigned SamplesPerPixel>
|
||||
void EdgeFlagPathRasterizer<SamplesPerPixel>::accumulate_even_odd_scanline(Painter& painter, int scanline, EdgeExtent edge_extent, auto& color_or_function)
|
||||
{
|
||||
auto dest_y = m_blit_origin.y() + scanline;
|
||||
if (!m_clip.contains_vertically(dest_y)) {
|
||||
// FIXME: This memset only really needs to be done on transition from clipped to not clipped,
|
||||
// or not at all if we properly clipped egde plotting.
|
||||
edge_extent.memset_extent(m_scanline.data(), 0);
|
||||
return;
|
||||
}
|
||||
|
||||
SampleType sample = 0;
|
||||
for (int x = edge_extent.min_x; x <= edge_extent.max_x; x += 1) {
|
||||
sample ^= m_scanline[x];
|
||||
|
@ -307,14 +326,6 @@ void EdgeFlagPathRasterizer<SamplesPerPixel>::accumulate_even_odd_scanline(Paint
|
|||
template<unsigned SamplesPerPixel>
|
||||
void EdgeFlagPathRasterizer<SamplesPerPixel>::accumulate_non_zero_scanline(Painter& painter, int scanline, EdgeExtent edge_extent, auto& color_or_function)
|
||||
{
|
||||
// NOTE: Same FIXMEs apply from accumulate_even_odd_scanline()
|
||||
auto dest_y = m_blit_origin.y() + scanline;
|
||||
if (!m_clip.contains_vertically(dest_y)) {
|
||||
edge_extent.memset_extent(m_scanline.data(), 0);
|
||||
edge_extent.memset_extent(m_windings.data(), 0);
|
||||
return;
|
||||
}
|
||||
|
||||
SampleType sample = 0;
|
||||
WindingCounts sum_winding = {};
|
||||
for (int x = edge_extent.min_x; x <= edge_extent.max_x; x += 1) {
|
||||
|
|
|
@ -163,13 +163,6 @@ private:
|
|||
struct EdgeExtent {
|
||||
int min_x;
|
||||
int max_x;
|
||||
|
||||
template<typename T>
|
||||
void memset_extent(T* data, int value)
|
||||
{
|
||||
if (min_x <= max_x)
|
||||
memset(data + min_x, value, (max_x - min_x + 1) * sizeof(T));
|
||||
}
|
||||
};
|
||||
|
||||
void fill_internal(Painter&, Path const&, auto color_or_function, Painter::WindingRule, FloatPoint offset);
|
||||
|
@ -190,7 +183,23 @@ private:
|
|||
|
||||
Vector<SampleType> m_scanline;
|
||||
Vector<WindingCounts> m_windings;
|
||||
Vector<Detail::Edge*> m_edge_table;
|
||||
|
||||
class EdgeTable {
|
||||
public:
|
||||
EdgeTable() = default;
|
||||
|
||||
void set_scanline_range(int min_scanline, int max_scanline)
|
||||
{
|
||||
this->min_scanline = min_scanline;
|
||||
edges.resize(max_scanline - min_scanline + 1);
|
||||
}
|
||||
|
||||
auto& operator[](int scanline) { return edges[scanline - min_scanline]; }
|
||||
|
||||
private:
|
||||
Vector<Detail::Edge*> edges;
|
||||
int min_scanline { 0 };
|
||||
} m_edge_table;
|
||||
};
|
||||
|
||||
extern template class EdgeFlagPathRasterizer<8>;
|
||||
|
|