From f974859323c06b493226dc8d85de66349a5ac4a9 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 26 May 2022 15:30:12 +0300 Subject: [PATCH] Change stream WIP --- .../Composition/Server/ServerVisual.cs | 51 ++++-- .../Composition/Transport/BatchStream.cs | 173 ++++++++++++++++++ .../Transport/BatchStreamArrayPool.cs | 144 +++++++++++++++ 3 files changed, 354 insertions(+), 14 deletions(-) create mode 100644 src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs create mode 100644 src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamArrayPool.cs diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerVisual.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerVisual.cs index 9bfc909fe4..801bfb2f65 100644 --- a/src/Avalonia.Base/Rendering/Composition/Server/ServerVisual.cs +++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerVisual.cs @@ -8,6 +8,7 @@ namespace Avalonia.Rendering.Composition.Server { private bool _isDirty; private ServerCompositionTarget? _root; + private bool _isBackface; protected virtual void RenderCore(CompositorDrawingContextProxy canvas, Matrix4x4 transform) { @@ -59,23 +60,35 @@ namespace Avalonia.Rendering.Composition.Server public virtual void Update(ServerCompositionTarget root, Matrix4x4 transform) { - var res = MatrixUtils.ComputeTransform(Size, AnchorPoint, CenterPoint, TransformMatrix, + // Calculate new parent-relative transform + CombinedTransformMatrix = MatrixUtils.ComputeTransform(Size, AnchorPoint, CenterPoint, TransformMatrix, Scale, RotationAngle, Orientation, Offset); - var i = Root!.Readback; - ref var readback = ref GetReadback(i.WriteIndex); - readback.Revision = root.Revision; - readback.Matrix = res; - readback.TargetId = Root.Id; - //TODO: check effective opacity too - IsVisibleInFrame = Visible && Opacity > 0; - CombinedTransformMatrix = res; - var newTransform = res * transform; + + var newTransform = CombinedTransformMatrix * transform; + + // Check if visual was moved and recalculate face orientation + var positionChanged = false; if (GlobalTransformMatrix != newTransform) { - // Visual was moved alongside with its parent - _isDirty = true; - Root.AddDirtyRect(TransformedBounds); + _isBackface = Vector3.Transform( + new Vector3(0, 0, float.PositiveInfinity), GlobalTransformMatrix).Z <= 0; + positionChanged = true; + } + + var wasVisible = IsVisibleInFrame; + //TODO: check effective opacity too + IsVisibleInFrame = Visible && Opacity > 0 && !_isBackface; + + // Invalidate previous rect and queue new rect based on visibility + if (positionChanged) + { + if(wasVisible) + Root!.AddDirtyRect(TransformedBounds); + + if (IsVisibleInFrame) + _isDirty = true; } + GlobalTransformMatrix = newTransform; //TODO: Cache TransformedBounds = ContentBounds.TransformToAABB(MatrixUtils.ToMatrix(GlobalTransformMatrix)); @@ -84,9 +97,18 @@ namespace Avalonia.Rendering.Composition.Server _isDirty = false; else if (_isDirty) { - Root.AddDirtyRect(TransformedBounds); + Root!.AddDirtyRect(TransformedBounds); _isDirty = false; } + + // Update readback indices + var i = Root!.Readback; + ref var readback = ref GetReadback(i.WriteIndex); + readback.Revision = root.Revision; + readback.Matrix = CombinedTransformMatrix; + readback.TargetId = Root.Id; + readback.Visible = IsVisibleInFrame; + } public struct ReadbackData @@ -94,6 +116,7 @@ namespace Avalonia.Rendering.Composition.Server public Matrix4x4 Matrix; public ulong Revision; public long TargetId; + public bool Visible; } partial void ApplyChangesExtra(CompositionVisualChanges c) diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs new file mode 100644 index 0000000000..feb892d134 --- /dev/null +++ b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStream.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Runtime.CompilerServices; +using Avalonia.Rendering.Composition.Server; + +namespace Avalonia.Rendering.Composition.Transport; + +internal class BatchStreamData +{ + public Queue> Objects { get; } = new(); + public Queue> Structs { get; } = new(); +} + +public struct BatchStreamSegment +{ + public TData Data { get; set; } + public int ElementCount { get; set; } +} + +internal class BatchStreamWriter : IDisposable +{ + private readonly BatchStreamData _output; + private readonly BatchStreamMemoryPool _memoryPool; + private readonly BatchStreamObjectPool _objectPool; + + private BatchStreamSegment _currentObjectSegment; + private BatchStreamSegment _currentDataSegment; + + public BatchStreamWriter(BatchStreamData output, BatchStreamMemoryPool memoryPool, BatchStreamObjectPool objectPool) + { + _output = output; + _memoryPool = memoryPool; + _objectPool = objectPool; + } + + void CommitDataSegment() + { + if (_currentDataSegment.Data != IntPtr.Zero) + _output.Structs.Enqueue(_currentDataSegment); + _currentDataSegment = new (); + } + + void NextDataSegment() + { + CommitDataSegment(); + _currentDataSegment.Data = _memoryPool.Get(); + } + + void CommitObjectSegment() + { + if (_currentObjectSegment.Data != null) + _output.Objects.Enqueue(_currentObjectSegment!); + _currentObjectSegment = new(); + } + + void NextObjectSegment() + { + CommitObjectSegment(); + _currentObjectSegment.Data = _objectPool.Get(); + } + + public unsafe void Write(T item) where T : unmanaged + { + var size = Unsafe.SizeOf(); + if (_currentDataSegment.Data == IntPtr.Zero || _currentDataSegment.ElementCount + size > _memoryPool.BufferSize) + NextDataSegment(); + *(T*)((byte*)_currentDataSegment.Data + _currentDataSegment.ElementCount) = item; + _currentDataSegment.ElementCount += size; + } + + public void Write(ServerObject item) + { + if (_currentObjectSegment.Data == null || + _currentObjectSegment.ElementCount >= _currentObjectSegment.Data.Length) + NextObjectSegment(); + _currentObjectSegment.Data![_currentObjectSegment.ElementCount] = item; + _currentObjectSegment.ElementCount++; + } + + public void Dispose() + { + CommitDataSegment(); + CommitObjectSegment(); + } +} + +internal class BatchStreamReader : IDisposable +{ + private readonly BatchStreamData _input; + private readonly BatchStreamMemoryPool _memoryPool; + private readonly BatchStreamObjectPool _objectPool; + + private BatchStreamSegment _currentObjectSegment; + private BatchStreamSegment _currentDataSegment; + private int _memoryOffset, _objectOffset; + + public BatchStreamReader(BatchStreamData _input, BatchStreamMemoryPool memoryPool, BatchStreamObjectPool objectPool) + { + this._input = _input; + _memoryPool = memoryPool; + _objectPool = objectPool; + } + + public unsafe T Read() where T : unmanaged + { + var size = Unsafe.SizeOf(); + if (_currentDataSegment.Data == IntPtr.Zero) + { + if (_input.Structs.Count == 0) + throw new EndOfStreamException(); + _currentDataSegment = _input.Structs.Dequeue(); + _memoryOffset = 0; + } + + if (_memoryOffset + size > _currentDataSegment.ElementCount) + throw new InvalidOperationException("Attempted to read more memory then left in the current segment"); + + var rv = *(T*)((byte*)_currentDataSegment.Data + size); + _memoryOffset += size; + if (_memoryOffset == _currentDataSegment.ElementCount) + { + _memoryPool.Return(_currentDataSegment.Data); + _currentDataSegment = new(); + } + + return rv; + } + + public ServerObject ReadObject() + { + if (_currentObjectSegment.Data == null) + { + if (_input.Objects.Count == 0) + throw new EndOfStreamException(); + _currentObjectSegment = _input.Objects.Dequeue()!; + _objectOffset = 0; + } + + var rv = _currentObjectSegment.Data![_objectOffset]; + _objectOffset++; + if (_objectOffset == _currentObjectSegment.ElementCount) + { + _objectPool.Return(_currentObjectSegment.Data); + _currentObjectSegment = new(); + } + + return rv; + } + + public bool IsStructEof => _currentDataSegment.Data == IntPtr.Zero && _input.Structs.Count == 0; + + public void Dispose() + { + if (_currentDataSegment.Data != IntPtr.Zero) + { + _memoryPool.Return(_currentDataSegment.Data); + _currentDataSegment = new(); + } + + while (_input.Structs.Count > 0) + _memoryPool.Return(_input.Structs.Dequeue().Data); + + if (_currentObjectSegment.Data != null) + { + _objectPool.Return(_currentObjectSegment.Data); + _currentObjectSegment = new(); + } + + while (_input.Objects.Count > 0) + _objectPool.Return(_input.Objects.Dequeue().Data); + } +} \ No newline at end of file diff --git a/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamArrayPool.cs b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamArrayPool.cs new file mode 100644 index 0000000000..913958765a --- /dev/null +++ b/src/Avalonia.Base/Rendering/Composition/Transport/BatchStreamArrayPool.cs @@ -0,0 +1,144 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.ConstrainedExecution; +using System.Runtime.InteropServices; +using Avalonia.Threading; + +namespace Avalonia.Rendering.Composition.Transport; + +/// +/// A pool that keeps a number of elements that was used in the last 10 seconds +/// +internal abstract class BatchStreamPoolBase : IDisposable +{ + readonly Stack _pool = new(); + bool _disposed; + int _usage; + readonly int[] _usageStatistics = new int[10]; + int _usageStatisticsSlot; + + public BatchStreamPoolBase(bool needsFinalize = false) + { + if(!needsFinalize) + GC.SuppressFinalize(needsFinalize); + + var updateRef = new WeakReference>(this); + StartUpdateTimer(updateRef); + } + + static void StartUpdateTimer(WeakReference> updateRef) + { + DispatcherTimer.Run(() => + { + if (updateRef.TryGetTarget(out var target)) + { + target.UpdateStatistics(); + return true; + } + return false; + + }, TimeSpan.FromSeconds(1)); + } + + private void UpdateStatistics() + { + lock (_pool) + { + var maximumUsage = _usageStatistics.Max(); + var recentlyUsedPooledSlots = maximumUsage - _usage; + while (recentlyUsedPooledSlots < _pool.Count) + DestroyItem(_pool.Pop()); + + _usageStatistics[_usage] = 0; + _usageStatisticsSlot = (_usageStatisticsSlot + 1) % _usageStatistics.Length; + } + } + + protected abstract T CreateItem(); + + protected virtual void DestroyItem(T item) + { + + } + + public T Get() + { + lock (_pool) + { + _usage++; + if (_usageStatistics[_usageStatisticsSlot] < _usage) + _usageStatistics[_usageStatisticsSlot] = _usage; + + if (_pool.Count != 0) + return _pool.Pop(); + } + + return CreateItem(); + } + + public void Return(T item) + { + lock (_pool) + { + _usage--; + if (!_disposed) + { + _pool.Push(item); + return; + } + } + + DestroyItem(item); + } + + public void Dispose() + { + lock (_pool) + { + _disposed = true; + foreach (var item in _pool) + DestroyItem(item); + _pool.Clear(); + } + } + + ~BatchStreamPoolBase() + { + Dispose(); + } +} + +internal sealed class BatchStreamObjectPool : BatchStreamPoolBase where T : class +{ + private readonly int _arraySize; + + public BatchStreamObjectPool(int arraySize = 1024) + { + _arraySize = arraySize; + } + + protected override T[] CreateItem() + { + return new T[_arraySize]; + } + + protected override void DestroyItem(T[] item) + { + Array.Clear(item, 0, item.Length); + } +} + +internal sealed class BatchStreamMemoryPool : BatchStreamPoolBase +{ + public int BufferSize { get; } + + public BatchStreamMemoryPool(int bufferSize = 16384) + { + BufferSize = bufferSize; + } + + protected override IntPtr CreateItem() => Marshal.AllocHGlobal(BufferSize); + + protected override void DestroyItem(IntPtr item) => Marshal.FreeHGlobal(item); +} \ No newline at end of file