mirror of
https://github.com/harfbuzz/harfbuzz.git
synced 2025-04-13 00:32:59 +00:00
[decycler] Implement an efficient graph cycle detector
This is an algorithm I came up with, based on the Floyd's Tortoise-Hare constant-memory linear-time linked-list cycle-detection algorithm. https://en.wikipedia.org/wiki/Cycle_detection#Floyd's_tortoise_and_hare It is linear-time and malloc-free. It *eventually* detects cycles, not immediately. The main different with Floyd's algorithm is that this algorithm detects cycles when one is traversing down a graph, not just a linked list. Our existing cycle-detection algorithms use a set-of-integers, either hb_set_t, or more efficient in this case, hb_map_t. Those include at least one malloc, and as such show up on profiles. Port hb-ot-font COLRv1 to use the decycler instead of previous hb_map_t usage for cycle detection. benchmark-font paint_glyph on NotoColorEmoji-Regular.ttf: Before: 8ms; After: 5.5ms. No cycle detection: 5.5ms. FT COLRv1 API is so slow (174ms) it's not worth porting to this. Other graphs (VARC, etc) to be ported. Test and documentation to be added.
This commit is contained in:
parent
ed76c8559e
commit
0aa400b1d8
3 changed files with 112 additions and 15 deletions
|
@ -29,6 +29,7 @@
|
|||
#define OT_COLOR_COLR_COLR_HH
|
||||
|
||||
#include "../../../hb.hh"
|
||||
#include "../../../hb-decycler.hh"
|
||||
#include "../../../hb-open-type.hh"
|
||||
#include "../../../hb-ot-var-common.hh"
|
||||
#include "../../../hb-paint.hh"
|
||||
|
@ -71,8 +72,8 @@ public:
|
|||
hb_array_t<const BGRAColor> palette;
|
||||
hb_color_t foreground;
|
||||
ItemVarStoreInstancer &instancer;
|
||||
hb_map_t current_glyphs;
|
||||
hb_map_t current_layers;
|
||||
hb_decycler_t glyphs_decycler;
|
||||
hb_decycler_t layers_decycler;
|
||||
int depth_left = HB_MAX_NESTING_LEVEL;
|
||||
int edge_count = HB_MAX_GRAPH_EDGE_COUNT;
|
||||
|
||||
|
@ -2576,7 +2577,9 @@ struct COLR
|
|||
&(get_delta_set_index_map ()),
|
||||
hb_array (font->coords, font->num_coords));
|
||||
hb_paint_context_t c (this, funcs, data, font, palette_index, foreground, instancer);
|
||||
c.current_glyphs.add (glyph);
|
||||
|
||||
hb_decycler_node_t node (c.glyphs_decycler);
|
||||
node.visit (glyph);
|
||||
|
||||
if (version >= 1)
|
||||
{
|
||||
|
@ -2692,19 +2695,16 @@ void PaintColrLayers::paint_glyph (hb_paint_context_t *c) const
|
|||
{
|
||||
TRACE_PAINT (this);
|
||||
const LayerList &paint_offset_lists = c->get_colr_table ()->get_layerList ();
|
||||
hb_decycler_node_t node (c->layers_decycler);
|
||||
for (unsigned i = firstLayerIndex; i < firstLayerIndex + numLayers; i++)
|
||||
{
|
||||
if (unlikely (c->current_layers.has (i)))
|
||||
continue;
|
||||
|
||||
c->current_layers.add (i);
|
||||
if (unlikely (!node.visit (i)))
|
||||
return;
|
||||
|
||||
const Paint &paint = paint_offset_lists.get_paint (i);
|
||||
c->funcs->push_group (c->data);
|
||||
c->recurse (paint);
|
||||
c->funcs->pop_group (c->data, HB_PAINT_COMPOSITE_MODE_SRC_OVER);
|
||||
|
||||
c->current_layers.del (i);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2712,16 +2712,14 @@ void PaintColrGlyph::paint_glyph (hb_paint_context_t *c) const
|
|||
{
|
||||
TRACE_PAINT (this);
|
||||
|
||||
if (unlikely (c->current_glyphs.has (gid)))
|
||||
hb_decycler_node_t node (c->glyphs_decycler);
|
||||
if (unlikely (!node.visit (gid)))
|
||||
return;
|
||||
|
||||
c->current_glyphs.add (gid);
|
||||
|
||||
c->funcs->push_inverse_root_transform (c->data, c->font);
|
||||
if (c->funcs->color_glyph (c->data, gid, c->font))
|
||||
{
|
||||
c->funcs->pop_transform (c->data);
|
||||
c->current_glyphs.del (gid);
|
||||
return;
|
||||
}
|
||||
c->funcs->pop_transform (c->data);
|
||||
|
@ -2744,8 +2742,6 @@ void PaintColrGlyph::paint_glyph (hb_paint_context_t *c) const
|
|||
|
||||
if (has_clip_box)
|
||||
c->funcs->pop_clip (c->data);
|
||||
|
||||
c->current_glyphs.del (gid);
|
||||
}
|
||||
|
||||
} /* namespace OT */
|
||||
|
|
100
src/hb-decycler.hh
Normal file
100
src/hb-decycler.hh
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright © 2025 Behdad Esfahbod
|
||||
*
|
||||
* This is part of HarfBuzz, a text shaping library.
|
||||
*
|
||||
* Permission is hereby granted, without written agreement and without
|
||||
* license or royalty fees, to use, copy, modify, and distribute this
|
||||
* software and its documentation for any purpose, provided that the
|
||||
* above copyright notice and the following two paragraphs appear in
|
||||
* all copies of this software.
|
||||
*
|
||||
* IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE TO ANY PARTY FOR
|
||||
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
|
||||
* ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN
|
||||
* IF THE COPYRIGHT HOLDER HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||
* DAMAGE.
|
||||
*
|
||||
* THE COPYRIGHT HOLDER SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING,
|
||||
* BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
|
||||
* ON AN "AS IS" BASIS, AND THE COPYRIGHT HOLDER HAS NO OBLIGATION TO
|
||||
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
|
||||
*
|
||||
* Author(s): Behdad Esfahbod
|
||||
*/
|
||||
|
||||
#ifndef HB_DECYCLER_HH
|
||||
#define HB_DECYCLER_HH
|
||||
|
||||
#include "hb.hh"
|
||||
|
||||
struct hb_decycler_node_t;
|
||||
|
||||
struct hb_decycler_t
|
||||
{
|
||||
friend struct hb_decycler_node_t;
|
||||
|
||||
private:
|
||||
hb_decycler_node_t *tortoise = nullptr;
|
||||
hb_decycler_node_t *hare = nullptr;
|
||||
bool tortoise_asleep = false;
|
||||
};
|
||||
|
||||
struct hb_decycler_node_t
|
||||
{
|
||||
hb_decycler_node_t (hb_decycler_t &decycler)
|
||||
: decycler (decycler)
|
||||
{
|
||||
snapshot = decycler;
|
||||
|
||||
if (!decycler.tortoise)
|
||||
{
|
||||
// First node.
|
||||
decycler.tortoise = decycler.hare = this;
|
||||
return;
|
||||
}
|
||||
|
||||
decycler.hare->next = this;
|
||||
decycler.hare = this;
|
||||
|
||||
if (decycler.tortoise_asleep)
|
||||
{
|
||||
// Wake up toirtoise.
|
||||
decycler.tortoise_asleep = false;
|
||||
// Time to move.
|
||||
decycler.tortoise = decycler.tortoise->next;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Put toirtoise to sleep.
|
||||
decycler.tortoise_asleep = true;
|
||||
}
|
||||
}
|
||||
|
||||
~hb_decycler_node_t ()
|
||||
{
|
||||
decycler = snapshot;
|
||||
}
|
||||
|
||||
bool visit (unsigned value_)
|
||||
{
|
||||
value = value_;
|
||||
|
||||
if (decycler.tortoise == this)
|
||||
return true; // First node; not a cycle.
|
||||
|
||||
if (decycler.tortoise->value == value)
|
||||
return false; // Cycle detected.
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
hb_decycler_t &decycler;
|
||||
hb_decycler_t snapshot;
|
||||
hb_decycler_node_t *next = nullptr;
|
||||
unsigned value = (unsigned) -1;
|
||||
};
|
||||
|
||||
#endif /* HB_DECYCLER_HH */
|
|
@ -43,6 +43,7 @@ hb_base_sources = files(
|
|||
'hb-common.cc',
|
||||
'hb-config.hh',
|
||||
'hb-debug.hh',
|
||||
'hb-decycler.hh',
|
||||
'hb-dispatch.hh',
|
||||
'hb-draw.cc',
|
||||
'hb-draw.hh',
|
||||
|
|
Loading…
Add table
Reference in a new issue