From fa678e3db7aa94cc0435de904da99e1f90ac202b Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Tue, 19 Nov 2019 16:44:18 +0800 Subject: [PATCH 01/15] Check canvas clipping bounds and record bounds. --- Runtime/ui/painting/path.cs | 8 +++ .../cmdbufferCanvas/rendering/canvas_impl.cs | 64 +++++++++++++++++++ .../ui/renderer/common/geometry/path/path.cs | 8 +++ Runtime/ui/txt/paragraph.cs | 3 +- 4 files changed, 82 insertions(+), 1 deletion(-) diff --git a/Runtime/ui/painting/path.cs b/Runtime/ui/painting/path.cs index 1c157ab1..b30c96da 100644 --- a/Runtime/ui/painting/path.cs +++ b/Runtime/ui/painting/path.cs @@ -226,6 +226,14 @@ public Rect getBounds() { return Rect.fromLTRB(this._minX, this._minY, this._maxX, this._maxY); } + public Rect getBoundsWithMargin(float margin) { + if (this._minX - margin >= this._maxX + margin || this._minY - margin >= this._maxY + margin) { + return Rect.zero; + } + + return Rect.fromLTRB(this._minX - margin, this._minY - margin, this._maxX + margin, this._maxY + margin); + } + public static Path combine(PathOperation operation, Path path1, Path path2) { D.assert(path1 != null); D.assert(path2 != null); diff --git a/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs b/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs index 9235d296..252b69a8 100644 --- a/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs +++ b/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs @@ -766,6 +766,9 @@ void _drawPicture(Picture picture, bool needsSave = true) { int saveCount = 0; var drawCmds = picture.drawCmds; + var queryBound = this._currentLayer.currentState.matrix?.invert().Value + .mapRect(this._currentLayer.layerBounds) ?? + this._currentLayer.layerBounds; foreach (var drawCmd in drawCmds) { switch (drawCmd) { case DrawSave _: @@ -839,6 +842,10 @@ void _drawPicture(Picture picture, bool needsSave = true) { } case DrawPath cmd: { + if (uiRectHelper.intersect(uiRectHelper.fromRect( + cmd.path.getBoundsWithMargin(5)).Value, queryBound).isEmpty) { + continue; + } var uipath = uiPath.fromPath(cmd.path); this._drawPath(uipath, uiPaint.fromPaint(cmd.paint)); uiPathCacheManager.putToCache(uipath); @@ -846,18 +853,35 @@ void _drawPicture(Picture picture, bool needsSave = true) { } case DrawImage cmd: { + if (uiRectHelper.intersect(uiRectHelper.fromLTWH( + cmd.offset.dx, cmd.offset.dy, + cmd.image.width / this._devicePixelRatio, + cmd.image.height / this._devicePixelRatio), queryBound).isEmpty) { + continue; + } this._drawImage(cmd.image, (uiOffset.fromOffset(cmd.offset)).Value, uiPaint.fromPaint(cmd.paint)); break; } case DrawImageRect cmd: { + if (uiRectHelper.intersect(uiRectHelper.fromRect(cmd.dst).Value, queryBound).isEmpty) { + continue; + } this._drawImageRect(cmd.image, uiRectHelper.fromRect(cmd.src), uiRectHelper.fromRect(cmd.dst).Value, uiPaint.fromPaint(cmd.paint)); break; } case DrawImageNine cmd: { + if (uiRectHelper.intersect(uiRectHelper.fromLTRB( + cmd.dst.left + ((cmd.center.left - cmd.src.left) * cmd.src.width), + cmd.dst.top + ((cmd.center.top - cmd.src.top) * cmd.src.height), + cmd.dst.right - ((cmd.src.right - cmd.center.right) * cmd.src.width), + cmd.dst.bottom - ((cmd.src.bottom - cmd.center.bottom) * cmd.src.height) + ), queryBound).isEmpty) { + continue; + } this._drawImageNine(cmd.image, uiRectHelper.fromRect(cmd.src), uiRectHelper.fromRect(cmd.center).Value, uiRectHelper.fromRect(cmd.dst).Value, uiPaint.fromPaint(cmd.paint)); @@ -865,11 +889,19 @@ void _drawPicture(Picture picture, bool needsSave = true) { } case DrawPicture cmd: { + if (uiRectHelper.intersect(uiRectHelper.fromRect( + cmd.picture.paintBounds).Value, queryBound).isEmpty) { + continue; + } this._drawPicture(cmd.picture); break; } case DrawTextBlob cmd: { + if (uiRectHelper.intersect(uiRectHelper.fromRect( + cmd.textBlob.Value.boundsInText.translate(cmd.offset.dx, cmd.offset.dy)).Value, queryBound).isEmpty) { + continue; + } this._paintTextShadow(cmd.textBlob, cmd.offset); this._drawTextBlob(cmd.textBlob, (uiOffset.fromOffset(cmd.offset)).Value, uiPaint.fromPaint(cmd.paint)); @@ -899,6 +931,9 @@ void _drawUIPicture(uiPicture picture, bool needsSave = true) { int saveCount = 0; var drawCmds = picture.drawCmds; + var queryBound = this._currentLayer.currentState.matrix?.invert().Value + .mapRect(this._currentLayer.layerBounds) ?? + this._currentLayer.layerBounds; foreach (var drawCmd in drawCmds) { switch (drawCmd) { case uiDrawSave _: @@ -970,31 +1005,60 @@ void _drawUIPicture(uiPicture picture, bool needsSave = true) { } case uiDrawPath cmd: { + if (uiRectHelper.intersect(cmd.path.getBoundsWithMargin(5), queryBound).isEmpty) { + continue; + } this._drawPath(cmd.path, cmd.paint); break; } case uiDrawImage cmd: { + if (uiRectHelper.intersect(uiRectHelper.fromLTWH( + cmd.offset.Value.dx, cmd.offset.Value.dy, + cmd.image.width / this._devicePixelRatio, + cmd.image.height / this._devicePixelRatio), queryBound).isEmpty) { + continue; + } this._drawImage(cmd.image, cmd.offset.Value, cmd.paint); break; } case uiDrawImageRect cmd: { + if (uiRectHelper.intersect(cmd.dst.Value, queryBound).isEmpty) { + continue; + } this._drawImageRect(cmd.image, cmd.src, cmd.dst.Value, cmd.paint); break; } case uiDrawImageNine cmd: { + if (uiRectHelper.intersect(uiRectHelper.fromLTRB( + cmd.dst.Value.left + ((cmd.center.Value.left - cmd.src.Value.left) * cmd.src.Value.width), + cmd.dst.Value.top + ((cmd.center.Value.top - cmd.src.Value.top) * cmd.src.Value.height), + cmd.dst.Value.right - ((cmd.src.Value.right - cmd.center.Value.right) * cmd.src.Value.width), + cmd.dst.Value.bottom - ((cmd.src.Value.bottom - cmd.center.Value.bottom) * cmd.src.Value.height) + ), queryBound).isEmpty) { + continue; + } this._drawImageNine(cmd.image, cmd.src, cmd.center.Value, cmd.dst.Value, cmd.paint); break; } case uiDrawPicture cmd: { + if (uiRectHelper.intersect(uiRectHelper.fromRect( + cmd.picture.paintBounds).Value, queryBound).isEmpty) { + continue; + } this._drawPicture(cmd.picture); break; } case uiDrawTextBlob cmd: { + if (uiRectHelper.intersect(uiRectHelper.fromRect( + cmd.textBlob.Value.boundsInText.translate(cmd.offset.Value.dx, cmd.offset.Value.dy)).Value, + queryBound).isEmpty) { + continue; + } this._paintTextShadow(cmd.textBlob, new Offset(cmd.offset.Value.dx, cmd.offset.Value.dy)); this._drawTextBlob(cmd.textBlob, cmd.offset.Value, cmd.paint); break; diff --git a/Runtime/ui/renderer/common/geometry/path/path.cs b/Runtime/ui/renderer/common/geometry/path/path.cs index 19141c9b..e4e25742 100644 --- a/Runtime/ui/renderer/common/geometry/path/path.cs +++ b/Runtime/ui/renderer/common/geometry/path/path.cs @@ -170,6 +170,14 @@ public uiRect getBounds() { return uiRectHelper.fromLTRB(this._minX, this._minY, this._maxX, this._maxY); } + public uiRect getBoundsWithMargin(float margin) { + if (this._minX - margin >= this._maxX + margin || this._minY - margin >= this._maxY + margin) { + return uiRectHelper.zero; + } + + return uiRectHelper.fromLTRB(this._minX - margin, this._minY - margin, this._maxX + margin, this._maxY + margin); + } + void _appendMoveTo(float x, float y) { this._commands.Add((float) uiPathCommand.moveTo); this._commands.Add(x); diff --git a/Runtime/ui/txt/paragraph.cs b/Runtime/ui/txt/paragraph.cs index 77ae2045..0c765351 100644 --- a/Runtime/ui/txt/paragraph.cs +++ b/Runtime/ui/txt/paragraph.cs @@ -472,7 +472,8 @@ public void layout(ParagraphConstraints constraints) { // If all the positions have not changed, use the cached ellipsized text // else update the cache - if (!(this._ellipsizedLength == textStart + textCount - truncateCount && + if (!(this._ellipsizedText != null && + this._ellipsizedLength == textStart + textCount - truncateCount && this._ellipsizedText.Length == this._ellipsizedLength + ellipsis.Length && this._ellipsizedText.EndsWith(ellipsis))) { this._ellipsizedText = From 15d57265d3f8a4b4c4b28ab049e5ab0ab99fed36 Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Tue, 19 Nov 2019 16:52:12 +0800 Subject: [PATCH 02/15] Split SplayTree in separate file. --- Runtime/external/SplayTree.cs | 493 ++++++++++++++++++++++ Runtime/external/SplayTree.cs.meta | 11 + Runtime/painting/gradient.cs | 1 + Runtime/ui/txt/paragraph.cs | 488 +-------------------- Runtime/widgets/list_wheel_scroll_view.cs | 1 + Runtime/widgets/sliver.cs | 1 + 6 files changed, 508 insertions(+), 487 deletions(-) create mode 100644 Runtime/external/SplayTree.cs create mode 100644 Runtime/external/SplayTree.cs.meta diff --git a/Runtime/external/SplayTree.cs b/Runtime/external/SplayTree.cs new file mode 100644 index 00000000..f35515ac --- /dev/null +++ b/Runtime/external/SplayTree.cs @@ -0,0 +1,493 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Unity.UIWidgets.Runtime.external +{ class SplayTree : IDictionary where TKey : IComparable { + SplayTreeNode root; + int count; + int version = 0; + + public void Add(TKey key, TValue value) { + this.Set(key, value, throwOnExisting: true); + } + + public void Add(KeyValuePair item) { + this.Set(item.Key, item.Value, throwOnExisting: true); + } + + public void AddAll(IEnumerable list) { + foreach (var key in list) { + this.Add(new KeyValuePair(key, default)); + } + } + + void Set(TKey key, TValue value, bool throwOnExisting) { + if (this.count == 0) { + this.version++; + this.root = new SplayTreeNode(key, value); + this.count = 1; + return; + } + + this.Splay(key); + + var c = key.CompareTo(this.root.Key); + if (c == 0) { + if (throwOnExisting) { + throw new ArgumentException("An item with the same key already exists in the tree."); + } + + this.version++; + this.root.Value = value; + return; + } + + var n = new SplayTreeNode(key, value); + if (c < 0) { + n.LeftChild = this.root.LeftChild; + n.RightChild = this.root; + this.root.LeftChild = null; + } + else { + n.RightChild = this.root.RightChild; + n.LeftChild = this.root; + this.root.RightChild = null; + } + + this.root = n; + this.count++; + this.Splay(key); + this.version++; + } + + public void Clear() { + this.root = null; + this.count = 0; + this.version++; + } + + public bool ContainsKey(TKey key) { + if (this.count == 0) { + return false; + } + + this.Splay(key); + + return key.CompareTo(this.root.Key) == 0; + } + + public bool Contains(KeyValuePair item) { + if (this.count == 0) { + return false; + } + + this.Splay(item.Key); + + return item.Key.CompareTo(this.root.Key) == 0 && + (ReferenceEquals(this.root.Value, item.Value) || + (!ReferenceEquals(item.Value, null) && item.Value.Equals(this.root.Value))); + } + + public KeyValuePair? First() { + SplayTreeNode t = this.root; + if (t == null) { + return null; + } + + while (t.LeftChild != null) { + t = t.LeftChild; + } + + return new KeyValuePair(t.Key, t.Value); + } + + public KeyValuePair FirstOrDefault() { + SplayTreeNode t = this.root; + if (t == null) { + return new KeyValuePair(default(TKey), default(TValue)); + } + + while (t.LeftChild != null) { + t = t.LeftChild; + } + + return new KeyValuePair(t.Key, t.Value); + } + + public KeyValuePair? Last() { + SplayTreeNode t = this.root; + if (t == null) { + return null; + } + + while (t.RightChild != null) { + t = t.RightChild; + } + + return new KeyValuePair(t.Key, t.Value); + } + + public KeyValuePair LastOrDefault() { + SplayTreeNode t = this.root; + if (t == null) { + return new KeyValuePair(default(TKey), default(TValue)); + } + + while (t.RightChild != null) { + t = t.RightChild; + } + + return new KeyValuePair(t.Key, t.Value); + } + + void Splay(TKey key) { + SplayTreeNode l, r, t, y, header; + l = r = header = new SplayTreeNode(default(TKey), default(TValue)); + t = this.root; + while (true) { + var c = key.CompareTo(t.Key); + if (c < 0) { + if (t.LeftChild == null) { + break; + } + + if (key.CompareTo(t.LeftChild.Key) < 0) { + y = t.LeftChild; + t.LeftChild = y.RightChild; + y.RightChild = t; + t = y; + if (t.LeftChild == null) { + break; + } + } + + r.LeftChild = t; + r = t; + t = t.LeftChild; + } + else if (c > 0) { + if (t.RightChild == null) { + break; + } + + if (key.CompareTo(t.RightChild.Key) > 0) { + y = t.RightChild; + t.RightChild = y.LeftChild; + y.LeftChild = t; + t = y; + if (t.RightChild == null) { + break; + } + } + + l.RightChild = t; + l = t; + t = t.RightChild; + } + else { + break; + } + } + + l.RightChild = t.LeftChild; + r.LeftChild = t.RightChild; + t.LeftChild = header.RightChild; + t.RightChild = header.LeftChild; + this.root = t; + } + + public bool Remove(TKey key) { + if (this.count == 0) { + return false; + } + + this.Splay(key); + + if (key.CompareTo(this.root.Key) != 0) { + return false; + } + + if (this.root.LeftChild == null) { + this.root = this.root.RightChild; + } + else { + var swap = this.root.RightChild; + this.root = this.root.LeftChild; + this.Splay(key); + this.root.RightChild = swap; + } + + this.version++; + this.count--; + return true; + } + + public bool TryGetValue(TKey key, out TValue value) { + if (this.count == 0) { + value = default(TValue); + return false; + } + + this.Splay(key); + if (key.CompareTo(this.root.Key) != 0) { + value = default(TValue); + return false; + } + + value = this.root.Value; + return true; + } + + public TValue this[TKey key] { + get { + if (this.count == 0) { + throw new KeyNotFoundException("The key was not found in the tree."); + } + + this.Splay(key); + if (key.CompareTo(this.root.Key) != 0) { + throw new KeyNotFoundException("The key was not found in the tree."); + } + + return this.root.Value; + } + + set { this.Set(key, value, throwOnExisting: false); } + } + + public int Count { + get { return this.count; } + } + + public bool IsReadOnly { + get { return false; } + } + + public bool Remove(KeyValuePair item) { + if (this.count == 0) { + return false; + } + + this.Splay(item.Key); + + if (item.Key.CompareTo(this.root.Key) == 0 && (ReferenceEquals(this.root.Value, item.Value) || + (!ReferenceEquals(item.Value, null) && + item.Value.Equals(this.root.Value)))) { + return false; + } + + if (this.root.LeftChild == null) { + this.root = this.root.RightChild; + } + else { + var swap = this.root.RightChild; + this.root = this.root.LeftChild; + this.Splay(item.Key); + this.root.RightChild = swap; + } + + this.version++; + this.count--; + return true; + } + + public void Trim(int depth) { + if (depth < 0) { + throw new ArgumentOutOfRangeException("depth", "The trim depth must not be negative."); + } + + if (this.count == 0) { + return; + } + + if (depth == 0) { + this.Clear(); + } + else { + var prevCount = this.count; + this.count = this.Trim(this.root, depth - 1); + if (prevCount != this.count) { + this.version++; + } + } + } + + int Trim(SplayTreeNode node, int depth) { + if (depth == 0) { + node.LeftChild = null; + node.RightChild = null; + return 1; + } + else { + int count = 1; + + if (node.LeftChild != null) { + count += this.Trim(node.LeftChild, depth - 1); + } + + if (node.RightChild != null) { + count += this.Trim(node.RightChild, depth - 1); + } + + return count; + } + } + + public ICollection Keys { + get { return new TiedList(this, this.version, this.AsList(node => node.Key)); } + } + + public ICollection Values { + get { return new TiedList(this, this.version, this.AsList(node => node.Value)); } + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) { + this.AsList(node => new KeyValuePair(node.Key, node.Value)).CopyTo(array, arrayIndex); + } + + public IEnumerator> GetEnumerator() { + return new TiedList>(this, this.version, + this.AsList(node => new KeyValuePair(node.Key, node.Value))).GetEnumerator(); + } + + IList AsList(Func selector) { + if (this.root == null) { + return new TEnumerator[0]; + } + + var result = new List(this.count); + this.PopulateList(this.root, result, selector); + return result; + } + + void PopulateList(SplayTreeNode node, List list, + Func selector) { + if (node.LeftChild != null) { + this.PopulateList(node.LeftChild, list, selector); + } + + list.Add(selector(node)); + if (node.RightChild != null) { + this.PopulateList(node.RightChild, list, selector); + } + } + + IEnumerator IEnumerable.GetEnumerator() { + return this.GetEnumerator(); + } + + sealed class SplayTreeNode { + public readonly TKey Key; + + public TValue Value; + public SplayTreeNode LeftChild; + public SplayTreeNode RightChild; + + public SplayTreeNode(TKey key, TValue value) { + this.Key = key; + this.Value = value; + } + } + + sealed class TiedList : IList { + readonly SplayTree tree; + readonly int version; + readonly IList backingList; + + public TiedList(SplayTree tree, int version, IList backingList) { + if (tree == null) { + throw new ArgumentNullException("tree"); + } + + if (backingList == null) { + throw new ArgumentNullException("backingList"); + } + + this.tree = tree; + this.version = version; + this.backingList = backingList; + } + + public int IndexOf(T item) { + if (this.tree.version != this.version) { + throw new InvalidOperationException("The collection has been modified."); + } + + return this.backingList.IndexOf(item); + } + + public void Insert(int index, T item) { + throw new NotSupportedException(); + } + + public void RemoveAt(int index) { + throw new NotSupportedException(); + } + + public T this[int index] { + get { + if (this.tree.version != this.version) { + throw new InvalidOperationException("The collection has been modified."); + } + + return this.backingList[index]; + } + set { throw new NotSupportedException(); } + } + + public void Add(T item) { + throw new NotSupportedException(); + } + + public void Clear() { + throw new NotSupportedException(); + } + + public bool Contains(T item) { + if (this.tree.version != this.version) { + throw new InvalidOperationException("The collection has been modified."); + } + + return this.backingList.Contains(item); + } + + public void CopyTo(T[] array, int arrayIndex) { + if (this.tree.version != this.version) { + throw new InvalidOperationException("The collection has been modified."); + } + + this.backingList.CopyTo(array, arrayIndex); + } + + public int Count { + get { return this.tree.count; } + } + + public bool IsReadOnly { + get { return true; } + } + + public bool Remove(T item) { + throw new NotSupportedException(); + } + + public IEnumerator GetEnumerator() { + if (this.tree.version != this.version) { + throw new InvalidOperationException("The collection has been modified."); + } + + foreach (var item in this.backingList) { + yield return item; + if (this.tree.version != this.version) { + throw new InvalidOperationException("The collection has been modified."); + } + } + } + + IEnumerator IEnumerable.GetEnumerator() { + return this.GetEnumerator(); + } + } + } +} \ No newline at end of file diff --git a/Runtime/external/SplayTree.cs.meta b/Runtime/external/SplayTree.cs.meta new file mode 100644 index 00000000..21a69192 --- /dev/null +++ b/Runtime/external/SplayTree.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 600f58b77585e6845aa9f0924a5af554 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/painting/gradient.cs b/Runtime/painting/gradient.cs index 289ec8b7..a4eafd51 100644 --- a/Runtime/painting/gradient.cs +++ b/Runtime/painting/gradient.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Unity.UIWidgets.foundation; +using Unity.UIWidgets.Runtime.external; using Unity.UIWidgets.ui; using UnityEngine; using Color = Unity.UIWidgets.ui.Color; diff --git a/Runtime/ui/txt/paragraph.cs b/Runtime/ui/txt/paragraph.cs index 0c765351..8304485f 100644 --- a/Runtime/ui/txt/paragraph.cs +++ b/Runtime/ui/txt/paragraph.cs @@ -1,7 +1,7 @@ using System; -using System.Collections; using System.Collections.Generic; using Unity.UIWidgets.foundation; +using Unity.UIWidgets.Runtime.external; using UnityEngine; namespace Unity.UIWidgets.ui { @@ -1121,491 +1121,5 @@ float getLineXOffset(float lineTotalAdvance) { } } - class SplayTree : IDictionary where TKey : IComparable { - SplayTreeNode root; - int count; - int version = 0; - public void Add(TKey key, TValue value) { - this.Set(key, value, throwOnExisting: true); - } - - public void Add(KeyValuePair item) { - this.Set(item.Key, item.Value, throwOnExisting: true); - } - - public void AddAll(IEnumerable list) { - foreach (var key in list) { - this.Add(new KeyValuePair(key, default)); - } - } - - void Set(TKey key, TValue value, bool throwOnExisting) { - if (this.count == 0) { - this.version++; - this.root = new SplayTreeNode(key, value); - this.count = 1; - return; - } - - this.Splay(key); - - var c = key.CompareTo(this.root.Key); - if (c == 0) { - if (throwOnExisting) { - throw new ArgumentException("An item with the same key already exists in the tree."); - } - - this.version++; - this.root.Value = value; - return; - } - - var n = new SplayTreeNode(key, value); - if (c < 0) { - n.LeftChild = this.root.LeftChild; - n.RightChild = this.root; - this.root.LeftChild = null; - } - else { - n.RightChild = this.root.RightChild; - n.LeftChild = this.root; - this.root.RightChild = null; - } - - this.root = n; - this.count++; - this.Splay(key); - this.version++; - } - - public void Clear() { - this.root = null; - this.count = 0; - this.version++; - } - - public bool ContainsKey(TKey key) { - if (this.count == 0) { - return false; - } - - this.Splay(key); - - return key.CompareTo(this.root.Key) == 0; - } - - public bool Contains(KeyValuePair item) { - if (this.count == 0) { - return false; - } - - this.Splay(item.Key); - - return item.Key.CompareTo(this.root.Key) == 0 && - (ReferenceEquals(this.root.Value, item.Value) || - (!ReferenceEquals(item.Value, null) && item.Value.Equals(this.root.Value))); - } - - public KeyValuePair? First() { - SplayTreeNode t = this.root; - if (t == null) { - return null; - } - - while (t.LeftChild != null) { - t = t.LeftChild; - } - - return new KeyValuePair(t.Key, t.Value); - } - - public KeyValuePair FirstOrDefault() { - SplayTreeNode t = this.root; - if (t == null) { - return new KeyValuePair(default(TKey), default(TValue)); - } - - while (t.LeftChild != null) { - t = t.LeftChild; - } - - return new KeyValuePair(t.Key, t.Value); - } - - public KeyValuePair? Last() { - SplayTreeNode t = this.root; - if (t == null) { - return null; - } - - while (t.RightChild != null) { - t = t.RightChild; - } - - return new KeyValuePair(t.Key, t.Value); - } - - public KeyValuePair LastOrDefault() { - SplayTreeNode t = this.root; - if (t == null) { - return new KeyValuePair(default(TKey), default(TValue)); - } - - while (t.RightChild != null) { - t = t.RightChild; - } - - return new KeyValuePair(t.Key, t.Value); - } - - void Splay(TKey key) { - SplayTreeNode l, r, t, y, header; - l = r = header = new SplayTreeNode(default(TKey), default(TValue)); - t = this.root; - while (true) { - var c = key.CompareTo(t.Key); - if (c < 0) { - if (t.LeftChild == null) { - break; - } - - if (key.CompareTo(t.LeftChild.Key) < 0) { - y = t.LeftChild; - t.LeftChild = y.RightChild; - y.RightChild = t; - t = y; - if (t.LeftChild == null) { - break; - } - } - - r.LeftChild = t; - r = t; - t = t.LeftChild; - } - else if (c > 0) { - if (t.RightChild == null) { - break; - } - - if (key.CompareTo(t.RightChild.Key) > 0) { - y = t.RightChild; - t.RightChild = y.LeftChild; - y.LeftChild = t; - t = y; - if (t.RightChild == null) { - break; - } - } - - l.RightChild = t; - l = t; - t = t.RightChild; - } - else { - break; - } - } - - l.RightChild = t.LeftChild; - r.LeftChild = t.RightChild; - t.LeftChild = header.RightChild; - t.RightChild = header.LeftChild; - this.root = t; - } - - public bool Remove(TKey key) { - if (this.count == 0) { - return false; - } - - this.Splay(key); - - if (key.CompareTo(this.root.Key) != 0) { - return false; - } - - if (this.root.LeftChild == null) { - this.root = this.root.RightChild; - } - else { - var swap = this.root.RightChild; - this.root = this.root.LeftChild; - this.Splay(key); - this.root.RightChild = swap; - } - - this.version++; - this.count--; - return true; - } - - public bool TryGetValue(TKey key, out TValue value) { - if (this.count == 0) { - value = default(TValue); - return false; - } - - this.Splay(key); - if (key.CompareTo(this.root.Key) != 0) { - value = default(TValue); - return false; - } - - value = this.root.Value; - return true; - } - - public TValue this[TKey key] { - get { - if (this.count == 0) { - throw new KeyNotFoundException("The key was not found in the tree."); - } - - this.Splay(key); - if (key.CompareTo(this.root.Key) != 0) { - throw new KeyNotFoundException("The key was not found in the tree."); - } - - return this.root.Value; - } - - set { this.Set(key, value, throwOnExisting: false); } - } - - public int Count { - get { return this.count; } - } - - public bool IsReadOnly { - get { return false; } - } - - public bool Remove(KeyValuePair item) { - if (this.count == 0) { - return false; - } - - this.Splay(item.Key); - - if (item.Key.CompareTo(this.root.Key) == 0 && (ReferenceEquals(this.root.Value, item.Value) || - (!ReferenceEquals(item.Value, null) && - item.Value.Equals(this.root.Value)))) { - return false; - } - - if (this.root.LeftChild == null) { - this.root = this.root.RightChild; - } - else { - var swap = this.root.RightChild; - this.root = this.root.LeftChild; - this.Splay(item.Key); - this.root.RightChild = swap; - } - - this.version++; - this.count--; - return true; - } - - public void Trim(int depth) { - if (depth < 0) { - throw new ArgumentOutOfRangeException("depth", "The trim depth must not be negative."); - } - - if (this.count == 0) { - return; - } - - if (depth == 0) { - this.Clear(); - } - else { - var prevCount = this.count; - this.count = this.Trim(this.root, depth - 1); - if (prevCount != this.count) { - this.version++; - } - } - } - - int Trim(SplayTreeNode node, int depth) { - if (depth == 0) { - node.LeftChild = null; - node.RightChild = null; - return 1; - } - else { - int count = 1; - - if (node.LeftChild != null) { - count += this.Trim(node.LeftChild, depth - 1); - } - - if (node.RightChild != null) { - count += this.Trim(node.RightChild, depth - 1); - } - - return count; - } - } - - public ICollection Keys { - get { return new TiedList(this, this.version, this.AsList(node => node.Key)); } - } - - public ICollection Values { - get { return new TiedList(this, this.version, this.AsList(node => node.Value)); } - } - - public void CopyTo(KeyValuePair[] array, int arrayIndex) { - this.AsList(node => new KeyValuePair(node.Key, node.Value)).CopyTo(array, arrayIndex); - } - - public IEnumerator> GetEnumerator() { - return new TiedList>(this, this.version, - this.AsList(node => new KeyValuePair(node.Key, node.Value))).GetEnumerator(); - } - - IList AsList(Func selector) { - if (this.root == null) { - return new TEnumerator[0]; - } - - var result = new List(this.count); - this.PopulateList(this.root, result, selector); - return result; - } - - void PopulateList(SplayTreeNode node, List list, - Func selector) { - if (node.LeftChild != null) { - this.PopulateList(node.LeftChild, list, selector); - } - - list.Add(selector(node)); - if (node.RightChild != null) { - this.PopulateList(node.RightChild, list, selector); - } - } - - IEnumerator IEnumerable.GetEnumerator() { - return this.GetEnumerator(); - } - - sealed class SplayTreeNode { - public readonly TKey Key; - - public TValue Value; - public SplayTreeNode LeftChild; - public SplayTreeNode RightChild; - - public SplayTreeNode(TKey key, TValue value) { - this.Key = key; - this.Value = value; - } - } - - sealed class TiedList : IList { - readonly SplayTree tree; - readonly int version; - readonly IList backingList; - - public TiedList(SplayTree tree, int version, IList backingList) { - if (tree == null) { - throw new ArgumentNullException("tree"); - } - - if (backingList == null) { - throw new ArgumentNullException("backingList"); - } - - this.tree = tree; - this.version = version; - this.backingList = backingList; - } - - public int IndexOf(T item) { - if (this.tree.version != this.version) { - throw new InvalidOperationException("The collection has been modified."); - } - - return this.backingList.IndexOf(item); - } - - public void Insert(int index, T item) { - throw new NotSupportedException(); - } - - public void RemoveAt(int index) { - throw new NotSupportedException(); - } - - public T this[int index] { - get { - if (this.tree.version != this.version) { - throw new InvalidOperationException("The collection has been modified."); - } - - return this.backingList[index]; - } - set { throw new NotSupportedException(); } - } - - public void Add(T item) { - throw new NotSupportedException(); - } - - public void Clear() { - throw new NotSupportedException(); - } - - public bool Contains(T item) { - if (this.tree.version != this.version) { - throw new InvalidOperationException("The collection has been modified."); - } - - return this.backingList.Contains(item); - } - - public void CopyTo(T[] array, int arrayIndex) { - if (this.tree.version != this.version) { - throw new InvalidOperationException("The collection has been modified."); - } - - this.backingList.CopyTo(array, arrayIndex); - } - - public int Count { - get { return this.tree.count; } - } - - public bool IsReadOnly { - get { return true; } - } - - public bool Remove(T item) { - throw new NotSupportedException(); - } - - public IEnumerator GetEnumerator() { - if (this.tree.version != this.version) { - throw new InvalidOperationException("The collection has been modified."); - } - - foreach (var item in this.backingList) { - yield return item; - if (this.tree.version != this.version) { - throw new InvalidOperationException("The collection has been modified."); - } - } - } - - IEnumerator IEnumerable.GetEnumerator() { - return this.GetEnumerator(); - } - } - } } \ No newline at end of file diff --git a/Runtime/widgets/list_wheel_scroll_view.cs b/Runtime/widgets/list_wheel_scroll_view.cs index bb050600..9aae4085 100644 --- a/Runtime/widgets/list_wheel_scroll_view.cs +++ b/Runtime/widgets/list_wheel_scroll_view.cs @@ -6,6 +6,7 @@ using Unity.UIWidgets.painting; using Unity.UIWidgets.physics; using Unity.UIWidgets.rendering; +using Unity.UIWidgets.Runtime.external; using Unity.UIWidgets.scheduler; using Unity.UIWidgets.ui; using UnityEngine; diff --git a/Runtime/widgets/sliver.cs b/Runtime/widgets/sliver.cs index cbd9bad0..8a52d4eb 100644 --- a/Runtime/widgets/sliver.cs +++ b/Runtime/widgets/sliver.cs @@ -5,6 +5,7 @@ using Unity.UIWidgets.foundation; using Unity.UIWidgets.painting; using Unity.UIWidgets.rendering; +using Unity.UIWidgets.Runtime.external; using Unity.UIWidgets.ui; namespace Unity.UIWidgets.widgets { From f941f660bd9cb896109b610980b31a8c08a4323c Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Wed, 20 Nov 2019 13:43:08 +0800 Subject: [PATCH 03/15] Add RTree. --- Runtime/external/RTree.cs | 496 +++++++++++++++++++++++++++++++++ Runtime/external/RTree.cs.meta | 11 + Runtime/ui/geometry.cs | 8 + 3 files changed, 515 insertions(+) create mode 100644 Runtime/external/RTree.cs create mode 100644 Runtime/external/RTree.cs.meta diff --git a/Runtime/external/RTree.cs b/Runtime/external/RTree.cs new file mode 100644 index 00000000..5b16f172 --- /dev/null +++ b/Runtime/external/RTree.cs @@ -0,0 +1,496 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Unity.UIWidgets.ui; + +namespace Unity.UIWidgets.Runtime.external +{ + public interface ISpatialData + { + ref readonly Rect Rect { get; } + } + + + /// + /// Non-generic class to produce instances of the generic class, + /// optionally using type inference. + /// + public static class ProjectionComparer + { + /// + /// Creates an instance of ProjectionComparer using the specified projection. + /// + /// Type parameter for the elements to be compared + /// Type parameter for the keys to be compared, after being projected from the elements + /// Projection to use when determining the key of an element + /// A comparer which will compare elements by projecting each element to its key, and comparing keys + public static ProjectionComparer Create(Func projection) + { + return new ProjectionComparer(projection); + } + + /// + /// Creates an instance of ProjectionComparer using the specified projection. + /// The ignored parameter is solely present to aid type inference. + /// + /// Type parameter for the elements to be compared + /// Type parameter for the keys to be compared, after being projected from the elements + /// Value is ignored - type may be used by type inference + /// Projection to use when determining the key of an element + /// A comparer which will compare elements by projecting each element to its key, and comparing keys + public static ProjectionComparer Create + (TSource ignored, + Func projection) + { + return new ProjectionComparer(projection); + } + + } + + /// + /// Class generic in the source only to produce instances of the + /// doubly generic class, optionally using type inference. + /// + public static class ProjectionComparer + { + /// + /// Creates an instance of ProjectionComparer using the specified projection. + /// + /// Type parameter for the keys to be compared, after being projected from the elements + /// Projection to use when determining the key of an element + /// A comparer which will compare elements by projecting each element to its key, and comparing keys + public static ProjectionComparer Create(Func projection) + { + return new ProjectionComparer(projection); + } + } + + /// + /// Comparer which projects each element of the comparison to a key, and then compares + /// those keys using the specified (or default) comparer for the key type. + /// + /// Type of elements which this comparer will be asked to compare + /// Type of the key projected from the element + public class ProjectionComparer : IComparer + { + readonly Func projection; + readonly IComparer comparer; + + /// + /// Creates a new instance using the specified projection, which must not be null. + /// The default comparer for the projected type is used. + /// + /// Projection to use during comparisons + public ProjectionComparer(Func projection) + : this(projection, null) + { + } + + /// + /// Creates a new instance using the specified projection, which must not be null. + /// + /// Projection to use during comparisons + /// The comparer to use on the keys. May be null, in + /// which case the default comparer will be used. + public ProjectionComparer(Func projection, IComparer comparer) + { + this.comparer = comparer ?? Comparer.Default; + this.projection = projection; + } + + /// + /// Compares x and y by projecting them to keys and then comparing the keys. + /// Null values are not projected; they obey the + /// standard comparer contract such that two null values are equal; any null value is + /// less than any non-null value. + /// + public int Compare(TSource x, TSource y) + { + // Don't want to project from nullity + if (x == null && y == null) + { + return 0; + } + if (x == null) + { + return -1; + } + if (y == null) + { + return 1; + } + return comparer.Compare(projection(x), projection(y)); + } + } + + public partial class RBush + { + public class Node : ISpatialData + { + private Rect _Rect; + + internal Node(List items, int height) + { + this.Height = height; + this.children = items; + ResetRect(); + } + + internal void Add(ISpatialData node) + { + children.Add(node); + _Rect = Rect.expandToInclude(node.Rect); + } + + internal void Remove(ISpatialData node) + { + children.Remove(node); + ResetRect(); + } + + internal void RemoveRange(int index, int count) + { + children.RemoveRange(index, count); + ResetRect(); + } + + internal void ResetRect() + { + _Rect = GetEnclosingRect(children); + } + + internal readonly List children; + + public IReadOnlyList Children => children; + public int Height { get; } + public bool IsLeaf => Height == 1; + public ref readonly Rect Rect => ref _Rect; + } + } + public partial class RBush + { + #region Sort Functions + private static readonly IComparer CompareMinX = + ProjectionComparer.Create(d => d.Rect.left); + private static readonly IComparer CompareMinY = + ProjectionComparer.Create(d => d.Rect.top); + #endregion + + #region Search + private List DoSearch(in Rect boundingBox) + { + if (!Root.Rect.overlaps(boundingBox)) + return new List(); + + var intersections = new List(); + var queue = new Queue(); + queue.Enqueue(Root); + + while (queue.Count != 0) + { + var item = queue.Dequeue(); + if (item.IsLeaf) + { + foreach (T leafChildItem in item.children.Cast()) + if (leafChildItem.Rect.overlaps(boundingBox)) + intersections.Add(leafChildItem); + } + else + { + foreach (var child in item.children.Cast()) + if (child.Rect.overlaps(boundingBox)) + queue.Enqueue(child); + } + } + + return intersections; + } + #endregion + + #region Insert + private List FindCoveringArea(in Rect area, int depth) + { + var path = new List(); + var node = this.Root; + var _area = area; //FIX CS1628 + + while (true) + { + path.Add(node); + if (node.IsLeaf || path.Count == depth) return path; + + node = node.children + .Select(c => new { EnlargedArea = c.Rect.expandToInclude(_area).area, c.Rect.area, Node = c as Node, }) + .OrderBy(x => x.EnlargedArea) + .ThenBy(x => x.area) + .Select(x => x.Node) + .First(); + } + } + + private void Insert(ISpatialData data, int depth) + { + var path = FindCoveringArea(data.Rect, depth); + + var insertNode = path.Last(); + insertNode.Add(data); + + while (--depth >= 0) + { + if (path[depth].children.Count > maxEntries) + { + var newNode = SplitNode(path[depth]); + if (depth == 0) + SplitRoot(newNode); + else + path[depth - 1].Add(newNode); + } + else + path[depth].ResetRect(); + } + } + + #region SplitNode + private void SplitRoot(Node newNode) => + this.Root = new Node(new List { this.Root, newNode }, this.Root.Height + 1); + + private Node SplitNode(Node node) + { + SortChildren(node); + + var splitPoint = GetBestSplitIndex(node.children); + var newChildren = node.children.Skip(splitPoint).ToList(); + node.RemoveRange(splitPoint, node.children.Count - splitPoint); + return new Node(newChildren, node.Height); + } + + #region SortChildren + private void SortChildren(Node node) + { + node.children.Sort(CompareMinX); + var splitsByX = GetPotentialSplitMargins(node.children); + node.children.Sort(CompareMinY); + var splitsByY = GetPotentialSplitMargins(node.children); + + if (splitsByX < splitsByY) + node.children.Sort(CompareMinX); + } + + private double GetPotentialSplitMargins(List children) => + GetPotentialEnclosingMargins(children) + + GetPotentialEnclosingMargins(children.AsEnumerable().Reverse().ToList()); + + private double GetPotentialEnclosingMargins(List children) + { + var rect = Rect.zero; + int i = 0; + for (; i < minEntries; i++) + { + rect = rect.expandToInclude(children[i].Rect); + } + + var totalMargin = rect.margin; + for (; i < children.Count - minEntries; i++) + { + rect = rect.expandToInclude(children[i].Rect); + totalMargin += rect.margin; + } + + return totalMargin; + } + #endregion + + private int GetBestSplitIndex(List children) + { + return Enumerable.Range(minEntries, children.Count - minEntries) + .Select(i => + { + var leftRect = GetEnclosingRect(children.Take(i)); + var rightRect = GetEnclosingRect(children.Skip(i)); + + var overlap = leftRect.intersect(rightRect).area; + var totalArea = leftRect.area + rightRect.area; + return new { i, overlap, totalArea }; + }) + .OrderBy(x => x.overlap) + .ThenBy(x => x.totalArea) + .Select(x => x.i) + .First(); + } + #endregion + #endregion + + #region BuildTree + private Node BuildTree(List data) + { + var treeHeight = GetDepth(data.Count); + var rootMaxEntries = (int)Math.Ceiling(data.Count / Math.Pow(this.maxEntries, treeHeight - 1)); + return BuildNodes(data, 0, data.Count - 1, treeHeight, rootMaxEntries); + } + + private int GetDepth(int numNodes) => + (int)Math.Ceiling(Math.Log(numNodes) / Math.Log(this.maxEntries)); + + private Node BuildNodes(List data, int left, int right, int height, int maxEntries) + { + var num = right - left + 1; + if (num <= maxEntries) + { + return height == 1 + ? new Node(data.GetRange(left, num), height) + : new Node( + new List + { + BuildNodes(data, left, right, height - 1, this.maxEntries), + }, + height); + } + + data.Sort(left, num, CompareMinX); + + var nodeSize = (num + (maxEntries - 1)) / maxEntries; + var subSortLength = nodeSize * (int)Math.Ceiling(Math.Sqrt(maxEntries)); + + var children = new List(maxEntries); + for (int subCounter = left; subCounter <= right; subCounter += subSortLength) + { + var subRight = Math.Min(subCounter + subSortLength - 1, right); + data.Sort(subCounter, subRight - subCounter + 1, CompareMinY); + + for (int nodeCounter = subCounter; nodeCounter <= subRight; nodeCounter += nodeSize) + { + children.Add( + BuildNodes( + data, + nodeCounter, + Math.Min(nodeCounter + nodeSize - 1, subRight), + height - 1, + this.maxEntries)); + } + } + + return new Node(children, height); + } + #endregion + + private static Rect GetEnclosingRect(IEnumerable items) + { + var rect = Rect.zero; + foreach (var data in items) + { + rect = rect.expandToInclude(data.Rect); + } + return rect; + } + + private List GetAllChildren(List list, Node n) + { + if (n.IsLeaf) + { + list.AddRange( + n.children.Cast()); + } + else + { + foreach (var node in n.children.Cast()) + GetAllChildren(list, node); + } + + return list; + } + + } + public partial class RBush where T : ISpatialData + { + private const int DefaultMaxEntries = 9; + private const int MinimumMaxEntries = 4; + private const int MinimumMinEntries = 2; + private const double DefaultFillFactor = 0.4; + + private readonly EqualityComparer comparer; + private readonly int maxEntries; + private readonly int minEntries; + + public Node Root { get; private set; } + public ref readonly Rect Rect => ref Root.Rect; + + public RBush() : this(DefaultMaxEntries) { } + public RBush(int maxEntries) + : this(maxEntries, EqualityComparer.Default) { } + public RBush(int maxEntries, EqualityComparer comparer) + { + this.comparer = comparer; + this.maxEntries = Math.Max(MinimumMaxEntries, maxEntries); + this.minEntries = Math.Max(MinimumMinEntries, (int)Math.Ceiling(this.maxEntries * DefaultFillFactor)); + + this.Clear(); + } + + public int Count { get; private set; } + + public void Clear() + { + this.Root = new Node(new List(), 1); + this.Count = 0; + } + + public IReadOnlyList Search() => GetAllChildren(new List(), this.Root); + + public IReadOnlyList Search(in Rect boundingBox) => + DoSearch(boundingBox); + + public void Insert(T item) + { + Insert(item, this.Root.Height); + this.Count++; + } + + public void BulkLoad(IEnumerable items) + { + var data = items.Cast().ToList(); + if (data.Count == 0) return; + + if (this.Root.IsLeaf && + this.Root.children.Count + data.Count < maxEntries) + { + foreach (var i in data) + Insert((T)i); + return; + } + + if (data.Count < this.minEntries) + { + foreach (var i in data) + Insert((T)i); + return; + } + + var dataRoot = BuildTree(data); + this.Count += data.Count; + + if (this.Root.children.Count == 0) + this.Root = dataRoot; + else if (this.Root.Height == dataRoot.Height) + { + if (this.Root.children.Count + dataRoot.children.Count <= this.maxEntries) + { + foreach (var isd in dataRoot.children) + this.Root.Add(isd); + } + else + SplitRoot(dataRoot); + } + else + { + if (this.Root.Height < dataRoot.Height) + { + var tmp = this.Root; + this.Root = dataRoot; + dataRoot = tmp; + } + + this.Insert(dataRoot, this.Root.Height - dataRoot.Height); + } + } + } +} \ No newline at end of file diff --git a/Runtime/external/RTree.cs.meta b/Runtime/external/RTree.cs.meta new file mode 100644 index 00000000..30f3da65 --- /dev/null +++ b/Runtime/external/RTree.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0a93ee633aad3dc4395851f56b650cc2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ui/geometry.cs b/Runtime/ui/geometry.cs index 9bea0fbb..c009d238 100644 --- a/Runtime/ui/geometry.cs +++ b/Runtime/ui/geometry.cs @@ -541,6 +541,14 @@ public Size size { get { return new Size(this.width, this.height); } } + public float area { + get { return this.width * this.height; } + } + + public float margin { + get { return this.width * this.height; } + } + public static readonly Rect zero = new Rect(0, 0, 0, 0); public static readonly Rect one = new Rect(0, 0, 1, 1); From 0bf499f04fbf3f7db51801f0ded47ae4c7d9fe87 Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Wed, 20 Nov 2019 13:47:22 +0800 Subject: [PATCH 04/15] Refactor RTree. --- Runtime/external/RTree.cs | 977 +++++++++++++++++++------------------- 1 file changed, 493 insertions(+), 484 deletions(-) diff --git a/Runtime/external/RTree.cs b/Runtime/external/RTree.cs index 5b16f172..42c0a09a 100644 --- a/Runtime/external/RTree.cs +++ b/Runtime/external/RTree.cs @@ -9,488 +9,497 @@ public interface ISpatialData { ref readonly Rect Rect { get; } } - - - /// - /// Non-generic class to produce instances of the generic class, - /// optionally using type inference. - /// - public static class ProjectionComparer - { - /// - /// Creates an instance of ProjectionComparer using the specified projection. - /// - /// Type parameter for the elements to be compared - /// Type parameter for the keys to be compared, after being projected from the elements - /// Projection to use when determining the key of an element - /// A comparer which will compare elements by projecting each element to its key, and comparing keys - public static ProjectionComparer Create(Func projection) - { - return new ProjectionComparer(projection); - } - - /// - /// Creates an instance of ProjectionComparer using the specified projection. - /// The ignored parameter is solely present to aid type inference. - /// - /// Type parameter for the elements to be compared - /// Type parameter for the keys to be compared, after being projected from the elements - /// Value is ignored - type may be used by type inference - /// Projection to use when determining the key of an element - /// A comparer which will compare elements by projecting each element to its key, and comparing keys - public static ProjectionComparer Create - (TSource ignored, - Func projection) - { - return new ProjectionComparer(projection); - } - - } - - /// - /// Class generic in the source only to produce instances of the - /// doubly generic class, optionally using type inference. - /// - public static class ProjectionComparer - { - /// - /// Creates an instance of ProjectionComparer using the specified projection. - /// - /// Type parameter for the keys to be compared, after being projected from the elements - /// Projection to use when determining the key of an element - /// A comparer which will compare elements by projecting each element to its key, and comparing keys - public static ProjectionComparer Create(Func projection) - { - return new ProjectionComparer(projection); - } - } - - /// - /// Comparer which projects each element of the comparison to a key, and then compares - /// those keys using the specified (or default) comparer for the key type. - /// - /// Type of elements which this comparer will be asked to compare - /// Type of the key projected from the element - public class ProjectionComparer : IComparer - { - readonly Func projection; - readonly IComparer comparer; - - /// - /// Creates a new instance using the specified projection, which must not be null. - /// The default comparer for the projected type is used. - /// - /// Projection to use during comparisons - public ProjectionComparer(Func projection) - : this(projection, null) - { - } - - /// - /// Creates a new instance using the specified projection, which must not be null. - /// - /// Projection to use during comparisons - /// The comparer to use on the keys. May be null, in - /// which case the default comparer will be used. - public ProjectionComparer(Func projection, IComparer comparer) - { - this.comparer = comparer ?? Comparer.Default; - this.projection = projection; - } - - /// - /// Compares x and y by projecting them to keys and then comparing the keys. - /// Null values are not projected; they obey the - /// standard comparer contract such that two null values are equal; any null value is - /// less than any non-null value. - /// - public int Compare(TSource x, TSource y) - { - // Don't want to project from nullity - if (x == null && y == null) - { - return 0; - } - if (x == null) - { - return -1; - } - if (y == null) - { - return 1; - } - return comparer.Compare(projection(x), projection(y)); - } - } - - public partial class RBush - { - public class Node : ISpatialData - { - private Rect _Rect; - - internal Node(List items, int height) - { - this.Height = height; - this.children = items; - ResetRect(); - } - - internal void Add(ISpatialData node) - { - children.Add(node); - _Rect = Rect.expandToInclude(node.Rect); - } - - internal void Remove(ISpatialData node) - { - children.Remove(node); - ResetRect(); - } - - internal void RemoveRange(int index, int count) - { - children.RemoveRange(index, count); - ResetRect(); - } - - internal void ResetRect() - { - _Rect = GetEnclosingRect(children); - } - - internal readonly List children; - - public IReadOnlyList Children => children; - public int Height { get; } - public bool IsLeaf => Height == 1; - public ref readonly Rect Rect => ref _Rect; - } - } - public partial class RBush - { - #region Sort Functions - private static readonly IComparer CompareMinX = - ProjectionComparer.Create(d => d.Rect.left); - private static readonly IComparer CompareMinY = - ProjectionComparer.Create(d => d.Rect.top); - #endregion - - #region Search - private List DoSearch(in Rect boundingBox) - { - if (!Root.Rect.overlaps(boundingBox)) - return new List(); - - var intersections = new List(); - var queue = new Queue(); - queue.Enqueue(Root); - - while (queue.Count != 0) - { - var item = queue.Dequeue(); - if (item.IsLeaf) - { - foreach (T leafChildItem in item.children.Cast()) - if (leafChildItem.Rect.overlaps(boundingBox)) - intersections.Add(leafChildItem); - } - else - { - foreach (var child in item.children.Cast()) - if (child.Rect.overlaps(boundingBox)) - queue.Enqueue(child); - } - } - - return intersections; - } - #endregion - - #region Insert - private List FindCoveringArea(in Rect area, int depth) - { - var path = new List(); - var node = this.Root; - var _area = area; //FIX CS1628 - - while (true) - { - path.Add(node); - if (node.IsLeaf || path.Count == depth) return path; - - node = node.children - .Select(c => new { EnlargedArea = c.Rect.expandToInclude(_area).area, c.Rect.area, Node = c as Node, }) - .OrderBy(x => x.EnlargedArea) - .ThenBy(x => x.area) - .Select(x => x.Node) - .First(); - } - } - - private void Insert(ISpatialData data, int depth) - { - var path = FindCoveringArea(data.Rect, depth); - - var insertNode = path.Last(); - insertNode.Add(data); - - while (--depth >= 0) - { - if (path[depth].children.Count > maxEntries) - { - var newNode = SplitNode(path[depth]); - if (depth == 0) - SplitRoot(newNode); - else - path[depth - 1].Add(newNode); - } - else - path[depth].ResetRect(); - } - } - - #region SplitNode - private void SplitRoot(Node newNode) => - this.Root = new Node(new List { this.Root, newNode }, this.Root.Height + 1); - - private Node SplitNode(Node node) - { - SortChildren(node); - - var splitPoint = GetBestSplitIndex(node.children); - var newChildren = node.children.Skip(splitPoint).ToList(); - node.RemoveRange(splitPoint, node.children.Count - splitPoint); - return new Node(newChildren, node.Height); - } - - #region SortChildren - private void SortChildren(Node node) - { - node.children.Sort(CompareMinX); - var splitsByX = GetPotentialSplitMargins(node.children); - node.children.Sort(CompareMinY); - var splitsByY = GetPotentialSplitMargins(node.children); - - if (splitsByX < splitsByY) - node.children.Sort(CompareMinX); - } - - private double GetPotentialSplitMargins(List children) => - GetPotentialEnclosingMargins(children) + - GetPotentialEnclosingMargins(children.AsEnumerable().Reverse().ToList()); - - private double GetPotentialEnclosingMargins(List children) - { - var rect = Rect.zero; - int i = 0; - for (; i < minEntries; i++) - { - rect = rect.expandToInclude(children[i].Rect); - } - - var totalMargin = rect.margin; - for (; i < children.Count - minEntries; i++) - { - rect = rect.expandToInclude(children[i].Rect); - totalMargin += rect.margin; - } - - return totalMargin; - } - #endregion - - private int GetBestSplitIndex(List children) - { - return Enumerable.Range(minEntries, children.Count - minEntries) - .Select(i => - { - var leftRect = GetEnclosingRect(children.Take(i)); - var rightRect = GetEnclosingRect(children.Skip(i)); - - var overlap = leftRect.intersect(rightRect).area; - var totalArea = leftRect.area + rightRect.area; - return new { i, overlap, totalArea }; - }) - .OrderBy(x => x.overlap) - .ThenBy(x => x.totalArea) - .Select(x => x.i) - .First(); - } - #endregion - #endregion - - #region BuildTree - private Node BuildTree(List data) - { - var treeHeight = GetDepth(data.Count); - var rootMaxEntries = (int)Math.Ceiling(data.Count / Math.Pow(this.maxEntries, treeHeight - 1)); - return BuildNodes(data, 0, data.Count - 1, treeHeight, rootMaxEntries); - } - - private int GetDepth(int numNodes) => - (int)Math.Ceiling(Math.Log(numNodes) / Math.Log(this.maxEntries)); - - private Node BuildNodes(List data, int left, int right, int height, int maxEntries) - { - var num = right - left + 1; - if (num <= maxEntries) - { - return height == 1 - ? new Node(data.GetRange(left, num), height) - : new Node( - new List - { - BuildNodes(data, left, right, height - 1, this.maxEntries), - }, - height); - } - - data.Sort(left, num, CompareMinX); - - var nodeSize = (num + (maxEntries - 1)) / maxEntries; - var subSortLength = nodeSize * (int)Math.Ceiling(Math.Sqrt(maxEntries)); - - var children = new List(maxEntries); - for (int subCounter = left; subCounter <= right; subCounter += subSortLength) - { - var subRight = Math.Min(subCounter + subSortLength - 1, right); - data.Sort(subCounter, subRight - subCounter + 1, CompareMinY); - - for (int nodeCounter = subCounter; nodeCounter <= subRight; nodeCounter += nodeSize) - { - children.Add( - BuildNodes( - data, - nodeCounter, - Math.Min(nodeCounter + nodeSize - 1, subRight), - height - 1, - this.maxEntries)); - } - } - - return new Node(children, height); - } - #endregion - - private static Rect GetEnclosingRect(IEnumerable items) - { - var rect = Rect.zero; - foreach (var data in items) - { - rect = rect.expandToInclude(data.Rect); - } - return rect; - } - - private List GetAllChildren(List list, Node n) - { - if (n.IsLeaf) - { - list.AddRange( - n.children.Cast()); - } - else - { - foreach (var node in n.children.Cast()) - GetAllChildren(list, node); - } - - return list; - } - - } - public partial class RBush where T : ISpatialData - { - private const int DefaultMaxEntries = 9; - private const int MinimumMaxEntries = 4; - private const int MinimumMinEntries = 2; - private const double DefaultFillFactor = 0.4; - - private readonly EqualityComparer comparer; - private readonly int maxEntries; - private readonly int minEntries; - - public Node Root { get; private set; } - public ref readonly Rect Rect => ref Root.Rect; - - public RBush() : this(DefaultMaxEntries) { } - public RBush(int maxEntries) - : this(maxEntries, EqualityComparer.Default) { } - public RBush(int maxEntries, EqualityComparer comparer) - { - this.comparer = comparer; - this.maxEntries = Math.Max(MinimumMaxEntries, maxEntries); - this.minEntries = Math.Max(MinimumMinEntries, (int)Math.Ceiling(this.maxEntries * DefaultFillFactor)); - - this.Clear(); - } - - public int Count { get; private set; } - - public void Clear() - { - this.Root = new Node(new List(), 1); - this.Count = 0; - } - - public IReadOnlyList Search() => GetAllChildren(new List(), this.Root); - - public IReadOnlyList Search(in Rect boundingBox) => - DoSearch(boundingBox); - - public void Insert(T item) - { - Insert(item, this.Root.Height); - this.Count++; - } - - public void BulkLoad(IEnumerable items) - { - var data = items.Cast().ToList(); - if (data.Count == 0) return; - - if (this.Root.IsLeaf && - this.Root.children.Count + data.Count < maxEntries) - { - foreach (var i in data) - Insert((T)i); - return; - } - - if (data.Count < this.minEntries) - { - foreach (var i in data) - Insert((T)i); - return; - } - - var dataRoot = BuildTree(data); - this.Count += data.Count; - - if (this.Root.children.Count == 0) - this.Root = dataRoot; - else if (this.Root.Height == dataRoot.Height) - { - if (this.Root.children.Count + dataRoot.children.Count <= this.maxEntries) - { - foreach (var isd in dataRoot.children) - this.Root.Add(isd); - } - else - SplitRoot(dataRoot); - } - else - { - if (this.Root.Height < dataRoot.Height) - { - var tmp = this.Root; - this.Root = dataRoot; - dataRoot = tmp; - } - - this.Insert(dataRoot, this.Root.Height - dataRoot.Height); - } - } - } + + + /// + /// Non-generic class to produce instances of the generic class, + /// optionally using type inference. + /// + public static class ProjectionComparer + { + /// + /// Creates an instance of ProjectionComparer using the specified projection. + /// + /// Type parameter for the elements to be compared + /// Type parameter for the keys to be compared, after being projected from the elements + /// Projection to use when determining the key of an element + /// A comparer which will compare elements by projecting each element to its key, and comparing keys + public static ProjectionComparer Create(Func projection) + { + return new ProjectionComparer(projection); + } + + /// + /// Creates an instance of ProjectionComparer using the specified projection. + /// The ignored parameter is solely present to aid type inference. + /// + /// Type parameter for the elements to be compared + /// Type parameter for the keys to be compared, after being projected from the elements + /// Value is ignored - type may be used by type inference + /// Projection to use when determining the key of an element + /// A comparer which will compare elements by projecting each element to its key, and comparing keys + public static ProjectionComparer Create + (TSource ignored, + Func projection) + { + return new ProjectionComparer(projection); + } + } + + /// + /// Class generic in the source only to produce instances of the + /// doubly generic class, optionally using type inference. + /// + public static class ProjectionComparer + { + /// + /// Creates an instance of ProjectionComparer using the specified projection. + /// + /// Type parameter for the keys to be compared, after being projected from the elements + /// Projection to use when determining the key of an element + /// A comparer which will compare elements by projecting each element to its key, and comparing keys + public static ProjectionComparer Create(Func projection) + { + return new ProjectionComparer(projection); + } + } + + /// + /// Comparer which projects each element of the comparison to a key, and then compares + /// those keys using the specified (or default) comparer for the key type. + /// + /// Type of elements which this comparer will be asked to compare + /// Type of the key projected from the element + public class ProjectionComparer : IComparer + { + private readonly IComparer comparer; + private readonly Func projection; + + /// + /// Creates a new instance using the specified projection, which must not be null. + /// The default comparer for the projected type is used. + /// + /// Projection to use during comparisons + public ProjectionComparer(Func projection) + : this(projection, null) + { + } + + /// + /// Creates a new instance using the specified projection, which must not be null. + /// + /// Projection to use during comparisons + /// + /// The comparer to use on the keys. May be null, in + /// which case the default comparer will be used. + /// + public ProjectionComparer(Func projection, IComparer comparer) + { + this.comparer = comparer ?? Comparer.Default; + this.projection = projection; + } + + /// + /// Compares x and y by projecting them to keys and then comparing the keys. + /// Null values are not projected; they obey the + /// standard comparer contract such that two null values are equal; any null value is + /// less than any non-null value. + /// + public int Compare(TSource x, TSource y) + { + // Don't want to project from nullity + if (x == null && y == null) return 0; + if (x == null) return -1; + if (y == null) return 1; + return comparer.Compare(projection(x), projection(y)); + } + } + + public partial class RTree + { + public class RTreeNode : ISpatialData + { + internal readonly List children; + private Rect _Rect; + + internal RTreeNode(List items, int height) + { + Height = height; + children = items; + ResetRect(); + } + + public IReadOnlyList Children => children; + public int Height { get; } + public bool IsLeaf => Height == 1; + public ref readonly Rect Rect => ref _Rect; + + internal void Add(ISpatialData node) + { + children.Add(node); + _Rect = Rect.expandToInclude(node.Rect); + } + + internal void Remove(ISpatialData node) + { + children.Remove(node); + ResetRect(); + } + + internal void RemoveRange(int index, int count) + { + children.RemoveRange(index, count); + ResetRect(); + } + + internal void ResetRect() + { + _Rect = GetEnclosingRect(children); + } + } + } + + public partial class RTree + { + #region Search + + private List DoSearch(in Rect boundingBox) + { + if (!Root.Rect.overlaps(boundingBox)) + return new List(); + + var intersections = new List(); + var queue = new Queue(); + queue.Enqueue(Root); + + while (queue.Count != 0) + { + var item = queue.Dequeue(); + if (item.IsLeaf) + { + foreach (var leafChildItem in item.children.Cast()) + if (leafChildItem.Rect.overlaps(boundingBox)) + intersections.Add(leafChildItem); + } + else + { + foreach (var child in item.children.Cast()) + if (child.Rect.overlaps(boundingBox)) + queue.Enqueue(child); + } + } + + return intersections; + } + + #endregion + + private static Rect GetEnclosingRect(IEnumerable items) + { + var rect = Rect.zero; + foreach (var data in items) rect = rect.expandToInclude(data.Rect); + return rect; + } + + private List GetAllChildren(List list, RTreeNode n) + { + if (n.IsLeaf) + list.AddRange( + n.children.Cast()); + else + foreach (var node in n.children.Cast()) + GetAllChildren(list, node); + + return list; + } + + #region Sort Functions + + private static readonly IComparer CompareMinX = + ProjectionComparer.Create(d => d.Rect.left); + + private static readonly IComparer CompareMinY = + ProjectionComparer.Create(d => d.Rect.top); + + #endregion + + #region Insert + + private List FindCoveringArea(in Rect area, int depth) + { + var path = new List(); + var node = Root; + var _area = area; //FIX CS1628 + + while (true) + { + path.Add(node); + if (node.IsLeaf || path.Count == depth) return path; + + node = node.children + .Select(c => new + {EnlargedArea = c.Rect.expandToInclude(_area).area, c.Rect.area, Node = c as RTreeNode}) + .OrderBy(x => x.EnlargedArea) + .ThenBy(x => x.area) + .Select(x => x.Node) + .First(); + } + } + + private void Insert(ISpatialData data, int depth) + { + var path = FindCoveringArea(data.Rect, depth); + + var insertNode = path.Last(); + insertNode.Add(data); + + while (--depth >= 0) + if (path[depth].children.Count > maxEntries) + { + var newNode = SplitNode(path[depth]); + if (depth == 0) + SplitRoot(newNode); + else + path[depth - 1].Add(newNode); + } + else + { + path[depth].ResetRect(); + } + } + + #region SplitNode + + private void SplitRoot(RTreeNode newRTreeNode) + { + Root = new RTreeNode(new List {Root, newRTreeNode}, Root.Height + 1); + } + + private RTreeNode SplitNode(RTreeNode rTreeNode) + { + SortChildren(rTreeNode); + + var splitPoint = GetBestSplitIndex(rTreeNode.children); + var newChildren = rTreeNode.children.Skip(splitPoint).ToList(); + rTreeNode.RemoveRange(splitPoint, rTreeNode.children.Count - splitPoint); + return new RTreeNode(newChildren, rTreeNode.Height); + } + + #region SortChildren + + private void SortChildren(RTreeNode rTreeNode) + { + rTreeNode.children.Sort(CompareMinX); + var splitsByX = GetPotentialSplitMargins(rTreeNode.children); + rTreeNode.children.Sort(CompareMinY); + var splitsByY = GetPotentialSplitMargins(rTreeNode.children); + + if (splitsByX < splitsByY) + rTreeNode.children.Sort(CompareMinX); + } + + private double GetPotentialSplitMargins(List children) + { + return GetPotentialEnclosingMargins(children) + + GetPotentialEnclosingMargins(children.AsEnumerable().Reverse().ToList()); + } + + private double GetPotentialEnclosingMargins(List children) + { + var rect = Rect.zero; + var i = 0; + for (; i < minEntries; i++) rect = rect.expandToInclude(children[i].Rect); + + var totalMargin = rect.margin; + for (; i < children.Count - minEntries; i++) + { + rect = rect.expandToInclude(children[i].Rect); + totalMargin += rect.margin; + } + + return totalMargin; + } + + #endregion + + private int GetBestSplitIndex(List children) + { + return Enumerable.Range(minEntries, children.Count - minEntries) + .Select(i => + { + var leftRect = GetEnclosingRect(children.Take(i)); + var rightRect = GetEnclosingRect(children.Skip(i)); + + var overlap = leftRect.intersect(rightRect).area; + var totalArea = leftRect.area + rightRect.area; + return new {i, overlap, totalArea}; + }) + .OrderBy(x => x.overlap) + .ThenBy(x => x.totalArea) + .Select(x => x.i) + .First(); + } + + #endregion + + #endregion + + #region BuildTree + + private RTreeNode BuildTree(List data) + { + var treeHeight = GetDepth(data.Count); + var rootMaxEntries = (int) Math.Ceiling(data.Count / Math.Pow(maxEntries, treeHeight - 1)); + return BuildNodes(data, 0, data.Count - 1, treeHeight, rootMaxEntries); + } + + private int GetDepth(int numNodes) + { + return (int) Math.Ceiling(Math.Log(numNodes) / Math.Log(maxEntries)); + } + + private RTreeNode BuildNodes(List data, int left, int right, int height, int maxEntries) + { + var num = right - left + 1; + if (num <= maxEntries) + return height == 1 + ? new RTreeNode(data.GetRange(left, num), height) + : new RTreeNode( + new List + { + BuildNodes(data, left, right, height - 1, this.maxEntries) + }, + height); + + data.Sort(left, num, CompareMinX); + + var nodeSize = (num + (maxEntries - 1)) / maxEntries; + var subSortLength = nodeSize * (int) Math.Ceiling(Math.Sqrt(maxEntries)); + + var children = new List(maxEntries); + for (var subCounter = left; subCounter <= right; subCounter += subSortLength) + { + var subRight = Math.Min(subCounter + subSortLength - 1, right); + data.Sort(subCounter, subRight - subCounter + 1, CompareMinY); + + for (var nodeCounter = subCounter; nodeCounter <= subRight; nodeCounter += nodeSize) + children.Add( + BuildNodes( + data, + nodeCounter, + Math.Min(nodeCounter + nodeSize - 1, subRight), + height - 1, + this.maxEntries)); + } + + return new RTreeNode(children, height); + } + + #endregion + } + + public partial class RTree where T : ISpatialData + { + private const int DefaultMaxEntries = 9; + private const int MinimumMaxEntries = 4; + private const int MinimumMinEntries = 2; + private const double DefaultFillFactor = 0.4; + + private readonly EqualityComparer comparer; + private readonly int maxEntries; + private readonly int minEntries; + + public RTree() : this(DefaultMaxEntries) + { + } + + public RTree(int maxEntries) + : this(maxEntries, EqualityComparer.Default) + { + } + + public RTree(int maxEntries, EqualityComparer comparer) + { + this.comparer = comparer; + this.maxEntries = Math.Max(MinimumMaxEntries, maxEntries); + minEntries = Math.Max(MinimumMinEntries, (int) Math.Ceiling(this.maxEntries * DefaultFillFactor)); + + Clear(); + } + + public RTreeNode Root { get; private set; } + public ref readonly Rect Rect => ref Root.Rect; + + public int Count { get; private set; } + + public void Clear() + { + Root = new RTreeNode(new List(), 1); + Count = 0; + } + + public IReadOnlyList Search() + { + return GetAllChildren(new List(), Root); + } + + public IReadOnlyList Search(in Rect boundingBox) + { + return DoSearch(boundingBox); + } + + public void Insert(T item) + { + Insert(item, Root.Height); + Count++; + } + + public void BulkLoad(IEnumerable items) + { + var data = items.Cast().ToList(); + if (data.Count == 0) return; + + if (Root.IsLeaf && + Root.children.Count + data.Count < maxEntries) + { + foreach (var i in data) + Insert((T) i); + return; + } + + if (data.Count < minEntries) + { + foreach (var i in data) + Insert((T) i); + return; + } + + var dataRoot = BuildTree(data); + Count += data.Count; + + if (Root.children.Count == 0) + { + Root = dataRoot; + } + else if (Root.Height == dataRoot.Height) + { + if (Root.children.Count + dataRoot.children.Count <= maxEntries) + foreach (var isd in dataRoot.children) + Root.Add(isd); + else + SplitRoot(dataRoot); + } + else + { + if (Root.Height < dataRoot.Height) + { + var tmp = Root; + Root = dataRoot; + dataRoot = tmp; + } + + Insert(dataRoot, Root.Height - dataRoot.Height); + } + } + } } \ No newline at end of file From c97b4e4fb4b106cb6eeb1b13abe2a10f7011df9b Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Wed, 20 Nov 2019 13:51:34 +0800 Subject: [PATCH 05/15] Refactor RTree. --- Runtime/external/RTree.cs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/Runtime/external/RTree.cs b/Runtime/external/RTree.cs index 42c0a09a..9226cebd 100644 --- a/Runtime/external/RTree.cs +++ b/Runtime/external/RTree.cs @@ -115,7 +115,7 @@ public int Compare(TSource x, TSource y) } } - public partial class RTree + public class RTree where T : ISpatialData { public class RTreeNode : ISpatialData { @@ -157,10 +157,6 @@ internal void ResetRect() _Rect = GetEnclosingRect(children); } } - } - - public partial class RTree - { #region Search private List DoSearch(in Rect boundingBox) @@ -298,13 +294,13 @@ private void SortChildren(RTreeNode rTreeNode) rTreeNode.children.Sort(CompareMinX); } - private double GetPotentialSplitMargins(List children) + private float GetPotentialSplitMargins(List children) { return GetPotentialEnclosingMargins(children) + GetPotentialEnclosingMargins(children.AsEnumerable().Reverse().ToList()); } - private double GetPotentialEnclosingMargins(List children) + private float GetPotentialEnclosingMargins(List children) { var rect = Rect.zero; var i = 0; @@ -396,14 +392,10 @@ private RTreeNode BuildNodes(List data, int left, int right, int h } #endregion - } - - public partial class RTree where T : ISpatialData - { private const int DefaultMaxEntries = 9; private const int MinimumMaxEntries = 4; private const int MinimumMinEntries = 2; - private const double DefaultFillFactor = 0.4; + private const float DefaultFillFactor = 0.4f; private readonly EqualityComparer comparer; private readonly int maxEntries; From 4802de1eba39c25bb5b7d8e64bcff7c6d6172f59 Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Wed, 20 Nov 2019 16:24:40 +0800 Subject: [PATCH 06/15] Filter records with r tree. --- Runtime/external/RTree.cs | 73 +++++++++------ Runtime/ui/painting/draw_cmd.cs | 73 ++++++++++++--- Runtime/ui/painting/picture.cs | 31 ++++++- .../cmdbufferCanvas/rendering/canvas_impl.cs | 90 +++++++------------ Runtime/ui/renderer/common/draw_cmd.cs | 69 +++++++++++--- Runtime/ui/renderer/common/geometry/rect.cs | 8 ++ Runtime/ui/renderer/common/picture.cs | 26 +++++- 7 files changed, 254 insertions(+), 116 deletions(-) diff --git a/Runtime/external/RTree.cs b/Runtime/external/RTree.cs index 9226cebd..58a07b12 100644 --- a/Runtime/external/RTree.cs +++ b/Runtime/external/RTree.cs @@ -7,9 +7,26 @@ namespace Unity.UIWidgets.Runtime.external { public interface ISpatialData { - ref readonly Rect Rect { get; } + uiRect bounds { get; } } + public class IndexedRect : ISpatialData + { + private uiRect _bounds; + + public uiRect bounds + { + get { return _bounds; } + } + + public readonly int index; + + public IndexedRect(uiRect bounds, int index) + { + this._bounds = bounds; + this.index = index; + } + } /// /// Non-generic class to produce instances of the generic class, @@ -115,12 +132,18 @@ public int Compare(TSource x, TSource y) } } - public class RTree where T : ISpatialData + public interface BBoxHierarchy where T : ISpatialData + { + IReadOnlyList Search(in uiRect boundingBox); + void BulkLoad(IEnumerable items); + } + + public class RTree : BBoxHierarchy where T : ISpatialData { public class RTreeNode : ISpatialData { internal readonly List children; - private Rect _Rect; + private uiRect _Rect; internal RTreeNode(List items, int height) { @@ -132,12 +155,12 @@ internal RTreeNode(List items, int height) public IReadOnlyList Children => children; public int Height { get; } public bool IsLeaf => Height == 1; - public ref readonly Rect Rect => ref _Rect; + public uiRect bounds => _Rect; internal void Add(ISpatialData node) { children.Add(node); - _Rect = Rect.expandToInclude(node.Rect); + _Rect = bounds.expandToInclude(node.bounds); } internal void Remove(ISpatialData node) @@ -159,9 +182,9 @@ internal void ResetRect() } #region Search - private List DoSearch(in Rect boundingBox) + private List DoSearch(in uiRect boundingBox) { - if (!Root.Rect.overlaps(boundingBox)) + if (!uiRectHelper.overlaps(Root.bounds, boundingBox)) return new List(); var intersections = new List(); @@ -174,13 +197,13 @@ private List DoSearch(in Rect boundingBox) if (item.IsLeaf) { foreach (var leafChildItem in item.children.Cast()) - if (leafChildItem.Rect.overlaps(boundingBox)) + if (uiRectHelper.overlaps(leafChildItem.bounds, boundingBox)) intersections.Add(leafChildItem); } else { foreach (var child in item.children.Cast()) - if (child.Rect.overlaps(boundingBox)) + if (uiRectHelper.overlaps(child.bounds, boundingBox)) queue.Enqueue(child); } } @@ -190,11 +213,11 @@ private List DoSearch(in Rect boundingBox) #endregion - private static Rect GetEnclosingRect(IEnumerable items) + private static uiRect GetEnclosingRect(IEnumerable items) { - var rect = Rect.zero; - foreach (var data in items) rect = rect.expandToInclude(data.Rect); - return rect; + var uiRect = uiRectHelper.zero; + foreach (var data in items) uiRect = uiRect.expandToInclude(data.bounds); + return uiRect; } private List GetAllChildren(List list, RTreeNode n) @@ -212,16 +235,16 @@ private List GetAllChildren(List list, RTreeNode n) #region Sort Functions private static readonly IComparer CompareMinX = - ProjectionComparer.Create(d => d.Rect.left); + ProjectionComparer.Create(d => d.bounds.left); private static readonly IComparer CompareMinY = - ProjectionComparer.Create(d => d.Rect.top); + ProjectionComparer.Create(d => d.bounds.top); #endregion #region Insert - private List FindCoveringArea(in Rect area, int depth) + private List FindCoveringArea(in uiRect area, int depth) { var path = new List(); var node = Root; @@ -234,7 +257,7 @@ private List FindCoveringArea(in Rect area, int depth) node = node.children .Select(c => new - {EnlargedArea = c.Rect.expandToInclude(_area).area, c.Rect.area, Node = c as RTreeNode}) + {EnlargedArea = c.bounds.expandToInclude(_area).area, c.bounds.area, Node = c as RTreeNode}) .OrderBy(x => x.EnlargedArea) .ThenBy(x => x.area) .Select(x => x.Node) @@ -244,7 +267,7 @@ private List FindCoveringArea(in Rect area, int depth) private void Insert(ISpatialData data, int depth) { - var path = FindCoveringArea(data.Rect, depth); + var path = FindCoveringArea(data.bounds, depth); var insertNode = path.Last(); insertNode.Add(data); @@ -302,15 +325,15 @@ private float GetPotentialSplitMargins(List children) private float GetPotentialEnclosingMargins(List children) { - var rect = Rect.zero; + var uiRect = uiRectHelper.zero; var i = 0; - for (; i < minEntries; i++) rect = rect.expandToInclude(children[i].Rect); + for (; i < minEntries; i++) uiRect = uiRect.expandToInclude(children[i].bounds); - var totalMargin = rect.margin; + var totalMargin = uiRect.margin; for (; i < children.Count - minEntries; i++) { - rect = rect.expandToInclude(children[i].Rect); - totalMargin += rect.margin; + uiRect = uiRect.expandToInclude(children[i].bounds); + totalMargin += uiRect.margin; } return totalMargin; @@ -420,7 +443,7 @@ public RTree(int maxEntries, EqualityComparer comparer) } public RTreeNode Root { get; private set; } - public ref readonly Rect Rect => ref Root.Rect; + public uiRect uiRect => Root.bounds; public int Count { get; private set; } @@ -435,7 +458,7 @@ public IReadOnlyList Search() return GetAllChildren(new List(), Root); } - public IReadOnlyList Search(in Rect boundingBox) + public IReadOnlyList Search(in uiRect boundingBox) { return DoSearch(boundingBox); } diff --git a/Runtime/ui/painting/draw_cmd.cs b/Runtime/ui/painting/draw_cmd.cs index 622dfd84..5fee3176 100644 --- a/Runtime/ui/painting/draw_cmd.cs +++ b/Runtime/ui/painting/draw_cmd.cs @@ -1,70 +1,93 @@ -namespace Unity.UIWidgets.ui { +using UnityEngine; + +namespace Unity.UIWidgets.ui { public abstract class DrawCmd { + public abstract uiRect bounds(float margin); + } + + public abstract class StateUpdateDrawCmd : DrawCmd { + public override uiRect bounds(float margin) { + return uiRectHelper.zero; + } } - public class DrawSave : DrawCmd { + public class DrawSave : StateUpdateDrawCmd { } - public class DrawSaveLayer : DrawCmd { + public class DrawSaveLayer : StateUpdateDrawCmd { public Rect rect; public Paint paint; } - public class DrawRestore : DrawCmd { + public class DrawRestore : StateUpdateDrawCmd { } - public class DrawTranslate : DrawCmd { + public class DrawTranslate : StateUpdateDrawCmd { public float dx; public float dy; } - public class DrawScale : DrawCmd { + public class DrawScale : StateUpdateDrawCmd { public float sx; public float? sy; } - public class DrawRotate : DrawCmd { + public class DrawRotate : StateUpdateDrawCmd { public float radians; public Offset offset; } - public class DrawSkew : DrawCmd { + public class DrawSkew : StateUpdateDrawCmd { public float sx; public float sy; } - public class DrawConcat : DrawCmd { + public class DrawConcat : StateUpdateDrawCmd { public Matrix3 matrix; } - public class DrawResetMatrix : DrawCmd { + public class DrawResetMatrix : StateUpdateDrawCmd { } - public class DrawSetMatrix : DrawCmd { + public class DrawSetMatrix : StateUpdateDrawCmd { public Matrix3 matrix; } - public class DrawClipRect : DrawCmd { + public class DrawClipRect : StateUpdateDrawCmd { public Rect rect; } - public class DrawClipRRect : DrawCmd { + public class DrawClipRRect : StateUpdateDrawCmd { public RRect rrect; } - public class DrawClipPath : DrawCmd { + public class DrawClipPath : StateUpdateDrawCmd { public Path path; } public class DrawPath : DrawCmd { public Path path; public Paint paint; + + public override uiRect bounds(float margin) { + return uiRectHelper.fromRect(this.path.getBoundsWithMargin(margin)).Value; + } } public class DrawImage : DrawCmd { public Image image; public Offset offset; public Paint paint; + + // TODO: Should divide by device pixel ratio here, which is not available as + // this DrawCmd is created. This bounds should only used as an upper bound, + // assuming that device pixel ratio is always >= 1 + public override uiRect bounds(float margin) { + return uiRectHelper.fromLTWH( + this.offset.dx - margin, this.offset.dy - margin, + this.image.width + 2 * margin, + this.image.height + 2 * margin); + } } public class DrawImageRect : DrawCmd { @@ -72,6 +95,10 @@ public class DrawImageRect : DrawCmd { public Rect src; public Rect dst; public Paint paint; + + public override uiRect bounds(float margin) { + return uiRectHelper.fromRect(this.dst.inflate(margin)).Value; + } } public class DrawImageNine : DrawCmd { @@ -80,15 +107,33 @@ public class DrawImageNine : DrawCmd { public Rect center; public Rect dst; public Paint paint; + + public override uiRect bounds(float margin) { + return uiRectHelper.fromLTRB( + this.dst.left + ((this.center.left - this.src.left) * this.src.width) - margin, + this.dst.top + ((this.center.top - this.src.top) * this.src.height) - margin, + this.dst.right - ((this.src.right - this.center.right) * this.src.width) + margin, + this.dst.bottom - ((this.src.bottom - this.center.bottom) * this.src.height) + margin + ); + } } public class DrawPicture : DrawCmd { public Picture picture; + public override uiRect bounds(float margin) { + return uiRectHelper.fromRect(this.picture.paintBounds.inflate(margin)).Value; + } } public class DrawTextBlob : DrawCmd { public TextBlob? textBlob; public Offset offset; public Paint paint; + + public override uiRect bounds(float margin) { + return uiRectHelper.fromRect(this.textBlob.Value.boundsInText + .translate(this.offset.dx, this.offset.dy) + .inflate(margin)).Value; + } } } \ No newline at end of file diff --git a/Runtime/ui/painting/picture.cs b/Runtime/ui/painting/picture.cs index 350a33f2..b8a130eb 100644 --- a/Runtime/ui/painting/picture.cs +++ b/Runtime/ui/painting/picture.cs @@ -1,17 +1,26 @@ using System; using System.Collections.Generic; using Unity.UIWidgets.foundation; +using Unity.UIWidgets.Runtime.external; namespace Unity.UIWidgets.ui { public class Picture { - public Picture(List drawCmds, Rect paintBounds, bool isDynamic = false) { + public Picture(List drawCmds, + Rect paintBounds, + bool isDynamic = false, + BBoxHierarchy bbh = null, + List stateUpdatesIndices = null) { this.drawCmds = drawCmds; this.paintBounds = paintBounds; this._isDynamic = isDynamic; + this.bbh = bbh; + this.stateUpdatesIndices = stateUpdatesIndices; } public readonly List drawCmds; public readonly Rect paintBounds; + public readonly BBoxHierarchy bbh; + public readonly List stateUpdatesIndices; public bool isDynamic { get { return this._isDynamic; } @@ -58,8 +67,26 @@ public Picture endRecording() { throw new Exception("unmatched save/restore commands"); } + int index = 0; + RTree bbh = new RTree(); + List stateUpdateIndices = new List(); + foreach (var cmd in this._drawCmds) { + if (cmd is StateUpdateDrawCmd) { + stateUpdateIndices.Add(index); + } + else { + bbh.Insert(new IndexedRect(cmd.bounds(5), index)); + } + index++; + } + var state = this._getState(); - return new Picture(new List(this._drawCmds), state.paintBounds, this._isDynamic); + return new Picture( + new List(this._drawCmds), + state.paintBounds, + this._isDynamic, + bbh, + stateUpdateIndices); } public void addDrawCmd(DrawCmd drawCmd) { diff --git a/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs b/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs index 252b69a8..eefe5e1b 100644 --- a/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs +++ b/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Linq; using Unity.UIWidgets.foundation; +using Unity.UIWidgets.Runtime.external; using UnityEngine; using UnityEngine.Rendering; @@ -769,6 +771,18 @@ void _drawPicture(Picture picture, bool needsSave = true) { var queryBound = this._currentLayer.currentState.matrix?.invert().Value .mapRect(this._currentLayer.layerBounds) ?? this._currentLayer.layerBounds; + + if (!uiRectHelper.contains(queryBound, uiRectHelper.fromRect(picture.paintBounds).Value)) { + var indices = picture.bbh.Search(queryBound).Select(bound => bound.index); + List cmdIndices = indices.ToList(); + cmdIndices.AddRange(picture.stateUpdatesIndices); + cmdIndices.Sort(); + drawCmds = new List(); + for (int i = 0; i < cmdIndices.Count; i++) { + drawCmds.Add(picture.drawCmds[cmdIndices[i]]); + } + } + foreach (var drawCmd in drawCmds) { switch (drawCmd) { case DrawSave _: @@ -842,46 +856,24 @@ void _drawPicture(Picture picture, bool needsSave = true) { } case DrawPath cmd: { - if (uiRectHelper.intersect(uiRectHelper.fromRect( - cmd.path.getBoundsWithMargin(5)).Value, queryBound).isEmpty) { - continue; - } var uipath = uiPath.fromPath(cmd.path); this._drawPath(uipath, uiPaint.fromPaint(cmd.paint)); uiPathCacheManager.putToCache(uipath); break; } - case DrawImage cmd: { - if (uiRectHelper.intersect(uiRectHelper.fromLTWH( - cmd.offset.dx, cmd.offset.dy, - cmd.image.width / this._devicePixelRatio, - cmd.image.height / this._devicePixelRatio), queryBound).isEmpty) { - continue; - } - this._drawImage(cmd.image, (uiOffset.fromOffset(cmd.offset)).Value, + case DrawImage cmd: {_drawImage(cmd.image, (uiOffset.fromOffset(cmd.offset)).Value, uiPaint.fromPaint(cmd.paint)); break; } case DrawImageRect cmd: { - if (uiRectHelper.intersect(uiRectHelper.fromRect(cmd.dst).Value, queryBound).isEmpty) { - continue; - } this._drawImageRect(cmd.image, uiRectHelper.fromRect(cmd.src), uiRectHelper.fromRect(cmd.dst).Value, uiPaint.fromPaint(cmd.paint)); break; } case DrawImageNine cmd: { - if (uiRectHelper.intersect(uiRectHelper.fromLTRB( - cmd.dst.left + ((cmd.center.left - cmd.src.left) * cmd.src.width), - cmd.dst.top + ((cmd.center.top - cmd.src.top) * cmd.src.height), - cmd.dst.right - ((cmd.src.right - cmd.center.right) * cmd.src.width), - cmd.dst.bottom - ((cmd.src.bottom - cmd.center.bottom) * cmd.src.height) - ), queryBound).isEmpty) { - continue; - } this._drawImageNine(cmd.image, uiRectHelper.fromRect(cmd.src), uiRectHelper.fromRect(cmd.center).Value, uiRectHelper.fromRect(cmd.dst).Value, uiPaint.fromPaint(cmd.paint)); @@ -889,19 +881,11 @@ void _drawPicture(Picture picture, bool needsSave = true) { } case DrawPicture cmd: { - if (uiRectHelper.intersect(uiRectHelper.fromRect( - cmd.picture.paintBounds).Value, queryBound).isEmpty) { - continue; - } this._drawPicture(cmd.picture); break; } case DrawTextBlob cmd: { - if (uiRectHelper.intersect(uiRectHelper.fromRect( - cmd.textBlob.Value.boundsInText.translate(cmd.offset.dx, cmd.offset.dy)).Value, queryBound).isEmpty) { - continue; - } this._paintTextShadow(cmd.textBlob, cmd.offset); this._drawTextBlob(cmd.textBlob, (uiOffset.fromOffset(cmd.offset)).Value, uiPaint.fromPaint(cmd.paint)); @@ -934,6 +918,21 @@ void _drawUIPicture(uiPicture picture, bool needsSave = true) { var queryBound = this._currentLayer.currentState.matrix?.invert().Value .mapRect(this._currentLayer.layerBounds) ?? this._currentLayer.layerBounds; + + if (!uiRectHelper.contains(queryBound, picture.paintBounds)) { + var indices = picture.bbh.Search(queryBound).Select(bound => bound.index); + List cmdIndices = indices.ToList(); + cmdIndices.Capacity += picture.stateUpdatesIndices.Count; + for (int i = 0; i < picture.stateUpdatesIndices.Count; i++) { + cmdIndices.Add(picture.stateUpdatesIndices[i]); + } + cmdIndices.Sort(); + drawCmds = new List(); + for (int i = 0; i < cmdIndices.Count; i++) { + drawCmds.Add(picture.drawCmds[cmdIndices[i]]); + } + } + foreach (var drawCmd in drawCmds) { switch (drawCmd) { case uiDrawSave _: @@ -1005,60 +1004,31 @@ void _drawUIPicture(uiPicture picture, bool needsSave = true) { } case uiDrawPath cmd: { - if (uiRectHelper.intersect(cmd.path.getBoundsWithMargin(5), queryBound).isEmpty) { - continue; - } this._drawPath(cmd.path, cmd.paint); break; } case uiDrawImage cmd: { - if (uiRectHelper.intersect(uiRectHelper.fromLTWH( - cmd.offset.Value.dx, cmd.offset.Value.dy, - cmd.image.width / this._devicePixelRatio, - cmd.image.height / this._devicePixelRatio), queryBound).isEmpty) { - continue; - } this._drawImage(cmd.image, cmd.offset.Value, cmd.paint); break; } case uiDrawImageRect cmd: { - if (uiRectHelper.intersect(cmd.dst.Value, queryBound).isEmpty) { - continue; - } this._drawImageRect(cmd.image, cmd.src, cmd.dst.Value, cmd.paint); break; } case uiDrawImageNine cmd: { - if (uiRectHelper.intersect(uiRectHelper.fromLTRB( - cmd.dst.Value.left + ((cmd.center.Value.left - cmd.src.Value.left) * cmd.src.Value.width), - cmd.dst.Value.top + ((cmd.center.Value.top - cmd.src.Value.top) * cmd.src.Value.height), - cmd.dst.Value.right - ((cmd.src.Value.right - cmd.center.Value.right) * cmd.src.Value.width), - cmd.dst.Value.bottom - ((cmd.src.Value.bottom - cmd.center.Value.bottom) * cmd.src.Value.height) - ), queryBound).isEmpty) { - continue; - } this._drawImageNine(cmd.image, cmd.src, cmd.center.Value, cmd.dst.Value, cmd.paint); break; } case uiDrawPicture cmd: { - if (uiRectHelper.intersect(uiRectHelper.fromRect( - cmd.picture.paintBounds).Value, queryBound).isEmpty) { - continue; - } this._drawPicture(cmd.picture); break; } case uiDrawTextBlob cmd: { - if (uiRectHelper.intersect(uiRectHelper.fromRect( - cmd.textBlob.Value.boundsInText.translate(cmd.offset.Value.dx, cmd.offset.Value.dy)).Value, - queryBound).isEmpty) { - continue; - } this._paintTextShadow(cmd.textBlob, new Offset(cmd.offset.Value.dx, cmd.offset.Value.dy)); this._drawTextBlob(cmd.textBlob, cmd.offset.Value, cmd.paint); break; diff --git a/Runtime/ui/renderer/common/draw_cmd.cs b/Runtime/ui/renderer/common/draw_cmd.cs index 91255b91..63723a57 100644 --- a/Runtime/ui/renderer/common/draw_cmd.cs +++ b/Runtime/ui/renderer/common/draw_cmd.cs @@ -1,9 +1,16 @@ namespace Unity.UIWidgets.ui { public abstract class uiDrawCmd : PoolObject { public abstract void release(); + public abstract uiRect bounds(float margin); } - public class uiDrawSave : uiDrawCmd { + public abstract class uiStateUpdateDrawCmd : uiDrawCmd { + public override uiRect bounds(float margin) { + return uiRectHelper.zero; + } + } + + public class uiDrawSave : uiStateUpdateDrawCmd { public uiDrawSave() { } @@ -17,7 +24,7 @@ public override void release() { } } - public class uiDrawSaveLayer : uiDrawCmd { + public class uiDrawSaveLayer : uiStateUpdateDrawCmd { public uiDrawSaveLayer() { } @@ -40,7 +47,7 @@ public override void clear() { public uiPaint paint; } - public class uiDrawRestore : uiDrawCmd { + public class uiDrawRestore : uiStateUpdateDrawCmd { public uiDrawRestore() { } @@ -54,7 +61,7 @@ public override void release() { } } - public class uiDrawTranslate : uiDrawCmd { + public class uiDrawTranslate : uiStateUpdateDrawCmd { public uiDrawTranslate() { } @@ -73,7 +80,7 @@ public override void release() { public float dy; } - public class uiDrawScale : uiDrawCmd { + public class uiDrawScale : uiStateUpdateDrawCmd { public uiDrawScale() { } @@ -92,7 +99,7 @@ public override void release() { public float? sy; } - public class uiDrawRotate : uiDrawCmd { + public class uiDrawRotate : uiStateUpdateDrawCmd { public uiDrawRotate() { } @@ -115,7 +122,7 @@ public override void clear() { public uiOffset? offset; } - public class uiDrawSkew : uiDrawCmd { + public class uiDrawSkew : uiStateUpdateDrawCmd { public uiDrawSkew() { } @@ -134,7 +141,7 @@ public override void release() { public float sy; } - public class uiDrawConcat : uiDrawCmd { + public class uiDrawConcat : uiStateUpdateDrawCmd { public uiDrawConcat() { } @@ -155,7 +162,7 @@ public override void clear() { public uiMatrix3? matrix; } - public class uiDrawResetMatrix : uiDrawCmd { + public class uiDrawResetMatrix : uiStateUpdateDrawCmd { public uiDrawResetMatrix() { } @@ -169,7 +176,7 @@ public override void release() { } } - public class uiDrawSetMatrix : uiDrawCmd { + public class uiDrawSetMatrix : uiStateUpdateDrawCmd { public uiDrawSetMatrix() { } @@ -190,7 +197,7 @@ public override void clear() { public uiMatrix3? matrix; } - public class uiDrawClipRect : uiDrawCmd { + public class uiDrawClipRect : uiStateUpdateDrawCmd { public uiDrawClipRect() { } @@ -211,7 +218,7 @@ public override void clear() { public uiRect? rect; } - public class uiDrawClipRRect : uiDrawCmd { + public class uiDrawClipRRect : uiStateUpdateDrawCmd { public uiDrawClipRRect() { } @@ -232,7 +239,7 @@ public override void clear() { public RRect rrect; } - public class uiDrawClipPath : uiDrawCmd { + public class uiDrawClipPath : uiStateUpdateDrawCmd { public uiDrawClipPath() { } @@ -278,6 +285,10 @@ public override void clear() { public uiPath path; public uiPaint paint; + + public override uiRect bounds(float margin) { + return this.path.getBoundsWithMargin(margin); + } } public class uiDrawImage : uiDrawCmd { @@ -304,6 +315,17 @@ public override void clear() { public Image image; public uiOffset? offset; public uiPaint paint; + + + // TODO: Should divide by device pixel ratio here, which is not available as + // this DrawCmd is created. This bounds should only used as an upper bound, + // assuming that device pixel ratio is always >= 1 + public override uiRect bounds(float margin) { + return uiRectHelper.fromLTWH( + this.offset.Value.dx - margin, this.offset.Value.dy - margin, + this.image.width + 2 * margin, + this.image.height + 2 * margin); + } } public class uiDrawImageRect : uiDrawCmd { @@ -333,6 +355,10 @@ public override void clear() { public uiRect? src; public uiRect? dst; public uiPaint paint; + + public override uiRect bounds(float margin) { + return uiRectHelper.inflate(this.dst.Value, margin); + } } public class uiDrawImageNine : uiDrawCmd { @@ -365,6 +391,15 @@ public override void clear() { public uiRect? center; public uiRect? dst; public uiPaint paint; + + public override uiRect bounds(float margin) { + return uiRectHelper.fromLTRB( + this.dst.Value.left + ((this.center.Value.left - this.src.Value.left) * this.src.Value.width) - margin, + this.dst.Value.top + ((this.center.Value.top - this.src.Value.top) * this.src.Value.height) - margin, + this.dst.Value.right - ((this.src.Value.right - this.center.Value.right) * this.src.Value.width) + margin, + this.dst.Value.bottom - ((this.src.Value.bottom - this.center.Value.bottom) * this.src.Value.height) + margin + ); + } } public class uiDrawPicture : uiDrawCmd { @@ -386,6 +421,9 @@ public override void clear() { } public Picture picture; + public override uiRect bounds(float margin) { + return uiRectHelper.fromRect(this.picture.paintBounds.inflate(margin)).Value; + } } public class uiDrawTextBlob : uiDrawCmd { @@ -412,5 +450,10 @@ public override void clear() { public TextBlob? textBlob; public uiOffset? offset; public uiPaint paint; + + public override uiRect bounds(float margin) { + return uiRectHelper.fromRect(this.textBlob.Value.boundsInText.translate( + this.offset.Value.dx, this.offset.Value.dy).inflate(margin)).Value; + } } } \ No newline at end of file diff --git a/Runtime/ui/renderer/common/geometry/rect.cs b/Runtime/ui/renderer/common/geometry/rect.cs index 07a68b51..aa7ea7cb 100644 --- a/Runtime/ui/renderer/common/geometry/rect.cs +++ b/Runtime/ui/renderer/common/geometry/rect.cs @@ -26,6 +26,14 @@ public float height { get { return this.bottom - this.top; } } + public float area { + get { return this.width * this.height; } + } + + public float margin { + get { return this.width + this.height; } + } + public uiOffset topLeft { get { return new uiOffset(this.left, this.top); } } diff --git a/Runtime/ui/renderer/common/picture.cs b/Runtime/ui/renderer/common/picture.cs index 8581deab..0a64729e 100644 --- a/Runtime/ui/renderer/common/picture.cs +++ b/Runtime/ui/renderer/common/picture.cs @@ -1,25 +1,34 @@ using System; using System.Collections.Generic; using Unity.UIWidgets.foundation; +using Unity.UIWidgets.Runtime.external; namespace Unity.UIWidgets.ui { public class uiPicture : PoolObject { public uiPicture() { } - public static uiPicture create(List drawCmds, uiRect paintBounds) { + public static uiPicture create(List drawCmds, + uiRect paintBounds, + BBoxHierarchy bbh = null, + uiList stateUpdateIndices = null) { var picture = ObjectPool.alloc(); picture.drawCmds = drawCmds; picture.paintBounds = paintBounds; + picture.bbh = bbh; + picture.stateUpdatesIndices = stateUpdateIndices; return picture; } public List drawCmds; public uiRect paintBounds; + public BBoxHierarchy bbh; + public uiList stateUpdatesIndices; public override void clear() { //the recorder will dispose the draw commands this.drawCmds = null; + ObjectPool>.release(this.stateUpdatesIndices); } } @@ -65,9 +74,22 @@ public uiPicture endRecording() { if (this._states.Count > 1) { throw new Exception("unmatched save/restore commands"); } + + int index = 0; + RTree bbh = new RTree(); + uiList stateUpdateIndices = ObjectPool>.alloc(); + foreach (var cmd in this._drawCmds) { + if (cmd is uiStateUpdateDrawCmd) { + stateUpdateIndices.Add(index); + } + else { + bbh.Insert(new IndexedRect(cmd.bounds(5), index)); + } + index++; + } var state = this._getState(); - return uiPicture.create(this._drawCmds, state.paintBounds); + return uiPicture.create(this._drawCmds, state.paintBounds, bbh: bbh, stateUpdateIndices: stateUpdateIndices); } public void addDrawCmd(uiDrawCmd drawCmd) { From 51415fbc7d03ab2814c1acd10a4a615da3690f80 Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Wed, 20 Nov 2019 16:25:56 +0800 Subject: [PATCH 07/15] Fix typo. --- Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs b/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs index eefe5e1b..f0c8b1cc 100644 --- a/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs +++ b/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs @@ -862,7 +862,7 @@ void _drawPicture(Picture picture, bool needsSave = true) { break; } - case DrawImage cmd: {_drawImage(cmd.image, (uiOffset.fromOffset(cmd.offset)).Value, + case DrawImage cmd: {this._drawImage(cmd.image, (uiOffset.fromOffset(cmd.offset)).Value, uiPaint.fromPaint(cmd.paint)); break; } From 23dee55e0d51c6431fbaed72a22ab71ac8b26e6f Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Wed, 20 Nov 2019 17:04:05 +0800 Subject: [PATCH 08/15] Optimize layout: request character only at begining. --- Runtime/ui/txt/layout.cs | 4 ++-- Runtime/ui/txt/paragraph.cs | 11 ++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Runtime/ui/txt/layout.cs b/Runtime/ui/txt/layout.cs index e13c8bff..3e65ce38 100644 --- a/Runtime/ui/txt/layout.cs +++ b/Runtime/ui/txt/layout.cs @@ -18,7 +18,7 @@ public static float measureText(string text, TextStyle style) { } else { Font font = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle).font; - font.RequestCharactersInTextureSafe(text, style.UnityFontSize, style.UnityFontStyle); + // font.RequestCharactersInTextureSafe(text, style.UnityFontSize, style.UnityFontStyle); for (int i = 0; i < text.Length; i++) { char ch = text[i]; if (font.getGlyphInfo(ch, out var glyphInfo, style.UnityFontSize, style.UnityFontStyle)) { @@ -100,7 +100,7 @@ public static float computeCharWidths(float offset, string text, int start, int else { Font font = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle).font; // TODO: it is kind of a waste to require the entire string for this style, but SubString causes alloc - font.RequestCharactersInTextureSafe(text, style.UnityFontSize, style.UnityFontStyle); + // font.RequestCharactersInTextureSafe(text, style.UnityFontSize, style.UnityFontStyle); for (int i = 0; i < count; i++) { char ch = text[start + i]; if (ch == '\t') { diff --git a/Runtime/ui/txt/paragraph.cs b/Runtime/ui/txt/paragraph.cs index 8304485f..65c3d0f6 100644 --- a/Runtime/ui/txt/paragraph.cs +++ b/Runtime/ui/txt/paragraph.cs @@ -662,10 +662,19 @@ internal int totalCodeUnitsInLine(int lineNumber) { return nextLineStart - lineStart; } - internal void setText(string text, StyledRuns runs) { + internal void setText(string text, StyledRuns runs, bool skipRequestCharacters = false) { this._text = text; this._runs = runs; this._needsLayout = true; + if (!skipRequestCharacters) { + for (int i = 0; i < runs.size; i++) { + var style = runs.getStyle(i); + var run = runs.getRun(i); + var font = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle).font; + font.RequestCharactersInTextureSafe(text.Substring(run.start, run.end - run.start), + Mathf.CeilToInt(style.fontSize), style.UnityFontStyle); + } + } } public void setParagraphStyle(ParagraphStyle style) { From 054e5d36117eb496716beaaa9390a377ca2943b3 Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Wed, 20 Nov 2019 17:57:56 +0800 Subject: [PATCH 09/15] Fix some issues. --- Runtime/ui/txt/paragraph.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Runtime/ui/txt/paragraph.cs b/Runtime/ui/txt/paragraph.cs index 65c3d0f6..ad044d66 100644 --- a/Runtime/ui/txt/paragraph.cs +++ b/Runtime/ui/txt/paragraph.cs @@ -318,7 +318,8 @@ public void paint(Canvas canvas, Offset offset) { } public void layout(ParagraphConstraints constraints) { - if (!this._needsLayout && this._width == constraints.width) { + if ((!this._needsLayout && this._width == constraints.width) || + this._text == null || this._text.isEmpty()) { return; } @@ -663,6 +664,13 @@ internal int totalCodeUnitsInLine(int lineNumber) { } internal void setText(string text, StyledRuns runs, bool skipRequestCharacters = false) { + if (text == null || text.isEmpty()) { + this.clear(); + this._text = text; + this._runs = runs; + this._needsLayout = false; + return; + } this._text = text; this._runs = runs; this._needsLayout = true; @@ -672,7 +680,7 @@ internal void setText(string text, StyledRuns runs, bool skipRequestCharacters = var run = runs.getRun(i); var font = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle).font; font.RequestCharactersInTextureSafe(text.Substring(run.start, run.end - run.start), - Mathf.CeilToInt(style.fontSize), style.UnityFontStyle); + style.UnityFontSize, style.UnityFontStyle); } } } From 09dfb52a9cc127869b945851f9f7ec9cf2bd48d0 Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Thu, 21 Nov 2019 16:00:35 +0800 Subject: [PATCH 10/15] Update bhh when creating picture. --- Runtime/external/RTree.cs | 4 ++ Runtime/ui/painting/picture.cs | 52 ++++++++++++----- .../cmdbufferCanvas/rendering/canvas_impl.cs | 2 +- Runtime/ui/renderer/common/picture.cs | 56 ++++++++++++++----- 4 files changed, 85 insertions(+), 29 deletions(-) diff --git a/Runtime/external/RTree.cs b/Runtime/external/RTree.cs index 58a07b12..9e20da44 100644 --- a/Runtime/external/RTree.cs +++ b/Runtime/external/RTree.cs @@ -136,6 +136,10 @@ public interface BBoxHierarchy where T : ISpatialData { IReadOnlyList Search(in uiRect boundingBox); void BulkLoad(IEnumerable items); + + void Insert(T data); + + void Clear(); } public class RTree : BBoxHierarchy where T : ISpatialData diff --git a/Runtime/ui/painting/picture.cs b/Runtime/ui/painting/picture.cs index b8a130eb..a003d1e4 100644 --- a/Runtime/ui/painting/picture.cs +++ b/Runtime/ui/painting/picture.cs @@ -33,6 +33,10 @@ public class PictureRecorder { readonly List _drawCmds = new List(); readonly List _states = new List(); + + readonly BBoxHierarchy _bbh = new RTree(); + + readonly List _stateUpdateIndices = new List(); bool _isDynamic; @@ -60,6 +64,8 @@ public void reset() { layerOffset = null, paintBounds = Rect.zero, }); + this._bbh.Clear(); + this._stateUpdateIndices.Clear(); } public Picture endRecording() { @@ -67,26 +73,13 @@ public Picture endRecording() { throw new Exception("unmatched save/restore commands"); } - int index = 0; - RTree bbh = new RTree(); - List stateUpdateIndices = new List(); - foreach (var cmd in this._drawCmds) { - if (cmd is StateUpdateDrawCmd) { - stateUpdateIndices.Add(index); - } - else { - bbh.Insert(new IndexedRect(cmd.bounds(5), index)); - } - index++; - } - var state = this._getState(); return new Picture( new List(this._drawCmds), state.paintBounds, this._isDynamic, - bbh, - stateUpdateIndices); + this._bbh, + this._stateUpdateIndices); } public void addDrawCmd(DrawCmd drawCmd) { @@ -95,6 +88,7 @@ public void addDrawCmd(DrawCmd drawCmd) { switch (drawCmd) { case DrawSave _: this._states.Add(this._getState().copy()); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; case DrawSaveLayer cmd: { this._states.Add(new CanvasState { @@ -104,6 +98,7 @@ public void addDrawCmd(DrawCmd drawCmd) { layerOffset = cmd.rect.topLeft, paintBounds = Rect.zero, }); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -120,6 +115,7 @@ public void addDrawCmd(DrawCmd drawCmd) { paintBounds = state.xform.mapRect(paintBounds); this._addPaintBounds(paintBounds); } + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -128,6 +124,7 @@ public void addDrawCmd(DrawCmd drawCmd) { var state = this._getState(); state.xform = new Matrix3(state.xform); state.xform.preTranslate(cmd.dx, cmd.dy); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -135,6 +132,7 @@ public void addDrawCmd(DrawCmd drawCmd) { var state = this._getState(); state.xform = new Matrix3(state.xform); state.xform.preScale(cmd.sx, (cmd.sy ?? cmd.sx)); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -149,6 +147,7 @@ public void addDrawCmd(DrawCmd drawCmd) { cmd.offset.dx, cmd.offset.dy); } + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -157,6 +156,7 @@ public void addDrawCmd(DrawCmd drawCmd) { var state = this._getState(); state.xform = new Matrix3(state.xform); state.xform.preSkew(cmd.sx, cmd.sy); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -164,18 +164,21 @@ public void addDrawCmd(DrawCmd drawCmd) { var state = this._getState(); state.xform = new Matrix3(state.xform); state.xform.preConcat(cmd.matrix); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case DrawResetMatrix _: { var state = this._getState(); state.xform = Matrix3.I(); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case DrawSetMatrix cmd: { var state = this._getState(); state.xform = new Matrix3(cmd.matrix); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -184,6 +187,7 @@ public void addDrawCmd(DrawCmd drawCmd) { var rect = state.xform.mapRect(cmd.rect); state.scissor = state.scissor == null ? rect : state.scissor.intersect(rect); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -192,6 +196,7 @@ public void addDrawCmd(DrawCmd drawCmd) { var rect = state.xform.mapRect(cmd.rrect.outerRect); state.scissor = state.scissor == null ? rect : state.scissor.intersect(rect); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -205,6 +210,7 @@ public void addDrawCmd(DrawCmd drawCmd) { cache.computeFillMesh(0.0f, out _); var rect = cache.fillMesh.transform(state.xform).bounds; state.scissor = state.scissor == null ? rect : state.scissor.intersect(rect); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -243,9 +249,13 @@ public void addDrawCmd(DrawCmd drawCmd) { float sigma = scale * paint.maskFilter.sigma; float sigma3 = 3 * sigma; this._addPaintBounds(mesh.bounds.inflate(sigma3)); + this._bbh.Insert(new IndexedRect(uiRectHelper.fromRect(mesh.bounds.inflate(sigma3 + 5)).Value, + this._drawCmds.Count - 1)); } else { this._addPaintBounds(mesh.bounds); + this._bbh.Insert(new IndexedRect(uiRectHelper.fromRect(mesh.bounds.inflate(5)).Value, + this._drawCmds.Count - 1)); } break; @@ -257,6 +267,8 @@ public void addDrawCmd(DrawCmd drawCmd) { cmd.image.width, cmd.image.height); rect = state.xform.mapRect(rect); this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.fromRect(rect.inflate(5)).Value, + this._drawCmds.Count - 1)); if (cmd.image.isDynamic) { this._isDynamic = true; } @@ -268,6 +280,8 @@ public void addDrawCmd(DrawCmd drawCmd) { var state = this._getState(); var rect = state.xform.mapRect(cmd.dst); this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.fromRect(rect.inflate(5)).Value, + this._drawCmds.Count - 1)); if (cmd.image.isDynamic) { this._isDynamic = true; } @@ -279,6 +293,8 @@ public void addDrawCmd(DrawCmd drawCmd) { var state = this._getState(); var rect = state.xform.mapRect(cmd.dst); this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.fromRect(rect.inflate(5)).Value, + this._drawCmds.Count - 1)); if (cmd.image.isDynamic) { this._isDynamic = true; } @@ -290,6 +306,8 @@ public void addDrawCmd(DrawCmd drawCmd) { var state = this._getState(); var rect = state.xform.mapRect(cmd.picture.paintBounds); this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.fromRect(rect.inflate(5)).Value, + this._drawCmds.Count - 1)); if (cmd.picture.isDynamic) { this._isDynamic = true; } @@ -308,9 +326,13 @@ public void addDrawCmd(DrawCmd drawCmd) { float sigma = scale * paint.maskFilter.sigma; float sigma3 = 3 * sigma; this._addPaintBounds(rect.inflate(sigma3)); + this._bbh.Insert(new IndexedRect(uiRectHelper.fromRect(rect.inflate(sigma3 + 5)).Value, + this._drawCmds.Count - 1)); } else { this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.fromRect(rect.inflate(5)).Value, + this._drawCmds.Count - 1)); } break; diff --git a/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs b/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs index f0c8b1cc..dc4447c0 100644 --- a/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs +++ b/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs @@ -932,7 +932,7 @@ void _drawUIPicture(uiPicture picture, bool needsSave = true) { drawCmds.Add(picture.drawCmds[cmdIndices[i]]); } } - + foreach (var drawCmd in drawCmds) { switch (drawCmd) { case uiDrawSave _: diff --git a/Runtime/ui/renderer/common/picture.cs b/Runtime/ui/renderer/common/picture.cs index 0a64729e..15496bdd 100644 --- a/Runtime/ui/renderer/common/picture.cs +++ b/Runtime/ui/renderer/common/picture.cs @@ -36,6 +36,10 @@ public class uiPictureRecorder { readonly List _drawCmds = new List(128); readonly List _states = new List(32); + + readonly BBoxHierarchy _bbh = new RTree(); + + readonly List _stateUpdateIndices = new List(); public uiPictureRecorder() { this.reset(); @@ -68,6 +72,8 @@ public void reset() { layerOffset = null, paintBounds = uiRectHelper.zero }); + this._bbh.Clear(); + this._stateUpdateIndices.Clear(); } public uiPicture endRecording() { @@ -75,21 +81,14 @@ public uiPicture endRecording() { throw new Exception("unmatched save/restore commands"); } - int index = 0; - RTree bbh = new RTree(); uiList stateUpdateIndices = ObjectPool>.alloc(); - foreach (var cmd in this._drawCmds) { - if (cmd is uiStateUpdateDrawCmd) { - stateUpdateIndices.Add(index); - } - else { - bbh.Insert(new IndexedRect(cmd.bounds(5), index)); - } - index++; - } - + stateUpdateIndices.AddRange(this._stateUpdateIndices); var state = this._getState(); - return uiPicture.create(this._drawCmds, state.paintBounds, bbh: bbh, stateUpdateIndices: stateUpdateIndices); + return uiPicture.create( + this._drawCmds, + state.paintBounds, + bbh: this._bbh, + stateUpdateIndices: stateUpdateIndices); } public void addDrawCmd(uiDrawCmd drawCmd) { @@ -98,6 +97,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { switch (drawCmd) { case uiDrawSave _: this._states.Add(this._getState().copy()); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; case uiDrawSaveLayer cmd: { this._states.Add(new uiCanvasState { @@ -107,6 +107,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { layerOffset = cmd.rect.Value.topLeft, paintBounds = uiRectHelper.zero }); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawRestore _: { @@ -124,6 +125,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { } this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawTranslate cmd: { @@ -131,6 +133,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { state.xform = new uiMatrix3(state.xform); state.xform.preTranslate(cmd.dx, cmd.dy); this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawScale cmd: { @@ -138,6 +141,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { state.xform = new uiMatrix3(state.xform); state.xform.preScale(cmd.sx, (cmd.sy ?? cmd.sx)); this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawRotate cmd: { @@ -153,6 +157,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { } this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawSkew cmd: { @@ -160,6 +165,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { state.xform = new uiMatrix3(state.xform); state.xform.preSkew(cmd.sx, cmd.sy); this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawConcat cmd: { @@ -167,18 +173,21 @@ public void addDrawCmd(uiDrawCmd drawCmd) { state.xform = new uiMatrix3(state.xform); state.xform.preConcat(cmd.matrix.Value); this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawResetMatrix _: { var state = this._getState(); state.xform = uiMatrix3.I(); this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawSetMatrix cmd: { var state = this._getState(); state.xform = new uiMatrix3(cmd.matrix.Value); this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawClipRect cmd: { @@ -187,6 +196,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { var rect = state.xform.mapRect(cmd.rect.Value); state.scissor = state.scissor == null ? rect : state.scissor.Value.intersect(rect); this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawClipRRect cmd: { @@ -195,6 +205,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { var rect = state.xform.mapRect(uiRectHelper.fromRect(cmd.rrect.outerRect).Value); state.scissor = state.scissor == null ? rect : state.scissor.Value.intersect(rect); this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawClipPath cmd: { @@ -210,6 +221,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { state.scissor = state.scissor == null ? rect : state.scissor.Value.intersect(rect); this._setState(state); ObjectPool.release(transformedMesh); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawPath cmd: { @@ -250,9 +262,13 @@ public void addDrawCmd(uiDrawCmd drawCmd) { float sigma = scale * paint.maskFilter.Value.sigma; float sigma3 = 3 * sigma; this._addPaintBounds(uiRectHelper.inflate(mesh.bounds, sigma3)); + this._bbh.Insert(new IndexedRect(uiRectHelper.inflate(mesh.bounds, sigma3 + 5), + this._drawCmds.Count - 1)); } else { this._addPaintBounds(mesh.bounds); + this._bbh.Insert(new IndexedRect(uiRectHelper.inflate(mesh.bounds, 5), + this._drawCmds.Count - 1)); } ObjectPool.release(mesh); @@ -264,24 +280,32 @@ public void addDrawCmd(uiDrawCmd drawCmd) { cmd.image.width, cmd.image.height); rect = state.xform.mapRect(rect); this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.inflate(rect, 5), + this._drawCmds.Count - 1)); break; } case uiDrawImageRect cmd: { var state = this._getState(); var rect = state.xform.mapRect(cmd.dst.Value); this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.inflate(rect, 5), + this._drawCmds.Count - 1)); break; } case uiDrawImageNine cmd: { var state = this._getState(); var rect = state.xform.mapRect(cmd.dst.Value); this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.inflate(rect, 5), + this._drawCmds.Count - 1)); break; } case uiDrawPicture cmd: { var state = this._getState(); var rect = state.xform.mapRect(uiRectHelper.fromRect(cmd.picture.paintBounds).Value); this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.inflate(rect, 5), + this._drawCmds.Count - 1)); break; } case uiDrawTextBlob cmd: { @@ -290,15 +314,21 @@ public void addDrawCmd(uiDrawCmd drawCmd) { var rect = uiRectHelper.fromRect( cmd.textBlob.Value.shiftedBoundsInText(cmd.offset.Value.dx, cmd.offset.Value.dy)).Value; rect = state.xform.mapRect(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.inflate(rect, 5), + this._drawCmds.Count - 1)); var paint = cmd.paint; if (paint.maskFilter != null && paint.maskFilter.Value.sigma != 0) { float sigma = scale * paint.maskFilter.Value.sigma; float sigma3 = 3 * sigma; this._addPaintBounds(uiRectHelper.inflate(rect, sigma3)); + this._bbh.Insert(new IndexedRect(uiRectHelper.inflate(rect, sigma3 + 5), + this._drawCmds.Count - 1)); } else { this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.inflate(rect, 5), + this._drawCmds.Count - 1)); } break; From ca73aa8daa781da58c429181ae828169126c8937 Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Thu, 21 Nov 2019 16:18:29 +0800 Subject: [PATCH 11/15] Cleanup. --- Runtime/ui/geometry.cs | 2 +- Runtime/ui/painting/draw_cmd.cs | 4 +--- Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs | 3 ++- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Runtime/ui/geometry.cs b/Runtime/ui/geometry.cs index c009d238..a6230747 100644 --- a/Runtime/ui/geometry.cs +++ b/Runtime/ui/geometry.cs @@ -546,7 +546,7 @@ public float area { } public float margin { - get { return this.width * this.height; } + get { return this.width + this.height; } } public static readonly Rect zero = new Rect(0, 0, 0, 0); diff --git a/Runtime/ui/painting/draw_cmd.cs b/Runtime/ui/painting/draw_cmd.cs index 5fee3176..909fa335 100644 --- a/Runtime/ui/painting/draw_cmd.cs +++ b/Runtime/ui/painting/draw_cmd.cs @@ -1,6 +1,4 @@ -using UnityEngine; - -namespace Unity.UIWidgets.ui { +namespace Unity.UIWidgets.ui { public abstract class DrawCmd { public abstract uiRect bounds(float margin); } diff --git a/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs b/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs index dc4447c0..1441b1c9 100644 --- a/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs +++ b/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs @@ -862,7 +862,8 @@ void _drawPicture(Picture picture, bool needsSave = true) { break; } - case DrawImage cmd: {this._drawImage(cmd.image, (uiOffset.fromOffset(cmd.offset)).Value, + case DrawImage cmd: { + this._drawImage(cmd.image, (uiOffset.fromOffset(cmd.offset)).Value, uiPaint.fromPaint(cmd.paint)); break; } From 18ebe774b69441ef7067b73553817922e99d1e6c Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Mon, 25 Nov 2019 15:36:26 +0800 Subject: [PATCH 12/15] Fix bug in Paragraph.setText. --- Runtime/ui/painting/draw_cmd.cs | 71 +++++--------------------- Runtime/ui/renderer/common/draw_cmd.cs | 69 +++++-------------------- Runtime/ui/txt/paragraph.cs | 2 +- 3 files changed, 28 insertions(+), 114 deletions(-) diff --git a/Runtime/ui/painting/draw_cmd.cs b/Runtime/ui/painting/draw_cmd.cs index 909fa335..9aa7089e 100644 --- a/Runtime/ui/painting/draw_cmd.cs +++ b/Runtime/ui/painting/draw_cmd.cs @@ -1,91 +1,70 @@ -namespace Unity.UIWidgets.ui { +namespace Unity.UIWidgets.ui { public abstract class DrawCmd { - public abstract uiRect bounds(float margin); } - public abstract class StateUpdateDrawCmd : DrawCmd { - public override uiRect bounds(float margin) { - return uiRectHelper.zero; - } + public class DrawSave : DrawCmd { } - public class DrawSave : StateUpdateDrawCmd { - } - - public class DrawSaveLayer : StateUpdateDrawCmd { + public class DrawSaveLayer : DrawCmd { public Rect rect; public Paint paint; } - public class DrawRestore : StateUpdateDrawCmd { + public class DrawRestore : DrawCmd { } - public class DrawTranslate : StateUpdateDrawCmd { + public class DrawTranslate : DrawCmd { public float dx; public float dy; } - public class DrawScale : StateUpdateDrawCmd { + public class DrawScale : DrawCmd { public float sx; public float? sy; } - public class DrawRotate : StateUpdateDrawCmd { + public class DrawRotate : DrawCmd { public float radians; public Offset offset; } - public class DrawSkew : StateUpdateDrawCmd { + public class DrawSkew : DrawCmd { public float sx; public float sy; } - public class DrawConcat : StateUpdateDrawCmd { + public class DrawConcat : DrawCmd { public Matrix3 matrix; } - public class DrawResetMatrix : StateUpdateDrawCmd { + public class DrawResetMatrix : DrawCmd { } - public class DrawSetMatrix : StateUpdateDrawCmd { + public class DrawSetMatrix : DrawCmd { public Matrix3 matrix; } - public class DrawClipRect : StateUpdateDrawCmd { + public class DrawClipRect : DrawCmd { public Rect rect; } - public class DrawClipRRect : StateUpdateDrawCmd { + public class DrawClipRRect : DrawCmd { public RRect rrect; } - public class DrawClipPath : StateUpdateDrawCmd { + public class DrawClipPath : DrawCmd { public Path path; } public class DrawPath : DrawCmd { public Path path; public Paint paint; - - public override uiRect bounds(float margin) { - return uiRectHelper.fromRect(this.path.getBoundsWithMargin(margin)).Value; - } } public class DrawImage : DrawCmd { public Image image; public Offset offset; public Paint paint; - - // TODO: Should divide by device pixel ratio here, which is not available as - // this DrawCmd is created. This bounds should only used as an upper bound, - // assuming that device pixel ratio is always >= 1 - public override uiRect bounds(float margin) { - return uiRectHelper.fromLTWH( - this.offset.dx - margin, this.offset.dy - margin, - this.image.width + 2 * margin, - this.image.height + 2 * margin); - } } public class DrawImageRect : DrawCmd { @@ -93,10 +72,6 @@ public class DrawImageRect : DrawCmd { public Rect src; public Rect dst; public Paint paint; - - public override uiRect bounds(float margin) { - return uiRectHelper.fromRect(this.dst.inflate(margin)).Value; - } } public class DrawImageNine : DrawCmd { @@ -105,33 +80,15 @@ public class DrawImageNine : DrawCmd { public Rect center; public Rect dst; public Paint paint; - - public override uiRect bounds(float margin) { - return uiRectHelper.fromLTRB( - this.dst.left + ((this.center.left - this.src.left) * this.src.width) - margin, - this.dst.top + ((this.center.top - this.src.top) * this.src.height) - margin, - this.dst.right - ((this.src.right - this.center.right) * this.src.width) + margin, - this.dst.bottom - ((this.src.bottom - this.center.bottom) * this.src.height) + margin - ); - } } public class DrawPicture : DrawCmd { public Picture picture; - public override uiRect bounds(float margin) { - return uiRectHelper.fromRect(this.picture.paintBounds.inflate(margin)).Value; - } } public class DrawTextBlob : DrawCmd { public TextBlob? textBlob; public Offset offset; public Paint paint; - - public override uiRect bounds(float margin) { - return uiRectHelper.fromRect(this.textBlob.Value.boundsInText - .translate(this.offset.dx, this.offset.dy) - .inflate(margin)).Value; - } } } \ No newline at end of file diff --git a/Runtime/ui/renderer/common/draw_cmd.cs b/Runtime/ui/renderer/common/draw_cmd.cs index 63723a57..91255b91 100644 --- a/Runtime/ui/renderer/common/draw_cmd.cs +++ b/Runtime/ui/renderer/common/draw_cmd.cs @@ -1,16 +1,9 @@ namespace Unity.UIWidgets.ui { public abstract class uiDrawCmd : PoolObject { public abstract void release(); - public abstract uiRect bounds(float margin); } - public abstract class uiStateUpdateDrawCmd : uiDrawCmd { - public override uiRect bounds(float margin) { - return uiRectHelper.zero; - } - } - - public class uiDrawSave : uiStateUpdateDrawCmd { + public class uiDrawSave : uiDrawCmd { public uiDrawSave() { } @@ -24,7 +17,7 @@ public override void release() { } } - public class uiDrawSaveLayer : uiStateUpdateDrawCmd { + public class uiDrawSaveLayer : uiDrawCmd { public uiDrawSaveLayer() { } @@ -47,7 +40,7 @@ public override void clear() { public uiPaint paint; } - public class uiDrawRestore : uiStateUpdateDrawCmd { + public class uiDrawRestore : uiDrawCmd { public uiDrawRestore() { } @@ -61,7 +54,7 @@ public override void release() { } } - public class uiDrawTranslate : uiStateUpdateDrawCmd { + public class uiDrawTranslate : uiDrawCmd { public uiDrawTranslate() { } @@ -80,7 +73,7 @@ public override void release() { public float dy; } - public class uiDrawScale : uiStateUpdateDrawCmd { + public class uiDrawScale : uiDrawCmd { public uiDrawScale() { } @@ -99,7 +92,7 @@ public override void release() { public float? sy; } - public class uiDrawRotate : uiStateUpdateDrawCmd { + public class uiDrawRotate : uiDrawCmd { public uiDrawRotate() { } @@ -122,7 +115,7 @@ public override void clear() { public uiOffset? offset; } - public class uiDrawSkew : uiStateUpdateDrawCmd { + public class uiDrawSkew : uiDrawCmd { public uiDrawSkew() { } @@ -141,7 +134,7 @@ public override void release() { public float sy; } - public class uiDrawConcat : uiStateUpdateDrawCmd { + public class uiDrawConcat : uiDrawCmd { public uiDrawConcat() { } @@ -162,7 +155,7 @@ public override void clear() { public uiMatrix3? matrix; } - public class uiDrawResetMatrix : uiStateUpdateDrawCmd { + public class uiDrawResetMatrix : uiDrawCmd { public uiDrawResetMatrix() { } @@ -176,7 +169,7 @@ public override void release() { } } - public class uiDrawSetMatrix : uiStateUpdateDrawCmd { + public class uiDrawSetMatrix : uiDrawCmd { public uiDrawSetMatrix() { } @@ -197,7 +190,7 @@ public override void clear() { public uiMatrix3? matrix; } - public class uiDrawClipRect : uiStateUpdateDrawCmd { + public class uiDrawClipRect : uiDrawCmd { public uiDrawClipRect() { } @@ -218,7 +211,7 @@ public override void clear() { public uiRect? rect; } - public class uiDrawClipRRect : uiStateUpdateDrawCmd { + public class uiDrawClipRRect : uiDrawCmd { public uiDrawClipRRect() { } @@ -239,7 +232,7 @@ public override void clear() { public RRect rrect; } - public class uiDrawClipPath : uiStateUpdateDrawCmd { + public class uiDrawClipPath : uiDrawCmd { public uiDrawClipPath() { } @@ -285,10 +278,6 @@ public override void clear() { public uiPath path; public uiPaint paint; - - public override uiRect bounds(float margin) { - return this.path.getBoundsWithMargin(margin); - } } public class uiDrawImage : uiDrawCmd { @@ -315,17 +304,6 @@ public override void clear() { public Image image; public uiOffset? offset; public uiPaint paint; - - - // TODO: Should divide by device pixel ratio here, which is not available as - // this DrawCmd is created. This bounds should only used as an upper bound, - // assuming that device pixel ratio is always >= 1 - public override uiRect bounds(float margin) { - return uiRectHelper.fromLTWH( - this.offset.Value.dx - margin, this.offset.Value.dy - margin, - this.image.width + 2 * margin, - this.image.height + 2 * margin); - } } public class uiDrawImageRect : uiDrawCmd { @@ -355,10 +333,6 @@ public override void clear() { public uiRect? src; public uiRect? dst; public uiPaint paint; - - public override uiRect bounds(float margin) { - return uiRectHelper.inflate(this.dst.Value, margin); - } } public class uiDrawImageNine : uiDrawCmd { @@ -391,15 +365,6 @@ public override void clear() { public uiRect? center; public uiRect? dst; public uiPaint paint; - - public override uiRect bounds(float margin) { - return uiRectHelper.fromLTRB( - this.dst.Value.left + ((this.center.Value.left - this.src.Value.left) * this.src.Value.width) - margin, - this.dst.Value.top + ((this.center.Value.top - this.src.Value.top) * this.src.Value.height) - margin, - this.dst.Value.right - ((this.src.Value.right - this.center.Value.right) * this.src.Value.width) + margin, - this.dst.Value.bottom - ((this.src.Value.bottom - this.center.Value.bottom) * this.src.Value.height) + margin - ); - } } public class uiDrawPicture : uiDrawCmd { @@ -421,9 +386,6 @@ public override void clear() { } public Picture picture; - public override uiRect bounds(float margin) { - return uiRectHelper.fromRect(this.picture.paintBounds.inflate(margin)).Value; - } } public class uiDrawTextBlob : uiDrawCmd { @@ -450,10 +412,5 @@ public override void clear() { public TextBlob? textBlob; public uiOffset? offset; public uiPaint paint; - - public override uiRect bounds(float margin) { - return uiRectHelper.fromRect(this.textBlob.Value.boundsInText.translate( - this.offset.Value.dx, this.offset.Value.dy).inflate(margin)).Value; - } } } \ No newline at end of file diff --git a/Runtime/ui/txt/paragraph.cs b/Runtime/ui/txt/paragraph.cs index ad044d66..77a62849 100644 --- a/Runtime/ui/txt/paragraph.cs +++ b/Runtime/ui/txt/paragraph.cs @@ -676,8 +676,8 @@ internal void setText(string text, StyledRuns runs, bool skipRequestCharacters = this._needsLayout = true; if (!skipRequestCharacters) { for (int i = 0; i < runs.size; i++) { - var style = runs.getStyle(i); var run = runs.getRun(i); + var style = run.style; var font = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle).font; font.RequestCharactersInTextureSafe(text.Substring(run.start, run.end - run.start), style.UnityFontSize, style.UnityFontStyle); From aff89288aab5daa36b7ab996985e614b3c3872d7 Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Mon, 25 Nov 2019 16:43:09 +0800 Subject: [PATCH 13/15] Fix bug. --- Runtime/painting/text_painter.cs | 6 +++++- Runtime/ui/txt/paragraph.cs | 10 ++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Runtime/painting/text_painter.cs b/Runtime/painting/text_painter.cs index e93f54d1..88be9c5a 100644 --- a/Runtime/painting/text_painter.cs +++ b/Runtime/painting/text_painter.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Unity.UIWidgets.foundation; using Unity.UIWidgets.service; using Unity.UIWidgets.ui; @@ -214,6 +215,9 @@ public void layout(float minWidth = 0.0f, float maxWidth = float.PositiveInfinit this._text.build(builder, this.textScaleFactor); this._paragraph = builder.build(); } + else { + Debug.Log($"Use Old Paragraph [{DateTime.Now.Millisecond}]"); + } this._lastMinWidth = minWidth; this._lastMaxWidth = maxWidth; diff --git a/Runtime/ui/txt/paragraph.cs b/Runtime/ui/txt/paragraph.cs index 77a62849..d8068008 100644 --- a/Runtime/ui/txt/paragraph.cs +++ b/Runtime/ui/txt/paragraph.cs @@ -318,8 +318,7 @@ public void paint(Canvas canvas, Offset offset) { } public void layout(ParagraphConstraints constraints) { - if ((!this._needsLayout && this._width == constraints.width) || - this._text == null || this._text.isEmpty()) { + if (!this._needsLayout && this._width == constraints.width) { return; } @@ -458,12 +457,17 @@ public void layout(ParagraphConstraints constraints) { int textCount = textEnd - textStart; // Keep track of the pointer to _glyphPositions in the start of this run int glyphPositionStyleRunStart = pGlyphPositions; + Font font = FontManager.instance.getOrCreate( + style.fontFamily, + style.fontWeight, + style.fontStyle).font; // Ellipsize the text if ellipsis string is set, and this is the last lineStyleRun of // the current line, and this is the last line or max line is not set if (!string.IsNullOrEmpty(ellipsis) && !hardBreak && !this._width.isInfinite() && lineStyleRunIndex == lineStyleRunCount - 1 && (lineNumber == lineLimit - 1 || this._paragraphStyle.maxLines == null)) { + font.RequestCharactersInTextureSafe(ellipsis, style.UnityFontSize, style.UnityFontStyle); float ellipsisWidth = Layout.measureText(ellipsis, style); // Find the minimum number of characters to truncate, so that the truncated text @@ -533,8 +537,6 @@ public void layout(ParagraphConstraints constraints) { } // Create paint record - var font = FontManager.instance.getOrCreate(style.fontFamily, - style.fontWeight, style.fontStyle).font; var metrics = FontMetrics.fromFont(font, style.UnityFontSize); PaintRecord paintRecord = new PaintRecord(style, runXOffset, 0, builder.make(), metrics, advance); From 9d5d355616cc9331147a611aa846797188d139ee Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Mon, 25 Nov 2019 17:07:16 +0800 Subject: [PATCH 14/15] Revert all optimization on paragraph layout. --- Runtime/painting/text_painter.cs | 6 +----- Runtime/ui/txt/layout.cs | 4 ++-- Runtime/ui/txt/paragraph.cs | 25 +++---------------------- 3 files changed, 6 insertions(+), 29 deletions(-) diff --git a/Runtime/painting/text_painter.cs b/Runtime/painting/text_painter.cs index 88be9c5a..7449ca25 100644 --- a/Runtime/painting/text_painter.cs +++ b/Runtime/painting/text_painter.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Unity.UIWidgets.foundation; using Unity.UIWidgets.service; using Unity.UIWidgets.ui; @@ -215,9 +214,6 @@ public void layout(float minWidth = 0.0f, float maxWidth = float.PositiveInfinit this._text.build(builder, this.textScaleFactor); this._paragraph = builder.build(); } - else { - Debug.Log($"Use Old Paragraph [{DateTime.Now.Millisecond}]"); - } this._lastMinWidth = minWidth; this._lastMaxWidth = maxWidth; diff --git a/Runtime/ui/txt/layout.cs b/Runtime/ui/txt/layout.cs index 3e65ce38..e13c8bff 100644 --- a/Runtime/ui/txt/layout.cs +++ b/Runtime/ui/txt/layout.cs @@ -18,7 +18,7 @@ public static float measureText(string text, TextStyle style) { } else { Font font = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle).font; - // font.RequestCharactersInTextureSafe(text, style.UnityFontSize, style.UnityFontStyle); + font.RequestCharactersInTextureSafe(text, style.UnityFontSize, style.UnityFontStyle); for (int i = 0; i < text.Length; i++) { char ch = text[i]; if (font.getGlyphInfo(ch, out var glyphInfo, style.UnityFontSize, style.UnityFontStyle)) { @@ -100,7 +100,7 @@ public static float computeCharWidths(float offset, string text, int start, int else { Font font = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle).font; // TODO: it is kind of a waste to require the entire string for this style, but SubString causes alloc - // font.RequestCharactersInTextureSafe(text, style.UnityFontSize, style.UnityFontStyle); + font.RequestCharactersInTextureSafe(text, style.UnityFontSize, style.UnityFontStyle); for (int i = 0; i < count; i++) { char ch = text[start + i]; if (ch == '\t') { diff --git a/Runtime/ui/txt/paragraph.cs b/Runtime/ui/txt/paragraph.cs index d8068008..8304485f 100644 --- a/Runtime/ui/txt/paragraph.cs +++ b/Runtime/ui/txt/paragraph.cs @@ -457,17 +457,12 @@ public void layout(ParagraphConstraints constraints) { int textCount = textEnd - textStart; // Keep track of the pointer to _glyphPositions in the start of this run int glyphPositionStyleRunStart = pGlyphPositions; - Font font = FontManager.instance.getOrCreate( - style.fontFamily, - style.fontWeight, - style.fontStyle).font; // Ellipsize the text if ellipsis string is set, and this is the last lineStyleRun of // the current line, and this is the last line or max line is not set if (!string.IsNullOrEmpty(ellipsis) && !hardBreak && !this._width.isInfinite() && lineStyleRunIndex == lineStyleRunCount - 1 && (lineNumber == lineLimit - 1 || this._paragraphStyle.maxLines == null)) { - font.RequestCharactersInTextureSafe(ellipsis, style.UnityFontSize, style.UnityFontStyle); float ellipsisWidth = Layout.measureText(ellipsis, style); // Find the minimum number of characters to truncate, so that the truncated text @@ -537,6 +532,8 @@ public void layout(ParagraphConstraints constraints) { } // Create paint record + var font = FontManager.instance.getOrCreate(style.fontFamily, + style.fontWeight, style.fontStyle).font; var metrics = FontMetrics.fromFont(font, style.UnityFontSize); PaintRecord paintRecord = new PaintRecord(style, runXOffset, 0, builder.make(), metrics, advance); @@ -665,26 +662,10 @@ internal int totalCodeUnitsInLine(int lineNumber) { return nextLineStart - lineStart; } - internal void setText(string text, StyledRuns runs, bool skipRequestCharacters = false) { - if (text == null || text.isEmpty()) { - this.clear(); - this._text = text; - this._runs = runs; - this._needsLayout = false; - return; - } + internal void setText(string text, StyledRuns runs) { this._text = text; this._runs = runs; this._needsLayout = true; - if (!skipRequestCharacters) { - for (int i = 0; i < runs.size; i++) { - var run = runs.getRun(i); - var style = run.style; - var font = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle).font; - font.RequestCharactersInTextureSafe(text.Substring(run.start, run.end - run.start), - style.UnityFontSize, style.UnityFontStyle); - } - } } public void setParagraphStyle(ParagraphStyle style) { From ddfde8357232f92e9fa43df6fed806b30b06bbcb Mon Sep 17 00:00:00 2001 From: "xingwei.zhu" Date: Fri, 29 Nov 2019 18:16:19 +0800 Subject: [PATCH 15/15] Merge branch 'master' of github.com:UnityTech/UIWidgets into dev # Conflicts: # Runtime/ui/painting/picture.cs --- Runtime/ui/painting/picture.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Runtime/ui/painting/picture.cs b/Runtime/ui/painting/picture.cs index 609eb2eb..d8e03c83 100644 --- a/Runtime/ui/painting/picture.cs +++ b/Runtime/ui/painting/picture.cs @@ -131,8 +131,8 @@ public void addDrawCmd(DrawCmd drawCmd) { //check for underflow if (this._states.Count > 1) { this.restore(); - this._stateUpdateIndices.Add(this._drawCmds.Count - 1); } + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; }