-
Notifications
You must be signed in to change notification settings - Fork 8.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
AtlasEngine: Implement LRU invalidation for glyph tiles #13458
Changes from 3 commits
da4bb74
0641bb3
b5763fa
4ba1f8d
4c7738a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -560,6 +560,7 @@ namespace Microsoft::Console::Render | |
const auto it = _map.find(key); | ||
if (it != _map.end()) | ||
{ | ||
// Move the key to the head of the LRU queue. | ||
_lru.splice(_lru.begin(), _lru, *it); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. holy crap, this is what does the LRU magic |
||
return &(*it)->second; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need this particular indirection? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, it's There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah it's a bit ugly, because the |
||
} | ||
|
@@ -568,8 +569,10 @@ namespace Microsoft::Console::Render | |
|
||
std::list<std::pair<AtlasKey, AtlasValue>>::iterator insert(AtlasKey&& key, AtlasValue&& value) | ||
{ | ||
// && decays to & when passed as an argument to emplace(). | ||
// What a fantastic language. | ||
// Insert the key/value right at the head of the LRU queue, just like find(). | ||
// | ||
// && decays to & if the argument is named, because C++ is a simple language | ||
// and so you have to std::move it again, because C++ is a simple language. | ||
_lru.emplace_front(std::move(key), std::move(value)); | ||
auto it = _lru.begin(); | ||
_map.emplace(it); | ||
|
@@ -602,6 +605,18 @@ namespace Microsoft::Console::Render | |
std::unordered_set<std::list<std::pair<AtlasKey, AtlasValue>>::iterator, AtlasKeyHasher, AtlasKeyEq> _map; | ||
}; | ||
|
||
// TileAllocator yields `tileSize`-sized tiles for our texture atlas. | ||
// While doing so it'll grow the atlas size() by a factor of 2 if needed. | ||
// Once the setMaxArea() is exceeded it'll stop growing and instead | ||
// snatch tiles back from the oldest TileHashMap entries. | ||
// | ||
// The quadratic growth works by alternating the size() | ||
// between an 1:1 and 2:1 aspect ratio, like so: | ||
// (64,64) -> (128,64) -> (128,128) -> (256,128) -> (256,256) | ||
// These initial tile positions allocate() returns are in a Z | ||
// pattern over the available space in the atlas texture. | ||
// You can log the `return _pos;` in allocate() using "Tracepoint"s | ||
// in Visual Studio if you'd like to understand the Z pattern better. | ||
struct TileAllocator | ||
{ | ||
TileAllocator() = default; | ||
|
@@ -624,7 +639,7 @@ namespace Microsoft::Console::Render | |
{ | ||
// _generate() uses a quadratic growth factor for _size's area. | ||
// Once it exceeds the _maxArea, it'll start snatching tiles back from the | ||
// TileHashMap using it's LRU queue. Since _size will at least reach half | ||
// TileHashMap using its LRU queue. Since _size will at least reach half | ||
// of _maxSize (because otherwise it could still grow by a factor of 2) | ||
// and by ensuring that _maxArea is at least twice the window size | ||
// we make it impossible* for _generate() to return false before | ||
|
@@ -637,7 +652,10 @@ namespace Microsoft::Console::Render | |
|
||
void setMaxArea(size_t max) noexcept | ||
{ | ||
_maxArea = clamp(max, _absoluteMinArea, _absoluteMaxArea); | ||
// We need to reserve at least 1 extra `tileArea`, because the tile | ||
// at position {0,0} is already reserved for the cursor texture. | ||
const auto tileArea = static_cast<size_t>(_tileSize.x) * static_cast<size_t>(_tileSize.y); | ||
_maxArea = clamp(max + tileArea, _absoluteMinArea, _absoluteMaxArea); | ||
_updateCanGenerate(); | ||
} | ||
|
||
|
@@ -659,13 +677,20 @@ namespace Microsoft::Console::Render | |
} | ||
|
||
private: | ||
// This method generates the Z pattern coordinates | ||
// described above in the TileAllocator comment. | ||
bool _generate() noexcept | ||
{ | ||
if (!_canGenerate) | ||
{ | ||
return false; | ||
} | ||
|
||
// We need to backup _pos/_size in case our resize below exceeds _maxArea. | ||
// In that case we have to restore _pos/_size so that if _maxArea is increased | ||
// (window resize for instance), we can pick up were we previously left off. | ||
lhecker marked this conversation as resolved.
Show resolved
Hide resolved
|
||
const auto pos = _pos; | ||
|
||
_pos.x += _tileSize.x; | ||
if (_pos.x <= _limit.x) | ||
{ | ||
|
@@ -679,9 +704,12 @@ namespace Microsoft::Console::Render | |
return true; | ||
} | ||
|
||
// Same as for pos. | ||
const auto size = _size; | ||
|
||
// This implements a quadratic growth factor for _size, by | ||
// alternating between an 1:1 and 2:1 aspect ratio, like so: | ||
// (64,64) -> (128,64) -> (128,128) -> (256,128) -> (256,256) | ||
// (64,64) -> (128,64) -> (128,128) -> (256,128) -> (256,256) | ||
// This behavior is strictly dependent on setMaxArea(u16x2)'s | ||
// behavior. See it's comment for an explanation. | ||
if (_size.x == _size.y) | ||
|
@@ -694,10 +722,19 @@ namespace Microsoft::Console::Render | |
_size.y *= 2; | ||
_pos.x = 0; | ||
} | ||
_limit = { gsl::narrow_cast<u16>(_size.x - _tileSize.x), gsl::narrow_cast<u16>(_size.y - _tileSize.y) }; | ||
_originX = _pos.x; | ||
|
||
_updateCanGenerate(); | ||
if (_canGenerate) | ||
{ | ||
_limit = { gsl::narrow_cast<u16>(_size.x - _tileSize.x), gsl::narrow_cast<u16>(_size.y - _tileSize.y) }; | ||
_originX = _pos.x; | ||
} | ||
else | ||
{ | ||
_size = size; | ||
_pos = pos; | ||
} | ||
|
||
return _canGenerate; | ||
} | ||
|
||
|
@@ -721,6 +758,8 @@ namespace Microsoft::Console::Render | |
// Coincidentially that's exactly what we want as the cursor texture lives at {0, 0}. | ||
u16x2 _pos; | ||
u16 _originX = 0; | ||
// Indicates whether we've exhausted our Z pattern across the atlas texture. | ||
// If this is false, we have to snatch tiles back from TileHashMap. | ||
bool _canGenerate = true; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. aka "has space"? |
||
}; | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
... it can split a multi-cell glyph into different pieces??? :O
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
At least, I thought it could because you give it the opportunity to allocate once per cell, and during fragmentation it might not get two tiles next to eachother
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh yeah that totally works. It needs to, because of very long ligatures. My initial version of this engine failed to render any ligatures wider than 2 cells because of this reason until I figured I could just split glyphs into independent tiles that aren't necessarily next to each other, thereby solving the texture fragmentation issue.