[subset] Add prioritization offset resolution.
Vertices can now be prioritized to force them to sort closer to their parent. The resolver will attempt to use this for overflows on non-shared vertices.
This commit is contained in:
parent
b452b2c76c
commit
a7a86a6eb4
|
@ -41,6 +41,13 @@ struct graph_t
|
|||
|
||||
struct vertex_t
|
||||
{
|
||||
vertex_t () :
|
||||
distance (0),
|
||||
incoming_edges (0),
|
||||
start (0),
|
||||
end (0),
|
||||
priority(0) {}
|
||||
|
||||
void fini () { obj.fini (); }
|
||||
|
||||
hb_serialize_context_t::object_t obj;
|
||||
|
@ -48,6 +55,38 @@ struct graph_t
|
|||
unsigned incoming_edges;
|
||||
unsigned start;
|
||||
unsigned end;
|
||||
unsigned priority;
|
||||
|
||||
bool is_shared () const
|
||||
{
|
||||
return incoming_edges > 1;
|
||||
}
|
||||
|
||||
bool is_leaf () const
|
||||
{
|
||||
return !obj.links.length;
|
||||
}
|
||||
|
||||
void raise_priority ()
|
||||
{
|
||||
priority++;
|
||||
}
|
||||
|
||||
int64_t modified_distance (unsigned order) const
|
||||
{
|
||||
// TODO(garretrieger): once priority is high enough, should try
|
||||
// setting distance = 0 which will force to sort immediately after
|
||||
// it's parent where possible.
|
||||
int64_t modified_distance = distance + distance_modifier ();
|
||||
return (modified_distance << 24) | (0x00FFFFFF & order);
|
||||
}
|
||||
|
||||
int64_t distance_modifier () const
|
||||
{
|
||||
if (!priority) return 0;
|
||||
int64_t table_size = obj.tail - obj.head;
|
||||
return -(table_size - table_size / (1 << priority));
|
||||
}
|
||||
};
|
||||
|
||||
struct overflow_record_t
|
||||
|
@ -246,7 +285,7 @@ struct graph_t
|
|||
// Object graphs are in reverse order, the first object is at the end
|
||||
// of the vector. Since the graph is topologically sorted it's safe to
|
||||
// assume the first object has no incoming edges.
|
||||
queue.insert (root_idx (), add_order(root ().distance, 0));
|
||||
queue.insert (root_idx (), root ().modified_distance (0));
|
||||
int new_id = root_idx ();
|
||||
unsigned order = 1;
|
||||
while (!queue.is_empty ())
|
||||
|
@ -265,7 +304,8 @@ struct graph_t
|
|||
// way. More specifically this is set up so that if a set of objects have the same
|
||||
// distance they'll be added to the topolical order in the order that they are
|
||||
// referenced from the parent object.
|
||||
queue.insert (link.objidx, add_order(vertices_[link.objidx].distance, order++));
|
||||
queue.insert (link.objidx,
|
||||
vertices_[link.objidx].modified_distance (order++));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -290,6 +330,9 @@ struct graph_t
|
|||
*/
|
||||
void duplicate (unsigned parent_idx, unsigned child_idx)
|
||||
{
|
||||
DEBUG_MSG (SUBSET_REPACK, nullptr, " Duplicating %d => %d",
|
||||
parent_idx, child_idx);
|
||||
|
||||
positions_invalid = true;
|
||||
|
||||
auto& child = vertices_[child_idx];
|
||||
|
@ -325,10 +368,25 @@ struct graph_t
|
|||
vertices_[vertices_.length - 1] = root;
|
||||
}
|
||||
|
||||
/*
|
||||
* Raises the sorting priority of all children.
|
||||
*/
|
||||
void raise_childrens_priority (unsigned parent_idx)
|
||||
{
|
||||
DEBUG_MSG (SUBSET_REPACK, nullptr, " Raising priority of all children of %d",
|
||||
parent_idx);
|
||||
// This operation doesn't change ordering until a sort is run, so no need
|
||||
// to invalidate positions. It does not change graph structure so no need
|
||||
// to update distances or edge counts.
|
||||
auto& parent = vertices_[parent_idx].obj;
|
||||
for (unsigned i = 0; i < parent.links.length; i++)
|
||||
vertices_[parent.links[i].objidx].raise_priority ();
|
||||
}
|
||||
|
||||
/*
|
||||
* Will any offsets overflow on graph when it's serialized?
|
||||
*/
|
||||
bool will_overflow (hb_vector_t<overflow_record_t>* overflows)
|
||||
bool will_overflow (hb_vector_t<overflow_record_t>* overflows = nullptr)
|
||||
{
|
||||
if (overflows) overflows->resize (0);
|
||||
update_positions ();
|
||||
|
@ -362,7 +420,7 @@ struct graph_t
|
|||
for (const auto& o : overflows)
|
||||
{
|
||||
const auto& child = vertices_[o.link->objidx];
|
||||
DEBUG_MSG (SUBSET_REPACK, nullptr, "overflow from %d => %d (%d incoming , %d outgoing)",
|
||||
DEBUG_MSG (SUBSET_REPACK, nullptr, " overflow from %d => %d (%d incoming , %d outgoing)",
|
||||
o.parent,
|
||||
o.link->objidx,
|
||||
child.incoming_edges,
|
||||
|
@ -393,12 +451,6 @@ struct graph_t
|
|||
edge_count_invalid = false;
|
||||
}
|
||||
|
||||
|
||||
int64_t add_order (int64_t distance, unsigned order)
|
||||
{
|
||||
return (distance << 24) | (0x00FFFFFF & order);
|
||||
}
|
||||
|
||||
/*
|
||||
* compute the serialized start and end positions for each vertex.
|
||||
*/
|
||||
|
@ -589,9 +641,11 @@ struct graph_t
|
|||
inline void
|
||||
hb_resolve_overflows (const hb_vector_t<hb_serialize_context_t::object_t *>& packed,
|
||||
hb_serialize_context_t* c) {
|
||||
// Kahn sort is ~twice as fast as shortest distance sort and works for many fonts
|
||||
// so try it first.
|
||||
graph_t sorted_graph (packed);
|
||||
sorted_graph.sort_kahn ();
|
||||
if (!sorted_graph.will_overflow (nullptr)) return;
|
||||
if (!sorted_graph.will_overflow ()) return;
|
||||
|
||||
sorted_graph.sort_shortest_distance ();
|
||||
|
||||
|
@ -599,36 +653,49 @@ hb_resolve_overflows (const hb_vector_t<hb_serialize_context_t::object_t *>& pac
|
|||
hb_vector_t<graph_t::overflow_record_t> overflows;
|
||||
// TODO(garretrieger): select a good limit for max rounds.
|
||||
while (sorted_graph.will_overflow (&overflows) && round++ < 10) {
|
||||
DEBUG_MSG (SUBSET_REPACK, nullptr, "Over flow resolution round %d", round);
|
||||
DEBUG_MSG (SUBSET_REPACK, nullptr, "=== Over flow resolution round %d ===", round);
|
||||
sorted_graph.print_overflows (overflows);
|
||||
|
||||
// Try resolving the furthest overflow first.
|
||||
bool resolution_attempted = false;
|
||||
hb_set_t priority_bumped_parents;
|
||||
// Try resolving the furthest overflows first.
|
||||
for (int i = overflows.length - 1; i >= 0; i--)
|
||||
{
|
||||
const graph_t::overflow_record_t& r = overflows[i];
|
||||
if (sorted_graph.vertices_[r.link->objidx].incoming_edges > 1)
|
||||
const auto& child = sorted_graph.vertices_[r.link->objidx];
|
||||
if (child.is_shared ())
|
||||
{
|
||||
DEBUG_MSG (SUBSET_REPACK, nullptr, "Duplicating %d => %d",
|
||||
r.parent, r.link->objidx);
|
||||
// The child object is shared, we may be able to eliminate the overflow
|
||||
// by duplicating it.
|
||||
sorted_graph.duplicate (r.parent, r.link->objidx);
|
||||
sorted_graph.sort_shortest_distance ();
|
||||
resolution_attempted = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (child.is_leaf () && !priority_bumped_parents.has (r.parent))
|
||||
{
|
||||
// TODO(garretrieger): initially limiting this to leaf's but likely
|
||||
// can be used for non-leafs as well.
|
||||
// TODO(garretrieger): add a maximum priority, don't try to raise past this.
|
||||
sorted_graph.raise_childrens_priority (r.parent);
|
||||
priority_bumped_parents.add (r.parent);
|
||||
resolution_attempted = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
// TODO(garretrieger): add additional offset resolution strategies
|
||||
// - Promotion to extension lookups.
|
||||
// - Table splitting.
|
||||
}
|
||||
|
||||
if (!resolution_attempted)
|
||||
if (resolution_attempted)
|
||||
{
|
||||
DEBUG_MSG (SUBSET_REPACK, nullptr, "No resolution available :(");
|
||||
break;
|
||||
sorted_graph.sort_shortest_distance ();
|
||||
continue;
|
||||
}
|
||||
|
||||
DEBUG_MSG (SUBSET_REPACK, nullptr, "No resolution available :(");
|
||||
break;
|
||||
}
|
||||
|
||||
sorted_graph.serialize (c);
|
||||
|
|
|
@ -449,6 +449,7 @@ static void test_resolve_overflows_via_duplication ()
|
|||
|
||||
// TODO(garretrieger): update will_overflow tests to check the overflows array.
|
||||
// TODO(garretrieger): add a test(s) using a real font.
|
||||
// TODO(garretrieger): add tests for priority raising.
|
||||
|
||||
int
|
||||
main (int argc, char **argv)
|
||||
|
|
Loading…
Reference in New Issue