genode/repos/os/include/util/dirty_rect.h
2024-07-02 12:00:11 +02:00

142 lines
3.3 KiB
C++

/*
* \brief Utility for tracking dirty areas on a 2D coordinate space
* \author Norman Feske
* \date 2014-04-30
*/
/*
* Copyright (C) 2014-2017 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
#ifndef _INCLUDE__UTIL__DIRTY_RECT_H_
#define _INCLUDE__UTIL__DIRTY_RECT_H_
#include <base/stdint.h>
namespace Genode { template <typename, unsigned> class Dirty_rect; }
/**
* Dirty-rectangle tracker
*
* \param RECT rectangle type (as defined in 'util/geometry.h')
* \param NUM_RECTS number of rectangles used to represent the dirty area
*
*/
template <typename RECT, unsigned NUM_RECTS>
class Genode::Dirty_rect
{
private:
using Rect = RECT;
using size_t = Genode::size_t;
Rect _rects[NUM_RECTS];
/**
* Return true if it is worthwhile to merge 'r1' and 'r2' into one
*/
static bool _should_be_merged(Rect const &r1, Rect const &r2)
{
size_t const cnt_sum = r1.area.count() + r2.area.count();
size_t const cnt_compound = Rect::compound(r1, r2).area.count();
return cnt_compound < cnt_sum;
}
/**
* Return the costs of adding a new to an existing rectangle
*/
static size_t _costs(Rect const &existing, Rect const &added)
{
/*
* If 'existing' is unused, using it will cost the area of the
* added rectangle.
*/
if (!existing.valid())
return added.area.count();
/*
* If the existing rectangle is already populated, the costs
* correspond to the increase of the area when replacing the
* existing rectangle by the compound of the existing and new
* rectangles.
*/
return Rect::compound(existing, added).area.count()
- existing.area.count();
}
public:
/**
* Call functor for each dirty area
*
* The functor 'fn' takes a 'Rect const &' as argument.
* This method resets the dirty rectangles.
*/
void flush(auto const &fn)
{
/*
* Merge rectangles if their compound is smaller than sum of their
* areas. This happens if both rectangles overlap. In this case, it
* is cheaper to process the compound (including some portions that
* aren't actually dirty) instead of processing the overlap twice.
*/
for (unsigned i = 0; i < NUM_RECTS - 1; i++) {
for (unsigned j = i + 1; j < NUM_RECTS; j++) {
Rect &r1 = _rects[i];
Rect &r2 = _rects[j];
if (r1.valid() && r2.valid() && _should_be_merged(r1, r2)) {
r1 = Rect::compound(r1, r2);
r2 = Rect();
}
}
}
/*
* Apply functor to each dirty rectangle and mark rectangle as
* clear.
*/
for (unsigned i = 0; i < NUM_RECTS; i++) {
if (_rects[i].valid()) {
fn(_rects[i]);
_rects[i] = Rect();
}
}
}
void mark_as_dirty(Rect added)
{
/* index of best matching rectangle in '_rects' array */
unsigned best = 0;
/* value to optimize */
size_t highest_costs = ~0;
/*
* Determine the most efficient rectangle to expand.
*/
for (unsigned i = 0; i < NUM_RECTS; i++) {
size_t const costs = _costs(_rects[i], added);
if (costs > highest_costs)
continue;
best = i;
highest_costs = costs;
}
Rect &rect = _rects[best];
rect = rect.valid() ? Rect::compound(rect, added) : added;
}
};
#endif /* _INCLUDE__UTIL__DIRTY_RECT_H_ */