The Birdfont Source Code


All Repositories / birdfont.git / commit – RSS feed

Speed optimizations for the overview tab

These changes was commited to the Birdfont repository Tue, 15 Dec 2015 19:45:10 +0000.

Contributing

Send patches or pull requests to johan.mattsson.m@gmail.com.
Clone this repository: git clone https://github.com/johanmattssonm/birdfont.git
author Johan Mattsson <johan.mattsson.m@gmail.com>
Tue, 15 Dec 2015 19:45:10 +0000 (20:45 +0100)
committer Johan Mattsson <johan.mattsson.m@gmail.com>
Tue, 15 Dec 2015 19:45:10 +0000 (20:45 +0100)
commit 1a7b08e939cb75ac0ef4a3b9d10e4cb1b38d2947
tree 4a4a4ab822ca6ffff40c02879fe4d7577a8b7e5d
parent cd3c0425c245752725d527e5478121fe6a48c286
Speed optimizations for the overview tab

This is done by caching labels and creating an array that stores the
length entire lenght of the set at all unicode ranges in GlyphRange.

The current version runs in a single thread but some work has been done
in order to render overview glyphs in a background thread.

libbirdfont/BirdFont.vala
libbirdfont/GlyphRange.vala
libbirdfont/LabelTool.vala
libbirdfont/MainWindow.vala
libbirdfont/OverView.vala
libbirdfont/OverViewItem.vala
--- a/libbirdfont/BirdFont.vala +++ b/libbirdfont/BirdFont.vala @@ -259,7 +259,7 @@ if (has_argument ("--codepages")) { codepage_bits = new CodePageBits (); codepage_bits.generate_codepage_database (); - } + } } public static bool has_logging () {
--- a/libbirdfont/GlyphRange.vala +++ b/libbirdfont/GlyphRange.vala @@ -1,5 +1,5 @@ /* - Copyright (C) 2012 2014 Johan Mattsson + Copyright (C) 2012 2014 2015 Johan Mattsson This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as @@ -26,11 +26,37 @@ uint32 len = 0; bool range_is_class = false; + uint32* range_index = null; + int index_size = 0; public GlyphRange () { ranges = new Gee.ArrayList<UniRange> (); unassigned = new Gee.ArrayList<string> (); name = "No name"; + } + + ~GlyphRange () { + if (range_index != null) { + delete range_index; + } + } + + private void generate_unirange_index () { + if (range_index != null) { + delete range_index; + } + + index_size = ranges.size; + range_index = new uint32[index_size]; + + int i = 0; + uint32 next_index = 0; + + foreach (UniRange range in ranges) { + range_index[i] = next_index; + next_index += (uint32) range.length (); + i++; + } } public void add_unassigned (string glyph_name) { @@ -53,13 +79,14 @@ unassigned.clear (); ranges.clear (); len = 0; + generate_unirange_index (); } public unowned Gee.ArrayList<UniRange> get_ranges () { return ranges; } - // TODO: complete localized alphabetical sort åäö is not the right order for example. + // sort by unicode value public void sort () { ranges.sort ((a, b) => { UniRange first, next; @@ -73,6 +100,8 @@ return (r) ? 1 : -1; }); + + generate_unirange_index (); } public void add_single (unichar c) { @@ -122,6 +151,8 @@ } } } + + generate_unirange_index (); } /** Parse ranges on the form a-z. Single characters can be added as well as @@ -131,6 +162,7 @@ */ public void parse_ranges (string ranges) throws MarkupError { parse_range_string (ranges); + generate_unirange_index (); } private void parse_range_string (string ranges) throws MarkupError { @@ -200,11 +232,11 @@ first = false; } + return s.str; } public static string serialize (string s) { - if (s == "space") { return s; } @@ -351,7 +383,7 @@ private void append_range (unichar start, unichar stop) { UniRange r; r = insert_range (start, stop); // insert a unique range - merge_range (r); // join connecting ranges + merge_range (r); } private void merge_range (UniRange r) { @@ -393,47 +425,89 @@ merge_range (r); } } + + /** Find a range which containd index. */ + private void get_unirange_index (uint32 index, out UniRange? range, out uint32 range_start_index) { + int lower = 0; + int upper = index_size - 1; + int i = lower + (upper - lower) / 2; + int end = index_size - 1; + + range_start_index = -1; + range = null; + + if (unlikely (ranges.size != index_size)) { + warning (@"Range size does not match index size: $(ranges.size) != $index_size"); + } + + while (true) { + if (i == end) { + range_start_index = range_index[i]; + range = ranges.get (i); + break; + } else if (range_index[i] <= index && range_index[i + 1] > index) { + range_start_index = range_index[i]; + range = ranges.get (i); + break; + } + + if (lower >= upper) { + break; + } + if (range_index[i] < index) { + lower = i + 1; + } else { + upper = i - 1; + } + + i = lower + (upper - lower) / 2; + } + } + public string get_char (uint32 index) { int64 ti; string chr; UniRange r; StringBuilder sb; unichar c; + UniRange? range; + uint32 range_start_index; - if (index > len + unassigned.size) { + if (unlikely (index > len + unassigned.size)) { return "\0".dup(); } if (index >= len) { - if (index - len >= unassigned.size) { + if (unlikely (index - len >= unassigned.size)) { return "\0".dup(); } chr = unassigned.get ((int) (index - len)); return chr; } - - r = ranges.get (0); - ti = index; - - foreach (UniRange u in ranges) { - ti -= u.length (); - - if (ti < 0) { - r = u; - break; - } + + get_unirange_index (index, out range, out range_start_index); + + if (unlikely (range == null)) { + warning (@"No range found for index $index"); + return ""; + } + + if (unlikely (range_start_index > index || range_start_index == -1)) { + warning ("Index out of bounds in glyph range."); + return ""; } - - sb = new StringBuilder (); - c = r.get_char ((unichar) (ti + r.length ())); + + r = (!) range; + c = r.get_char ((unichar) (index - range_start_index)); if (unlikely (!c.validate ())) { warning ("Not a valid unicode character."); return ""; } + sb = new StringBuilder (); sb.append_unichar (c); return sb.str;
--- a/libbirdfont/LabelTool.vala +++ b/libbirdfont/LabelTool.vala @@ -24,11 +24,24 @@ } set { + clear_cache (); label_text.set_text (value); } } - public string number { get; set; } + public string number { + get { + return counter_number; + } + + set { + clear_cache (); + counter_number = value; + } + } + + string counter_number = ""; + public bool has_counter { get; set; } public bool has_delete_button { get; set; } public signal void delete_action (LabelTool self); @@ -38,6 +51,9 @@ double counter_box_height = 11 * Toolbox.get_scale (); Text label_text; + + Surface? selected_cache = null; + Surface? deselected_cache = null; public LabelTool (string label) { double text_height; @@ -61,19 +77,59 @@ delete_action (this); } }); + } + + void clear_cache () { + selected_cache = null; + deselected_cache = null; } public override void draw_tool (Context cr, double px, double py) { + double x = this.x - px; + double y = this.y - py; + + if (is_selected ()) { + + if (selected_cache == null) { + selected_cache = Screen.create_background_surface ((int) w, (int) h + 2); + Context c = new Context ((!) selected_cache); + c.scale (1 / Screen.get_scale (), 1 / Screen.get_scale ()); + draw_tool_surface (c, x, 2, true); + } + + cr.save (); + cr.set_antialias (Cairo.Antialias.NONE); + cr.set_source_surface ((!) selected_cache, 0, (int) y - 2); + cr.paint (); + cr.restore (); + } else { + + if (deselected_cache == null) { + deselected_cache = Screen.create_background_surface ((int) w, (int) h + 2); + Context c = new Context ((!) deselected_cache); + c.scale (1 / Screen.get_scale (), 1 / Screen.get_scale ()); + draw_tool_surface (c, x, 2, false); + } + + cr.save (); + cr.set_antialias (Cairo.Antialias.NONE); + cr.set_source_surface ((!) deselected_cache, 0, (int) y - 2); + cr.paint (); + cr.restore (); + } + } + + public void draw_tool_surface (Context cr, double px, double py, bool selected) { Text glyph_count; double bgx, bgy; double center_x, center_y; - double x = this.x - px; - double y = this.y - py; double text_height; double text_width; + double x = px; + double y = py; // background - if (is_selected ()) { + if (selected) { cr.save (); Theme.color (cr, "Menu Background"); cr.rectangle (0, y - 2 * Toolbox.get_scale (), w, h); // labels overlap with 2 pixels @@ -84,11 +140,7 @@ // tab label cr.save (); - if (is_selected ()) { - Theme.text_color (label_text, "Text Tool Box"); - } else { - Theme.text_color (label_text, "Text Tool Box"); - } + Theme.text_color (label_text, "Text Tool Box"); text_width = Toolbox.allocation_width; @@ -101,7 +153,6 @@ } label_text.truncate (text_width); - label_text.draw_at_top (cr, x, y); cr.restore ();
--- a/libbirdfont/MainWindow.vala +++ b/libbirdfont/MainWindow.vala @@ -170,6 +170,7 @@ public void set_native (NativeWindow nw) { native_window = nw; + OverViewItem.start_thumbnail_processing (); } public static FontDisplay get_current_display () {
--- a/libbirdfont/OverView.vala +++ b/libbirdfont/OverView.vala @@ -75,6 +75,8 @@ double scroll_size = 1; const double UCD_LINE_HEIGHT = 17 * 1.3; + + private bool update_scheduled = true; public OverView (GlyphRange? range = null, bool open_selected = true, bool default_character_set = true) { @@ -512,7 +514,11 @@ return i - 1; } - public void update_item_list (int item_list_length = -1) { + public void update_item_list () { + update_scheduled = true; + } + + public void process_update_item_list () { string character_string; Font f = BirdFont.get_current_font (); GlyphCollection? glyphs = null; @@ -522,17 +528,21 @@ unichar character; Glyph glyph; double tab_with; + int item_list_length; + + Test test = new Test.time("update_item_list"); tab_with = allocation.width - 30; // scrollbar items_per_row = get_items_per_row (); rows = (int) (allocation.height / OverViewItem.full_height ()) + 2; - if (item_list_length == -1) { - item_list_length = items_per_row * rows; + item_list_length = items_per_row * rows; + + foreach (OverViewItem overview_item in visible_items) { + overview_item.cancel_thumbnail_rendering (); } - visible_items.clear (); visible_items = new Gee.ArrayList<OverViewItem> (); // update item list @@ -560,9 +570,6 @@ glyphs = f.get_glyph_collection_by_name (character_string); character = character_string.get_char (0); } - - item = new OverViewItem (glyphs, character, x, y); - item.adjust_scale (); x += OverViewItem.full_width (); @@ -571,31 +578,44 @@ y += OverViewItem.full_height (); } - item.selected = (i == selected); + bool selected_item = (i == selected); if (glyphs != null) { - item.selected |= selected_items.index_of ((!) glyphs) != -1; + selected_item |= selected_items.index_of ((!) glyphs) != -1; + } + + if (i >= visible_items.size) { + item = new OverViewItem (glyphs, character, x, y); + item.adjust_scale (); + item.selected = selected_item; + visible_items.add (item); + } else { + visible_items.get (i).init (glyphs, character, x, y); } - visible_items.add (item); index++; } - + foreach (OverViewItem i in visible_items) { i.y += view_offset_y; i.x += view_offset_x; - } + } + + warning (test.get_test_time ()); + + update_scheduled = false; } public override void draw (WidgetAllocation allocation, Context cr) { - - if (this.allocation.width != allocation.width + if (update_scheduled + || this.allocation.width != allocation.width || this.allocation.height != allocation.height || this.allocation.width == 0) { this.allocation = allocation; - update_item_list (); + process_update_item_list (); } + Test test = new Test.time("Overview.draw"); this.allocation = allocation; // clear canvas @@ -616,6 +636,8 @@ if (unlikely (character_info != null)) { draw_character_info (cr); } + + warning (test.get_test_time ()); } void draw_empty_canvas (WidgetAllocation allocation, Context cr) {
--- a/libbirdfont/OverViewItem.vala +++ b/libbirdfont/OverViewItem.vala @@ -37,13 +37,27 @@ public VersionList version_menu; Text label; + + private Surface? cache = null; public static Surface? label_background = null; public static Surface? selected_label_background = null; public static Surface? label_background_no_menu = null; public static Surface? selected_label_background_no_menu = null; + + private static Task thumbnail_task; + private static Gee.PriorityQueue<OverViewItem> thumbnail_queue; + private static Cond has_thumnail_task = new Cond (); + private static Mutex thumbnail_mutex = new Mutex (); + + private bool cancel_thumbnail = false; public OverViewItem (GlyphCollection? glyphs, unichar character, double x, double y) { + init (glyphs, character, x, y); + } + + // this method makes it possible for the overview tab to reuse its items as a speed optimization + public void init (GlyphCollection? glyphs, unichar character, double x, double y) { this.x = x; this.y = y; this.character = character; @@ -68,6 +82,123 @@ } else { version_menu = new VersionList (new GlyphCollection (character, (!) character.to_string ())); } + + thumbnail_mutex.lock (); + if (!is_null (thumbnail_queue)) { + thumbnail_queue.offer (this); + has_thumnail_task.signal (); + } + thumbnail_mutex.unlock (); + } + + public static void start_thumbnail_processing () { + thumbnail_task = new Task (process_thumbnails, true); + thumbnail_queue = new Gee.PriorityQueue<OverViewItem> (); + MainWindow.native_window.run_non_blocking_background_thread (thumbnail_task); + } + + public void cancel_thumbnail_rendering () { + thumbnail_mutex.lock (); + cancel_thumbnail = true; + thumbnail_mutex.unlock (); + } + + private static void process_thumbnails () { + bool cancel = false; + + while (!thumbnail_task.is_cancelled ()) { + OverViewItem item; + + thumbnail_mutex.lock (); + item = thumbnail_queue.poll (); + thumbnail_mutex.unlock (); + + if (!is_null (item)) { + thumbnail_mutex.lock (); + cancel = item.cancel_thumbnail; + thumbnail_mutex.unlock (); + + if (!cancel) { + IdleSource idle = new IdleSource (); + idle.set_callback (() => { + item.draw_background (); + return false; + }); + idle.attach (null); + } + } else { + thumbnail_mutex.lock (); + has_thumnail_task.wait (thumbnail_mutex); + thumbnail_mutex.unlock (); + } + } + } + + public void draw_background () { // FIXME: LOCK for Text and thread exit + Glyph g; + Font font; + double gx, gy; + double x1, x2, y1, y2; + double scale_box; + double w, h; + double glyph_width, glyph_height; + Surface s; + Context c; + Color color = Color.black (); + + w = width; + h = height; + + scale_box = width / DEFAULT_WIDTH; + + s = Screen.create_background_surface ((int) width, (int) height - 20); + c = new Context (s); + + if (glyphs != null) { + font = BirdFont.get_current_font (); + g = ((!) glyphs).get_current (); + + c.save (); + g.boundaries (out x1, out y1, out x2, out y2); + + glyph_width = x2 - x1; + glyph_height = y2 - y1; + + c.save (); + c.scale (glyph_scale * Screen.get_scale (), glyph_scale * Screen.get_scale ()); + + g.add_help_lines (); + + gx = ((w / glyph_scale) - glyph_width) / 2 - g.get_left_side_bearing (); + gy = (h / glyph_scale) - 25 / glyph_scale; + + c.translate (gx - Glyph.xc () - g.get_lsb (), g.get_baseline () + gy - Glyph.yc ()); + + g.draw_paths (c, color); + c.restore (); + } else { + c.scale (Screen.get_scale (), Screen.get_scale ()); + + c.save (); + Text fallback = new Text (); + fallback.set_use_cache (false); + Theme.text_color (fallback, "Overview Glyph"); + fallback.set_text ((!) character.to_string ()); + double font_size = height * 0.8; + gx = (width - fallback.get_extent ()) / 2.0; + gy = height - 30; + fallback.set_font_size (font_size); + fallback.draw_at_baseline (c, gx, gy); + c.restore (); + } + + IdleSource idle = new IdleSource (); + idle.set_callback (() => { + cache = s; + GlyphCanvas.redraw (); + return false; + }); + idle.attach (null); } public static void reset_label () { @@ -143,7 +274,7 @@ if (!is_on_screen (allocation)) { return; } - + cr.save (); Theme.color (cr, "Background 1"); cr.rectangle (x, y, width, height); @@ -157,9 +288,10 @@ cr.stroke (); cr.restore (); - draw_thumbnail (cr, glyphs, x, y + height); draw_caption (cr); draw_menu (cr); + + draw_thumbnail (cr, x, y + height); } public void adjust_scale () { @@ -189,72 +321,15 @@ } } - private void draw_thumbnail (Context cr, GlyphCollection? gl, double x, double y) { - Glyph g; - Font font; - double gx, gy; - double x1, x2, y1, y2; - double scale_box; - double w, h; - double glyph_width, glyph_height; - Surface s; - Context c; - Color color = Color.black (); - - w = width; - h = height; - - scale_box = width / DEFAULT_WIDTH; - - s = Screen.create_background_surface ((int) width, (int) height - 20); - c = new Context (s); - - if (gl != null) { - font = BirdFont.get_current_font (); - g = ((!) gl).get_interpolated_fast (OverviewTools.current_master_size); - - c.save (); - g.boundaries (out x1, out y1, out x2, out y2); - - glyph_width = x2 - x1; - glyph_height = y2 - y1; - - c.save (); - c.scale (glyph_scale * Screen.get_scale (), glyph_scale * Screen.get_scale ()); - - g.add_help_lines (); - - gx = ((w / glyph_scale) - glyph_width) / 2 - g.get_left_side_bearing (); - gy = (h / glyph_scale) - 25 / glyph_scale; - - c.translate (gx - Glyph.xc () - g.get_lsb (), g.get_baseline () + gy - Glyph.yc ()); - - g.draw_paths (c, color); - c.restore (); - } else { - c.scale (Screen.get_scale (), Screen.get_scale ()); - - c.save (); - Text fallback = new Text (); - fallback.set_use_cache (false); - Theme.text_color (fallback, "Overview Glyph"); - fallback.set_text ((!) character.to_string ()); - double font_size = height * 0.8; - fallback.set_font_size (font_size); - - gx = (width - fallback.get_extent ()) / 2.0; - gy = height - 30; - fallback.set_font_size (font_size); - fallback.draw_at_baseline (c, gx, gy); - c.restore (); + private void draw_thumbnail (Context cr, double x, double y) { + if (cache != null) { + cr.save (); + cr.set_antialias (Cairo.Antialias.NONE); + cr.scale (1 / Screen.get_scale (), 1 / Screen.get_scale ()); + cr.set_source_surface ((!) cache, (int) (x * Screen.get_scale ()), (int) ((y - height)) * Screen.get_scale ()); + cr.paint (); + cr.restore (); } - - cr.save (); - cr.set_antialias (Cairo.Antialias.NONE); - cr.scale (1 / Screen.get_scale (), 1 / Screen.get_scale ()); - cr.set_source_surface (s, (int) (x * Screen.get_scale ()), (int) ((y - h)) * Screen.get_scale ()); - cr.paint (); - cr.restore (); } public bool has_icons () { @@ -321,7 +396,7 @@ selected_label_background = (!) cache; - // unselected item without menu icon + // deselected item without menu icon cache = Screen.create_background_surface ((int) width, 20); cc = new Context (cache); cc.scale(Screen.get_scale(), Screen.get_scale());