mirror of
https://gitlab.acidiclight.dev/sociallydistant/sociallydistant.git
synced 2025-01-22 09:31:47 -05:00
Do not throw away widget geometry during layout invalidation, in case the content rectangle stays the same
This commit is contained in:
parent
982b0c0fe1
commit
c9c3f4288b
14 changed files with 326 additions and 48 deletions
|
@ -166,8 +166,12 @@ public sealed class GuiManager : IFontFamilyProvider
|
|||
{
|
||||
isRendering = true;
|
||||
|
||||
renderer.SetLayer(0);
|
||||
|
||||
foreach (Widget widget in topLevels)
|
||||
widget.RenderInternal(renderer);
|
||||
|
||||
renderer.RenderBatches();
|
||||
|
||||
isRendering = false;
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ public interface IGuiContext
|
|||
GraphicsDevice GraphicsDevice { get; }
|
||||
|
||||
void Render(VertexPositionColorTexture[] vertices, int[] indices, Texture2D? texture, LayoutRect? clipRect = null);
|
||||
|
||||
void Render(VertexBuffer vertices, IndexBuffer indices, int offset, int primitiveCount, Texture2D? texture, LayoutRect? clipRect = null);
|
||||
|
||||
IFontFamily GetFallbackFont();
|
||||
}
|
|
@ -68,6 +68,26 @@ public struct LayoutRect
|
|||
return new LayoutRect(0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static bool operator ==(LayoutRect left, LayoutRect right)
|
||||
{
|
||||
return (Math.Abs(left.Left - right.Left) < float.Epsilon) && (Math.Abs(left.Top - right.Top) < float.Epsilon) && (Math.Abs(left.Width - right.Width) < float.Epsilon) && (Math.Abs(left.Height - right.Height) < float.Epsilon);
|
||||
}
|
||||
|
||||
public static bool operator !=(LayoutRect left, LayoutRect right)
|
||||
{
|
||||
return !(left == right);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
return obj is LayoutRect right && this == right;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Left, Top, Width, Height);
|
||||
}
|
||||
}
|
||||
|
||||
public enum ClippingMode
|
||||
|
|
|
@ -21,7 +21,7 @@ public class GeometryHelper : IFontStashRenderer2
|
|||
this.guiRenderer = guiRenderer;
|
||||
this.clipRect = clipRect;
|
||||
|
||||
whiteMesh = new GuiMeshBuilder(null, opacity, desaturate);
|
||||
whiteMesh = new GuiMeshBuilder(null, guiRenderer.GetVertexCount(null), guiRenderer.Layer, opacity, desaturate);
|
||||
}
|
||||
|
||||
public GuiMesh ExportMesh()
|
||||
|
@ -47,7 +47,7 @@ public class GeometryHelper : IFontStashRenderer2
|
|||
|
||||
if (!meshes.TryGetValue(texture, out GuiMeshBuilder? builder))
|
||||
{
|
||||
builder = new GuiMeshBuilder(texture, opacity, desaturate);
|
||||
builder = new GuiMeshBuilder(texture, guiRenderer.GetVertexCount(texture), guiRenderer.Layer, opacity, desaturate);
|
||||
meshes.Add(texture, builder);
|
||||
}
|
||||
|
||||
|
|
|
@ -9,11 +9,21 @@ public struct GuiSubMesh
|
|||
public readonly int[] Indices;
|
||||
public readonly Texture2D? Texture;
|
||||
|
||||
private bool isNewMesh = true;
|
||||
|
||||
public bool IsNew => isNewMesh;
|
||||
|
||||
public GuiSubMesh(VertexPositionColorTexture[] vertices, int[] indices, Texture2D? texture)
|
||||
{
|
||||
Vertices = vertices;
|
||||
Indices = indices;
|
||||
this.Texture = texture;
|
||||
isNewMesh = true;
|
||||
}
|
||||
|
||||
public void MarkAsOld()
|
||||
{
|
||||
isNewMesh = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,16 +6,20 @@ namespace AcidicGUI.Rendering;
|
|||
public sealed class GuiMeshBuilder
|
||||
{
|
||||
private readonly List<VertexPositionColorTexture> vertices = new();
|
||||
private readonly List<int> indices = new();
|
||||
private readonly Texture2D? texture;
|
||||
private readonly float opacity;
|
||||
private readonly bool desaturate;
|
||||
private readonly List<int> indices = new();
|
||||
private readonly Texture2D? texture;
|
||||
private readonly float opacity;
|
||||
private readonly bool desaturate;
|
||||
private readonly int baseVertex;
|
||||
private readonly float layer;
|
||||
|
||||
public GuiMeshBuilder(Texture2D? texture, float opacity, bool desaturate)
|
||||
public GuiMeshBuilder(Texture2D? texture, int baseVertex, int layer, float opacity, bool desaturate)
|
||||
{
|
||||
this.texture = texture;
|
||||
this.opacity = opacity;
|
||||
this.desaturate = desaturate;
|
||||
//this.baseVertex = baseVertex;
|
||||
//this.layer = layer * float.Epsilon;
|
||||
}
|
||||
|
||||
public VertexPositionColorTexture this[int index]
|
||||
|
@ -32,6 +36,7 @@ public sealed class GuiMeshBuilder
|
|||
{
|
||||
int index = vertices.Count;
|
||||
|
||||
vertex.Position.Z = layer;
|
||||
vertex.Color.A = (byte)(vertex.Color.A * opacity);
|
||||
|
||||
if (desaturate)
|
||||
|
@ -63,9 +68,9 @@ public sealed class GuiMeshBuilder
|
|||
|
||||
public void AddTriangle(int a, int b, int c)
|
||||
{
|
||||
indices.Add(a);
|
||||
indices.Add(b);
|
||||
indices.Add(c);
|
||||
indices.Add(a + baseVertex);
|
||||
indices.Add(b + baseVertex);
|
||||
indices.Add(c + baseVertex);
|
||||
}
|
||||
|
||||
public void AddQuad(int a, int b, int c, int d)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
using System.Collections;
|
||||
using System.Numerics;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
|
||||
|
@ -5,20 +6,206 @@ namespace AcidicGUI.Rendering;
|
|||
|
||||
public sealed class GuiRenderer
|
||||
{
|
||||
private readonly IGuiContext context;
|
||||
private GuiBatch? whiteBatch;
|
||||
private readonly Dictionary<Texture2D, GuiBatch> batches = new();
|
||||
private readonly IGuiContext context;
|
||||
private int layer;
|
||||
|
||||
public GuiRenderer(IGuiContext context)
|
||||
{
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public int Layer => layer;
|
||||
|
||||
public void SetLayer(int newLayer)
|
||||
{
|
||||
this.layer = newLayer;
|
||||
}
|
||||
|
||||
public void PushLayer()
|
||||
{
|
||||
layer++;
|
||||
}
|
||||
|
||||
public void PopLayer()
|
||||
{
|
||||
layer--;
|
||||
}
|
||||
|
||||
public void RenderGuiMesh(GuiMesh mesh)
|
||||
{
|
||||
foreach (GuiSubMesh subMesh in mesh.SubMeshes)
|
||||
{
|
||||
context.Render(subMesh.Vertices, subMesh.Indices, subMesh.Texture, mesh.ClipRect);
|
||||
SubmitSubMesh(subMesh);
|
||||
}
|
||||
}
|
||||
|
||||
public int GetVertexCount(Texture2D? texture)
|
||||
{
|
||||
if (texture == null)
|
||||
{
|
||||
if (whiteBatch==null)
|
||||
whiteBatch = new GuiBatch(context, GraphicsDevice, null);
|
||||
|
||||
return whiteBatch.VertexCount;
|
||||
}
|
||||
|
||||
if (!batches.TryGetValue(texture, out GuiBatch? batch))
|
||||
{
|
||||
batch = new GuiBatch(context, GraphicsDevice, texture);
|
||||
batches.Add(texture, batch);
|
||||
}
|
||||
|
||||
return batch.VertexCount;
|
||||
}
|
||||
|
||||
private void SubmitSubMesh(GuiSubMesh subMesh)
|
||||
{
|
||||
if (subMesh.Texture != null)
|
||||
{
|
||||
if (!batches.TryGetValue(subMesh.Texture, out GuiBatch? batch))
|
||||
{
|
||||
batch = new GuiBatch(context, GraphicsDevice, subMesh.Texture);
|
||||
batches.Add(subMesh.Texture, batch);
|
||||
}
|
||||
|
||||
batch.Submit(subMesh);
|
||||
return;
|
||||
}
|
||||
|
||||
if (whiteBatch==null)
|
||||
whiteBatch = new GuiBatch(context, GraphicsDevice, null);
|
||||
|
||||
whiteBatch.Submit(subMesh);
|
||||
}
|
||||
|
||||
public void RenderBatches()
|
||||
{
|
||||
whiteBatch?.DrawBatch();
|
||||
|
||||
foreach (GuiBatch batch in batches.Values)
|
||||
batch.DrawBatch();
|
||||
}
|
||||
|
||||
public GraphicsDevice GraphicsDevice => context.GraphicsDevice;
|
||||
}
|
||||
|
||||
public sealed class GrowingList<T>
|
||||
{
|
||||
private readonly List<T> list = new();
|
||||
private int insertIndex = 0;
|
||||
|
||||
public IEnumerator<T> GetEnumerator()
|
||||
{
|
||||
return list.GetEnumerator();
|
||||
}
|
||||
|
||||
public void AddRange(IEnumerable<T> items)
|
||||
{
|
||||
foreach (T item in items)
|
||||
Add(item);
|
||||
}
|
||||
|
||||
public void Add(T item)
|
||||
{
|
||||
if (insertIndex == list.Count)
|
||||
list.Add(item);
|
||||
else
|
||||
list[insertIndex] = item;
|
||||
|
||||
insertIndex++;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
insertIndex = 0;
|
||||
}
|
||||
|
||||
public int Count => insertIndex;
|
||||
|
||||
public T[] ToArray()
|
||||
{
|
||||
return list.ToArray().AsSpan().Slice(0, insertIndex).ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GuiBatch
|
||||
{
|
||||
private readonly GraphicsDevice device;
|
||||
private VertexBuffer? vertexBuffer;
|
||||
private IndexBuffer? indexBuffer;
|
||||
private readonly Texture2D? texture;
|
||||
private readonly IGuiContext context;
|
||||
private readonly GrowingList<VertexPositionColorTexture> cpuVertices = new();
|
||||
private readonly GrowingList<int> cpuIndices = new();
|
||||
|
||||
private int vertexDirtiness = 0;
|
||||
private int indexDirtiness = 0;
|
||||
private bool dirty;
|
||||
|
||||
public GuiBatch(IGuiContext context, GraphicsDevice device, Texture2D? texture)
|
||||
{
|
||||
this.context = context;
|
||||
this.device = device;
|
||||
this.texture = texture;
|
||||
}
|
||||
|
||||
public int VertexCount => cpuVertices.Count;
|
||||
|
||||
public void Submit(GuiSubMesh subMesh)
|
||||
{
|
||||
if (subMesh.IsNew)
|
||||
{
|
||||
subMesh.MarkAsOld();
|
||||
if (!dirty)
|
||||
{
|
||||
vertexDirtiness = cpuVertices.Count;
|
||||
indexDirtiness = cpuIndices.Count;
|
||||
}
|
||||
|
||||
dirty = true;
|
||||
}
|
||||
|
||||
cpuVertices.AddRange(subMesh.Vertices);
|
||||
cpuIndices.AddRange(subMesh.Indices);
|
||||
}
|
||||
|
||||
public void DrawBatch()
|
||||
{
|
||||
if (cpuIndices.Count == 0 || cpuVertices.Count == 0)
|
||||
return;
|
||||
|
||||
if (vertexBuffer == null || vertexBuffer.VertexCount < cpuVertices.Count)
|
||||
{
|
||||
vertexBuffer?.Dispose();
|
||||
dirty = true;
|
||||
vertexDirtiness = 0;
|
||||
vertexBuffer = new VertexBuffer(device, typeof(VertexPositionColorTexture), cpuVertices.Count, BufferUsage.None);
|
||||
}
|
||||
|
||||
if (indexBuffer == null || indexBuffer.IndexCount < cpuIndices.Count)
|
||||
{
|
||||
indexBuffer?.Dispose();
|
||||
dirty = true;
|
||||
indexDirtiness = 0;
|
||||
indexBuffer = new IndexBuffer(device, typeof(int), cpuIndices.Count, BufferUsage.None);
|
||||
}
|
||||
|
||||
if (dirty)
|
||||
{
|
||||
var vertexData = cpuVertices.ToArray();
|
||||
var indexData = cpuIndices.ToArray();
|
||||
|
||||
vertexBuffer.SetData(vertexData, vertexDirtiness, vertexData.Length - vertexDirtiness);
|
||||
indexBuffer.SetData(indexData, indexDirtiness, indexData.Length - indexDirtiness);
|
||||
|
||||
dirty = false;
|
||||
}
|
||||
|
||||
context.Render(vertexBuffer, indexBuffer, 0, cpuIndices.Count / 3, texture, null);
|
||||
|
||||
cpuVertices.Clear();
|
||||
cpuIndices.Clear();
|
||||
}
|
||||
}
|
|
@ -779,29 +779,29 @@ public class TextWidget : Widget
|
|||
);
|
||||
}
|
||||
|
||||
private struct MarkupData
|
||||
private class MarkupData
|
||||
{
|
||||
public int Length;
|
||||
public Color? ColorOverride;
|
||||
public Color Highlight;
|
||||
public FontInfo? FontOverride;
|
||||
public int Length;
|
||||
public Color? ColorOverride;
|
||||
public Color Highlight;
|
||||
public FontInfo? FontOverride;
|
||||
public FontWeight? Weight;
|
||||
public int? FontSize;
|
||||
public bool Italic;
|
||||
public bool Underline;
|
||||
public bool Strikethrough;
|
||||
public bool Selected;
|
||||
public string? Link;
|
||||
public int? FontSize;
|
||||
public bool Italic;
|
||||
public bool Underline;
|
||||
public bool Strikethrough;
|
||||
public bool Selected;
|
||||
public string? Link;
|
||||
}
|
||||
|
||||
private class TextElement
|
||||
{
|
||||
public string Text;
|
||||
public Vector2 Position;
|
||||
public Vector2? MeasuredSize;
|
||||
public bool IsNewLine;
|
||||
public int SourceStart;
|
||||
public int SourceEnd;
|
||||
public MarkupData MarkupData;
|
||||
public string Text;
|
||||
public Vector2 Position;
|
||||
public Vector2? MeasuredSize;
|
||||
public bool IsNewLine;
|
||||
public int SourceStart;
|
||||
public int SourceEnd;
|
||||
public MarkupData MarkupData = new();
|
||||
}
|
||||
}
|
|
@ -5,18 +5,19 @@ namespace AcidicGUI.Widgets;
|
|||
|
||||
public partial class Widget
|
||||
{
|
||||
private Widget? layoutRoot;
|
||||
private bool layoutIsDirty = true;
|
||||
private Widget? layoutRoot;
|
||||
private bool layoutIsDirty = true;
|
||||
private HorizontalAlignment horizontalAlignment;
|
||||
private VerticalAlignment verticalAlignment;
|
||||
private LayoutRect calculatedLayoutRect;
|
||||
private Vector2? cachedContentSize;
|
||||
private Padding padding;
|
||||
private Padding margin;
|
||||
private Vector2 minimumSize;
|
||||
private Vector2 maximumSize;
|
||||
private Vector2 previousAvailableSize;
|
||||
private Visibility visibility;
|
||||
private VerticalAlignment verticalAlignment;
|
||||
private LayoutRect geometryRect;
|
||||
private LayoutRect calculatedLayoutRect;
|
||||
private Vector2? cachedContentSize;
|
||||
private Padding padding;
|
||||
private Padding margin;
|
||||
private Vector2 minimumSize;
|
||||
private Vector2 maximumSize;
|
||||
private Vector2 previousAvailableSize;
|
||||
private Visibility visibility;
|
||||
|
||||
public Visibility Visibility
|
||||
{
|
||||
|
@ -199,6 +200,12 @@ public partial class Widget
|
|||
|
||||
ArrangeChildren(context, calculatedLayoutRect - margin);
|
||||
|
||||
if (geometryRect != calculatedLayoutRect)
|
||||
{
|
||||
InvalidateGeometry();
|
||||
geometryRect = calculatedLayoutRect;
|
||||
}
|
||||
|
||||
CalculateClipRect();
|
||||
|
||||
layoutIsDirty = false;
|
||||
|
@ -290,6 +297,5 @@ public partial class Widget
|
|||
|
||||
layoutIsDirty = true;
|
||||
cachedContentSize = null;
|
||||
cachedGeometry = null;
|
||||
}
|
||||
}
|
|
@ -199,7 +199,10 @@ public abstract partial class Widget : IFontFamilyProvider
|
|||
}
|
||||
|
||||
renderer.RenderGuiMesh(cachedGeometry.Value);
|
||||
renderer.RenderBatches();
|
||||
|
||||
renderer.PushLayer();
|
||||
|
||||
foreach (Widget child in children)
|
||||
{
|
||||
child.RenderInternal(renderer);
|
||||
|
|
|
@ -154,6 +154,44 @@ public sealed class GuiService :
|
|||
vertices.Length, indices, 0, indices.Length / 3);
|
||||
}
|
||||
|
||||
public void Render(
|
||||
VertexBuffer vertices,
|
||||
IndexBuffer indices,
|
||||
int offset,
|
||||
int primitiveCount,
|
||||
Texture2D? texture,
|
||||
LayoutRect? clipRect = null
|
||||
)
|
||||
{
|
||||
var device = Game.GraphicsDevice;
|
||||
|
||||
if (defaultEffect == null)
|
||||
{
|
||||
defaultEffect = new SpriteEffect(Game.GraphicsDevice);
|
||||
}
|
||||
|
||||
if (white == null)
|
||||
{
|
||||
white = new Texture2D(Game.GraphicsDevice, 1, 1);
|
||||
white.SetData(new Color[] { Color.White });
|
||||
}
|
||||
|
||||
if (primitiveCount == 0)
|
||||
return;
|
||||
|
||||
defaultEffect.Techniques[0].Passes[0].Apply();
|
||||
device.Textures[0] = texture ?? white;
|
||||
device.SamplerStates[0] = SamplerState.LinearClamp;
|
||||
device.BlendState = blendState;
|
||||
device.RasterizerState = GetRasterizerState(clipRect);
|
||||
device.DepthStencilState = DepthStencilState.DepthRead;
|
||||
device.ScissorRectangle = clipRect.GetValueOrDefault();
|
||||
|
||||
device.SetVertexBuffer(vertices);
|
||||
device.Indices = indices;
|
||||
device.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, offset, primitiveCount);
|
||||
}
|
||||
|
||||
private RasterizerState GetRasterizerState(LayoutRect? clipRect)
|
||||
{
|
||||
scissor ??= new RasterizerState()
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/outputDir:bin/Debug/net8.0/
|
||||
/intermediateDir:obj/Debug/net8.0/
|
||||
/outputDir:bin/Release/net8.0/
|
||||
/intermediateDir:obj/Release/net8.0/
|
||||
/platform:DesktopGL
|
||||
/config:
|
||||
/profile:Reach
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
using System.Reactive.Subjects;
|
||||
using System.Text;
|
||||
using AcidicGUI;
|
||||
using Microsoft.VisualBasic;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework.Graphics;
|
||||
using Serilog;
|
||||
|
@ -132,6 +133,7 @@ internal sealed class SociallyDistantGame :
|
|||
|
||||
timeData = Time.Initialize();
|
||||
graphicsManager = new GraphicsDeviceManager(this);
|
||||
graphicsManager.GraphicsProfile = GraphicsProfile.HiDef;
|
||||
|
||||
tabbedTools = new TabbedToolCollection(this);
|
||||
|
||||
|
|
|
@ -30,6 +30,7 @@ public class GuiController : GameComponent,
|
|||
private readonly FlexPanel mainPanel = new();
|
||||
private readonly StatusBar statusBar;
|
||||
private readonly OverlayWidget workArea = new();
|
||||
private readonly OverlayWidget overlays = new();
|
||||
private readonly Box mainBox = new();
|
||||
private readonly FloatingWorkspace floatingWindowArea = new();
|
||||
private readonly GuiService guiService;
|
||||
|
@ -58,7 +59,8 @@ public class GuiController : GameComponent,
|
|||
notificationManager = new NotificationManager(game.WorldManager);
|
||||
|
||||
guiService.GuiRoot.TopLevels.Add(mainPanel);
|
||||
|
||||
guiService.GuiRoot.TopLevels.Add(overlays);
|
||||
|
||||
mainPanel.ChildWidgets.Add(statusBar);
|
||||
mainPanel.ChildWidgets.Add(workArea);
|
||||
|
||||
|
@ -127,7 +129,7 @@ public class GuiController : GameComponent,
|
|||
private OverlayWorkspace CreateOverlayWorkspace()
|
||||
{
|
||||
var workspace = new OverlayWorkspace();
|
||||
this.workArea.ChildWidgets.Add(workspace);
|
||||
this.overlays.ChildWidgets.Add(workspace);
|
||||
return workspace;
|
||||
}
|
||||
|
||||
|
@ -172,7 +174,7 @@ public class GuiController : GameComponent,
|
|||
systemSettings?.Dispose();
|
||||
systemSettings = null;
|
||||
|
||||
workArea.ChildWidgets.Remove(overlay);
|
||||
overlays.ChildWidgets.Remove(overlay);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue