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 {
|
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;
|
Vector<Detail::Edge> edges;
|
||||||
edges.ensure_capacity(lines.size());
|
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) {
|
for (auto& line : lines) {
|
||||||
auto p0 = line.a() - origin;
|
auto p0 = line.a() - origin;
|
||||||
auto p1 = line.b() - 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 min_y = static_cast<int>(p0.y());
|
||||||
auto max_y = static_cast<int>(p1.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 start_x = p0.x();
|
||||||
float end_x = p1.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 dy = max_y - min_y;
|
||||||
auto dxdy = dx / dy;
|
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 {
|
edges.unchecked_append(Detail::Edge {
|
||||||
start_x,
|
start_x,
|
||||||
min_y,
|
min_y,
|
||||||
|
@ -76,8 +104,8 @@ template<unsigned SamplesPerPixel>
|
||||||
EdgeFlagPathRasterizer<SamplesPerPixel>::EdgeFlagPathRasterizer(IntSize size)
|
EdgeFlagPathRasterizer<SamplesPerPixel>::EdgeFlagPathRasterizer(IntSize size)
|
||||||
: m_size(size.width() + 1, size.height() + 1)
|
: 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_scanline.resize(m_size.width());
|
||||||
m_edge_table.resize(m_size.height());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<unsigned SamplesPerPixel>
|
template<unsigned SamplesPerPixel>
|
||||||
|
@ -122,25 +150,24 @@ void EdgeFlagPathRasterizer<SamplesPerPixel>::fill_internal(Painter& painter, Pa
|
||||||
if (lines.is_empty())
|
if (lines.is_empty())
|
||||||
return;
|
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 min_scanline = min_edge_y / SamplesPerPixel;
|
||||||
int max_scanline = 0;
|
int max_scanline = max_edge_y / SamplesPerPixel;
|
||||||
|
m_edge_table.set_scanline_range(min_scanline, max_scanline);
|
||||||
for (auto& edge : edges) {
|
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:
|
// 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];
|
edge.next_edge = m_edge_table[start_scanline];
|
||||||
m_edge_table[start_scanline] = &edge;
|
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 = [&] {
|
auto empty_edge_extent = [&] {
|
||||||
return EdgeExtent { m_size.width() - 1, 0 };
|
return EdgeExtent { m_size.width() - 1, 0 };
|
||||||
};
|
};
|
||||||
|
@ -288,14 +315,6 @@ void EdgeFlagPathRasterizer<SamplesPerPixel>::write_pixel(Painter& painter, int
|
||||||
template<unsigned SamplesPerPixel>
|
template<unsigned SamplesPerPixel>
|
||||||
void EdgeFlagPathRasterizer<SamplesPerPixel>::accumulate_even_odd_scanline(Painter& painter, int scanline, EdgeExtent edge_extent, auto& color_or_function)
|
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;
|
SampleType sample = 0;
|
||||||
for (int x = edge_extent.min_x; x <= edge_extent.max_x; x += 1) {
|
for (int x = edge_extent.min_x; x <= edge_extent.max_x; x += 1) {
|
||||||
sample ^= m_scanline[x];
|
sample ^= m_scanline[x];
|
||||||
|
@ -307,14 +326,6 @@ void EdgeFlagPathRasterizer<SamplesPerPixel>::accumulate_even_odd_scanline(Paint
|
||||||
template<unsigned SamplesPerPixel>
|
template<unsigned SamplesPerPixel>
|
||||||
void EdgeFlagPathRasterizer<SamplesPerPixel>::accumulate_non_zero_scanline(Painter& painter, int scanline, EdgeExtent edge_extent, auto& color_or_function)
|
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;
|
SampleType sample = 0;
|
||||||
WindingCounts sum_winding = {};
|
WindingCounts sum_winding = {};
|
||||||
for (int x = edge_extent.min_x; x <= edge_extent.max_x; x += 1) {
|
for (int x = edge_extent.min_x; x <= edge_extent.max_x; x += 1) {
|
||||||
|
|
|
@ -163,13 +163,6 @@ private:
|
||||||
struct EdgeExtent {
|
struct EdgeExtent {
|
||||||
int min_x;
|
int min_x;
|
||||||
int max_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);
|
void fill_internal(Painter&, Path const&, auto color_or_function, Painter::WindingRule, FloatPoint offset);
|
||||||
|
@ -190,7 +183,23 @@ private:
|
||||||
|
|
||||||
Vector<SampleType> m_scanline;
|
Vector<SampleType> m_scanline;
|
||||||
Vector<WindingCounts> m_windings;
|
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>;
|
extern template class EdgeFlagPathRasterizer<8>;
|
||||||
|
|